import type { SeedRecord, SeedStore } from "../types/common.js"; /** * Factory for generating test fixture data with auto-incrementing indices * and customizable overrides. */ export class FixtureFactory { private readonly _defaults: (index: number) => T; private _counter = 0; constructor(defaults: (index: number) => T) { this._defaults = defaults; } /** Creates a single record with optional overrides. */ create(overrides?: Partial): T { const record = this._defaults(this._counter++); if (overrides) { return { ...record, ...overrides }; } return record; } /** Creates multiple records with optional per-record overrides. */ createMany(count: number, overrides?: Partial[]): T[] { const records: T[] = []; for (let i = 0; i < count; i++) { records.push(this.create(overrides?.[i])); } return records; } /** Resets the internal counter. */ reset(): void { this._counter = 0; } /** Returns the current counter value. */ get counter(): number { return this._counter; } } /** * Creates a fixture factory that generates records with incrementing IDs * and customizable overrides. */ export function createFixtureFactory( defaults: (index: number) => T ): FixtureFactory { return new FixtureFactory(defaults); } /** * Simple in-memory seed store for testing seed definitions without a real * database. Implements the universal `SeedStore` contract; `AbortSignal` * parameters are accepted for interface compatibility but ignored because * every operation is synchronous in-memory. */ export class InMemorySeedStore implements SeedStore { private readonly _tables = new Map(); async insert( table: string, records: readonly SeedRecord[], _signal?: AbortSignal ): Promise { const existing = this._tables.get(table) ?? []; existing.push(...records); this._tables.set(table, existing); return records.length; } async update( table: string, records: readonly SeedRecord[], keyFields: readonly string[], _signal?: AbortSignal ): Promise { const existing = this._tables.get(table) ?? []; let updated = 0; for (const record of records) { const key = keyFields .map((k) => String(record[k] ?? "")) .join("::"); const idx = existing.findIndex( (r) => keyFields.map((k) => String(r[k] ?? "")).join("::") === key ); if (idx >= 0) { existing[idx] = { ...existing[idx], ...record }; updated++; } } return updated; } async findExisting( table: string, records: readonly SeedRecord[], keyFields: readonly string[], _signal?: AbortSignal ): Promise { const existing = this._tables.get(table) ?? []; const recordKeys = new Set( records.map((r) => keyFields.map((k) => String(r[k] ?? "")).join("::") ) ); return existing.filter((r) => { const key = keyFields.map((k) => String(r[k] ?? "")).join("::"); return recordKeys.has(key); }); } async truncate(table: string, _signal?: AbortSignal): Promise { this._tables.set(table, []); } async tableExists(table: string, _signal?: AbortSignal): Promise { return this._tables.has(table); } /** Returns a defensive copy of all records in a table (for test assertions). */ getRecords(table: string): SeedRecord[] { return [...(this._tables.get(table) ?? [])]; } /** Clears all tables. */ clear(): void { this._tables.clear(); } } /** * Creates a simple in-memory seed store for testing. Records are stored in a * map keyed by table name. */ export function createInMemoryStore(): InMemorySeedStore { return new InMemorySeedStore(); }