diff --git a/src/__tests__/setup.ts b/src/__tests__/setup.ts index 8e5f93a..8c85eae 100644 --- a/src/__tests__/setup.ts +++ b/src/__tests__/setup.ts @@ -1,4 +1,5 @@ import { PGlite } from '@electric-sql/pglite'; +import { initializeDatabase, dropTables } from '../schema'; // Set up test environment variables before any tests run process.env.SKIP_SIGNING_VERIFICATION = 'true'; @@ -29,94 +30,20 @@ class DatabaseManager { if (!this.db) { this.db = new PGlite('memory://'); await this.db.ready; - - // Wrap table creation in a transaction - await this.db.query('BEGIN'); - try { - // Create sessions table - await this.db.query(` - CREATE TABLE IF NOT EXISTS sessions ( - id TEXT PRIMARY KEY, - address TEXT NOT NULL, - nonce TEXT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP WITH TIME ZONE NOT NULL, - domain TEXT NOT NULL - ) - `); - - // Create nonces table - await this.db.query(` - CREATE TABLE IF NOT EXISTS nonces ( - id TEXT PRIMARY KEY, - chain_id TEXT NOT NULL, - nonce TEXT NOT NULL, - consumed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - UNIQUE(chain_id, nonce) - ) - `); - - // Create compacts table - await this.db.query(` - CREATE TABLE IF NOT EXISTS compacts ( - id TEXT PRIMARY KEY, - chain_id TEXT NOT NULL, - claim_hash TEXT NOT NULL, - arbiter TEXT NOT NULL, - sponsor TEXT NOT NULL, - nonce TEXT NOT NULL, - expires BIGINT NOT NULL, - compact_id TEXT NOT NULL, - amount TEXT NOT NULL, - witness_type_string TEXT, - witness_hash TEXT, - signature TEXT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - UNIQUE(chain_id, claim_hash) - ) - `); - - // Create indexes - await this.db.query( - 'CREATE INDEX IF NOT EXISTS idx_sessions_address ON sessions(address)' - ); - await this.db.query( - 'CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at)' - ); - await this.db.query( - 'CREATE INDEX IF NOT EXISTS idx_compacts_sponsor ON compacts(sponsor)' - ); - await this.db.query( - 'CREATE INDEX IF NOT EXISTS idx_compacts_chain_claim ON compacts(chain_id, claim_hash)' - ); - await this.db.query( - 'CREATE INDEX IF NOT EXISTS idx_nonces_chain_nonce ON nonces(chain_id, nonce)' - ); - - await this.db.query('COMMIT'); - } catch (error) { - await this.db.query('ROLLBACK'); - throw error; - } + await initializeDatabase(this.db); } - return; } async getDb(): Promise { if (!this.db) { - return this.initialize().then(() => this.db as PGlite); + await this.initialize(); } return this.db as PGlite; } async cleanup(): Promise { if (this.db) { - // Drop all tables - await this.db.query('DROP TABLE IF EXISTS sessions CASCADE'); - await this.db.query('DROP TABLE IF EXISTS nonces CASCADE'); - await this.db.query('DROP TABLE IF EXISTS compacts CASCADE'); - - // Reset the database connection + await dropTables(this.db); this.db = null; } } diff --git a/src/database.ts b/src/database.ts index 2b466ea..a373468 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,34 +1,10 @@ import { FastifyInstance } from 'fastify'; import { PGlite } from '@electric-sql/pglite'; +import { initializeDatabase } from './schema'; export async function setupDatabase(server: FastifyInstance): Promise { const db = new PGlite(); - - // Create sessions table - await db.query(` - CREATE TABLE IF NOT EXISTS sessions ( - id TEXT PRIMARY KEY, - address TEXT NOT NULL, - nonce TEXT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP WITH TIME ZONE NOT NULL, - domain TEXT NOT NULL - ) - `); - - // Create compacts table - await db.query(` - CREATE TABLE IF NOT EXISTS compacts ( - id TEXT PRIMARY KEY, - address TEXT NOT NULL, - nonce TEXT NOT NULL, - signature TEXT NOT NULL, - message TEXT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP WITH TIME ZONE NOT NULL, - domain TEXT NOT NULL - ) - `); + await initializeDatabase(db); // Add the database instance to the server server.decorate('db', db); diff --git a/src/schema.ts b/src/schema.ts new file mode 100644 index 0000000..b853ea9 --- /dev/null +++ b/src/schema.ts @@ -0,0 +1,88 @@ +import { PGlite } from '@electric-sql/pglite'; + +export const schemas = { + sessions: ` + CREATE TABLE IF NOT EXISTS sessions ( + id TEXT PRIMARY KEY, + address TEXT NOT NULL, + nonce TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL, + domain TEXT NOT NULL + ) + `, + nonces: ` + CREATE TABLE IF NOT EXISTS nonces ( + id TEXT PRIMARY KEY, + chain_id TEXT NOT NULL, + nonce TEXT NOT NULL, + consumed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(chain_id, nonce) + ) + `, + compacts: ` + CREATE TABLE IF NOT EXISTS compacts ( + id TEXT PRIMARY KEY, + chain_id TEXT NOT NULL, + claim_hash TEXT NOT NULL, + arbiter TEXT NOT NULL, + sponsor TEXT NOT NULL, + nonce TEXT NOT NULL, + expires BIGINT NOT NULL, + compact_id TEXT NOT NULL, + amount TEXT NOT NULL, + witness_type_string TEXT, + witness_hash TEXT, + signature TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(chain_id, claim_hash) + ) + `, +}; + +export const indexes = { + sessions: [ + 'CREATE INDEX IF NOT EXISTS idx_sessions_address ON sessions(address)', + 'CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at)', + ], + compacts: [ + 'CREATE INDEX IF NOT EXISTS idx_compacts_sponsor ON compacts(sponsor)', + 'CREATE INDEX IF NOT EXISTS idx_compacts_chain_claim ON compacts(chain_id, claim_hash)', + ], + nonces: [ + 'CREATE INDEX IF NOT EXISTS idx_nonces_chain_nonce ON nonces(chain_id, nonce)', + ], +}; + +export async function initializeDatabase(db: PGlite): Promise { + await db.query('BEGIN'); + try { + // Create tables + await Promise.all(Object.values(schemas).map((schema) => db.query(schema))); + + // Create indexes + await Promise.all( + Object.values(indexes) + .flat() + .map((index) => db.query(index)) + ); + + await db.query('COMMIT'); + } catch (error) { + await db.query('ROLLBACK'); + throw error; + } +} + +export async function dropTables(db: PGlite): Promise { + await db.query('BEGIN'); + try { + for (const table of Object.keys(schemas)) { + await db.query(`DROP TABLE IF EXISTS ${table} CASCADE`); + } + await db.query('COMMIT'); + } catch (error) { + await db.query('ROLLBACK'); + throw error; + } +}