Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
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<string, unknown>();
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 };

View File

@@ -0,0 +1,4 @@
// Polyfills for the test environment. The injectable IDBFactory adapter in
// `idb-adapter` means the package itself never reaches for `globalThis.indexedDB`
// during tests — tests pass a real `fake-indexeddb` factory directly to the
// constructor. No globals are mutated here.