chore: consolidate new foundation and archive v1 (#1495)

This commit is contained in:
2026-09-07 12:32:57 -05:00
3511 changed files with 727899 additions and 10 deletions
@@ -0,0 +1,202 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { PgliteAdapter } from './pglite.js';
describe('PgliteAdapter', () => {
let adapter: PgliteAdapter;
beforeEach(async () => {
// In-memory PGlite instance — no dataDir = memory mode
adapter = new PgliteAdapter({ type: 'pglite' });
await adapter.migrate();
});
afterEach(async () => {
await adapter.close();
});
describe('CRUD', () => {
it('creates and reads a record', async () => {
const created = await adapter.create('users', { name: 'Alice', email: '[email protected]' });
expect(created.id).toBeDefined();
expect(created.name).toBe('Alice');
const read = await adapter.read('users', created.id);
expect(read).not.toBeNull();
expect(read!.name).toBe('Alice');
expect(read!.email).toBe('[email protected]');
});
it('returns null for non-existent record', async () => {
const result = await adapter.read('users', 'does-not-exist');
expect(result).toBeNull();
});
it('updates a record', async () => {
const created = await adapter.create('users', { name: 'Alice' });
const updated = await adapter.update('users', created.id, { name: 'Bob' });
expect(updated).toBe(true);
const read = await adapter.read('users', created.id);
expect(read!.name).toBe('Bob');
});
it('update returns false for non-existent record', async () => {
const result = await adapter.update('users', 'does-not-exist', { name: 'X' });
expect(result).toBe(false);
});
it('deletes a record', async () => {
const created = await adapter.create('users', { name: 'Alice' });
const deleted = await adapter.delete('users', created.id);
expect(deleted).toBe(true);
const read = await adapter.read('users', created.id);
expect(read).toBeNull();
});
it('delete returns false for non-existent record', async () => {
const result = await adapter.delete('users', 'does-not-exist');
expect(result).toBe(false);
});
});
describe('find', () => {
it('finds records with filter', async () => {
await adapter.create('users', { name: 'Alice', role: 'admin' });
await adapter.create('users', { name: 'Bob', role: 'user' });
await adapter.create('users', { name: 'Charlie', role: 'admin' });
const admins = await adapter.find('users', { role: 'admin' });
expect(admins).toHaveLength(2);
expect(admins.map((u) => u.name).sort()).toEqual(['Alice', 'Charlie']);
});
it('finds all records without filter', async () => {
await adapter.create('users', { name: 'Alice' });
await adapter.create('users', { name: 'Bob' });
const all = await adapter.find('users');
expect(all).toHaveLength(2);
});
it('supports limit and offset', async () => {
for (let i = 0; i < 5; i++) {
await adapter.create('users', { name: `User${i.toString()}`, idx: i });
}
const page = await adapter.find('users', undefined, {
limit: 2,
offset: 1,
orderBy: 'created_at',
});
expect(page).toHaveLength(2);
});
it('findOne returns first match', async () => {
await adapter.create('users', { name: 'Alice', role: 'admin' });
await adapter.create('users', { name: 'Bob', role: 'user' });
const found = await adapter.findOne('users', { role: 'user' });
expect(found).not.toBeNull();
expect(found!.name).toBe('Bob');
});
it('findOne returns null when no match', async () => {
const result = await adapter.findOne('users', { role: 'nonexistent' });
expect(result).toBeNull();
});
});
describe('count', () => {
it('counts all records', async () => {
await adapter.create('users', { name: 'Alice' });
await adapter.create('users', { name: 'Bob' });
const total = await adapter.count('users');
expect(total).toBe(2);
});
it('counts with filter', async () => {
await adapter.create('users', { name: 'Alice', role: 'admin' });
await adapter.create('users', { name: 'Bob', role: 'user' });
await adapter.create('users', { name: 'Charlie', role: 'admin' });
const adminCount = await adapter.count('users', { role: 'admin' });
expect(adminCount).toBe(2);
});
it('returns 0 for empty collection', async () => {
const count = await adapter.count('users');
expect(count).toBe(0);
});
});
describe('transaction', () => {
it('commits on success', async () => {
await adapter.transaction(async (tx) => {
await tx.create('users', { name: 'Alice' });
await tx.create('users', { name: 'Bob' });
});
const count = await adapter.count('users');
expect(count).toBe(2);
});
it('rolls back on error', async () => {
await expect(
adapter.transaction(async (tx) => {
await tx.create('users', { name: 'Alice' });
throw new Error('rollback test');
}),
).rejects.toThrow('rollback test');
const count = await adapter.count('users');
expect(count).toBe(0);
});
});
describe('migrate', () => {
it('creates all tables', async () => {
// migrate() was already called in beforeEach — verify tables exist
const collections = [
'users',
'sessions',
'accounts',
'projects',
'missions',
'tasks',
'agents',
'conversations',
'messages',
'preferences',
'insights',
'skills',
'events',
'routing_rules',
'provider_credentials',
'agent_logs',
'teams',
'team_members',
'mission_tasks',
'tickets',
'summarization_jobs',
'appreciations',
'verifications',
];
for (const collection of collections) {
// Should not throw
const count = await adapter.count(collection);
expect(count).toBe(0);
}
});
it('is idempotent', async () => {
await adapter.migrate();
await adapter.migrate();
// Should not throw
const count = await adapter.count('users');
expect(count).toBe(0);
});
});
});
+290
View File
@@ -0,0 +1,290 @@
import { PGlite } from '@electric-sql/pglite';
import { randomUUID } from 'node:crypto';
import type { StorageAdapter, StorageConfig } from '../types.js';
/* eslint-disable @typescript-eslint/no-explicit-any */
const COLLECTIONS = [
'users',
'sessions',
'accounts',
'projects',
'missions',
'tasks',
'agents',
'conversations',
'messages',
'preferences',
'insights',
'skills',
'events',
'routing_rules',
'provider_credentials',
'agent_logs',
'teams',
'team_members',
'mission_tasks',
'tickets',
'summarization_jobs',
'appreciations',
'verifications',
] as const;
function buildFilterClause(filter?: Record<string, unknown>): {
clause: string;
params: unknown[];
} {
if (!filter || Object.keys(filter).length === 0) return { clause: '', params: [] };
const conditions: string[] = [];
const params: unknown[] = [];
let paramIdx = 1;
for (const [key, value] of Object.entries(filter)) {
if (key === 'id') {
conditions.push(`id = $${paramIdx.toString()}`);
params.push(value);
paramIdx++;
} else {
conditions.push(`data->>'${key}' = $${paramIdx.toString()}`);
params.push(typeof value === 'object' ? JSON.stringify(value) : value);
paramIdx++;
}
}
return { clause: ` WHERE ${conditions.join(' AND ')}`, params };
}
type PgClient = PGlite | { query: PGlite['query'] };
async function pgCreate<T extends Record<string, unknown>>(
pg: PgClient,
collection: string,
data: T,
): Promise<T & { id: string }> {
const id = (data as any).id ?? randomUUID();
const rest = Object.fromEntries(Object.entries(data).filter(([k]) => k !== 'id'));
await pg.query(`INSERT INTO ${collection} (id, data) VALUES ($1, $2::jsonb)`, [
id,
JSON.stringify(rest),
]);
return { ...data, id } as T & { id: string };
}
async function pgRead<T extends Record<string, unknown>>(
pg: PgClient,
collection: string,
id: string,
): Promise<T | null> {
const result = await pg.query<{ id: string; data: Record<string, unknown> }>(
`SELECT id, data FROM ${collection} WHERE id = $1`,
[id],
);
const row = result.rows[0];
if (!row) return null;
return { id: row.id, ...(row.data as object) } as unknown as T;
}
async function pgUpdate(
pg: PgClient,
collection: string,
id: string,
data: Record<string, unknown>,
): Promise<boolean> {
const existing = await pg.query<{ data: Record<string, unknown> }>(
`SELECT data FROM ${collection} WHERE id = $1`,
[id],
);
const row = existing.rows[0];
if (!row) return false;
const merged = { ...(row.data as object), ...data };
const result = await pg.query(
`UPDATE ${collection} SET data = $1::jsonb, updated_at = now() WHERE id = $2`,
[JSON.stringify(merged), id],
);
return (result.affectedRows ?? 0) > 0;
}
async function pgDelete(pg: PgClient, collection: string, id: string): Promise<boolean> {
const result = await pg.query(`DELETE FROM ${collection} WHERE id = $1`, [id]);
return (result.affectedRows ?? 0) > 0;
}
async function pgFind<T extends Record<string, unknown>>(
pg: PgClient,
collection: string,
filter?: Record<string, unknown>,
opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' },
): Promise<T[]> {
const { clause, params } = buildFilterClause(filter);
let paramIdx = params.length + 1;
let query = `SELECT id, data FROM ${collection}${clause}`;
if (opts?.orderBy) {
const dir = opts.order === 'desc' ? 'DESC' : 'ASC';
const col =
opts.orderBy === 'id'
? 'id'
: opts.orderBy === 'created_at' || opts.orderBy === 'updated_at'
? opts.orderBy
: `data->>'${opts.orderBy}'`;
query += ` ORDER BY ${col} ${dir}`;
}
if (opts?.limit !== undefined) {
query += ` LIMIT $${paramIdx.toString()}`;
params.push(opts.limit);
paramIdx++;
}
if (opts?.offset !== undefined) {
query += ` OFFSET $${paramIdx.toString()}`;
params.push(opts.offset);
paramIdx++;
}
const result = await pg.query<{ id: string; data: Record<string, unknown> }>(query, params);
return result.rows.map((row) => ({ id: row.id, ...(row.data as object) }) as unknown as T);
}
async function pgCount(
pg: PgClient,
collection: string,
filter?: Record<string, unknown>,
): Promise<number> {
const { clause, params } = buildFilterClause(filter);
const result = await pg.query<{ count: string }>(
`SELECT COUNT(*) as count FROM ${collection}${clause}`,
params,
);
return parseInt(result.rows[0]?.count ?? '0', 10);
}
export class PgliteAdapter implements StorageAdapter {
readonly name = 'pglite';
private pg: PGlite;
constructor(config: Extract<StorageConfig, { type: 'pglite' }>) {
this.pg = new PGlite(config.dataDir);
}
async create<T extends Record<string, unknown>>(
collection: string,
data: T,
): Promise<T & { id: string }> {
return pgCreate(this.pg, collection, data);
}
async read<T extends Record<string, unknown>>(collection: string, id: string): Promise<T | null> {
return pgRead(this.pg, collection, id);
}
async update(collection: string, id: string, data: Record<string, unknown>): Promise<boolean> {
return pgUpdate(this.pg, collection, id, data);
}
async delete(collection: string, id: string): Promise<boolean> {
return pgDelete(this.pg, collection, id);
}
async find<T extends Record<string, unknown>>(
collection: string,
filter?: Record<string, unknown>,
opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' },
): Promise<T[]> {
return pgFind(this.pg, collection, filter, opts);
}
async findOne<T extends Record<string, unknown>>(
collection: string,
filter: Record<string, unknown>,
): Promise<T | null> {
const results = await this.find<T>(collection, filter, { limit: 1 });
return results[0] ?? null;
}
async count(collection: string, filter?: Record<string, unknown>): Promise<number> {
return pgCount(this.pg, collection, filter);
}
async transaction<T>(fn: (tx: StorageAdapter) => Promise<T>): Promise<T> {
return this.pg.transaction(async (tx) => {
const txAdapter = new PgliteTxAdapter(tx as unknown as PgClient);
return fn(txAdapter);
});
}
async migrate(): Promise<void> {
for (const name of COLLECTIONS) {
await this.pg.query(`
CREATE TABLE IF NOT EXISTS ${name} (
id TEXT PRIMARY KEY,
data JSONB NOT NULL DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
`);
}
}
async close(): Promise<void> {
await this.pg.close();
}
}
/**
* Transaction wrapper that delegates to the PGlite transaction connection.
*/
class PgliteTxAdapter implements StorageAdapter {
readonly name = 'pglite';
private pg: PgClient;
constructor(pg: PgClient) {
this.pg = pg;
}
async create<T extends Record<string, unknown>>(
collection: string,
data: T,
): Promise<T & { id: string }> {
return pgCreate(this.pg, collection, data);
}
async read<T extends Record<string, unknown>>(collection: string, id: string): Promise<T | null> {
return pgRead(this.pg, collection, id);
}
async update(collection: string, id: string, data: Record<string, unknown>): Promise<boolean> {
return pgUpdate(this.pg, collection, id, data);
}
async delete(collection: string, id: string): Promise<boolean> {
return pgDelete(this.pg, collection, id);
}
async find<T extends Record<string, unknown>>(
collection: string,
filter?: Record<string, unknown>,
opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' },
): Promise<T[]> {
return pgFind(this.pg, collection, filter, opts);
}
async findOne<T extends Record<string, unknown>>(
collection: string,
filter: Record<string, unknown>,
): Promise<T | null> {
const results = await this.find<T>(collection, filter, { limit: 1 });
return results[0] ?? null;
}
async count(collection: string, filter?: Record<string, unknown>): Promise<number> {
return pgCount(this.pg, collection, filter);
}
async transaction<T>(fn: (tx: StorageAdapter) => Promise<T>): Promise<T> {
// Already inside a transaction — run directly
return fn(this);
}
async migrate(): Promise<void> {
// No-op inside transaction
}
async close(): Promise<void> {
// No-op inside transaction
}
}
@@ -0,0 +1,107 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { DbHandle } from '@mosaicstack/db';
// Mock @mosaicstack/db before importing the adapter
vi.mock('@mosaicstack/db', async (importOriginal) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const actual = await importOriginal<Record<string, any>>();
return {
...actual,
createDb: vi.fn(),
runMigrations: vi.fn().mockResolvedValue(undefined),
};
});
import { createDb, runMigrations } from '@mosaicstack/db';
import { PostgresAdapter } from './postgres.js';
describe('PostgresAdapter — vector extension gating', () => {
let mockExecute: ReturnType<typeof vi.fn>;
let mockDb: { execute: ReturnType<typeof vi.fn> };
let mockHandle: Pick<DbHandle, 'close'> & { db: typeof mockDb };
beforeEach(() => {
vi.clearAllMocks();
mockExecute = vi.fn().mockResolvedValue(undefined);
mockDb = { execute: mockExecute };
mockHandle = { db: mockDb, close: vi.fn().mockResolvedValue(undefined) };
vi.mocked(createDb).mockReturnValue(mockHandle as unknown as DbHandle);
});
it('calls db.execute with CREATE EXTENSION IF NOT EXISTS vector when enableVector=true', async () => {
const adapter = new PostgresAdapter({
type: 'postgres',
url: 'postgresql://test:test@localhost:5432/test',
enableVector: true,
});
await adapter.migrate();
// Should have called execute
expect(mockExecute).toHaveBeenCalledTimes(1);
// Verify the SQL contains the extension creation statement.
// Prefer Drizzle's public toSQL() API; fall back to queryChunks if unavailable.
// NOTE: queryChunks is an undocumented Drizzle internal (drizzle-orm ^0.45.x).
// toSQL() was not present on the raw sql`` result in this version — if a future
// Drizzle upgrade adds it, remove the fallback path and delete this comment.
const sqlObj = mockExecute.mock.calls[0]![0] as {
toSQL?: () => { sql: string; params: unknown[] };
queryChunks?: Array<{ value: string[] }>;
};
const sqlText = sqlObj.toSQL
? sqlObj.toSQL().sql.toLowerCase()
: (sqlObj.queryChunks ?? [])
.flatMap((chunk) => chunk.value)
.join('')
.toLowerCase();
expect(sqlText).toContain('create extension if not exists vector');
});
it('does NOT call db.execute for extension when enableVector is false', async () => {
const adapter = new PostgresAdapter({
type: 'postgres',
url: 'postgresql://test:test@localhost:5432/test',
enableVector: false,
});
await adapter.migrate();
expect(mockExecute).not.toHaveBeenCalled();
expect(vi.mocked(runMigrations)).toHaveBeenCalledOnce();
});
it('does NOT call db.execute for extension when enableVector is unset', async () => {
const adapter = new PostgresAdapter({
type: 'postgres',
url: 'postgresql://test:test@localhost:5432/test',
});
await adapter.migrate();
expect(mockExecute).not.toHaveBeenCalled();
expect(vi.mocked(runMigrations)).toHaveBeenCalledOnce();
});
it('calls runMigrations after the extension is created', async () => {
const callOrder: string[] = [];
mockExecute.mockImplementation(() => {
callOrder.push('execute');
return Promise.resolve(undefined);
});
vi.mocked(runMigrations).mockImplementation(() => {
callOrder.push('runMigrations');
return Promise.resolve();
});
const adapter = new PostgresAdapter({
type: 'postgres',
url: 'postgresql://test:test@localhost:5432/test',
enableVector: true,
});
await adapter.migrate();
expect(callOrder).toEqual(['execute', 'runMigrations']);
});
});
@@ -0,0 +1,261 @@
import {
createDb,
runMigrations,
eq,
and,
asc,
desc,
sql,
type Db,
type DbHandle,
} from '@mosaicstack/db';
import * as schema from '@mosaicstack/db';
import type { StorageAdapter, StorageConfig } from '../types.js';
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Maps collection name → Drizzle table object.
* Typed as `any` because the generic StorageAdapter interface erases table
* types — all runtime values are still strongly-typed Drizzle table objects.
*/
const TABLE_MAP: Record<string, any> = {
users: schema.users,
sessions: schema.sessions,
accounts: schema.accounts,
verifications: schema.verifications,
teams: schema.teams,
team_members: schema.teamMembers,
projects: schema.projects,
missions: schema.missions,
tasks: schema.tasks,
mission_tasks: schema.missionTasks,
events: schema.events,
agents: schema.agents,
tickets: schema.tickets,
appreciations: schema.appreciations,
conversations: schema.conversations,
messages: schema.messages,
preferences: schema.preferences,
insights: schema.insights,
agent_logs: schema.agentLogs,
skills: schema.skills,
routing_rules: schema.routingRules,
provider_credentials: schema.providerCredentials,
summarization_jobs: schema.summarizationJobs,
};
function getTable(collection: string): any {
const table = TABLE_MAP[collection];
if (!table) throw new Error(`Unknown collection: ${collection}`);
return table;
}
function buildWhereClause(table: any, filter?: Record<string, unknown>) {
if (!filter || Object.keys(filter).length === 0) return undefined;
const conditions = Object.entries(filter).map(([key, value]) => {
const column = table[key];
if (!column) throw new Error(`Unknown column "${key}" on table`);
return eq(column, value);
});
return conditions.length === 1 ? conditions[0]! : and(...conditions);
}
export class PostgresAdapter implements StorageAdapter {
readonly name = 'postgres';
private handle: DbHandle;
private db: Db;
private url: string;
private enableVector: boolean;
constructor(config: Extract<StorageConfig, { type: 'postgres' }>) {
this.url = config.url;
this.enableVector = config.enableVector ?? false;
this.handle = createDb(config.url);
this.db = this.handle.db;
}
private async ensureVectorExtension(): Promise<void> {
await this.db.execute(sql`CREATE EXTENSION IF NOT EXISTS vector`);
}
async create<T extends Record<string, unknown>>(
collection: string,
data: T,
): Promise<T & { id: string }> {
const table = getTable(collection);
const [row] = await (this.db as any).insert(table).values(data).returning();
return row as T & { id: string };
}
async read<T extends Record<string, unknown>>(collection: string, id: string): Promise<T | null> {
const table = getTable(collection);
const [row] = await (this.db as any).select().from(table).where(eq(table.id, id));
return (row as T) ?? null;
}
async update(collection: string, id: string, data: Record<string, unknown>): Promise<boolean> {
const table = getTable(collection);
const result = await (this.db as any)
.update(table)
.set(data)
.where(eq(table.id, id))
.returning();
return result.length > 0;
}
async delete(collection: string, id: string): Promise<boolean> {
const table = getTable(collection);
const result = await (this.db as any).delete(table).where(eq(table.id, id)).returning();
return result.length > 0;
}
async find<T extends Record<string, unknown>>(
collection: string,
filter?: Record<string, unknown>,
opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' },
): Promise<T[]> {
const table = getTable(collection);
let query = (this.db as any).select().from(table);
const where = buildWhereClause(table, filter);
if (where) query = query.where(where);
if (opts?.orderBy) {
const col = table[opts.orderBy];
if (col) {
query = query.orderBy(opts.order === 'desc' ? desc(col) : asc(col));
}
}
if (opts?.limit) query = query.limit(opts.limit);
if (opts?.offset) query = query.offset(opts.offset);
return (await query) as T[];
}
async findOne<T extends Record<string, unknown>>(
collection: string,
filter: Record<string, unknown>,
): Promise<T | null> {
const results = await this.find<T>(collection, filter, { limit: 1 });
return results[0] ?? null;
}
async count(collection: string, filter?: Record<string, unknown>): Promise<number> {
const table = getTable(collection);
let query = (this.db as any).select({ count: sql<number>`count(*)::int` }).from(table);
const where = buildWhereClause(table, filter);
if (where) query = query.where(where);
const [row] = await query;
return (row as any)?.count ?? 0;
}
async transaction<T>(fn: (tx: StorageAdapter) => Promise<T>): Promise<T> {
return (this.db as any).transaction(async (drizzleTx: any) => {
const txAdapter = new PostgresTxAdapter(drizzleTx, this.url);
return fn(txAdapter);
});
}
async migrate(): Promise<void> {
if (this.enableVector) {
await this.ensureVectorExtension();
}
await runMigrations(this.url);
}
async close(): Promise<void> {
await this.handle.close();
}
}
/**
* Thin transaction wrapper — delegates to the Drizzle transaction object
* instead of the top-level db handle.
*/
class PostgresTxAdapter implements StorageAdapter {
readonly name = 'postgres';
private tx: any;
private url: string;
constructor(tx: any, url: string) {
this.tx = tx;
this.url = url;
}
async create<T extends Record<string, unknown>>(
collection: string,
data: T,
): Promise<T & { id: string }> {
const table = getTable(collection);
const [row] = await this.tx.insert(table).values(data).returning();
return row as T & { id: string };
}
async read<T extends Record<string, unknown>>(collection: string, id: string): Promise<T | null> {
const table = getTable(collection);
const [row] = await this.tx.select().from(table).where(eq(table.id, id));
return (row as T) ?? null;
}
async update(collection: string, id: string, data: Record<string, unknown>): Promise<boolean> {
const table = getTable(collection);
const result = await this.tx.update(table).set(data).where(eq(table.id, id)).returning();
return result.length > 0;
}
async delete(collection: string, id: string): Promise<boolean> {
const table = getTable(collection);
const result = await this.tx.delete(table).where(eq(table.id, id)).returning();
return result.length > 0;
}
async find<T extends Record<string, unknown>>(
collection: string,
filter?: Record<string, unknown>,
opts?: { limit?: number; offset?: number; orderBy?: string; order?: 'asc' | 'desc' },
): Promise<T[]> {
const table = getTable(collection);
let query = this.tx.select().from(table);
const where = buildWhereClause(table, filter);
if (where) query = query.where(where);
if (opts?.orderBy) {
const col = table[opts.orderBy];
if (col) {
query = query.orderBy(opts.order === 'desc' ? desc(col) : asc(col));
}
}
if (opts?.limit) query = query.limit(opts.limit);
if (opts?.offset) query = query.offset(opts.offset);
return (await query) as T[];
}
async findOne<T extends Record<string, unknown>>(
collection: string,
filter: Record<string, unknown>,
): Promise<T | null> {
const results = await this.find<T>(collection, filter, { limit: 1 });
return results[0] ?? null;
}
async count(collection: string, filter?: Record<string, unknown>): Promise<number> {
const table = getTable(collection);
let query = this.tx.select({ count: sql<number>`count(*)::int` }).from(table);
const where = buildWhereClause(table, filter);
if (where) query = query.where(where);
const [row] = await query;
return (row as any)?.count ?? 0;
}
async transaction<T>(fn: (tx: StorageAdapter) => Promise<T>): Promise<T> {
return this.tx.transaction(async (nestedTx: any) => {
const nested = new PostgresTxAdapter(nestedTx, this.url);
return fn(nested);
});
}
async migrate(): Promise<void> {
await runMigrations(this.url);
}
async close(): Promise<void> {
// No-op inside a transaction
}
}