235 lines
7.2 KiB
TypeScript
235 lines
7.2 KiB
TypeScript
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 };
|