Initial Commit
This commit is contained in:
141
packages/persistence/database/seed/src/fixtures/fixtures.ts
Normal file
141
packages/persistence/database/seed/src/fixtures/fixtures.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import type { SeedRecord, SeedStore } from "../types/common.js";
|
||||
|
||||
/**
|
||||
* Factory for generating test fixture data with auto-incrementing indices
|
||||
* and customizable overrides.
|
||||
*/
|
||||
export class FixtureFactory<T extends SeedRecord> {
|
||||
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>): 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>[]): 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<T extends SeedRecord>(
|
||||
defaults: (index: number) => T
|
||||
): FixtureFactory<T> {
|
||||
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<string, SeedRecord[]>();
|
||||
|
||||
async insert(
|
||||
table: string,
|
||||
records: readonly SeedRecord[],
|
||||
_signal?: AbortSignal
|
||||
): Promise<number> {
|
||||
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<number> {
|
||||
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<SeedRecord[]> {
|
||||
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<void> {
|
||||
this._tables.set(table, []);
|
||||
}
|
||||
|
||||
async tableExists(table: string, _signal?: AbortSignal): Promise<boolean> {
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user