import { IDBFactory } from "fake-indexeddb"; import type { IdbDatabaseLike, IdbFactoryLike, IdbObjectStoreLike, IdbOpenDbRequestLike, IdbRequestLike, IdbTransactionLike, } from "../src/types/idb-like.types.js"; let dbCounter = 0; /** * Wrap a `fake-indexeddb` IDBFactory in our structural surface. */ export function fakeFactory(): IdbFactoryLike { const real = new IDBFactory(); return { open: (n, v) => real.open(n, v) as unknown as IdbOpenDbRequestLike, }; } export function uniqueDbName(prefix = "idb"): string { dbCounter++; return `${prefix}-${dbCounter}-${Date.now()}-${Math.random()}`; } interface InjectOptions { failPutOn?: string; failClearWith?: string; } /** * Asynchronously deliver a failure to a request via its onerror handler. * Used by the synthetic IDB factory below — we wait one macrotask so the * caller has time to install onsuccess/onerror handlers before we trigger * the failure. */ function deliverError(req: IdbRequestLike, errorName: string): void { setTimeout(() => { (req as unknown as { error: DOMException }).error = Object.assign( new Error("induced"), { name: errorName } ) as unknown as DOMException; req.onerror?.(new Event("error")); }, 0); } function deliverSuccess(req: IdbRequestLike, result: unknown): void { setTimeout(() => { (req as unknown as { result: unknown }).result = result; req.onsuccess?.(new Event("success")); }, 0); } /** * Build a fully synthetic IdbFactoryLike that supports get/openCursor * over an in-memory Map but fails put / clear with a configurable * DOMException name. This is a REAL implementation of the structural * IdbFactoryLike interface — no mocks, no monkey-patching. */ export function injectingFactory(options: InjectOptions): IdbFactoryLike { const data = new Map(); function makeStore(): IdbObjectStoreLike { return { put(value) { const req: IdbRequestLike = { result: undefined, error: null, onsuccess: null, onerror: null, }; if (options.failPutOn !== undefined) { deliverError(req, options.failPutOn); } else { const v = value as { key: string }; data.set(v.key, v); deliverSuccess(req, undefined); } return req; }, get(key) { const req: IdbRequestLike = { result: undefined, error: null, onsuccess: null, onerror: null, }; deliverSuccess(req, data.get(key as string)); return req; }, delete(key) { const req: IdbRequestLike = { result: undefined, error: null, onsuccess: null, onerror: null, }; data.delete(key as string); deliverSuccess(req, undefined); return req; }, clear() { const req: IdbRequestLike = { result: undefined, error: null, onsuccess: null, onerror: null, }; if (options.failClearWith !== undefined) { deliverError(req, options.failClearWith); } else { data.clear(); deliverSuccess(req, undefined); } return req; }, count() { const req: IdbRequestLike = { result: data.size, error: null, onsuccess: null, onerror: null, }; deliverSuccess(req, data.size); return req; }, openCursor() { const req: IdbRequestLike = { result: null, error: null, onsuccess: null, onerror: null, }; // Emit one cursor event per entry, then a final null. const entries = [...data.entries()]; setTimeout(() => { let i = 0; const step = (): void => { if (i >= entries.length) { (req as unknown as { result: unknown }).result = null; req.onsuccess?.(new Event("success")); return; } const [_key, val] = entries[i] as [string, unknown]; i++; const cursor = { value: val, key: _key, continue: () => step(), }; (req as unknown as { result: unknown }).result = cursor; req.onsuccess?.(new Event("success")); }; step(); }, 0); return req; }, createIndex() { return undefined; }, index() { return undefined; }, } satisfies IdbObjectStoreLike; } function makeTransaction(): IdbTransactionLike { const store = makeStore(); const tx: IdbTransactionLike = { objectStore() { return store; }, oncomplete: null, onerror: null, onabort: null, abort() { /* no-op */ }, error: null, }; // Auto-complete after all queued microtasks settle. setTimeout(() => tx.oncomplete?.(new Event("complete")), 5); return tx; } function makeDb(): IdbDatabaseLike { return { name: "synthetic", version: 1, objectStoreNames: { contains: () => true }, close() { /* no-op */ }, transaction() { return makeTransaction(); }, createObjectStore() { return makeStore(); }, }; } return { open() { const req: IdbOpenDbRequestLike = { result: makeDb(), error: null, onsuccess: null, onerror: null, onupgradeneeded: null, onblocked: null, }; setTimeout(() => req.onsuccess?.(new Event("success")), 0); return req; }, }; } /** * Build an `IdbFactoryLike` whose `open` method throws synchronously * with a controlled DOMException name. */ export function syncThrowingFactory(name: string): IdbFactoryLike { return { open() { throw Object.assign(new Error("induced"), { name }); }, }; } export type { IdbFactoryLike, IdbRequestLike };