@prisma-idb/driver-idb 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/control.d.mts +1 -0
- package/dist/control.d.mts.map +1 -1
- package/dist/control.mjs +1 -0
- package/dist/control.mjs.map +1 -1
- package/dist/runtime.d.mts +4 -1
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +93 -26
- package/dist/runtime.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,6 +10,12 @@ npm install --save-dev @prisma-idb/driver-idb
|
|
|
10
10
|
|
|
11
11
|
Part of [Prisma 8 IDB](https://prisma-idb.dev/docs/prisma-8), a typed IndexedDB client for Prisma 8. Start with the [Quick Start](https://prisma-idb.dev/docs/prisma-8/getting-started). For how the packages fit together, see [ARCHITECTURE.md](https://github.com/prisma-idb/prisma-idb/blob/main/packages/prisma-orm/docs/ARCHITECTURE.md).
|
|
12
12
|
|
|
13
|
+
## Update identity
|
|
14
|
+
|
|
15
|
+
`update` and `scan-write` preserve the existing primary key. Changing an inline key rejects with `IdbExecuteError` code `PRIMARY_KEY_CHANGE_UNSUPPORTED` and aborts the transaction. Repeating an equivalent key is allowed, including compound, date and binary keys.
|
|
16
|
+
|
|
17
|
+
Primary keys are immutable. Delete the row and create it again only after handling dependent records. Restrictive relations can block the delete, and cascading relations can delete dependent records. See [ADR 020](../docs/adrs/ADR%20020%20-%20Primary%20Keys%20Are%20Immutable.md) for the reasons.
|
|
18
|
+
|
|
13
19
|
## License
|
|
14
20
|
|
|
15
21
|
MIT
|
package/dist/control.d.mts
CHANGED
|
@@ -13,6 +13,7 @@ import { ControlDriverDescriptor, ControlDriverInstance } from "@prisma/orm-fram
|
|
|
13
13
|
declare class IdbControlDriver implements ControlDriverInstance<"idb", "idb"> {
|
|
14
14
|
readonly familyId: "idb";
|
|
15
15
|
readonly targetId: "idb";
|
|
16
|
+
/** Return no rows because the Node control driver has no browser database to query. */
|
|
16
17
|
query<Row = Record<string, unknown>>(_sql: string, _params?: readonly unknown[]): Promise<{
|
|
17
18
|
readonly rows: Row[];
|
|
18
19
|
}>;
|
package/dist/control.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control.d.mts","names":[],"sources":["../src/exports/control.ts"],"mappings":";;;;;;;;;;;;cAaM,4BAA4B;WACvB;WACA
|
|
1
|
+
{"version":3,"file":"control.d.mts","names":[],"sources":["../src/exports/control.ts"],"mappings":";;;;;;;;;;;;cAaM,4BAA4B;WACvB;WACA;;EAGH,MAAM,MAAM,yBAChB,cACA,+BACC;aAAmB,MAAM;;EAItB,SAAS;;cAGX,4BAA4B,sCAAsC"}
|
package/dist/control.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import { t as idbDriverDescriptorMeta } from "./descriptor-meta-CuQTSQ3P.mjs";
|
|
|
13
13
|
var IdbControlDriver = class {
|
|
14
14
|
familyId = "idb";
|
|
15
15
|
targetId = "idb";
|
|
16
|
+
/** Return no rows because the Node control driver has no browser database to query. */
|
|
16
17
|
async query(_sql, _params) {
|
|
17
18
|
return { rows: [] };
|
|
18
19
|
}
|
package/dist/control.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control.mjs","names":[],"sources":["../src/exports/control.ts"],"sourcesContent":["import type { ControlDriverDescriptor, ControlDriverInstance } from \"@prisma/orm-framework/components/control\";\nimport { idbDriverDescriptorMeta } from \"../core/descriptor-meta\";\n\n/**\n * Control-plane driver stub for IndexedDB.\n *\n * `ControlDriverInstance` is a SQL-flavoured interface (it exposes `query()`\n * and `close()`), which does not map onto IndexedDB. All IDB control-plane\n * operations (`verify`, `sign`, `db init`, etc.) return structured refusals\n * because IndexedDB only exists in the browser; the CLI runs in Node.js.\n *\n * @see IdbControlFamilyInstance — where the refusal logic lives.\n */\nclass IdbControlDriver implements ControlDriverInstance<\"idb\", \"idb\"> {\n readonly familyId = \"idb\" as const;\n readonly targetId = \"idb\" as const;\n\n async query<Row = Record<string, unknown>>(\n _sql: string,\n _params?: readonly unknown[]\n ): Promise<{ readonly rows: Row[] }> {\n return { rows: [] };\n }\n\n async close(): Promise<void> {}\n}\n\nconst idbControlDriverDescriptor: ControlDriverDescriptor<\"idb\", \"idb\", IdbControlDriver> = {\n ...idbDriverDescriptorMeta,\n async create(_connection: string): Promise<IdbControlDriver> {\n return new IdbControlDriver();\n },\n};\n\nexport default idbControlDriverDescriptor;\n"],"mappings":";;;;;;;;;;;;AAaA,IAAM,mBAAN,MAAsE;CACpE,WAAoB;CACpB,WAAoB
|
|
1
|
+
{"version":3,"file":"control.mjs","names":[],"sources":["../src/exports/control.ts"],"sourcesContent":["import type { ControlDriverDescriptor, ControlDriverInstance } from \"@prisma/orm-framework/components/control\";\nimport { idbDriverDescriptorMeta } from \"../core/descriptor-meta\";\n\n/**\n * Control-plane driver stub for IndexedDB.\n *\n * `ControlDriverInstance` is a SQL-flavoured interface (it exposes `query()`\n * and `close()`), which does not map onto IndexedDB. All IDB control-plane\n * operations (`verify`, `sign`, `db init`, etc.) return structured refusals\n * because IndexedDB only exists in the browser; the CLI runs in Node.js.\n *\n * @see IdbControlFamilyInstance — where the refusal logic lives.\n */\nclass IdbControlDriver implements ControlDriverInstance<\"idb\", \"idb\"> {\n readonly familyId = \"idb\" as const;\n readonly targetId = \"idb\" as const;\n\n /** Return no rows because the Node control driver has no browser database to query. */\n async query<Row = Record<string, unknown>>(\n _sql: string,\n _params?: readonly unknown[]\n ): Promise<{ readonly rows: Row[] }> {\n return { rows: [] };\n }\n\n async close(): Promise<void> {}\n}\n\nconst idbControlDriverDescriptor: ControlDriverDescriptor<\"idb\", \"idb\", IdbControlDriver> = {\n ...idbDriverDescriptorMeta,\n async create(_connection: string): Promise<IdbControlDriver> {\n return new IdbControlDriver();\n },\n};\n\nexport default idbControlDriverDescriptor;\n"],"mappings":";;;;;;;;;;;;AAaA,IAAM,mBAAN,MAAsE;CACpE,WAAoB;CACpB,WAAoB;;CAGpB,MAAM,MACJ,MACA,SACmC;EACnC,OAAO,EAAE,MAAM,CAAC,EAAE;CACpB;CAEA,MAAM,QAAuB,CAAC;AAChC;AAEA,MAAM,6BAAsF;CAC1F,GAAG;CACH,MAAM,OAAO,aAAgD;EAC3D,OAAO,IAAI,iBAAiB;CAC9B;AACF"}
|
package/dist/runtime.d.mts
CHANGED
|
@@ -296,7 +296,9 @@ interface IdbBatchPlan extends ExecutionPlan {
|
|
|
296
296
|
type IdbPlanBody = IdbAtomicPlan | IdbBatchPlan;
|
|
297
297
|
//#endregion
|
|
298
298
|
//#region src/core/transaction-scope.d.ts
|
|
299
|
+
/** A record returned by an IDB operation. */
|
|
299
300
|
type Row = Record<string, unknown>;
|
|
301
|
+
/** Atomic operations and commit or rollback controls over one multi-store transaction. */
|
|
300
302
|
interface IdbTransactionScope {
|
|
301
303
|
/**
|
|
302
304
|
* Execute a single atomic plan inside the shared IDB transaction.
|
|
@@ -330,6 +332,7 @@ interface IdbTransactionScope {
|
|
|
330
332
|
declare function createTransactionScope(db: IDBDatabase, storeNames: string[], mode?: IDBTransactionMode): IdbTransactionScope;
|
|
331
333
|
//#endregion
|
|
332
334
|
//#region src/core/idb-driver.d.ts
|
|
335
|
+
/** Own the database connection and execute driver plans after it opens. */
|
|
333
336
|
declare class IdbRuntimeDriverInstance implements RuntimeDriverInstance<"idb", "idb"> {
|
|
334
337
|
readonly familyId: "idb";
|
|
335
338
|
readonly targetId: "idb";
|
|
@@ -382,7 +385,7 @@ declare class IdbRuntimeDriverInstance implements RuntimeDriverInstance<"idb", "
|
|
|
382
385
|
* handle specific failure modes (e.g. retry on TRANSACTION_ABORTED) can
|
|
383
386
|
* branch on `error.code` without string-matching the message.
|
|
384
387
|
*/
|
|
385
|
-
type IdbExecuteErrorCode = "STORE_NOT_FOUND" | "KEY_GET_FAILED" | "GET_ALL_FAILED" | "CURSOR_SCAN_FAILED" | "KEYS_FAILED" | "COUNT_FAILED" | "ADD_FAILED" | "PUT_FAILED" | "DELETE_FAILED" | "BATCH_FAILED" | "TRANSACTION_ABORTED" | "TRANSACTION_INACTIVE";
|
|
388
|
+
type IdbExecuteErrorCode = "PRIMARY_KEY_CHANGE_UNSUPPORTED" | "STORE_NOT_FOUND" | "KEY_GET_FAILED" | "GET_ALL_FAILED" | "CURSOR_SCAN_FAILED" | "KEYS_FAILED" | "COUNT_FAILED" | "ADD_FAILED" | "PUT_FAILED" | "DELETE_FAILED" | "BATCH_FAILED" | "TRANSACTION_ABORTED" | "TRANSACTION_INACTIVE";
|
|
386
389
|
/**
|
|
387
390
|
* Structured error raised by the IDB driver's `execute()` path.
|
|
388
391
|
*
|
package/dist/runtime.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/core/plan-body.ts","../src/core/transaction-scope.ts","../src/core/idb-driver.ts","../src/core/execute/error.ts","../src/exports/runtime.ts"],"mappings":";;;;;;;;;;;;;cAca;;;;;;;;;;;;;;UAeI;;WAEN;;WAEA;;WAEA;;WAEA,YAAY;WACZ;WACA;WACA;WACA;WACA,OAAO;;;;;;;;;;;;;;KAeN;WACG;WAAuB,KAAK;;WAC5B;WAAwB,KAAK;WAAsB;;WACnD;WAAwB,KAAK;WAAsB;;WAEnD;WACA,OAAO;WACP,OAAO;WACP;WACA;;;KAIH;;;;;KAMA,gBAAgB,KAAK;;;;;KAMrB,oBAAoB,GAAG,yBAAyB,GAAG;;;;;;;;;;;;;;;;UAiB9C,0BAA0B;WAChC;WACA;WACA;WACA,QAAQ;WACR,YAAY;WACZ,SAAS;WACT,aAAa;WACb;WACA;;;;;;;UAQM,sBAAsB;WAC5B;WACA;WACA,KAAK;;;;;;;;;;;;;UAcC,sBAAsB;WAC5B;WACA;WACA;WACA,QAAQ;WACR;;;;;;;;;;;UAYM,mBAAmB;WACzB;WACA;WACA,QAAQ;WACR,MAAM;;;;;;;;;;;UAYA,mBAAmB;WACzB;WACA;WACA,QAAQ;WACR,MAAM;;;;;;;;;;;;;;;;;UAkBA,sBAAsB;WAC5B;WACA;WACA,KAAK;WACL,OAAO;;;;;;;;;;UAWD,sBAAsB;WAC5B;WACA;WACA,KAAK,cAAc;;;;;;;;;;;;;;;;;;;;UAqBb,yBAAyB;WAC/B;WACA;WACA;WACA,QAAQ;WACR,SAAS;WACT;WACA;WACA,QAAQ;;;;;;;;;;;;;UAcF,qBAAqB;WAC3B;WACA;WACA;WACA,QAAQ;;;;;;;;;;;;;;;;;;UAmBF,oBAAoB;WAC1B;WACA;WACA;WACA,QAAQ;WACR;;;;;KAMC,gBACR,oBACA,gBACA,gBACA,eACA,cACA,aACA,aACA,gBACA,gBACA;;;;;;;;;;;;;;UAea,qBAAqB;WAC3B;WACA,YAAY;WACZ,KAAK,cAAc;;;;;KAMlB,cAAc,gBAAgB
|
|
1
|
+
{"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/core/plan-body.ts","../src/core/transaction-scope.ts","../src/core/idb-driver.ts","../src/core/execute/error.ts","../src/exports/runtime.ts"],"mappings":";;;;;;;;;;;;;cAca;;;;;;;;;;;;;;UAeI;;WAEN;;WAEA;;WAEA;;WAEA,YAAY;WACZ;WACA;WACA;WACA;WACA,OAAO;;;;;;;;;;;;;;KAeN;WACG;WAAuB,KAAK;;WAC5B;WAAwB,KAAK;WAAsB;;WACnD;WAAwB,KAAK;WAAsB;;WAEnD;WACA,OAAO;WACP,OAAO;WACP;WACA;;;KAIH;;;;;KAMA,gBAAgB,KAAK;;;;;KAMrB,oBAAoB,GAAG,yBAAyB,GAAG;;;;;;;;;;;;;;;;UAiB9C,0BAA0B;WAChC;WACA;WACA;WACA,QAAQ;WACR,YAAY;WACZ,SAAS;WACT,aAAa;WACb;WACA;;;;;;;UAQM,sBAAsB;WAC5B;WACA;WACA,KAAK;;;;;;;;;;;;;UAcC,sBAAsB;WAC5B;WACA;WACA;WACA,QAAQ;WACR;;;;;;;;;;;UAYM,mBAAmB;WACzB;WACA;WACA,QAAQ;WACR,MAAM;;;;;;;;;;;UAYA,mBAAmB;WACzB;WACA;WACA,QAAQ;WACR,MAAM;;;;;;;;;;;;;;;;;UAkBA,sBAAsB;WAC5B;WACA;WACA,KAAK;WACL,OAAO;;;;;;;;;;UAWD,sBAAsB;WAC5B;WACA;WACA,KAAK,cAAc;;;;;;;;;;;;;;;;;;;;UAqBb,yBAAyB;WAC/B;WACA;WACA;WACA,QAAQ;WACR,SAAS;WACT;WACA;WACA,QAAQ;;;;;;;;;;;;;UAcF,qBAAqB;WAC3B;WACA;WACA;WACA,QAAQ;;;;;;;;;;;;;;;;;;UAmBF,oBAAoB;WAC1B;WACA;WACA;WACA,QAAQ;WACR;;;;;KAMC,gBACR,oBACA,gBACA,gBACA,eACA,cACA,aACA,aACA,gBACA,gBACA;;;;;;;;;;;;;;UAea,qBAAqB;WAC3B;WACA,YAAY;WACZ,KAAK,cAAc;;;;;KAMlB,cAAc,gBAAgB;;;;KCnSrC,MAAM;;UAKM;;;;;;;;EAQf,QAAQ,MAAM,gBAAgB,QAAQ;;;;;;;EAQtC,UAAU;;;;;;;EAQV;;;;;;;;iBAkGc,uBACd,IAAI,aACJ,sBACA,OAAM,qBACL;;;;cCvJU,oCAAoC;WACtC;WACA;;;;;;;;WAQA,IAAI,QAAQ;EAET,YAAA,gBAAgB,kBAAkB,UAAU;EAIlD,SAAS;;;;;;;;;;EAaT,cAAc,QAAQ;;;;;;;;;EA4BtB,YAAY,sBAAsB,OAAM,qBAAmC,QAAQ;;;;;;;;;;EAczF,QAAQ,MAAM,cAAc,cAAc;;;;;;;;;;;KCtEhC;;;;;;;;cAsBC,wBAAwB;WAC1B,MAAM;WACN;WACA;;WAEA;;WAEA;WACS;EAGhB,YAAA;aACW,MAAM;aACN;aACA;aACA;KAEX;;;;;;;;;;;;;;;;;;;;;;;;iBCKY,uBACd,gBACA,kBACA;WAAqB,UAAU;IAC9B,4CAA4C"}
|
package/dist/runtime.mjs
CHANGED
|
@@ -65,6 +65,7 @@ function keyIdentity(key) {
|
|
|
65
65
|
}
|
|
66
66
|
/** The `byteLength` getter throws unless its receiver has an `[[ArrayBufferData]]` slot, in any realm. */
|
|
67
67
|
const arrayBufferByteLength = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get;
|
|
68
|
+
/** Detect the ArrayBuffer internal slot across realms without relying on instanceof. */
|
|
68
69
|
function isArrayBuffer(value) {
|
|
69
70
|
try {
|
|
70
71
|
arrayBufferByteLength.call(value);
|
|
@@ -120,6 +121,29 @@ function storeTarget(plan) {
|
|
|
120
121
|
const index = plan.indexName !== void 0 ? ` (index "${plan.indexName}")` : "";
|
|
121
122
|
return `store "${plan.storeName}"${index}`;
|
|
122
123
|
}
|
|
124
|
+
/** Route synchronous event-handler failures through the same channel as request errors. */
|
|
125
|
+
function guardCallback(plan, code, what, onError, run) {
|
|
126
|
+
return () => {
|
|
127
|
+
try {
|
|
128
|
+
run();
|
|
129
|
+
} catch (cause) {
|
|
130
|
+
onError(cause instanceof IdbExecuteError ? cause : opError(code, plan, what, cause));
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Updates preserve identity. A key move needs referential and sync delete/create semantics. */
|
|
135
|
+
function assertUnchangedPrimaryKey(store, plan, key, row) {
|
|
136
|
+
const keyPath = store.keyPath;
|
|
137
|
+
if (keyPath == null) return;
|
|
138
|
+
/** Resolve an inline key path, including dotted paths, from the merged record. */
|
|
139
|
+
const extract = (path) => path === "" ? row : path.split(".").reduce((value, field) => value !== null && typeof value === "object" ? value[field] : void 0, row);
|
|
140
|
+
const nextKey = typeof keyPath === "string" ? extract(keyPath) : keyPath.map(extract);
|
|
141
|
+
if (indexedDB.cmp(key, nextKey) !== 0) throw new IdbExecuteError({
|
|
142
|
+
code: "PRIMARY_KEY_CHANGE_UNSUPPORTED",
|
|
143
|
+
planKind: plan.kind,
|
|
144
|
+
storeName: plan.storeName
|
|
145
|
+
}, `Cannot change the primary key of a row in store "${plan.storeName}". Primary keys are immutable. Delete the row and create it again only after handling dependent records. Restrictive relations can block the delete, and cascading relations can delete dependent records.`);
|
|
146
|
+
}
|
|
123
147
|
/**
|
|
124
148
|
* Dispatch an atomic plan to its IDB operation within a live transaction.
|
|
125
149
|
*
|
|
@@ -132,9 +156,10 @@ function executeOpInTx(store, plan, onComplete, onError) {
|
|
|
132
156
|
dispatchOp(store, plan, onComplete, onError);
|
|
133
157
|
} catch (err) {
|
|
134
158
|
if (isTransactionInactiveError(err)) return onError(transactionInactiveError(plan.kind, plan.storeName, err));
|
|
135
|
-
|
|
159
|
+
onError(err);
|
|
136
160
|
}
|
|
137
161
|
}
|
|
162
|
+
/** Select the executor for an atomic plan without introducing asynchronous gaps. */
|
|
138
163
|
function dispatchOp(store, plan, onComplete, onError) {
|
|
139
164
|
switch (plan.kind) {
|
|
140
165
|
case "key-get": return execKeyGet(store, plan, onComplete, onError);
|
|
@@ -154,6 +179,7 @@ function dispatchOp(store, plan, onComplete, onError) {
|
|
|
154
179
|
function planTxMode(plan) {
|
|
155
180
|
return plan.kind === "add" || plan.kind === "put" || plan.kind === "update" || plan.kind === "delete" || plan.kind === "scan-write" ? "readwrite" : "readonly";
|
|
156
181
|
}
|
|
182
|
+
/** Read one primary key and return no rows when it is absent. */
|
|
157
183
|
function execKeyGet(store, plan, onComplete, onError) {
|
|
158
184
|
const req = store.get(plan.key);
|
|
159
185
|
req.onsuccess = () => {
|
|
@@ -162,12 +188,14 @@ function execKeyGet(store, plan, onComplete, onError) {
|
|
|
162
188
|
};
|
|
163
189
|
req.onerror = () => onError(opError("KEY_GET_FAILED", plan, "key-get", req.error));
|
|
164
190
|
}
|
|
191
|
+
/** Read a bounded range from a store or index, handling a zero cap explicitly. */
|
|
165
192
|
function execGetAll(store, plan, onComplete, onError) {
|
|
166
193
|
if (plan.count === 0) return onComplete([]);
|
|
167
194
|
const req = (plan.indexName !== void 0 ? store.index(plan.indexName) : store).getAll(toOptionalIdbKeyRange(plan.range), plan.count);
|
|
168
195
|
req.onsuccess = () => onComplete(req.result);
|
|
169
196
|
req.onerror = () => onError(opError("GET_ALL_FAILED", plan, "get-all", req.error, storeTarget(plan)));
|
|
170
197
|
}
|
|
198
|
+
/** Count a store or index range without materializing record values. */
|
|
171
199
|
function execCount(store, plan, onComplete, onError) {
|
|
172
200
|
const source = plan.indexName !== void 0 ? store.index(plan.indexName) : store;
|
|
173
201
|
const range = toOptionalIdbKeyRange(plan.range);
|
|
@@ -175,6 +203,7 @@ function execCount(store, plan, onComplete, onError) {
|
|
|
175
203
|
req.onsuccess = () => onComplete([{ count: req.result }]);
|
|
176
204
|
req.onerror = () => onError(opError("COUNT_FAILED", plan, "count", req.error, storeTarget(plan)));
|
|
177
205
|
}
|
|
206
|
+
/** Read primary keys for existence checks without fetching record values. */
|
|
178
207
|
function execKeys(store, plan, onComplete, onError) {
|
|
179
208
|
const source = plan.indexName !== void 0 ? store.index(plan.indexName) : store;
|
|
180
209
|
const fail = (cause) => onError(opError("KEYS_FAILED", plan, "keys read", cause, storeTarget(plan)));
|
|
@@ -193,6 +222,7 @@ function execKeys(store, plan, onComplete, onError) {
|
|
|
193
222
|
req.onsuccess = () => onComplete(req.result.map((key) => ({ key })));
|
|
194
223
|
req.onerror = () => fail(req.error);
|
|
195
224
|
}
|
|
225
|
+
/** Filter cursor rows and stop at the page limit unless sorting needs the full range. */
|
|
196
226
|
function execCursorScan(store, plan, onComplete, onError) {
|
|
197
227
|
const comparator = plan.comparator;
|
|
198
228
|
const filter = plan.filter;
|
|
@@ -202,7 +232,7 @@ function execCursorScan(store, plan, onComplete, onError) {
|
|
|
202
232
|
const req = (plan.indexName !== void 0 ? store.index(plan.indexName) : store).openCursor(toOptionalIdbKeyRange(plan.range) ?? null, plan.direction ?? "next");
|
|
203
233
|
const collected = [];
|
|
204
234
|
let skippedCount = 0;
|
|
205
|
-
req.onsuccess = () => {
|
|
235
|
+
req.onsuccess = guardCallback(plan, "CURSOR_SCAN_FAILED", "cursor-scan callback", onError, () => {
|
|
206
236
|
const cursor = req.result;
|
|
207
237
|
if (cursor === null) {
|
|
208
238
|
if (comparator !== void 0) {
|
|
@@ -225,7 +255,7 @@ function execCursorScan(store, plan, onComplete, onError) {
|
|
|
225
255
|
}
|
|
226
256
|
}
|
|
227
257
|
cursor.continue();
|
|
228
|
-
};
|
|
258
|
+
});
|
|
229
259
|
req.onerror = () => onError(opError("CURSOR_SCAN_FAILED", plan, "cursor-scan", req.error));
|
|
230
260
|
}
|
|
231
261
|
/**
|
|
@@ -241,33 +271,38 @@ function withGeneratedKey(store, record, key) {
|
|
|
241
271
|
[store.keyPath]: key
|
|
242
272
|
};
|
|
243
273
|
}
|
|
274
|
+
/** Insert without overwriting, then echo any generated inline key. */
|
|
244
275
|
function execAdd(store, plan, onComplete, onError) {
|
|
245
276
|
const req = plan.key !== void 0 ? store.add(plan.record, plan.key) : store.add(plan.record);
|
|
246
277
|
req.onsuccess = () => onComplete([withGeneratedKey(store, plan.record, req.result)]);
|
|
247
278
|
req.onerror = () => onError(opError("ADD_FAILED", plan, "add", req.error));
|
|
248
279
|
}
|
|
280
|
+
/** Replace or insert a record and echo any generated inline key. */
|
|
249
281
|
function execPut(store, plan, onComplete, onError) {
|
|
250
282
|
const req = plan.key !== void 0 ? store.put(plan.record, plan.key) : store.put(plan.record);
|
|
251
283
|
req.onsuccess = () => onComplete([withGeneratedKey(store, plan.record, req.result)]);
|
|
252
284
|
req.onerror = () => onError(opError("PUT_FAILED", plan, "put", req.error));
|
|
253
285
|
}
|
|
286
|
+
/** Read, merge, and write a patch in one transaction while preserving primary-key identity. */
|
|
254
287
|
function execUpdate(store, plan, onComplete, onError) {
|
|
255
288
|
const getReq = store.get(plan.key);
|
|
256
|
-
getReq.onsuccess = () => {
|
|
289
|
+
getReq.onsuccess = guardCallback(plan, "PUT_FAILED", "update (put phase)", onError, () => {
|
|
257
290
|
const merged = {
|
|
258
291
|
...getReq.result ?? {},
|
|
259
292
|
...plan.patch
|
|
260
293
|
};
|
|
261
|
-
|
|
294
|
+
assertUnchangedPrimaryKey(store, plan, plan.key, merged);
|
|
295
|
+
const putReq = store.keyPath === null ? store.put(merged, plan.key) : store.put(merged);
|
|
262
296
|
putReq.onsuccess = () => onComplete([merged]);
|
|
263
297
|
putReq.onerror = () => onError(opError("PUT_FAILED", plan, "update (put phase)", putReq.error));
|
|
264
|
-
};
|
|
298
|
+
});
|
|
265
299
|
getReq.onerror = () => onError(opError("KEY_GET_FAILED", plan, "update (get phase)", getReq.error));
|
|
266
300
|
}
|
|
301
|
+
/** Delete each key in the requested range and echo its previous row. */
|
|
267
302
|
function execDelete(store, plan, onComplete, onError) {
|
|
268
303
|
const req = store.openCursor(plan.key);
|
|
269
304
|
const collected = [];
|
|
270
|
-
req.onsuccess = () => {
|
|
305
|
+
req.onsuccess = guardCallback(plan, "DELETE_FAILED", "delete callback", onError, () => {
|
|
271
306
|
const cursor = req.result;
|
|
272
307
|
if (cursor === null) {
|
|
273
308
|
onComplete(collected);
|
|
@@ -275,17 +310,18 @@ function execDelete(store, plan, onComplete, onError) {
|
|
|
275
310
|
}
|
|
276
311
|
collected.push(cursor.value);
|
|
277
312
|
const delReq = cursor.delete();
|
|
278
|
-
delReq.onsuccess = () => cursor.continue();
|
|
313
|
+
delReq.onsuccess = guardCallback(plan, "DELETE_FAILED", "delete callback", onError, () => cursor.continue());
|
|
279
314
|
delReq.onerror = () => onError(opError("DELETE_FAILED", plan, "delete", delReq.error));
|
|
280
|
-
};
|
|
315
|
+
});
|
|
281
316
|
req.onerror = () => onError(opError("DELETE_FAILED", plan, "delete", req.error));
|
|
282
317
|
}
|
|
318
|
+
/** Write each matching cursor row once, even when its indexed value moves forward. */
|
|
283
319
|
function execScanWrite(store, plan, onComplete, onError) {
|
|
284
320
|
if (plan.take === 0) return onComplete([]);
|
|
285
321
|
const req = (plan.indexName !== void 0 ? store.index(plan.indexName) : store).openCursor(toOptionalIdbKeyRange(plan.range) ?? null, "next");
|
|
286
322
|
const collected = [];
|
|
287
323
|
const writtenKeys = plan.indexName !== void 0 && plan.write === "put-merged" ? /* @__PURE__ */ new Set() : void 0;
|
|
288
|
-
req.onsuccess = () => {
|
|
324
|
+
req.onsuccess = guardCallback(plan, "PUT_FAILED", "scan-write callback", onError, () => {
|
|
289
325
|
const cursor = req.result;
|
|
290
326
|
if (cursor === null) {
|
|
291
327
|
onComplete(collected);
|
|
@@ -303,21 +339,22 @@ function execScanWrite(store, plan, onComplete, onError) {
|
|
|
303
339
|
if (plan.write === "delete") {
|
|
304
340
|
collected.push(row);
|
|
305
341
|
const delReq = cursor.delete();
|
|
306
|
-
delReq.onsuccess = () => {
|
|
342
|
+
delReq.onsuccess = guardCallback(plan, "PUT_FAILED", "scan-write callback", onError, () => {
|
|
307
343
|
if (plan.take !== void 0 && collected.length >= plan.take) {
|
|
308
344
|
onComplete(collected);
|
|
309
345
|
return;
|
|
310
346
|
}
|
|
311
347
|
cursor.continue();
|
|
312
|
-
};
|
|
348
|
+
});
|
|
313
349
|
delReq.onerror = () => onError(opError("DELETE_FAILED", plan, "scan-write (delete)", delReq.error));
|
|
314
350
|
} else {
|
|
315
351
|
const merged = {
|
|
316
352
|
...row,
|
|
317
353
|
...plan.patch
|
|
318
354
|
};
|
|
355
|
+
assertUnchangedPrimaryKey(store, plan, cursor.primaryKey, merged);
|
|
319
356
|
const updReq = cursor.update(merged);
|
|
320
|
-
updReq.onsuccess = () => {
|
|
357
|
+
updReq.onsuccess = guardCallback(plan, "PUT_FAILED", "scan-write callback", onError, () => {
|
|
321
358
|
writtenKeys?.add(keyIdentity(cursor.primaryKey));
|
|
322
359
|
collected.push(merged);
|
|
323
360
|
if (plan.take !== void 0 && collected.length >= plan.take) {
|
|
@@ -325,10 +362,10 @@ function execScanWrite(store, plan, onComplete, onError) {
|
|
|
325
362
|
return;
|
|
326
363
|
}
|
|
327
364
|
cursor.continue();
|
|
328
|
-
};
|
|
365
|
+
});
|
|
329
366
|
updReq.onerror = () => onError(opError("PUT_FAILED", plan, "scan-write (put-merged)", updReq.error));
|
|
330
367
|
}
|
|
331
|
-
};
|
|
368
|
+
});
|
|
332
369
|
req.onerror = () => onError(opError("CURSOR_SCAN_FAILED", plan, "scan-write cursor", req.error));
|
|
333
370
|
}
|
|
334
371
|
//#endregion
|
|
@@ -344,6 +381,7 @@ function executeIdbPlan(db, plan) {
|
|
|
344
381
|
if (plan.kind === "batch") return executeBatchPlan(db, plan);
|
|
345
382
|
return executeAtomicPlan(db, plan);
|
|
346
383
|
}
|
|
384
|
+
/** Resolve a single-store operation only after its transaction commits. */
|
|
347
385
|
function executeAtomicPlan(db, plan) {
|
|
348
386
|
return new Promise((resolve, reject) => {
|
|
349
387
|
let tx;
|
|
@@ -373,9 +411,15 @@ function executeAtomicPlan(db, plan) {
|
|
|
373
411
|
}, `IDB transaction aborted on store "${plan.storeName}"`));
|
|
374
412
|
executeOpInTx(tx.objectStore(plan.storeName), plan, (opRows) => {
|
|
375
413
|
for (const row of opRows) rows.push(row);
|
|
376
|
-
}, (err) =>
|
|
414
|
+
}, (err) => {
|
|
415
|
+
reject(err);
|
|
416
|
+
try {
|
|
417
|
+
tx.abort();
|
|
418
|
+
} catch {}
|
|
419
|
+
});
|
|
377
420
|
});
|
|
378
421
|
}
|
|
422
|
+
/** Run all operations in one transaction and deliver rows after commit. */
|
|
379
423
|
function executeBatchPlan(db, plan) {
|
|
380
424
|
return new Promise((resolve, reject) => {
|
|
381
425
|
const mode = plan.ops.some((op) => planTxMode(op) === "readwrite") ? "readwrite" : "readonly";
|
|
@@ -401,7 +445,12 @@ function executeBatchPlan(db, plan) {
|
|
|
401
445
|
code: "TRANSACTION_ABORTED",
|
|
402
446
|
planKind: "batch"
|
|
403
447
|
}, "IDB batch transaction was aborted"));
|
|
404
|
-
runOpsSequentially(tx, plan.ops, 0, rows,
|
|
448
|
+
runOpsSequentially(tx, plan.ops, 0, rows, (err) => {
|
|
449
|
+
reject(err);
|
|
450
|
+
try {
|
|
451
|
+
tx.abort();
|
|
452
|
+
} catch {}
|
|
453
|
+
});
|
|
405
454
|
});
|
|
406
455
|
}
|
|
407
456
|
/**
|
|
@@ -449,9 +498,11 @@ function runOpsSequentially(tx, ops, index, rows, onError) {
|
|
|
449
498
|
const MARKER_STORE_NAME = "_prisma_next_marker";
|
|
450
499
|
//#endregion
|
|
451
500
|
//#region src/core/transaction-scope.ts
|
|
501
|
+
/** Keep pending operations attached to the same transaction and settle them on abort. */
|
|
452
502
|
var IdbTransactionScopeImpl = class {
|
|
453
503
|
#tx;
|
|
454
504
|
#onComplete;
|
|
505
|
+
#pending = /* @__PURE__ */ new Set();
|
|
455
506
|
constructor(tx) {
|
|
456
507
|
this.#tx = tx;
|
|
457
508
|
this.#onComplete = new Promise((resolve, reject) => {
|
|
@@ -461,19 +512,26 @@ var IdbTransactionScopeImpl = class {
|
|
|
461
512
|
planKind: "batch",
|
|
462
513
|
cause: tx.error
|
|
463
514
|
}, `IDB transaction scope failed: ${String(tx.error)}`));
|
|
464
|
-
tx.onabort = () =>
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
515
|
+
tx.onabort = () => {
|
|
516
|
+
const error = new IdbExecuteError({
|
|
517
|
+
code: "TRANSACTION_ABORTED",
|
|
518
|
+
planKind: "batch",
|
|
519
|
+
cause: tx.error
|
|
520
|
+
}, "IDB transaction scope aborted");
|
|
521
|
+
for (const fail of this.#pending) fail(error);
|
|
522
|
+
reject(error);
|
|
523
|
+
};
|
|
468
524
|
});
|
|
469
525
|
this.#onComplete.catch(() => {});
|
|
470
526
|
}
|
|
527
|
+
/** Issue the operation in the existing scope and abort that scope on failure. */
|
|
471
528
|
execute(plan) {
|
|
472
529
|
return new Promise((resolve, reject) => {
|
|
473
530
|
let store;
|
|
474
531
|
try {
|
|
475
532
|
store = this.#tx.objectStore(plan.storeName);
|
|
476
533
|
} catch (err) {
|
|
534
|
+
this.rollback();
|
|
477
535
|
if (isTransactionInactiveError(err)) {
|
|
478
536
|
reject(transactionInactiveError(plan.kind, plan.storeName, err));
|
|
479
537
|
return;
|
|
@@ -486,16 +544,22 @@ var IdbTransactionScopeImpl = class {
|
|
|
486
544
|
}, `IDB transaction scope: store "${plan.storeName}" not found`));
|
|
487
545
|
return;
|
|
488
546
|
}
|
|
489
|
-
const
|
|
547
|
+
const fail = (error) => {
|
|
548
|
+
this.#pending.delete(fail);
|
|
549
|
+
reject(error);
|
|
550
|
+
this.rollback();
|
|
551
|
+
};
|
|
552
|
+
this.#pending.add(fail);
|
|
490
553
|
executeOpInTx(store, plan, (opRows) => {
|
|
491
|
-
|
|
492
|
-
resolve(
|
|
493
|
-
},
|
|
554
|
+
this.#pending.delete(fail);
|
|
555
|
+
resolve(opRows);
|
|
556
|
+
}, fail);
|
|
494
557
|
});
|
|
495
558
|
}
|
|
496
559
|
commit() {
|
|
497
560
|
return this.#onComplete;
|
|
498
561
|
}
|
|
562
|
+
/** Abort uncommitted writes; a finished transaction has nothing left to roll back. */
|
|
499
563
|
rollback() {
|
|
500
564
|
try {
|
|
501
565
|
this.#tx.abort();
|
|
@@ -513,6 +577,7 @@ function createTransactionScope(db, storeNames, mode = "readwrite") {
|
|
|
513
577
|
}
|
|
514
578
|
//#endregion
|
|
515
579
|
//#region src/core/idb-driver.ts
|
|
580
|
+
/** Own the database connection and execute driver plans after it opens. */
|
|
516
581
|
var IdbRuntimeDriverInstance = class {
|
|
517
582
|
familyId = "idb";
|
|
518
583
|
targetId = "idb";
|
|
@@ -575,7 +640,9 @@ var IdbRuntimeDriverInstance = class {
|
|
|
575
640
|
*/
|
|
576
641
|
execute(plan) {
|
|
577
642
|
const dbPromise = this.db;
|
|
578
|
-
return {
|
|
643
|
+
return {
|
|
644
|
+
/** Wait for commit before exposing materialized rows to the runtime. */
|
|
645
|
+
[Symbol.asyncIterator]() {
|
|
579
646
|
return (async function* () {
|
|
580
647
|
yield* await executeIdbPlan(await dbPromise, plan);
|
|
581
648
|
})();
|
package/dist/runtime.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.mjs","names":["unknown","#tx","#onComplete"],"sources":["../src/core/execute/error.ts","../src/core/execute/key-identity.ts","../src/core/execute/key-range.ts","../src/core/execute/ops.ts","../src/core/execute/index.ts","../src/core/plan-body.ts","../src/core/transaction-scope.ts","../src/core/idb-driver.ts","../src/exports/runtime.ts"],"sourcesContent":["/**\n * Stable error codes for IDB execution failures.\n *\n * Each code corresponds to a specific IDB operation. Callers that need to\n * handle specific failure modes (e.g. retry on TRANSACTION_ABORTED) can\n * branch on `error.code` without string-matching the message.\n */\nexport type IdbExecuteErrorCode =\n | \"STORE_NOT_FOUND\" // IDB store requested by plan does not exist in the database\n | \"KEY_GET_FAILED\" // store.get(key) request failed\n | \"GET_ALL_FAILED\" // store.getAll(range) / index.getAll(range) request failed\n | \"CURSOR_SCAN_FAILED\" // openCursor iteration failed\n | \"KEYS_FAILED\" // getKey(range) / getAllKeys(range) request failed\n | \"COUNT_FAILED\" // store.count(range) / index.count(range) request failed\n | \"ADD_FAILED\" // store.add(record) request failed\n | \"PUT_FAILED\" // store.put(record) request failed\n | \"DELETE_FAILED\" // store.delete(key) request failed\n | \"BATCH_FAILED\" // one op inside a batch plan failed\n | \"TRANSACTION_ABORTED\" // IDB transaction aborted before oncomplete fired\n | \"TRANSACTION_INACTIVE\"; // request issued on a transaction that already auto-committed / finished\n\n/**\n * Structured error raised by the IDB driver's `execute()` path.\n *\n * Follows the pattern used in the upstream sql-runtime error normalizers so\n * that callers can rely on stable, machine-readable codes rather than\n * inspecting message strings.\n */\nexport class IdbExecuteError extends Error {\n readonly code: IdbExecuteErrorCode;\n readonly category = \"DRIVER\" as const;\n readonly severity = \"error\" as const;\n /** The `kind` field of the plan that was being executed. */\n readonly planKind: string;\n /** The object store name, when known at the point of failure. */\n readonly storeName: string | undefined;\n override readonly cause: unknown;\n\n constructor(\n details: {\n readonly code: IdbExecuteErrorCode;\n readonly planKind: string;\n readonly storeName?: string;\n readonly cause?: unknown;\n },\n message: string\n ) {\n super(message);\n this.name = \"IdbExecuteError\";\n this.code = details.code;\n this.planKind = details.planKind;\n this.storeName = details.storeName;\n this.cause = details.cause;\n }\n}\n\n/**\n * `true` for the synchronous DOMExceptions IDB throws when a request is issued\n * on a transaction that is no longer active (`TransactionInactiveError`) or has\n * already finished (`InvalidStateError`) — the signature of an `await` on\n * something that isn't an IDB request between two operations (see ADR 005/007).\n */\nexport function isTransactionInactiveError(err: unknown): boolean {\n const name = (err as { name?: unknown } | null)?.name;\n return name === \"TransactionInactiveError\" || name === \"InvalidStateError\";\n}\n\n/** Builds the actionable `TRANSACTION_INACTIVE` error for a failed request/objectStore call. */\nexport function transactionInactiveError(planKind: string, storeName: string, cause: unknown): IdbExecuteError {\n return new IdbExecuteError(\n { code: \"TRANSACTION_INACTIVE\", planKind, storeName, cause },\n `IDB transaction is no longer active when running \"${planKind}\" on store \"${storeName}\". ` +\n `IndexedDB transactions auto-commit as soon as the microtask queue drains with no pending request, ` +\n `so code between two operations must only \\`await\\` IDB-request-resolving promises ` +\n `(no fetch, timers or other async work inside a transaction). See ADR 005/007. Cause: ${String(cause)}`\n );\n}\n","/**\n * A string that is equal for two `IDBValidKey`s exactly when IndexedDB considers the keys equal.\n *\n * `Set` compares arrays, dates and binary keys by reference, so it cannot track primary keys directly.\n * Each key type gets its own prefix so, for example, the string \"1\" and the number 1 stay distinct.\n *\n * Dates and binary keys are detected by their internal slots, not `instanceof`, because a key can come\n * from another realm (for example an iframe), where its constructors differ from this realm's.\n */\nexport function keyIdentity(key: IDBValidKey): string {\n if (typeof key === \"number\") return `n:${Object.is(key, -0) ? 0 : key}`;\n if (typeof key === \"string\") return `s:${JSON.stringify(key)}`;\n if (Array.isArray(key)) return `[${key.map(keyIdentity).join(\",\")}]`;\n if (ArrayBuffer.isView(key)) return binaryIdentity(new Uint8Array(key.buffer, key.byteOffset, key.byteLength));\n if (isArrayBuffer(key)) return binaryIdentity(new Uint8Array(key));\n return `d:${Date.prototype.getTime.call(key)}`;\n}\n\n/** The `byteLength` getter throws unless its receiver has an `[[ArrayBufferData]]` slot, in any realm. */\nconst arrayBufferByteLength = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, \"byteLength\")!.get!;\n\nfunction isArrayBuffer(value: unknown): value is ArrayBuffer {\n try {\n arrayBufferByteLength.call(value);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction binaryIdentity(bytes: Uint8Array): string {\n return `b:${bytes.join(\".\")}`;\n}\n","import type { IdbKeyRangeDescriptor } from \"../plan-body\";\n\n/**\n * Builds the `IDBKeyRange` a plan's range descriptor stands for.\n *\n * The `range` fields of a plan carry descriptors, not `IDBKeyRange` objects, so that part of a plan stays\n * plain data that callers can build, hash and log without touching IndexedDB. Each descriptor maps to the `IDBKeyRange` factory of the same\n * name, so the factory's own validation applies: an invalid key, or `bound` with `lower` above `upper`,\n * throws `DataError`.\n */\nexport function toIdbKeyRange(range: IdbKeyRangeDescriptor): IDBKeyRange {\n switch (range.kind) {\n case \"only\":\n return IDBKeyRange.only(range.key);\n case \"lower\":\n return IDBKeyRange.lowerBound(range.key, range.open);\n case \"upper\":\n return IDBKeyRange.upperBound(range.key, range.open);\n case \"bound\":\n return IDBKeyRange.bound(range.lower, range.upper, range.lowerOpen, range.upperOpen);\n }\n}\n\n/**\n * `toIdbKeyRange` for an optional range. `undefined` means \"no restriction\", which IndexedDB expresses\n * as an absent query.\n */\nexport function toOptionalIdbKeyRange(range: IdbKeyRangeDescriptor | undefined): IDBKeyRange | undefined {\n return range === undefined ? undefined : toIdbKeyRange(range);\n}\n","/**\n * Callback-based IDB operation executors.\n *\n * All functions are purely event-driven — no Promises or async/await inside\n * IDB transaction event handlers. This is required because IDB transactions\n * auto-commit when no pending requests exist and the microtask queue drains;\n * interleaving a microtask boundary (await) between IDB requests would risk\n * premature auto-commit.\n *\n * Pattern: each `exec*` function\n * 1. Issues one or more IDB requests **synchronously**.\n * 2. Calls `onComplete(rows)` when the op's work is done (inside onsuccess).\n * 3. Calls `onError(err)` if a request or the transaction fails.\n *\n * The caller (execute/index.ts) resolves the outer Promise from `tx.oncomplete`\n * so write durability is guaranteed before rows are delivered.\n */\nimport type {\n IdbAddPlan,\n IdbAtomicPlan,\n IdbCountPlan,\n IdbCursorScanPlan,\n IdbDeletePlan,\n IdbGetAllPlan,\n IdbKeyGetPlan,\n IdbKeysPlan,\n IdbPutPlan,\n IdbScanWritePlan,\n IdbUpdatePlan,\n} from \"../plan-body\";\nimport {\n IdbExecuteError,\n isTransactionInactiveError,\n transactionInactiveError,\n type IdbExecuteErrorCode,\n} from \"./error\";\nimport { keyIdentity } from \"./key-identity\";\nimport { toOptionalIdbKeyRange } from \"./key-range\";\n\ntype Row = Record<string, unknown>;\ntype OnComplete = (rows: Row[]) => void;\ntype OnError = (err: unknown) => void;\n\n/**\n * The error for a failed request: `IDB <what> failed on <target>: <cause>`.\n * `target` defaults to the plan's store; pass it when the message also names an index.\n */\nfunction opError(\n code: IdbExecuteErrorCode,\n plan: IdbAtomicPlan,\n what: string,\n cause: unknown,\n target = `store \"${plan.storeName}\"`\n): IdbExecuteError {\n return new IdbExecuteError(\n { code, planKind: plan.kind, storeName: plan.storeName, cause },\n `IDB ${what} failed on ${target}: ${String(cause)}`\n );\n}\n\n/** `store \"users\"` or `store \"users\" (index \"by_name\")`, for plans that may read through an index. */\nfunction storeTarget(plan: { storeName: string; indexName?: string }): string {\n const index = plan.indexName !== undefined ? ` (index \"${plan.indexName}\")` : \"\";\n return `store \"${plan.storeName}\"${index}`;\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Dispatch an atomic plan to its IDB operation within a live transaction.\n *\n * All IDB requests are issued synchronously. `onComplete` is called once all\n * requests for this op have resolved (still inside IDB event handlers — no\n * async gap). `onError` is called on the first request failure.\n */\nexport function executeOpInTx(\n store: IDBObjectStore,\n plan: IdbAtomicPlan,\n onComplete: OnComplete,\n onError: OnError\n): void {\n try {\n dispatchOp(store, plan, onComplete, onError);\n } catch (err) {\n // Requests are issued synchronously, so a dead transaction surfaces here as a\n // thrown DOMException. Anything else (DataError, unknown index, ...) is unchanged.\n if (isTransactionInactiveError(err)) return onError(transactionInactiveError(plan.kind, plan.storeName, err));\n throw err;\n }\n}\n\nfunction dispatchOp(store: IDBObjectStore, plan: IdbAtomicPlan, onComplete: OnComplete, onError: OnError): void {\n switch (plan.kind) {\n case \"key-get\":\n return execKeyGet(store, plan, onComplete, onError);\n case \"get-all\":\n return execGetAll(store, plan, onComplete, onError);\n case \"cursor-scan\":\n return execCursorScan(store, plan, onComplete, onError);\n case \"count\":\n return execCount(store, plan, onComplete, onError);\n case \"keys\":\n return execKeys(store, plan, onComplete, onError);\n case \"add\":\n return execAdd(store, plan, onComplete, onError);\n case \"put\":\n return execPut(store, plan, onComplete, onError);\n case \"update\":\n return execUpdate(store, plan, onComplete, onError);\n case \"delete\":\n return execDelete(store, plan, onComplete, onError);\n case \"scan-write\":\n return execScanWrite(store, plan, onComplete, onError);\n default: {\n // Unreachable for a well-typed plan; a stale driver build receiving a newer plan kind\n // would otherwise never call onComplete/onError and hang the caller.\n const unknown: never = plan;\n return onError(new Error(`Unknown IDB plan kind: ${String((unknown as { kind?: unknown }).kind)}`));\n }\n }\n}\n\n/** Returns the IDB transaction mode appropriate for a given atomic plan. */\nexport function planTxMode(plan: IdbAtomicPlan): IDBTransactionMode {\n return plan.kind === \"add\" ||\n plan.kind === \"put\" ||\n plan.kind === \"update\" ||\n plan.kind === \"delete\" ||\n plan.kind === \"scan-write\"\n ? \"readwrite\"\n : \"readonly\";\n}\n\n// ── Per-operation executors ──────────────────────────────────────────────────\n\nfunction execKeyGet(store: IDBObjectStore, plan: IdbKeyGetPlan, onComplete: OnComplete, onError: OnError): void {\n const req = store.get(plan.key);\n req.onsuccess = () => {\n const value = req.result as Row | undefined;\n onComplete(value !== undefined ? [value] : []);\n };\n req.onerror = () => onError(opError(\"KEY_GET_FAILED\", plan, \"key-get\", req.error));\n}\n\nfunction execGetAll(store: IDBObjectStore, plan: IdbGetAllPlan, onComplete: OnComplete, onError: OnError): void {\n // getAll(undefined, 0) would read everything, so a zero cap is answered without a request.\n if (plan.count === 0) return onComplete([]);\n\n const source = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n const req = source.getAll(toOptionalIdbKeyRange(plan.range), plan.count);\n req.onsuccess = () => onComplete(req.result as Row[]);\n req.onerror = () => onError(opError(\"GET_ALL_FAILED\", plan, \"get-all\", req.error, storeTarget(plan)));\n}\n\nfunction execCount(store: IDBObjectStore, plan: IdbCountPlan, onComplete: OnComplete, onError: OnError): void {\n const source: IDBObjectStore | IDBIndex = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n const range = toOptionalIdbKeyRange(plan.range);\n const req = range !== undefined ? source.count(range) : source.count();\n req.onsuccess = () => onComplete([{ count: req.result }]);\n req.onerror = () => onError(opError(\"COUNT_FAILED\", plan, \"count\", req.error, storeTarget(plan)));\n}\n\nfunction execKeys(store: IDBObjectStore, plan: IdbKeysPlan, onComplete: OnComplete, onError: OnError): void {\n const source: IDBObjectStore | IDBIndex = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n const fail = (cause: unknown) => onError(opError(\"KEYS_FAILED\", plan, \"keys read\", cause, storeTarget(plan)));\n const range = toOptionalIdbKeyRange(plan.range);\n\n // getKey() requires a query (null/absent throws), so a range-less single-key\n // read is expressed as getAllKeys(undefined, 1) instead.\n if (plan.take === 1 && range !== undefined) {\n const req = source.getKey(range);\n req.onsuccess = () => onComplete(req.result === undefined ? [] : [{ key: req.result }]);\n req.onerror = () => fail(req.error);\n return;\n }\n\n if (plan.take === 0) {\n onComplete([]);\n return;\n }\n\n const req = source.getAllKeys(range, plan.take);\n req.onsuccess = () => onComplete((req.result as IDBValidKey[]).map((key) => ({ key })));\n req.onerror = () => fail(req.error);\n}\n\nfunction execCursorScan(\n store: IDBObjectStore,\n plan: IdbCursorScanPlan,\n onComplete: OnComplete,\n onError: OnError\n): void {\n // Capture plan fields to avoid repeated property access inside the hot loop.\n const comparator = plan.comparator;\n const filter = plan.filter;\n const skip = plan.skip;\n const take = plan.take;\n\n // Nothing can be returned, so don't open a cursor.\n if (take === 0) return onComplete([]);\n\n const source: IDBObjectStore | IDBIndex = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n\n // openCursor accepts null to mean \"no range restriction\".\n const req = source.openCursor(toOptionalIdbKeyRange(plan.range) ?? null, plan.direction ?? \"next\");\n const collected: Row[] = [];\n let skippedCount = 0;\n\n req.onsuccess = () => {\n const cursor = req.result as IDBCursorWithValue | null;\n\n if (cursor === null) {\n // Cursor exhausted. If a comparator is set we collected all matching rows\n // before sorting; apply sort + skip/take now.\n if (comparator !== undefined) {\n collected.sort(comparator);\n const offset = skip ?? 0;\n onComplete(take !== undefined ? collected.slice(offset, offset + take) : collected.slice(offset));\n } else {\n onComplete(collected);\n }\n return;\n }\n\n const row = cursor.value as Row;\n\n if (filter === undefined || filter(row)) {\n if (comparator !== undefined) {\n // Must collect ALL matching rows before sorting — can't apply skip/take\n // inline because earlier rows might be sorted out.\n collected.push(row);\n } else {\n // No comparator: apply skip/take inline so we avoid collecting rows\n // that will be discarded.\n if (skip !== undefined && skippedCount < skip) {\n skippedCount++;\n } else if (take === undefined || collected.length < take) {\n collected.push(row);\n if (take !== undefined && collected.length === take) {\n // Take limit reached — stop the cursor early. The transaction has\n // no more pending requests and will auto-commit, triggering\n // tx.oncomplete → resolve(rows) in the outer wrapper.\n onComplete(collected);\n return; // intentionally no cursor.continue()\n }\n }\n }\n }\n\n cursor.continue();\n };\n\n req.onerror = () => onError(opError(\"CURSOR_SCAN_FAILED\", plan, \"cursor-scan\", req.error));\n}\n\n/**\n * The record as stored: `record` itself, plus the generated key when an\n * `autoIncrement` store with an inline `keyPath` filled it in. IDB writes the\n * generated key into its own stored copy, never into the caller's object, so\n * the echo needs it set from `req.result`.\n */\nfunction withGeneratedKey(\n store: IDBObjectStore,\n record: Record<string, unknown>,\n key: IDBValidKey\n): Record<string, unknown> {\n if (!store.autoIncrement || typeof store.keyPath !== \"string\" || record[store.keyPath] !== undefined) return record;\n return { ...record, [store.keyPath]: key };\n}\n\nfunction execAdd(store: IDBObjectStore, plan: IdbAddPlan, onComplete: OnComplete, onError: OnError): void {\n // Use the optional out-of-line key when provided; otherwise IDB derives the\n // key from the record via the store's keyPath.\n const req = plan.key !== undefined ? store.add(plan.record, plan.key) : store.add(plan.record);\n // Echo the record back — IDB has no RETURNING clause.\n req.onsuccess = () => onComplete([withGeneratedKey(store, plan.record, req.result)]);\n req.onerror = () => onError(opError(\"ADD_FAILED\", plan, \"add\", req.error));\n}\n\nfunction execPut(store: IDBObjectStore, plan: IdbPutPlan, onComplete: OnComplete, onError: OnError): void {\n // Use the optional out-of-line key when provided; otherwise IDB derives the\n // key from the record via the store's keyPath.\n const req = plan.key !== undefined ? store.put(plan.record, plan.key) : store.put(plan.record);\n // Echo the record back — IDB has no RETURNING clause.\n req.onsuccess = () => onComplete([withGeneratedKey(store, plan.record, req.result)]);\n req.onerror = () => onError(opError(\"PUT_FAILED\", plan, \"put\", req.error));\n}\n\nfunction execUpdate(store: IDBObjectStore, plan: IdbUpdatePlan, onComplete: OnComplete, onError: OnError): void {\n // Step 1: read the current record.\n const getReq = store.get(plan.key);\n getReq.onsuccess = () => {\n const existing = (getReq.result as Row | undefined) ?? {};\n // Step 2: shallow-merge patch onto existing record.\n const merged: Row = { ...existing, ...plan.patch };\n // Step 3: write the merged record back.\n const putReq = store.put(merged);\n putReq.onsuccess = () => onComplete([merged]);\n putReq.onerror = () => onError(opError(\"PUT_FAILED\", plan, \"update (put phase)\", putReq.error));\n };\n getReq.onerror = () => onError(opError(\"KEY_GET_FAILED\", plan, \"update (get phase)\", getReq.error));\n}\n\nfunction execDelete(store: IDBObjectStore, plan: IdbDeletePlan, onComplete: OnComplete, onError: OnError): void {\n // `plan.key` may be a single key or an `IDBKeyRange` spanning several\n // records (deleteMany). Walk a cursor over it so every matched record is\n // captured (and its key individually deleted) before moving on — this\n // both echoes the deleted row(s), mirroring add/put/update, and gives an\n // accurate affected-row count for both the single-key and range cases via\n // the same drain-and-count `runExecute()` already uses for every other op.\n const req = store.openCursor(plan.key);\n const collected: Row[] = [];\n\n req.onsuccess = () => {\n const cursor = req.result as IDBCursorWithValue | null;\n if (cursor === null) {\n onComplete(collected);\n return;\n }\n collected.push(cursor.value as Row);\n const delReq = cursor.delete();\n delReq.onsuccess = () => cursor.continue();\n delReq.onerror = () => onError(opError(\"DELETE_FAILED\", plan, \"delete\", delReq.error));\n };\n req.onerror = () => onError(opError(\"DELETE_FAILED\", plan, \"delete\", req.error));\n}\n\nfunction execScanWrite(store: IDBObjectStore, plan: IdbScanWritePlan, onComplete: OnComplete, onError: OnError): void {\n // A zero limit writes nothing; without this the first match would be written before the limit check.\n if (plan.take === 0) return onComplete([]);\n\n // Cursor must be opened on a readwrite transaction (enforced by planTxMode).\n const source = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n const req = source.openCursor(toOptionalIdbKeyRange(plan.range) ?? null, \"next\");\n const collected: Row[] = [];\n // An index cursor revisits a record whose indexed field the patch moved forward (or that has several\n // multiEntry entries). Track written primary keys so each record is written and counted once.\n const writtenKeys = plan.indexName !== undefined && plan.write === \"put-merged\" ? new Set<string>() : undefined;\n\n req.onsuccess = () => {\n const cursor = req.result as IDBCursorWithValue | null;\n\n if (cursor === null) {\n // Cursor exhausted — deliver all collected rows.\n onComplete(collected);\n return;\n }\n\n const row = cursor.value as Row;\n\n // Skip rows that don't match the filter.\n if (plan.filter !== undefined && !plan.filter(row)) {\n cursor.continue();\n return;\n }\n\n if (writtenKeys !== undefined && writtenKeys.has(keyIdentity(cursor.primaryKey))) {\n cursor.continue();\n return;\n }\n\n if (plan.write === \"delete\") {\n // Capture the row value before deleting (so deleteAll can return it).\n collected.push(row);\n const delReq = cursor.delete();\n delReq.onsuccess = () => {\n if (plan.take !== undefined && collected.length >= plan.take) {\n onComplete(collected);\n return; // intentionally no cursor.continue() — transaction auto-commits\n }\n cursor.continue();\n };\n delReq.onerror = () => onError(opError(\"DELETE_FAILED\", plan, \"scan-write (delete)\", delReq.error));\n } else {\n // put-merged: shallow-merge patch onto existing row, write back in-place.\n const merged: Row = { ...row, ...plan.patch };\n const updReq = cursor.update(merged);\n updReq.onsuccess = () => {\n writtenKeys?.add(keyIdentity(cursor.primaryKey));\n collected.push(merged);\n if (plan.take !== undefined && collected.length >= plan.take) {\n onComplete(collected);\n return; // intentionally no cursor.continue()\n }\n cursor.continue();\n };\n updReq.onerror = () => onError(opError(\"PUT_FAILED\", plan, \"scan-write (put-merged)\", updReq.error));\n }\n };\n\n req.onerror = () => onError(opError(\"CURSOR_SCAN_FAILED\", plan, \"scan-write cursor\", req.error));\n}\n","/**\n * Top-level IDB plan dispatcher.\n *\n * `executeIdbPlan(db, plan)` is the single entry point for the IDB driver's\n * `execute()` path. It:\n * 1. Opens an IDB transaction scoped to the store(s) the plan touches.\n * 2. Dispatches to the appropriate op executor (ops.ts).\n * 3. Resolves the returned Promise only when `tx.oncomplete` fires —\n * guaranteeing write durability before rows are delivered.\n *\n * Transaction modes:\n * - Atomic read plans (key-get, get-all, cursor-scan): `readonly`\n * - Atomic write plans (add, put, delete): `readwrite`\n * - Batch plans: `readwrite` if any op is a write, `readonly` otherwise\n */\nimport type { IdbAtomicPlan, IdbBatchPlan, IdbPlanBody } from \"../plan-body\";\nimport { IdbExecuteError, isTransactionInactiveError, transactionInactiveError } from \"./error\";\nimport { executeOpInTx, planTxMode } from \"./ops\";\n\ntype Row = Record<string, unknown>;\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Execute an IDB plan against a live database and collect all result rows.\n *\n * Batch plans run all ops inside a single transaction; atomic plans open a\n * single-store transaction. Resolves after `tx.oncomplete` so writes are\n * durable before the caller receives rows.\n */\nexport function executeIdbPlan(db: IDBDatabase, plan: IdbPlanBody): Promise<Row[]> {\n if (plan.kind === \"batch\") return executeBatchPlan(db, plan);\n return executeAtomicPlan(db, plan);\n}\n\n// ── Atomic plans ─────────────────────────────────────────────────────────────\n\nfunction executeAtomicPlan(db: IDBDatabase, plan: IdbAtomicPlan): Promise<Row[]> {\n return new Promise<Row[]>((resolve, reject) => {\n let tx: IDBTransaction;\n try {\n tx = db.transaction([plan.storeName], planTxMode(plan));\n } catch (err) {\n reject(\n new IdbExecuteError(\n { code: \"STORE_NOT_FOUND\", planKind: plan.kind, storeName: plan.storeName, cause: err },\n `IDB store \"${plan.storeName}\" not found in database`\n )\n );\n return;\n }\n\n const rows: Row[] = [];\n\n tx.oncomplete = () => resolve(rows);\n tx.onerror = () =>\n reject(\n new IdbExecuteError(\n {\n code: \"TRANSACTION_ABORTED\",\n planKind: plan.kind,\n storeName: plan.storeName,\n cause: tx.error,\n },\n `IDB transaction failed on store \"${plan.storeName}\": ${String(tx.error)}`\n )\n );\n tx.onabort = () =>\n reject(\n new IdbExecuteError(\n { code: \"TRANSACTION_ABORTED\", planKind: plan.kind, storeName: plan.storeName },\n `IDB transaction aborted on store \"${plan.storeName}\"`\n )\n );\n\n const store = tx.objectStore(plan.storeName);\n executeOpInTx(\n store,\n plan,\n (opRows) => {\n for (const row of opRows) rows.push(row);\n },\n (err) => reject(err)\n );\n });\n}\n\n// ── Batch plans ──────────────────────────────────────────────────────────────\n\nfunction executeBatchPlan(db: IDBDatabase, plan: IdbBatchPlan): Promise<Row[]> {\n return new Promise<Row[]>((resolve, reject) => {\n const mode: IDBTransactionMode = plan.ops.some((op) => planTxMode(op) === \"readwrite\") ? \"readwrite\" : \"readonly\";\n\n let tx: IDBTransaction;\n try {\n tx = db.transaction([...plan.storeNames], mode);\n } catch (err) {\n reject(\n new IdbExecuteError(\n { code: \"STORE_NOT_FOUND\", planKind: \"batch\", cause: err },\n `IDB batch transaction failed to open: ${String(err)}`\n )\n );\n return;\n }\n\n const rows: Row[] = [];\n\n tx.oncomplete = () => resolve(rows);\n tx.onerror = () =>\n reject(\n new IdbExecuteError(\n { code: \"TRANSACTION_ABORTED\", planKind: \"batch\", cause: tx.error },\n `IDB batch transaction failed: ${String(tx.error)}`\n )\n );\n tx.onabort = () =>\n reject(\n new IdbExecuteError({ code: \"TRANSACTION_ABORTED\", planKind: \"batch\" }, \"IDB batch transaction was aborted\")\n );\n\n // Run ops sequentially using recursive callbacks. Each op's `onComplete`\n // triggers the next op synchronously (still inside IDB event handlers),\n // which ensures we never cross a microtask boundary between IDB requests.\n runOpsSequentially(tx, plan.ops, 0, rows, reject);\n });\n}\n\n/**\n * Recursively runs batch ops one at a time — op N+1 is initiated from inside\n * op N's `onComplete` callback, which fires synchronously within the IDB\n * event handler. This keeps the transaction alive and prevents auto-commit\n * gaps between ops.\n */\nfunction runOpsSequentially(\n tx: IDBTransaction,\n ops: ReadonlyArray<IdbAtomicPlan>,\n index: number,\n rows: Row[],\n onError: (err: unknown) => void\n): void {\n const op = ops[index];\n if (op === undefined) return; // all ops initiated; tx.oncomplete will resolve\n\n let store: IDBObjectStore;\n try {\n store = tx.objectStore(op.storeName);\n } catch (err) {\n if (isTransactionInactiveError(err)) {\n onError(transactionInactiveError(\"batch\", op.storeName, err));\n return;\n }\n onError(\n new IdbExecuteError(\n { code: \"STORE_NOT_FOUND\", planKind: \"batch\", storeName: op.storeName, cause: err },\n `IDB batch: store \"${op.storeName}\" not found`\n )\n );\n return;\n }\n\n executeOpInTx(\n store,\n op,\n (opRows) => {\n for (const row of opRows) rows.push(row);\n runOpsSequentially(tx, ops, index + 1, rows, onError);\n },\n onError\n );\n}\n","import type { ExecutionPlan } from \"@prisma/orm-framework/components/runtime\";\n\n// ── Marker store ─────────────────────────────────────────────────────────────\n\n/**\n * Name of the object store that holds the contract marker.\n * The client-idb marker compatibility test checks the migration writer and this reader.\n *\n * Created during `upgradeneeded` by the migration runner. `IdbRuntime.verifyMarker()`\n * compares its stored hash with the contract's to detect schema drift.\n *\n * Upstream equivalent: `_prisma_marker` table in SQLite/Postgres,\n * `_prisma_marker` collection in MongoDB.\n */\nexport const MARKER_STORE_NAME = \"_prisma_next_marker\";\n\n/**\n * Shape of a marker record stored in {@link MARKER_STORE_NAME}.\n *\n * Mirrors the framework's `ContractMarkerRecord` plus the `space` keying\n * field. Records are keyed by `space` (`\"app\"` for the single app space) so\n * the storage layout can accommodate future IDB extension spaces.\n *\n * `updatedAt` is a JavaScript `Date` (not an ISO string) because IndexedDB\n * serialises Dates natively via structured-clone.\n *\n * Non-key fields are optional for forward compatibility with future contract\n * versions that may add or remove marker metadata.\n */\nexport interface IdbMarkerRecord {\n /** Contract-space identifier (`\"app\"` for the single app space). */\n readonly space?: string;\n /** The `storageHash` from the contract that was last signed. */\n readonly storageHash: string;\n /** The `profileHash` from the contract that was last signed. */\n readonly profileHash?: string;\n /** Date the marker was last written. */\n readonly updatedAt?: Date | string;\n readonly invariants?: readonly string[];\n readonly contractJson?: unknown;\n readonly canonicalVersion?: number | null;\n readonly appTag?: string | null;\n readonly meta?: Record<string, unknown>;\n}\n\n/**\n * A key range as plain data. The driver builds the matching `IDBKeyRange` when it runs the plan, so a\n * plan's `range` fields never hold an `IDBKeyRange`. (`IdbDeletePlan.key` is the one exception: it still\n * takes an `IDBKeyRange`.) Each variant maps to the `IDBKeyRange` factory of the same name:\n *\n * - `only`: exactly `key`.\n * - `lower`: keys at or above `key`; above only when `open` is true.\n * - `upper`: keys at or below `key`; below only when `open` is true.\n * - `bound`: keys from `lower` to `upper`. Each end is closed unless `lowerOpen` or `upperOpen` is true.\n *\n * For a compound index, the keys are arrays.\n */\nexport type IdbKeyRangeDescriptor =\n | { readonly kind: \"only\"; readonly key: IDBValidKey }\n | { readonly kind: \"lower\"; readonly key: IDBValidKey; readonly open?: boolean }\n | { readonly kind: \"upper\"; readonly key: IDBValidKey; readonly open?: boolean }\n | {\n readonly kind: \"bound\";\n readonly lower: IDBValidKey;\n readonly upper: IDBValidKey;\n readonly lowerOpen?: boolean;\n readonly upperOpen?: boolean;\n };\n\n/** The order a cursor walks keys in: ascending (`next`) or descending (`prev`). */\nexport type IdbScanDirection = \"next\" | \"prev\";\n\n/**\n * In-memory predicate applied to each row while iterating a cursor.\n * Returns true if the row should be included in the result set.\n */\nexport type IdbRowFilter = (row: Record<string, unknown>) => boolean;\n\n/**\n * In-memory comparator for ORDER BY.\n * Follows the same contract as Array.prototype.sort: negative / zero / positive.\n */\nexport type IdbRowComparator = (a: Record<string, unknown>, b: Record<string, unknown>) => number;\n\n/**\n * Full cursor scan over an object store or index.\n *\n * Used for `findMany`, `findFirst`, and any query where an exact key lookup\n * isn't possible. Supports key-range restriction, in-memory filtering,\n * in-memory ordering, and OFFSET + LIMIT.\n *\n * When `indexName` is set, the cursor iterates the named secondary index\n * instead of the primary key order.\n *\n * Without a `comparator`, rows come back in cursor order (shaped by `indexName`\n * and `direction`), and the cursor stops as soon as `take` rows are collected.\n * That is how a caller gets `orderBy` + `take` without reading the whole range.\n * With a `comparator`, the cursor reads the whole range before sorting.\n */\nexport interface IdbCursorScanPlan extends ExecutionPlan {\n readonly kind: \"cursor-scan\";\n readonly storeName: string;\n readonly indexName?: string; // if set, iterate via this index\n readonly range?: IdbKeyRangeDescriptor; // restrict the cursor's key range\n readonly direction?: IdbScanDirection; // defaults to \"next\"\n readonly filter?: IdbRowFilter; // in-memory WHERE\n readonly comparator?: IdbRowComparator; // in-memory ORDER BY (when index order isn't enough)\n readonly skip?: number; // OFFSET\n readonly take?: number; // LIMIT (undefined = no limit)\n}\n\n/**\n * Primary-key O(1) lookup via `store.get(key)`.\n *\n * Used for `findUnique` on `@id` fields.\n */\nexport interface IdbKeyGetPlan extends ExecutionPlan {\n readonly kind: \"key-get\";\n readonly storeName: string;\n readonly key: IDBValidKey;\n}\n\n/**\n * Bulk read via `getAll`, in key order. Cheaper than a cursor when no row\n * filter, skip or early stop is needed.\n *\n * - Without `indexName`, reads the store; with it, reads through that index.\n * - Without `range`, reads everything.\n * - `count` caps the number of rows. `count: 0` returns no rows.\n *\n * Used for `findUnique` on `@@unique` fields and for index-backed `findMany`\n * on `@@index` fields.\n */\nexport interface IdbGetAllPlan extends ExecutionPlan {\n readonly kind: \"get-all\";\n readonly storeName: string;\n readonly indexName?: string;\n readonly range?: IdbKeyRangeDescriptor;\n readonly count?: number;\n}\n\n/**\n * Insert a single record via `store.add(record[, key])`.\n *\n * Used for Prisma `create` semantics. IndexedDB rejects `add()` when the\n * primary key already exists, which preserves the \"create never overwrites\"\n * contract expected by callers.\n *\n * `key` is only needed for out-of-line key stores (`keyPath: null`).\n */\nexport interface IdbAddPlan extends ExecutionPlan {\n readonly kind: \"add\";\n readonly storeName: string;\n readonly record: Record<string, unknown>;\n readonly key?: IDBValidKey;\n}\n\n/**\n * Upsert or replace a single record via `store.put(record[, key])`.\n *\n * Used for upsert paths where the full replacement record is already known.\n * The driver echoes `record` back as the result row — IDB does not have\n * RETURNING.\n *\n * `key` is only needed for out-of-line key stores (`keyPath: null`).\n */\nexport interface IdbPutPlan extends ExecutionPlan {\n readonly kind: \"put\";\n readonly storeName: string;\n readonly record: Record<string, unknown>;\n readonly key?: IDBValidKey;\n}\n\n/**\n * Atomic partial update: get → merge → put in a single readwrite transaction.\n *\n * Used for Prisma `update` where only a subset of fields change. The driver:\n * 1. Reads the current record via `store.get(key)`.\n * 2. Shallow-merges `patch` onto the existing record with `{ ...existing, ...patch }`.\n * 3. Writes the merged record back via `store.put(merged)`.\n * 4. Echoes the merged record as the result row.\n *\n * This preserves fields not mentioned in `patch` — unlike `IdbPutPlan` which\n * does a full replacement. The get and put are issued inside the same\n * readwrite transaction so no other writer can interleave.\n *\n * If no record exists for `key`, the driver echoes `patch` as-is (insert semantics).\n */\nexport interface IdbUpdatePlan extends ExecutionPlan {\n readonly kind: \"update\";\n readonly storeName: string;\n readonly key: IDBValidKey;\n readonly patch: Record<string, unknown>;\n}\n\n/**\n * Delete one or more records by key or key range.\n *\n * Used for `delete` and `deleteMany`. The driver walks a cursor over\n * `key` (a single key or a range) and yields each deleted row, so callers\n * can echo them back and `execute()` can report an accurate affected-row\n * count — same convention as `add`/`put`/`update`.\n */\nexport interface IdbDeletePlan extends ExecutionPlan {\n readonly kind: \"delete\";\n readonly storeName: string;\n readonly key: IDBValidKey | IDBKeyRange;\n}\n\n/**\n * Cursor scan with in-place write for each matching row.\n *\n * Used for `update` (take:1), `updateAll`, `updateCount`, `deleteAll`,\n * `deleteCount`. Opens a readwrite cursor and applies `write` to each\n * row that passes `filter`:\n *\n * - `\"put-merged\"`: shallow-merges `patch` onto the row and calls\n * `cursor.update(merged)`. Yields merged rows.\n * - `\"delete\"`: captures `cursor.value` then calls `cursor.delete()`.\n * Yields the deleted rows (so callers can echo them back).\n *\n * With `take`, the cursor stops after the first `take` matches (no\n * `cursor.continue()` call after the limit is reached).\n *\n * `indexName` and `range` narrow the rows the cursor visits; `filter` still\n * applies to each of them.\n */\nexport interface IdbScanWritePlan extends ExecutionPlan {\n readonly kind: \"scan-write\";\n readonly storeName: string;\n readonly indexName?: string; // if set, walk this index instead of the primary key order\n readonly range?: IdbKeyRangeDescriptor; // restrict the walk to this key range\n readonly filter?: IdbRowFilter;\n readonly take?: number;\n readonly write: \"put-merged\" | \"delete\";\n readonly patch?: Record<string, unknown>;\n}\n\n/**\n * Native count via `store.count(range)` / `index.count(range)`.\n *\n * Used by the ORM `.count()` terminal when the whole `where` is captured by a\n * key range (or there is no `where`), so no row needs to be deserialized or\n * filtered in memory. Counts index *entries*, so it must not be used with a\n * `multiEntry` index (one record can occupy several entries).\n *\n * Result: exactly one row, `{ count: number }` — the driver's `Row[]` result\n * contract is unchanged; the caller unwraps `rows[0].count`.\n */\nexport interface IdbCountPlan extends ExecutionPlan {\n readonly kind: \"count\";\n readonly storeName: string;\n readonly indexName?: string; // count via this index instead of the store's primary keys\n readonly range?: IdbKeyRangeDescriptor; // omit to count every entry\n}\n\n/**\n * Key-only read via `getKey(range)` / `getAllKeys(range, count)` — returns\n * PRIMARY keys without deserializing any row value.\n *\n * Meant for existence checks (\"does any row match this key range?\") where the\n * caller never reads a field. Because no value is fetched, it can only\n * express criteria that are a key range on the store's primary key or on an\n * index — never an in-memory row filter.\n *\n * - `take: 1` with a `range` → `getKey(range)` (one request, no cursor).\n * - otherwise → `getAllKeys(range, take)`; `take` omitted returns every key.\n *\n * Result rows: one `{ key }` per match (the primary key — an array for a\n * compound key) or `[]` when nothing matches. Same synthetic-row convention as\n * {@link IdbCountPlan}.\n */\nexport interface IdbKeysPlan extends ExecutionPlan {\n readonly kind: \"keys\";\n readonly storeName: string;\n readonly indexName?: string; // resolve through this index; returned keys are still primary keys\n readonly range?: IdbKeyRangeDescriptor; // omit to cover the whole store/index\n readonly take?: number; // max keys to return (undefined = all)\n}\n\n/**\n * All single-store atomic op types — valid both standalone and inside a batch.\n */\nexport type IdbAtomicPlan =\n | IdbCursorScanPlan\n | IdbKeyGetPlan\n | IdbGetAllPlan\n | IdbCountPlan\n | IdbKeysPlan\n | IdbAddPlan\n | IdbPutPlan\n | IdbUpdatePlan\n | IdbDeletePlan\n | IdbScanWritePlan;\n\n/**\n * Multi-op atomic batch executed inside a single IDB transaction.\n *\n * Used whenever a Prisma operation must touch multiple stores atomically\n * (e.g. `create` with nested relations, `update` + cascade `delete`).\n *\n * The driver opens ONE readwrite transaction scoped to `storeNames`, runs\n * every op in `ops` sequentially, and collects rows from read ops. Write ops\n * (`put`/`delete`) echo their result inline (see `IdbPutPlan`).\n *\n * `storeNames` MUST include every store referenced by any op in `ops`.\n * IDB requires the full store scope to be declared when opening a transaction.\n */\nexport interface IdbBatchPlan extends ExecutionPlan {\n readonly kind: \"batch\";\n readonly storeNames: ReadonlyArray<string>;\n readonly ops: ReadonlyArray<IdbAtomicPlan>;\n}\n\n/**\n * The full union of plan shapes the adapter produces and the driver executes.\n */\nexport type IdbPlanBody = IdbAtomicPlan | IdbBatchPlan;\n","/**\n * Multi-store IDB transaction scope.\n *\n * A single `IDBTransaction` spanning one or more object stores, exposed as a\n * typed execution surface for the ORM's `withMutationScope()` helper.\n *\n * Design notes:\n * - `execute(plan)` runs `executeOpInTx` directly in the pre-opened transaction —\n * NO new transaction is opened per call, and the middleware chain is bypassed\n * entirely. This keeps the shared IDB transaction alive across multiple awaited\n * execute() calls (IDB auto-commits in a macro-task, not a microtask; each\n * execute() issues new requests before the auto-commit check fires).\n * - `commit()` resolves when `tx.oncomplete` fires — the caller awaits this after\n * all scope operations are complete to get write-durability confirmation.\n * - `rollback()` is idempotent: calling it on an already-aborted transaction is\n * a no-op (the DOMException from IDB is swallowed).\n *\n * Issue #6 (scope hard-coding): since this scope's execute() bypasses both of\n * `RuntimeCore`'s middleware chains (query()'s and execute()'s alike), the\n * cache middleware never fires inside a scope → reads are always fresh.\n */\nimport type { IdbAtomicPlan } from \"./plan-body\";\nimport { IdbExecuteError, isTransactionInactiveError, transactionInactiveError } from \"./execute/error\";\nimport { executeOpInTx } from \"./execute/ops\";\n\ntype Row = Record<string, unknown>;\n\n// ── Public interface ──────────────────────────────────────────────────────────\n\nexport interface IdbTransactionScope {\n /**\n * Execute a single atomic plan inside the shared IDB transaction.\n *\n * The plan runs synchronously inside IDB event callbacks; the returned\n * Promise resolves once the operation's `onsuccess` fires. The transaction\n * stays open — the next `execute()` call can be issued right after `await`.\n */\n execute(plan: IdbAtomicPlan): Promise<Row[]>;\n\n /**\n * Wait for the IDB transaction to fully commit.\n *\n * Resolves when `tx.oncomplete` fires (all writes are durable). Call this\n * after all `execute()` calls; `withMutationScope` does this automatically.\n */\n commit(): Promise<void>;\n\n /**\n * Abort the transaction, rolling back all uncommitted writes.\n *\n * Idempotent — safe to call when the transaction has already been aborted\n * (e.g. due to a request error). `withMutationScope` calls this on catch.\n */\n rollback(): void;\n}\n\n// ── Implementation ────────────────────────────────────────────────────────────\n\nclass IdbTransactionScopeImpl implements IdbTransactionScope {\n readonly #tx: IDBTransaction;\n readonly #onComplete: Promise<void>;\n\n constructor(tx: IDBTransaction) {\n this.#tx = tx;\n this.#onComplete = new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(\n new IdbExecuteError(\n { code: \"TRANSACTION_ABORTED\", planKind: \"batch\", cause: tx.error },\n `IDB transaction scope failed: ${String(tx.error)}`\n )\n );\n tx.onabort = () =>\n reject(\n new IdbExecuteError({ code: \"TRANSACTION_ABORTED\", planKind: \"batch\" }, \"IDB transaction scope aborted\")\n );\n });\n // Suppress \"unhandled rejection\" when the scope is rolled back without\n // commit() being awaited (e.g. withMutationScope catches an error and\n // calls rollback() without ever calling commit()).\n this.#onComplete.catch(() => {});\n }\n\n execute(plan: IdbAtomicPlan): Promise<Row[]> {\n return new Promise<Row[]>((resolve, reject) => {\n let store: IDBObjectStore;\n try {\n store = this.#tx.objectStore(plan.storeName);\n } catch (err) {\n if (isTransactionInactiveError(err)) {\n reject(transactionInactiveError(plan.kind, plan.storeName, err));\n return;\n }\n reject(\n new IdbExecuteError(\n { code: \"STORE_NOT_FOUND\", planKind: plan.kind, storeName: plan.storeName, cause: err },\n `IDB transaction scope: store \"${plan.storeName}\" not found`\n )\n );\n return;\n }\n\n const rows: Row[] = [];\n executeOpInTx(\n store,\n plan,\n (opRows) => {\n for (const row of opRows) rows.push(row);\n resolve(rows);\n },\n reject\n );\n });\n }\n\n commit(): Promise<void> {\n return this.#onComplete;\n }\n\n rollback(): void {\n try {\n this.#tx.abort();\n } catch {\n // Transaction already committed or aborted — no-op.\n }\n }\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\n/**\n * Open an IDB transaction spanning `storeNames` and wrap it in a scope.\n *\n * Throws synchronously if any store in `storeNames` is not found in the\n * database (IDB throws when opening a transaction with unknown store names).\n */\nexport function createTransactionScope(\n db: IDBDatabase,\n storeNames: string[],\n mode: IDBTransactionMode = \"readwrite\"\n): IdbTransactionScope {\n const tx = db.transaction(storeNames, mode);\n return new IdbTransactionScopeImpl(tx);\n}\n","import type { RuntimeDriverInstance } from \"@prisma/orm-framework/components/execution\";\nimport { executeIdbPlan } from \"./execute\";\nimport { MARKER_STORE_NAME, type IdbMarkerRecord, type IdbPlanBody } from \"./plan-body\";\nimport { createTransactionScope, type IdbTransactionScope } from \"./transaction-scope\";\n\nexport class IdbRuntimeDriverInstance implements RuntimeDriverInstance<\"idb\", \"idb\"> {\n readonly familyId = \"idb\" as const;\n readonly targetId = \"idb\" as const;\n /**\n * The live IDBDatabase connection. Resolves once `upgradeneeded` (if any)\n * completes and the database is ready for use.\n *\n * `execute()` and `transaction()` await this before opening a transaction.\n * The Promise is shared — all concurrent callers get the same database object.\n */\n readonly db: Promise<IDBDatabase>;\n\n constructor(dbName: string, version?: number, factory?: IDBFactory) {\n this.db = openIdbDatabase(dbName, version, factory);\n }\n\n async close(): Promise<void> {\n (await this.db).close();\n }\n\n /**\n * Read the contract marker from the `_prisma_next_marker` store.\n *\n * Returns `null` when the marker store does not exist (fresh database\n * that hasn't been initialised yet) or when no marker record is present.\n *\n * Called by `IdbRuntimeImpl.verifyMarker()` to check that the live IDB\n * schema matches the contract.\n */\n async readMarker(): Promise<IdbMarkerRecord | null> {\n const db = await this.db;\n if (!db.objectStoreNames.contains(MARKER_STORE_NAME)) {\n return null;\n }\n return new Promise((resolve, reject) => {\n const tx = db.transaction(MARKER_STORE_NAME, \"readonly\");\n const store = tx.objectStore(MARKER_STORE_NAME);\n\n const appReq = store.get(\"app\");\n appReq.onsuccess = () => {\n const result = appReq.result as IdbMarkerRecord | undefined;\n resolve(result ?? null);\n };\n appReq.onerror = () => reject(appReq.error);\n\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Open a multi-store readwrite transaction and wrap it in an\n * {@link IdbTransactionScope}.\n *\n * Awaits the live `IDBDatabase` connection before opening the transaction.\n * Used by `withMutationScope()` in `client-idb` to run cross-store writes\n * atomically.\n */\n async transaction(storeNames: string[], mode: IDBTransactionMode = \"readwrite\"): Promise<IdbTransactionScope> {\n const db = await this.db;\n return createTransactionScope(db, storeNames, mode);\n }\n\n /**\n * Execute an IDB plan body and yield rows as an async iterable.\n *\n * Opens a new IDB transaction for each call, collects all rows inside the\n * transaction (collect-then-yield; see execute/index.ts for rationale),\n * and yields them after the transaction commits.\n *\n * Called by `IdbRuntimeImpl.runDriver()` in `@prisma-idb/runtime-idb`.\n */\n execute(plan: IdbPlanBody): AsyncIterable<Record<string, unknown>> {\n const dbPromise = this.db;\n return {\n [Symbol.asyncIterator]() {\n return (async function* () {\n const db = await dbPromise;\n const rows = await executeIdbPlan(db, plan);\n yield* rows;\n })();\n },\n };\n }\n}\n\n/**\n * Opens an IDB database and resolves once it is ready for use.\n *\n * Wraps the IDB event-based open API in a Promise. There is no `upgradeneeded`\n * handler: the driver does not own schema. Migrations are applied by\n * `openAndUpgrade` (target-idb), which opens the database at a specific\n * version and runs DDL inside the version-change transaction.\n *\n * When version is omitted the IDB spec opens the database at its current version\n * (or version 1 for a brand-new database). This is the correct runtime behaviour\n * per ADR 001: the migration runner owns version bumping; the runtime just connects\n * to whatever schema is already in place.\n */\nfunction openIdbDatabase(dbName: string, version?: number, factory: IDBFactory = indexedDB): Promise<IDBDatabase> {\n return new Promise<IDBDatabase>((resolve, reject) => {\n const req = factory.open(dbName, version);\n\n req.onerror = () => {\n reject(req.error);\n };\n\n req.onblocked = () => {\n reject(new Error(`IDB open blocked for \"${dbName}\": another connection is open with an older version.`));\n };\n\n req.onsuccess = () => {\n const db = req.result;\n // Multi-tab safety: when another tab opens this database at a higher\n // version, the IDB spec fires `versionchange` on every other open\n // connection. If we don't close, the new tab's open request hangs\n // indefinitely on `blocked`. Closing here releases the lock; the\n // application layer can listen for `db.onclose` to surface a\n // \"please reload\" toast if it cares.\n db.onversionchange = () => {\n db.close();\n };\n resolve(db);\n };\n });\n}\n","import type { RuntimeDriverDescriptor } from \"@prisma/orm-framework/components/execution\";\nimport { idbDriverDescriptorMeta } from \"../core/descriptor-meta\";\nimport { IdbRuntimeDriverInstance } from \"../core/idb-driver\";\n\nexport type {\n IdbPlanBody,\n IdbAtomicPlan,\n IdbBatchPlan,\n IdbAddPlan,\n IdbCursorScanPlan,\n IdbCountPlan,\n IdbKeysPlan,\n IdbKeyGetPlan,\n IdbGetAllPlan,\n IdbPutPlan,\n IdbUpdatePlan,\n IdbDeletePlan,\n IdbScanWritePlan,\n IdbKeyRangeDescriptor,\n IdbScanDirection,\n IdbRowFilter,\n IdbRowComparator,\n IdbMarkerRecord,\n} from \"../core/plan-body\";\nexport { MARKER_STORE_NAME } from \"../core/plan-body\";\nexport type { IdbRuntimeDriverInstance } from \"../core/idb-driver\";\nexport { IdbExecuteError } from \"../core/execute/error\";\nexport type { IdbExecuteErrorCode } from \"../core/execute/error\";\nexport type { IdbTransactionScope } from \"../core/transaction-scope\";\nexport { createTransactionScope } from \"../core/transaction-scope\";\n\n/**\n * Creates a runtime driver descriptor for IndexedDB.\n *\n * The returned descriptor's `create()` starts opening the named IDB database\n * in the background; the driver awaits the connection before its first query.\n *\n * @param dbName - The IDB database name to open.\n * @param version - The IDB version number. When omitted, IndexedDB opens the\n * database at its current version (version 1 for a new database). The\n * driver never runs migrations; `openAndUpgrade` (target-idb) bumps the version.\n *\n * @example\n * ```ts\n * const stack = createRuntimeStack({\n * target: idbRuntimeTargetDescriptor,\n * adapter: idbRuntimeAdapterDescriptor,\n * driver: createIDBRuntimeDriver(\"my-app\"),\n * });\n * ```\n */\nexport function createIDBRuntimeDriver(\n dbName: string,\n version?: number,\n options?: { readonly factory?: IDBFactory }\n): RuntimeDriverDescriptor<\"idb\", \"idb\", void, IdbRuntimeDriverInstance> {\n return {\n ...idbDriverDescriptorMeta,\n create(): IdbRuntimeDriverInstance {\n return new IdbRuntimeDriverInstance(dbName, version, options?.factory);\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runtime.mjs","names":["unknown","#tx","#onComplete","#pending"],"sources":["../src/core/execute/error.ts","../src/core/execute/key-identity.ts","../src/core/execute/key-range.ts","../src/core/execute/ops.ts","../src/core/execute/index.ts","../src/core/plan-body.ts","../src/core/transaction-scope.ts","../src/core/idb-driver.ts","../src/exports/runtime.ts"],"sourcesContent":["/**\n * Stable error codes for IDB execution failures.\n *\n * Each code corresponds to a specific IDB operation. Callers that need to\n * handle specific failure modes (e.g. retry on TRANSACTION_ABORTED) can\n * branch on `error.code` without string-matching the message.\n */\nexport type IdbExecuteErrorCode =\n | \"PRIMARY_KEY_CHANGE_UNSUPPORTED\" // update would change an inline primary key\n | \"STORE_NOT_FOUND\" // IDB store requested by plan does not exist in the database\n | \"KEY_GET_FAILED\" // store.get(key) request failed\n | \"GET_ALL_FAILED\" // store.getAll(range) / index.getAll(range) request failed\n | \"CURSOR_SCAN_FAILED\" // openCursor iteration failed\n | \"KEYS_FAILED\" // getKey(range) / getAllKeys(range) request failed\n | \"COUNT_FAILED\" // store.count(range) / index.count(range) request failed\n | \"ADD_FAILED\" // store.add(record) request failed\n | \"PUT_FAILED\" // store.put(record) request failed\n | \"DELETE_FAILED\" // store.delete(key) request failed\n | \"BATCH_FAILED\" // one op inside a batch plan failed\n | \"TRANSACTION_ABORTED\" // IDB transaction aborted before oncomplete fired\n | \"TRANSACTION_INACTIVE\"; // request issued on a transaction that already auto-committed / finished\n\n/**\n * Structured error raised by the IDB driver's `execute()` path.\n *\n * Follows the pattern used in the upstream sql-runtime error normalizers so\n * that callers can rely on stable, machine-readable codes rather than\n * inspecting message strings.\n */\nexport class IdbExecuteError extends Error {\n readonly code: IdbExecuteErrorCode;\n readonly category = \"DRIVER\" as const;\n readonly severity = \"error\" as const;\n /** The `kind` field of the plan that was being executed. */\n readonly planKind: string;\n /** The object store name, when known at the point of failure. */\n readonly storeName: string | undefined;\n override readonly cause: unknown;\n\n constructor(\n details: {\n readonly code: IdbExecuteErrorCode;\n readonly planKind: string;\n readonly storeName?: string;\n readonly cause?: unknown;\n },\n message: string\n ) {\n super(message);\n this.name = \"IdbExecuteError\";\n this.code = details.code;\n this.planKind = details.planKind;\n this.storeName = details.storeName;\n this.cause = details.cause;\n }\n}\n\n/**\n * `true` for the synchronous DOMExceptions IDB throws when a request is issued\n * on a transaction that is no longer active (`TransactionInactiveError`) or has\n * already finished (`InvalidStateError`) — the signature of an `await` on\n * something that isn't an IDB request between two operations (see ADR 005/007).\n */\nexport function isTransactionInactiveError(err: unknown): boolean {\n const name = (err as { name?: unknown } | null)?.name;\n return name === \"TransactionInactiveError\" || name === \"InvalidStateError\";\n}\n\n/** Builds the actionable `TRANSACTION_INACTIVE` error for a failed request/objectStore call. */\nexport function transactionInactiveError(planKind: string, storeName: string, cause: unknown): IdbExecuteError {\n return new IdbExecuteError(\n { code: \"TRANSACTION_INACTIVE\", planKind, storeName, cause },\n `IDB transaction is no longer active when running \"${planKind}\" on store \"${storeName}\". ` +\n `IndexedDB transactions auto-commit as soon as the microtask queue drains with no pending request, ` +\n `so code between two operations must only \\`await\\` IDB-request-resolving promises ` +\n `(no fetch, timers or other async work inside a transaction). See ADR 005/007. Cause: ${String(cause)}`\n );\n}\n","/**\n * A string that is equal for two `IDBValidKey`s exactly when IndexedDB considers the keys equal.\n *\n * `Set` compares arrays, dates and binary keys by reference, so it cannot track primary keys directly.\n * Each key type gets its own prefix so, for example, the string \"1\" and the number 1 stay distinct.\n *\n * Dates and binary keys are detected by their internal slots, not `instanceof`, because a key can come\n * from another realm (for example an iframe), where its constructors differ from this realm's.\n */\nexport function keyIdentity(key: IDBValidKey): string {\n if (typeof key === \"number\") return `n:${Object.is(key, -0) ? 0 : key}`;\n if (typeof key === \"string\") return `s:${JSON.stringify(key)}`;\n if (Array.isArray(key)) return `[${key.map(keyIdentity).join(\",\")}]`;\n if (ArrayBuffer.isView(key)) return binaryIdentity(new Uint8Array(key.buffer, key.byteOffset, key.byteLength));\n if (isArrayBuffer(key)) return binaryIdentity(new Uint8Array(key));\n return `d:${Date.prototype.getTime.call(key)}`;\n}\n\n/** The `byteLength` getter throws unless its receiver has an `[[ArrayBufferData]]` slot, in any realm. */\nconst arrayBufferByteLength = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, \"byteLength\")!.get!;\n\n/** Detect the ArrayBuffer internal slot across realms without relying on instanceof. */\nfunction isArrayBuffer(value: unknown): value is ArrayBuffer {\n try {\n arrayBufferByteLength.call(value);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction binaryIdentity(bytes: Uint8Array): string {\n return `b:${bytes.join(\".\")}`;\n}\n","import type { IdbKeyRangeDescriptor } from \"../plan-body\";\n\n/**\n * Builds the `IDBKeyRange` a plan's range descriptor stands for.\n *\n * The `range` fields of a plan carry descriptors, not `IDBKeyRange` objects, so that part of a plan stays\n * plain data that callers can build, hash and log without touching IndexedDB. Each descriptor maps to the `IDBKeyRange` factory of the same\n * name, so the factory's own validation applies: an invalid key, or `bound` with `lower` above `upper`,\n * throws `DataError`.\n */\nexport function toIdbKeyRange(range: IdbKeyRangeDescriptor): IDBKeyRange {\n switch (range.kind) {\n case \"only\":\n return IDBKeyRange.only(range.key);\n case \"lower\":\n return IDBKeyRange.lowerBound(range.key, range.open);\n case \"upper\":\n return IDBKeyRange.upperBound(range.key, range.open);\n case \"bound\":\n return IDBKeyRange.bound(range.lower, range.upper, range.lowerOpen, range.upperOpen);\n }\n}\n\n/**\n * `toIdbKeyRange` for an optional range. `undefined` means \"no restriction\", which IndexedDB expresses\n * as an absent query.\n */\nexport function toOptionalIdbKeyRange(range: IdbKeyRangeDescriptor | undefined): IDBKeyRange | undefined {\n return range === undefined ? undefined : toIdbKeyRange(range);\n}\n","/**\n * Callback-based IDB operation executors.\n *\n * All functions are purely event-driven — no Promises or async/await inside\n * IDB transaction event handlers. This is required because IDB transactions\n * auto-commit when no pending requests exist and the microtask queue drains;\n * interleaving a microtask boundary (await) between IDB requests would risk\n * premature auto-commit.\n *\n * Pattern: each `exec*` function\n * 1. Issues one or more IDB requests **synchronously**.\n * 2. Calls `onComplete(rows)` when the op's work is done (inside onsuccess).\n * 3. Calls `onError(err)` if a request or the transaction fails.\n *\n * The caller (execute/index.ts) resolves the outer Promise from `tx.oncomplete`\n * so write durability is guaranteed before rows are delivered.\n */\nimport type {\n IdbAddPlan,\n IdbAtomicPlan,\n IdbCountPlan,\n IdbCursorScanPlan,\n IdbDeletePlan,\n IdbGetAllPlan,\n IdbKeyGetPlan,\n IdbKeysPlan,\n IdbPutPlan,\n IdbScanWritePlan,\n IdbUpdatePlan,\n} from \"../plan-body\";\nimport {\n IdbExecuteError,\n isTransactionInactiveError,\n transactionInactiveError,\n type IdbExecuteErrorCode,\n} from \"./error\";\nimport { keyIdentity } from \"./key-identity\";\nimport { toOptionalIdbKeyRange } from \"./key-range\";\n\n/** A record returned by an IDB operation. */\ntype Row = Record<string, unknown>;\n/** Deliver rows while the operation is still inside an IDB event callback. */\ntype OnComplete = (rows: Row[]) => void;\n/** Route operation failures to the transaction owner. */\ntype OnError = (err: unknown) => void;\n\n/**\n * The error for a failed request: `IDB <what> failed on <target>: <cause>`.\n * `target` defaults to the plan's store; pass it when the message also names an index.\n */\nfunction opError(\n code: IdbExecuteErrorCode,\n plan: IdbAtomicPlan,\n what: string,\n cause: unknown,\n target = `store \"${plan.storeName}\"`\n): IdbExecuteError {\n return new IdbExecuteError(\n { code, planKind: plan.kind, storeName: plan.storeName, cause },\n `IDB ${what} failed on ${target}: ${String(cause)}`\n );\n}\n\n/** `store \"users\"` or `store \"users\" (index \"by_name\")`, for plans that may read through an index. */\nfunction storeTarget(plan: { storeName: string; indexName?: string }): string {\n const index = plan.indexName !== undefined ? ` (index \"${plan.indexName}\")` : \"\";\n return `store \"${plan.storeName}\"${index}`;\n}\n\n/** Route synchronous event-handler failures through the same channel as request errors. */\nfunction guardCallback(\n plan: IdbAtomicPlan,\n code: IdbExecuteErrorCode,\n what: string,\n onError: OnError,\n run: () => void\n): () => void {\n return () => {\n try {\n run();\n } catch (cause) {\n onError(cause instanceof IdbExecuteError ? cause : opError(code, plan, what, cause));\n }\n };\n}\n\n/** Updates preserve identity. A key move needs referential and sync delete/create semantics. */\nfunction assertUnchangedPrimaryKey(store: IDBObjectStore, plan: IdbAtomicPlan, key: IDBValidKey, row: Row): void {\n const keyPath = store.keyPath;\n if (keyPath == null) return;\n /** Resolve an inline key path, including dotted paths, from the merged record. */\n const extract = (path: string): unknown =>\n path === \"\"\n ? row\n : path\n .split(\".\")\n .reduce<unknown>(\n (value, field) => (value !== null && typeof value === \"object\" ? (value as Row)[field] : undefined),\n row\n );\n const nextKey = (typeof keyPath === \"string\" ? extract(keyPath) : keyPath.map(extract)) as IDBValidKey;\n if (indexedDB.cmp(key, nextKey) !== 0) {\n throw new IdbExecuteError(\n { code: \"PRIMARY_KEY_CHANGE_UNSUPPORTED\", planKind: plan.kind, storeName: plan.storeName },\n `Cannot change the primary key of a row in store \"${plan.storeName}\". Primary keys are immutable. Delete the row and create it again only after handling dependent records. Restrictive relations can block the delete, and cascading relations can delete dependent records.`\n );\n }\n}\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Dispatch an atomic plan to its IDB operation within a live transaction.\n *\n * All IDB requests are issued synchronously. `onComplete` is called once all\n * requests for this op have resolved (still inside IDB event handlers — no\n * async gap). `onError` is called on the first request failure.\n */\nexport function executeOpInTx(\n store: IDBObjectStore,\n plan: IdbAtomicPlan,\n onComplete: OnComplete,\n onError: OnError\n): void {\n try {\n dispatchOp(store, plan, onComplete, onError);\n } catch (err) {\n // Requests are issued synchronously, so a dead transaction surfaces here as a\n // thrown DOMException. Route every failure to onError so the caller can abort.\n if (isTransactionInactiveError(err)) return onError(transactionInactiveError(plan.kind, plan.storeName, err));\n onError(err);\n }\n}\n\n/** Select the executor for an atomic plan without introducing asynchronous gaps. */\nfunction dispatchOp(store: IDBObjectStore, plan: IdbAtomicPlan, onComplete: OnComplete, onError: OnError): void {\n switch (plan.kind) {\n case \"key-get\":\n return execKeyGet(store, plan, onComplete, onError);\n case \"get-all\":\n return execGetAll(store, plan, onComplete, onError);\n case \"cursor-scan\":\n return execCursorScan(store, plan, onComplete, onError);\n case \"count\":\n return execCount(store, plan, onComplete, onError);\n case \"keys\":\n return execKeys(store, plan, onComplete, onError);\n case \"add\":\n return execAdd(store, plan, onComplete, onError);\n case \"put\":\n return execPut(store, plan, onComplete, onError);\n case \"update\":\n return execUpdate(store, plan, onComplete, onError);\n case \"delete\":\n return execDelete(store, plan, onComplete, onError);\n case \"scan-write\":\n return execScanWrite(store, plan, onComplete, onError);\n default: {\n // Unreachable for a well-typed plan; a stale driver build receiving a newer plan kind\n // would otherwise never call onComplete/onError and hang the caller.\n const unknown: never = plan;\n return onError(new Error(`Unknown IDB plan kind: ${String((unknown as { kind?: unknown }).kind)}`));\n }\n }\n}\n\n/** Returns the IDB transaction mode appropriate for a given atomic plan. */\nexport function planTxMode(plan: IdbAtomicPlan): IDBTransactionMode {\n return plan.kind === \"add\" ||\n plan.kind === \"put\" ||\n plan.kind === \"update\" ||\n plan.kind === \"delete\" ||\n plan.kind === \"scan-write\"\n ? \"readwrite\"\n : \"readonly\";\n}\n\n// ── Per-operation executors ──────────────────────────────────────────────────\n\n/** Read one primary key and return no rows when it is absent. */\nfunction execKeyGet(store: IDBObjectStore, plan: IdbKeyGetPlan, onComplete: OnComplete, onError: OnError): void {\n const req = store.get(plan.key);\n req.onsuccess = () => {\n const value = req.result as Row | undefined;\n onComplete(value !== undefined ? [value] : []);\n };\n req.onerror = () => onError(opError(\"KEY_GET_FAILED\", plan, \"key-get\", req.error));\n}\n\n/** Read a bounded range from a store or index, handling a zero cap explicitly. */\nfunction execGetAll(store: IDBObjectStore, plan: IdbGetAllPlan, onComplete: OnComplete, onError: OnError): void {\n // getAll(undefined, 0) would read everything, so a zero cap is answered without a request.\n if (plan.count === 0) return onComplete([]);\n\n const source = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n const req = source.getAll(toOptionalIdbKeyRange(plan.range), plan.count);\n req.onsuccess = () => onComplete(req.result as Row[]);\n req.onerror = () => onError(opError(\"GET_ALL_FAILED\", plan, \"get-all\", req.error, storeTarget(plan)));\n}\n\n/** Count a store or index range without materializing record values. */\nfunction execCount(store: IDBObjectStore, plan: IdbCountPlan, onComplete: OnComplete, onError: OnError): void {\n const source: IDBObjectStore | IDBIndex = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n const range = toOptionalIdbKeyRange(plan.range);\n const req = range !== undefined ? source.count(range) : source.count();\n req.onsuccess = () => onComplete([{ count: req.result }]);\n req.onerror = () => onError(opError(\"COUNT_FAILED\", plan, \"count\", req.error, storeTarget(plan)));\n}\n\n/** Read primary keys for existence checks without fetching record values. */\nfunction execKeys(store: IDBObjectStore, plan: IdbKeysPlan, onComplete: OnComplete, onError: OnError): void {\n const source: IDBObjectStore | IDBIndex = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n const fail = (cause: unknown) => onError(opError(\"KEYS_FAILED\", plan, \"keys read\", cause, storeTarget(plan)));\n const range = toOptionalIdbKeyRange(plan.range);\n\n // getKey() requires a query (null/absent throws), so a range-less single-key\n // read is expressed as getAllKeys(undefined, 1) instead.\n if (plan.take === 1 && range !== undefined) {\n const req = source.getKey(range);\n req.onsuccess = () => onComplete(req.result === undefined ? [] : [{ key: req.result }]);\n req.onerror = () => fail(req.error);\n return;\n }\n\n if (plan.take === 0) {\n onComplete([]);\n return;\n }\n\n const req = source.getAllKeys(range, plan.take);\n req.onsuccess = () => onComplete((req.result as IDBValidKey[]).map((key) => ({ key })));\n req.onerror = () => fail(req.error);\n}\n\n/** Filter cursor rows and stop at the page limit unless sorting needs the full range. */\nfunction execCursorScan(\n store: IDBObjectStore,\n plan: IdbCursorScanPlan,\n onComplete: OnComplete,\n onError: OnError\n): void {\n // Capture plan fields to avoid repeated property access inside the hot loop.\n const comparator = plan.comparator;\n const filter = plan.filter;\n const skip = plan.skip;\n const take = plan.take;\n\n // Nothing can be returned, so don't open a cursor.\n if (take === 0) return onComplete([]);\n\n const source: IDBObjectStore | IDBIndex = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n\n // openCursor accepts null to mean \"no range restriction\".\n const req = source.openCursor(toOptionalIdbKeyRange(plan.range) ?? null, plan.direction ?? \"next\");\n const collected: Row[] = [];\n let skippedCount = 0;\n\n req.onsuccess = guardCallback(plan, \"CURSOR_SCAN_FAILED\", \"cursor-scan callback\", onError, () => {\n const cursor = req.result as IDBCursorWithValue | null;\n\n if (cursor === null) {\n // Cursor exhausted. If a comparator is set we collected all matching rows\n // before sorting; apply sort + skip/take now.\n if (comparator !== undefined) {\n collected.sort(comparator);\n const offset = skip ?? 0;\n onComplete(take !== undefined ? collected.slice(offset, offset + take) : collected.slice(offset));\n } else {\n onComplete(collected);\n }\n return;\n }\n\n const row = cursor.value as Row;\n\n if (filter === undefined || filter(row)) {\n if (comparator !== undefined) {\n // Must collect ALL matching rows before sorting — can't apply skip/take\n // inline because earlier rows might be sorted out.\n collected.push(row);\n } else {\n // No comparator: apply skip/take inline so we avoid collecting rows\n // that will be discarded.\n if (skip !== undefined && skippedCount < skip) {\n skippedCount++;\n } else if (take === undefined || collected.length < take) {\n collected.push(row);\n if (take !== undefined && collected.length === take) {\n // Take limit reached — stop the cursor early. The transaction has\n // no more pending requests and will auto-commit, triggering\n // tx.oncomplete → resolve(rows) in the outer wrapper.\n onComplete(collected);\n return; // intentionally no cursor.continue()\n }\n }\n }\n }\n\n cursor.continue();\n });\n\n req.onerror = () => onError(opError(\"CURSOR_SCAN_FAILED\", plan, \"cursor-scan\", req.error));\n}\n\n/**\n * The record as stored: `record` itself, plus the generated key when an\n * `autoIncrement` store with an inline `keyPath` filled it in. IDB writes the\n * generated key into its own stored copy, never into the caller's object, so\n * the echo needs it set from `req.result`.\n */\nfunction withGeneratedKey(\n store: IDBObjectStore,\n record: Record<string, unknown>,\n key: IDBValidKey\n): Record<string, unknown> {\n if (!store.autoIncrement || typeof store.keyPath !== \"string\" || record[store.keyPath] !== undefined) return record;\n return { ...record, [store.keyPath]: key };\n}\n\n/** Insert without overwriting, then echo any generated inline key. */\nfunction execAdd(store: IDBObjectStore, plan: IdbAddPlan, onComplete: OnComplete, onError: OnError): void {\n // Use the optional out-of-line key when provided; otherwise IDB derives the\n // key from the record via the store's keyPath.\n const req = plan.key !== undefined ? store.add(plan.record, plan.key) : store.add(plan.record);\n // Echo the record back — IDB has no RETURNING clause.\n req.onsuccess = () => onComplete([withGeneratedKey(store, plan.record, req.result)]);\n req.onerror = () => onError(opError(\"ADD_FAILED\", plan, \"add\", req.error));\n}\n\n/** Replace or insert a record and echo any generated inline key. */\nfunction execPut(store: IDBObjectStore, plan: IdbPutPlan, onComplete: OnComplete, onError: OnError): void {\n // Use the optional out-of-line key when provided; otherwise IDB derives the\n // key from the record via the store's keyPath.\n const req = plan.key !== undefined ? store.put(plan.record, plan.key) : store.put(plan.record);\n // Echo the record back — IDB has no RETURNING clause.\n req.onsuccess = () => onComplete([withGeneratedKey(store, plan.record, req.result)]);\n req.onerror = () => onError(opError(\"PUT_FAILED\", plan, \"put\", req.error));\n}\n\n/** Read, merge, and write a patch in one transaction while preserving primary-key identity. */\nfunction execUpdate(store: IDBObjectStore, plan: IdbUpdatePlan, onComplete: OnComplete, onError: OnError): void {\n // Step 1: read the current record.\n const getReq = store.get(plan.key);\n getReq.onsuccess = guardCallback(plan, \"PUT_FAILED\", \"update (put phase)\", onError, () => {\n const existing = (getReq.result as Row | undefined) ?? {};\n // Step 2: shallow-merge patch onto existing record.\n const merged: Row = { ...existing, ...plan.patch };\n // Step 3: write the merged record back.\n assertUnchangedPrimaryKey(store, plan, plan.key, merged);\n const putReq = store.keyPath === null ? store.put(merged, plan.key) : store.put(merged);\n putReq.onsuccess = () => onComplete([merged]);\n putReq.onerror = () => onError(opError(\"PUT_FAILED\", plan, \"update (put phase)\", putReq.error));\n });\n getReq.onerror = () => onError(opError(\"KEY_GET_FAILED\", plan, \"update (get phase)\", getReq.error));\n}\n\n/** Delete each key in the requested range and echo its previous row. */\nfunction execDelete(store: IDBObjectStore, plan: IdbDeletePlan, onComplete: OnComplete, onError: OnError): void {\n // `plan.key` may be a single key or an `IDBKeyRange` spanning several\n // records (deleteMany). Walk a cursor over it so every matched record is\n // captured (and its key individually deleted) before moving on — this\n // both echoes the deleted row(s), mirroring add/put/update, and gives an\n // accurate affected-row count for both the single-key and range cases via\n // the same drain-and-count `runExecute()` already uses for every other op.\n const req = store.openCursor(plan.key);\n const collected: Row[] = [];\n\n req.onsuccess = guardCallback(plan, \"DELETE_FAILED\", \"delete callback\", onError, () => {\n const cursor = req.result as IDBCursorWithValue | null;\n if (cursor === null) {\n onComplete(collected);\n return;\n }\n collected.push(cursor.value as Row);\n const delReq = cursor.delete();\n delReq.onsuccess = guardCallback(plan, \"DELETE_FAILED\", \"delete callback\", onError, () => cursor.continue());\n delReq.onerror = () => onError(opError(\"DELETE_FAILED\", plan, \"delete\", delReq.error));\n });\n req.onerror = () => onError(opError(\"DELETE_FAILED\", plan, \"delete\", req.error));\n}\n\n/** Write each matching cursor row once, even when its indexed value moves forward. */\nfunction execScanWrite(store: IDBObjectStore, plan: IdbScanWritePlan, onComplete: OnComplete, onError: OnError): void {\n // A zero limit writes nothing; without this the first match would be written before the limit check.\n if (plan.take === 0) return onComplete([]);\n\n // Cursor must be opened on a readwrite transaction (enforced by planTxMode).\n const source = plan.indexName !== undefined ? store.index(plan.indexName) : store;\n const req = source.openCursor(toOptionalIdbKeyRange(plan.range) ?? null, \"next\");\n const collected: Row[] = [];\n // An index cursor revisits a record whose indexed field the patch moved forward (or that has several\n // multiEntry entries). Track written primary keys so each record is written and counted once.\n const writtenKeys = plan.indexName !== undefined && plan.write === \"put-merged\" ? new Set<string>() : undefined;\n\n req.onsuccess = guardCallback(plan, \"PUT_FAILED\", \"scan-write callback\", onError, () => {\n const cursor = req.result as IDBCursorWithValue | null;\n\n if (cursor === null) {\n // Cursor exhausted — deliver all collected rows.\n onComplete(collected);\n return;\n }\n\n const row = cursor.value as Row;\n\n // Skip rows that don't match the filter.\n if (plan.filter !== undefined && !plan.filter(row)) {\n cursor.continue();\n return;\n }\n\n if (writtenKeys !== undefined && writtenKeys.has(keyIdentity(cursor.primaryKey))) {\n cursor.continue();\n return;\n }\n\n if (plan.write === \"delete\") {\n // Capture the row value before deleting (so deleteAll can return it).\n collected.push(row);\n const delReq = cursor.delete();\n delReq.onsuccess = guardCallback(plan, \"PUT_FAILED\", \"scan-write callback\", onError, () => {\n if (plan.take !== undefined && collected.length >= plan.take) {\n onComplete(collected);\n return; // intentionally no cursor.continue() — transaction auto-commits\n }\n cursor.continue();\n });\n delReq.onerror = () => onError(opError(\"DELETE_FAILED\", plan, \"scan-write (delete)\", delReq.error));\n } else {\n // put-merged: shallow-merge patch onto existing row, write back in-place.\n const merged: Row = { ...row, ...plan.patch };\n assertUnchangedPrimaryKey(store, plan, cursor.primaryKey, merged);\n const updReq = cursor.update(merged);\n updReq.onsuccess = guardCallback(plan, \"PUT_FAILED\", \"scan-write callback\", onError, () => {\n writtenKeys?.add(keyIdentity(cursor.primaryKey));\n collected.push(merged);\n if (plan.take !== undefined && collected.length >= plan.take) {\n onComplete(collected);\n return; // intentionally no cursor.continue()\n }\n cursor.continue();\n });\n updReq.onerror = () => onError(opError(\"PUT_FAILED\", plan, \"scan-write (put-merged)\", updReq.error));\n }\n });\n\n req.onerror = () => onError(opError(\"CURSOR_SCAN_FAILED\", plan, \"scan-write cursor\", req.error));\n}\n","/**\n * Top-level IDB plan dispatcher.\n *\n * `executeIdbPlan(db, plan)` is the single entry point for the IDB driver's\n * `execute()` path. It:\n * 1. Opens an IDB transaction scoped to the store(s) the plan touches.\n * 2. Dispatches to the appropriate op executor (ops.ts).\n * 3. Resolves the returned Promise only when `tx.oncomplete` fires —\n * guaranteeing write durability before rows are delivered.\n *\n * Transaction modes:\n * - Atomic read plans (key-get, get-all, cursor-scan): `readonly`\n * - Atomic write plans (add, put, delete): `readwrite`\n * - Batch plans: `readwrite` if any op is a write, `readonly` otherwise\n */\nimport type { IdbAtomicPlan, IdbBatchPlan, IdbPlanBody } from \"../plan-body\";\nimport { IdbExecuteError, isTransactionInactiveError, transactionInactiveError } from \"./error\";\nimport { executeOpInTx, planTxMode } from \"./ops\";\n\n/** A record returned by an IDB operation. */\ntype Row = Record<string, unknown>;\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Execute an IDB plan against a live database and collect all result rows.\n *\n * Batch plans run all ops inside a single transaction; atomic plans open a\n * single-store transaction. Resolves after `tx.oncomplete` so writes are\n * durable before the caller receives rows.\n */\nexport function executeIdbPlan(db: IDBDatabase, plan: IdbPlanBody): Promise<Row[]> {\n if (plan.kind === \"batch\") return executeBatchPlan(db, plan);\n return executeAtomicPlan(db, plan);\n}\n\n// ── Atomic plans ─────────────────────────────────────────────────────────────\n\n/** Resolve a single-store operation only after its transaction commits. */\nfunction executeAtomicPlan(db: IDBDatabase, plan: IdbAtomicPlan): Promise<Row[]> {\n return new Promise<Row[]>((resolve, reject) => {\n let tx: IDBTransaction;\n try {\n tx = db.transaction([plan.storeName], planTxMode(plan));\n } catch (err) {\n reject(\n new IdbExecuteError(\n { code: \"STORE_NOT_FOUND\", planKind: plan.kind, storeName: plan.storeName, cause: err },\n `IDB store \"${plan.storeName}\" not found in database`\n )\n );\n return;\n }\n\n const rows: Row[] = [];\n\n tx.oncomplete = () => resolve(rows);\n tx.onerror = () =>\n reject(\n new IdbExecuteError(\n {\n code: \"TRANSACTION_ABORTED\",\n planKind: plan.kind,\n storeName: plan.storeName,\n cause: tx.error,\n },\n `IDB transaction failed on store \"${plan.storeName}\": ${String(tx.error)}`\n )\n );\n tx.onabort = () =>\n reject(\n new IdbExecuteError(\n { code: \"TRANSACTION_ABORTED\", planKind: plan.kind, storeName: plan.storeName },\n `IDB transaction aborted on store \"${plan.storeName}\"`\n )\n );\n\n const store = tx.objectStore(plan.storeName);\n executeOpInTx(\n store,\n plan,\n (opRows) => {\n for (const row of opRows) rows.push(row);\n },\n (err) => {\n reject(err);\n try {\n tx.abort();\n } catch {\n /* Already finished. */\n }\n }\n );\n });\n}\n\n// ── Batch plans ──────────────────────────────────────────────────────────────\n\n/** Run all operations in one transaction and deliver rows after commit. */\nfunction executeBatchPlan(db: IDBDatabase, plan: IdbBatchPlan): Promise<Row[]> {\n return new Promise<Row[]>((resolve, reject) => {\n const mode: IDBTransactionMode = plan.ops.some((op) => planTxMode(op) === \"readwrite\") ? \"readwrite\" : \"readonly\";\n\n let tx: IDBTransaction;\n try {\n tx = db.transaction([...plan.storeNames], mode);\n } catch (err) {\n reject(\n new IdbExecuteError(\n { code: \"STORE_NOT_FOUND\", planKind: \"batch\", cause: err },\n `IDB batch transaction failed to open: ${String(err)}`\n )\n );\n return;\n }\n\n const rows: Row[] = [];\n\n tx.oncomplete = () => resolve(rows);\n tx.onerror = () =>\n reject(\n new IdbExecuteError(\n { code: \"TRANSACTION_ABORTED\", planKind: \"batch\", cause: tx.error },\n `IDB batch transaction failed: ${String(tx.error)}`\n )\n );\n tx.onabort = () =>\n reject(\n new IdbExecuteError({ code: \"TRANSACTION_ABORTED\", planKind: \"batch\" }, \"IDB batch transaction was aborted\")\n );\n\n // Run ops sequentially using recursive callbacks. Each op's `onComplete`\n // triggers the next op synchronously (still inside IDB event handlers),\n // which ensures we never cross a microtask boundary between IDB requests.\n runOpsSequentially(tx, plan.ops, 0, rows, (err) => {\n reject(err);\n try {\n tx.abort();\n } catch {\n /* Already finished. */\n }\n });\n });\n}\n\n/**\n * Recursively runs batch ops one at a time — op N+1 is initiated from inside\n * op N's `onComplete` callback, which fires synchronously within the IDB\n * event handler. This keeps the transaction alive and prevents auto-commit\n * gaps between ops.\n */\nfunction runOpsSequentially(\n tx: IDBTransaction,\n ops: ReadonlyArray<IdbAtomicPlan>,\n index: number,\n rows: Row[],\n onError: (err: unknown) => void\n): void {\n const op = ops[index];\n if (op === undefined) return; // all ops initiated; tx.oncomplete will resolve\n\n let store: IDBObjectStore;\n try {\n store = tx.objectStore(op.storeName);\n } catch (err) {\n if (isTransactionInactiveError(err)) {\n onError(transactionInactiveError(\"batch\", op.storeName, err));\n return;\n }\n onError(\n new IdbExecuteError(\n { code: \"STORE_NOT_FOUND\", planKind: \"batch\", storeName: op.storeName, cause: err },\n `IDB batch: store \"${op.storeName}\" not found`\n )\n );\n return;\n }\n\n executeOpInTx(\n store,\n op,\n (opRows) => {\n for (const row of opRows) rows.push(row);\n runOpsSequentially(tx, ops, index + 1, rows, onError);\n },\n onError\n );\n}\n","import type { ExecutionPlan } from \"@prisma/orm-framework/components/runtime\";\n\n// ── Marker store ─────────────────────────────────────────────────────────────\n\n/**\n * Name of the object store that holds the contract marker.\n * The client-idb marker compatibility test checks the migration writer and this reader.\n *\n * Created during `upgradeneeded` by the migration runner. `IdbRuntime.verifyMarker()`\n * compares its stored hash with the contract's to detect schema drift.\n *\n * Upstream equivalent: `_prisma_marker` table in SQLite/Postgres,\n * `_prisma_marker` collection in MongoDB.\n */\nexport const MARKER_STORE_NAME = \"_prisma_next_marker\";\n\n/**\n * Shape of a marker record stored in {@link MARKER_STORE_NAME}.\n *\n * Mirrors the framework's `ContractMarkerRecord` plus the `space` keying\n * field. Records are keyed by `space` (`\"app\"` for the single app space) so\n * the storage layout can accommodate future IDB extension spaces.\n *\n * `updatedAt` is a JavaScript `Date` (not an ISO string) because IndexedDB\n * serialises Dates natively via structured-clone.\n *\n * Non-key fields are optional for forward compatibility with future contract\n * versions that may add or remove marker metadata.\n */\nexport interface IdbMarkerRecord {\n /** Contract-space identifier (`\"app\"` for the single app space). */\n readonly space?: string;\n /** The `storageHash` from the contract that was last signed. */\n readonly storageHash: string;\n /** The `profileHash` from the contract that was last signed. */\n readonly profileHash?: string;\n /** Date the marker was last written. */\n readonly updatedAt?: Date | string;\n readonly invariants?: readonly string[];\n readonly contractJson?: unknown;\n readonly canonicalVersion?: number | null;\n readonly appTag?: string | null;\n readonly meta?: Record<string, unknown>;\n}\n\n/**\n * A key range as plain data. The driver builds the matching `IDBKeyRange` when it runs the plan, so a\n * plan's `range` fields never hold an `IDBKeyRange`. (`IdbDeletePlan.key` is the one exception: it still\n * takes an `IDBKeyRange`.) Each variant maps to the `IDBKeyRange` factory of the same name:\n *\n * - `only`: exactly `key`.\n * - `lower`: keys at or above `key`; above only when `open` is true.\n * - `upper`: keys at or below `key`; below only when `open` is true.\n * - `bound`: keys from `lower` to `upper`. Each end is closed unless `lowerOpen` or `upperOpen` is true.\n *\n * For a compound index, the keys are arrays.\n */\nexport type IdbKeyRangeDescriptor =\n | { readonly kind: \"only\"; readonly key: IDBValidKey }\n | { readonly kind: \"lower\"; readonly key: IDBValidKey; readonly open?: boolean }\n | { readonly kind: \"upper\"; readonly key: IDBValidKey; readonly open?: boolean }\n | {\n readonly kind: \"bound\";\n readonly lower: IDBValidKey;\n readonly upper: IDBValidKey;\n readonly lowerOpen?: boolean;\n readonly upperOpen?: boolean;\n };\n\n/** The order a cursor walks keys in: ascending (`next`) or descending (`prev`). */\nexport type IdbScanDirection = \"next\" | \"prev\";\n\n/**\n * In-memory predicate applied to each row while iterating a cursor.\n * Returns true if the row should be included in the result set.\n */\nexport type IdbRowFilter = (row: Record<string, unknown>) => boolean;\n\n/**\n * In-memory comparator for ORDER BY.\n * Follows the same contract as Array.prototype.sort: negative / zero / positive.\n */\nexport type IdbRowComparator = (a: Record<string, unknown>, b: Record<string, unknown>) => number;\n\n/**\n * Full cursor scan over an object store or index.\n *\n * Used for `findMany`, `findFirst`, and any query where an exact key lookup\n * isn't possible. Supports key-range restriction, in-memory filtering,\n * in-memory ordering, and OFFSET + LIMIT.\n *\n * When `indexName` is set, the cursor iterates the named secondary index\n * instead of the primary key order.\n *\n * Without a `comparator`, rows come back in cursor order (shaped by `indexName`\n * and `direction`), and the cursor stops as soon as `take` rows are collected.\n * That is how a caller gets `orderBy` + `take` without reading the whole range.\n * With a `comparator`, the cursor reads the whole range before sorting.\n */\nexport interface IdbCursorScanPlan extends ExecutionPlan {\n readonly kind: \"cursor-scan\";\n readonly storeName: string;\n readonly indexName?: string; // if set, iterate via this index\n readonly range?: IdbKeyRangeDescriptor; // restrict the cursor's key range\n readonly direction?: IdbScanDirection; // defaults to \"next\"\n readonly filter?: IdbRowFilter; // in-memory WHERE\n readonly comparator?: IdbRowComparator; // in-memory ORDER BY (when index order isn't enough)\n readonly skip?: number; // OFFSET\n readonly take?: number; // LIMIT (undefined = no limit)\n}\n\n/**\n * Primary-key O(1) lookup via `store.get(key)`.\n *\n * Used for `findUnique` on `@id` fields.\n */\nexport interface IdbKeyGetPlan extends ExecutionPlan {\n readonly kind: \"key-get\";\n readonly storeName: string;\n readonly key: IDBValidKey;\n}\n\n/**\n * Bulk read via `getAll`, in key order. Cheaper than a cursor when no row\n * filter, skip or early stop is needed.\n *\n * - Without `indexName`, reads the store; with it, reads through that index.\n * - Without `range`, reads everything.\n * - `count` caps the number of rows. `count: 0` returns no rows.\n *\n * Used for `findUnique` on `@@unique` fields and for index-backed `findMany`\n * on `@@index` fields.\n */\nexport interface IdbGetAllPlan extends ExecutionPlan {\n readonly kind: \"get-all\";\n readonly storeName: string;\n readonly indexName?: string;\n readonly range?: IdbKeyRangeDescriptor;\n readonly count?: number;\n}\n\n/**\n * Insert a single record via `store.add(record[, key])`.\n *\n * Used for Prisma `create` semantics. IndexedDB rejects `add()` when the\n * primary key already exists, which preserves the \"create never overwrites\"\n * contract expected by callers.\n *\n * `key` is only needed for out-of-line key stores (`keyPath: null`).\n */\nexport interface IdbAddPlan extends ExecutionPlan {\n readonly kind: \"add\";\n readonly storeName: string;\n readonly record: Record<string, unknown>;\n readonly key?: IDBValidKey;\n}\n\n/**\n * Upsert or replace a single record via `store.put(record[, key])`.\n *\n * Used for upsert paths where the full replacement record is already known.\n * The driver echoes `record` back as the result row — IDB does not have\n * RETURNING.\n *\n * `key` is only needed for out-of-line key stores (`keyPath: null`).\n */\nexport interface IdbPutPlan extends ExecutionPlan {\n readonly kind: \"put\";\n readonly storeName: string;\n readonly record: Record<string, unknown>;\n readonly key?: IDBValidKey;\n}\n\n/**\n * Atomic partial update: get → merge → put in a single readwrite transaction.\n *\n * Used for Prisma `update` where only a subset of fields change. The driver:\n * 1. Reads the current record via `store.get(key)`.\n * 2. Shallow-merges `patch` onto the existing record with `{ ...existing, ...patch }`.\n * 3. Writes the merged record back via `store.put(merged)`.\n * 4. Echoes the merged record as the result row.\n *\n * This preserves fields not mentioned in `patch` — unlike `IdbPutPlan` which\n * does a full replacement. The get and put are issued inside the same\n * readwrite transaction so no other writer can interleave.\n *\n * If no record exists for `key`, the driver echoes `patch` as-is (insert semantics).\n */\nexport interface IdbUpdatePlan extends ExecutionPlan {\n readonly kind: \"update\";\n readonly storeName: string;\n readonly key: IDBValidKey;\n readonly patch: Record<string, unknown>;\n}\n\n/**\n * Delete one or more records by key or key range.\n *\n * Used for `delete` and `deleteMany`. The driver walks a cursor over\n * `key` (a single key or a range) and yields each deleted row, so callers\n * can echo them back and `execute()` can report an accurate affected-row\n * count — same convention as `add`/`put`/`update`.\n */\nexport interface IdbDeletePlan extends ExecutionPlan {\n readonly kind: \"delete\";\n readonly storeName: string;\n readonly key: IDBValidKey | IDBKeyRange;\n}\n\n/**\n * Cursor scan with in-place write for each matching row.\n *\n * Used for `update` (take:1), `updateAll`, `updateCount`, `deleteAll`,\n * `deleteCount`. Opens a readwrite cursor and applies `write` to each\n * row that passes `filter`:\n *\n * - `\"put-merged\"`: shallow-merges `patch` onto the row and calls\n * `cursor.update(merged)`. Yields merged rows.\n * - `\"delete\"`: captures `cursor.value` then calls `cursor.delete()`.\n * Yields the deleted rows (so callers can echo them back).\n *\n * With `take`, the cursor stops after the first `take` matches (no\n * `cursor.continue()` call after the limit is reached).\n *\n * `indexName` and `range` narrow the rows the cursor visits; `filter` still\n * applies to each of them.\n */\nexport interface IdbScanWritePlan extends ExecutionPlan {\n readonly kind: \"scan-write\";\n readonly storeName: string;\n readonly indexName?: string; // if set, walk this index instead of the primary key order\n readonly range?: IdbKeyRangeDescriptor; // restrict the walk to this key range\n readonly filter?: IdbRowFilter;\n readonly take?: number;\n readonly write: \"put-merged\" | \"delete\";\n readonly patch?: Record<string, unknown>;\n}\n\n/**\n * Native count via `store.count(range)` / `index.count(range)`.\n *\n * Used by the ORM `.count()` terminal when the whole `where` is captured by a\n * key range (or there is no `where`), so no row needs to be deserialized or\n * filtered in memory. Counts index *entries*, so it must not be used with a\n * `multiEntry` index (one record can occupy several entries).\n *\n * Result: exactly one row, `{ count: number }` — the driver's `Row[]` result\n * contract is unchanged; the caller unwraps `rows[0].count`.\n */\nexport interface IdbCountPlan extends ExecutionPlan {\n readonly kind: \"count\";\n readonly storeName: string;\n readonly indexName?: string; // count via this index instead of the store's primary keys\n readonly range?: IdbKeyRangeDescriptor; // omit to count every entry\n}\n\n/**\n * Key-only read via `getKey(range)` / `getAllKeys(range, count)` — returns\n * PRIMARY keys without deserializing any row value.\n *\n * Meant for existence checks (\"does any row match this key range?\") where the\n * caller never reads a field. Because no value is fetched, it can only\n * express criteria that are a key range on the store's primary key or on an\n * index — never an in-memory row filter.\n *\n * - `take: 1` with a `range` → `getKey(range)` (one request, no cursor).\n * - otherwise → `getAllKeys(range, take)`; `take` omitted returns every key.\n *\n * Result rows: one `{ key }` per match (the primary key — an array for a\n * compound key) or `[]` when nothing matches. Same synthetic-row convention as\n * {@link IdbCountPlan}.\n */\nexport interface IdbKeysPlan extends ExecutionPlan {\n readonly kind: \"keys\";\n readonly storeName: string;\n readonly indexName?: string; // resolve through this index; returned keys are still primary keys\n readonly range?: IdbKeyRangeDescriptor; // omit to cover the whole store/index\n readonly take?: number; // max keys to return (undefined = all)\n}\n\n/**\n * All single-store atomic op types — valid both standalone and inside a batch.\n */\nexport type IdbAtomicPlan =\n | IdbCursorScanPlan\n | IdbKeyGetPlan\n | IdbGetAllPlan\n | IdbCountPlan\n | IdbKeysPlan\n | IdbAddPlan\n | IdbPutPlan\n | IdbUpdatePlan\n | IdbDeletePlan\n | IdbScanWritePlan;\n\n/**\n * Multi-op atomic batch executed inside a single IDB transaction.\n *\n * Used whenever a Prisma operation must touch multiple stores atomically\n * (e.g. `create` with nested relations, `update` + cascade `delete`).\n *\n * The driver opens ONE readwrite transaction scoped to `storeNames`, runs\n * every op in `ops` sequentially, and collects rows from read ops. Write ops\n * (`put`/`delete`) echo their result inline (see `IdbPutPlan`).\n *\n * `storeNames` MUST include every store referenced by any op in `ops`.\n * IDB requires the full store scope to be declared when opening a transaction.\n */\nexport interface IdbBatchPlan extends ExecutionPlan {\n readonly kind: \"batch\";\n readonly storeNames: ReadonlyArray<string>;\n readonly ops: ReadonlyArray<IdbAtomicPlan>;\n}\n\n/**\n * The full union of plan shapes the adapter produces and the driver executes.\n */\nexport type IdbPlanBody = IdbAtomicPlan | IdbBatchPlan;\n","/**\n * Multi-store IDB transaction scope.\n *\n * A single `IDBTransaction` spanning one or more object stores, exposed as a\n * typed execution surface for the ORM's `withMutationScope()` helper.\n *\n * Design notes:\n * - `execute(plan)` runs `executeOpInTx` directly in the pre-opened transaction —\n * NO new transaction is opened per call, and the middleware chain is bypassed\n * entirely. This keeps the shared IDB transaction alive across multiple awaited\n * execute() calls (IDB auto-commits in a macro-task, not a microtask; each\n * execute() issues new requests before the auto-commit check fires).\n * - `commit()` resolves when `tx.oncomplete` fires — the caller awaits this after\n * all scope operations are complete to get write-durability confirmation.\n * - `rollback()` is idempotent: calling it on an already-aborted transaction is\n * a no-op (the DOMException from IDB is swallowed).\n *\n * Since this scope's execute() bypasses both of\n * `RuntimeCore`'s middleware chains (query()'s and execute()'s alike), the\n * cache middleware never fires inside a scope → reads are always fresh.\n */\nimport type { IdbAtomicPlan } from \"./plan-body\";\nimport { IdbExecuteError, isTransactionInactiveError, transactionInactiveError } from \"./execute/error\";\nimport { executeOpInTx } from \"./execute/ops\";\n\n/** A record returned by an IDB operation. */\ntype Row = Record<string, unknown>;\n\n// ── Public interface ──────────────────────────────────────────────────────────\n\n/** Atomic operations and commit or rollback controls over one multi-store transaction. */\nexport interface IdbTransactionScope {\n /**\n * Execute a single atomic plan inside the shared IDB transaction.\n *\n * The plan runs synchronously inside IDB event callbacks; the returned\n * Promise resolves once the operation's `onsuccess` fires. The transaction\n * stays open — the next `execute()` call can be issued right after `await`.\n */\n execute(plan: IdbAtomicPlan): Promise<Row[]>;\n\n /**\n * Wait for the IDB transaction to fully commit.\n *\n * Resolves when `tx.oncomplete` fires (all writes are durable). Call this\n * after all `execute()` calls; `withMutationScope` does this automatically.\n */\n commit(): Promise<void>;\n\n /**\n * Abort the transaction, rolling back all uncommitted writes.\n *\n * Idempotent — safe to call when the transaction has already been aborted\n * (e.g. due to a request error). `withMutationScope` calls this on catch.\n */\n rollback(): void;\n}\n\n// ── Implementation ────────────────────────────────────────────────────────────\n\n/** Keep pending operations attached to the same transaction and settle them on abort. */\nclass IdbTransactionScopeImpl implements IdbTransactionScope {\n readonly #tx: IDBTransaction;\n readonly #onComplete: Promise<void>;\n readonly #pending = new Set<(err: unknown) => void>();\n\n constructor(tx: IDBTransaction) {\n this.#tx = tx;\n this.#onComplete = new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(\n new IdbExecuteError(\n { code: \"TRANSACTION_ABORTED\", planKind: \"batch\", cause: tx.error },\n `IDB transaction scope failed: ${String(tx.error)}`\n )\n );\n tx.onabort = () => {\n const error = new IdbExecuteError(\n { code: \"TRANSACTION_ABORTED\", planKind: \"batch\", cause: tx.error },\n \"IDB transaction scope aborted\"\n );\n for (const fail of this.#pending) fail(error);\n reject(error);\n };\n });\n // Suppress \"unhandled rejection\" when the scope is rolled back without\n // commit() being awaited (e.g. withMutationScope catches an error and\n // calls rollback() without ever calling commit()).\n this.#onComplete.catch(() => {});\n }\n\n /** Issue the operation in the existing scope and abort that scope on failure. */\n execute(plan: IdbAtomicPlan): Promise<Row[]> {\n return new Promise<Row[]>((resolve, reject) => {\n let store: IDBObjectStore;\n try {\n store = this.#tx.objectStore(plan.storeName);\n } catch (err) {\n this.rollback();\n if (isTransactionInactiveError(err)) {\n reject(transactionInactiveError(plan.kind, plan.storeName, err));\n return;\n }\n reject(\n new IdbExecuteError(\n { code: \"STORE_NOT_FOUND\", planKind: plan.kind, storeName: plan.storeName, cause: err },\n `IDB transaction scope: store \"${plan.storeName}\" not found`\n )\n );\n return;\n }\n\n const fail = (error: unknown) => {\n this.#pending.delete(fail);\n reject(error);\n this.rollback();\n };\n this.#pending.add(fail);\n executeOpInTx(\n store,\n plan,\n (opRows) => {\n this.#pending.delete(fail);\n resolve(opRows);\n },\n fail\n );\n });\n }\n\n commit(): Promise<void> {\n return this.#onComplete;\n }\n\n /** Abort uncommitted writes; a finished transaction has nothing left to roll back. */\n rollback(): void {\n try {\n this.#tx.abort();\n } catch {\n // Transaction already committed or aborted — no-op.\n }\n }\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\n/**\n * Open an IDB transaction spanning `storeNames` and wrap it in a scope.\n *\n * Throws synchronously if any store in `storeNames` is not found in the\n * database (IDB throws when opening a transaction with unknown store names).\n */\nexport function createTransactionScope(\n db: IDBDatabase,\n storeNames: string[],\n mode: IDBTransactionMode = \"readwrite\"\n): IdbTransactionScope {\n const tx = db.transaction(storeNames, mode);\n return new IdbTransactionScopeImpl(tx);\n}\n","import type { RuntimeDriverInstance } from \"@prisma/orm-framework/components/execution\";\nimport { executeIdbPlan } from \"./execute\";\nimport { MARKER_STORE_NAME, type IdbMarkerRecord, type IdbPlanBody } from \"./plan-body\";\nimport { createTransactionScope, type IdbTransactionScope } from \"./transaction-scope\";\n\n/** Own the database connection and execute driver plans after it opens. */\nexport class IdbRuntimeDriverInstance implements RuntimeDriverInstance<\"idb\", \"idb\"> {\n readonly familyId = \"idb\" as const;\n readonly targetId = \"idb\" as const;\n /**\n * The live IDBDatabase connection. Resolves once `upgradeneeded` (if any)\n * completes and the database is ready for use.\n *\n * `execute()` and `transaction()` await this before opening a transaction.\n * The Promise is shared — all concurrent callers get the same database object.\n */\n readonly db: Promise<IDBDatabase>;\n\n constructor(dbName: string, version?: number, factory?: IDBFactory) {\n this.db = openIdbDatabase(dbName, version, factory);\n }\n\n async close(): Promise<void> {\n (await this.db).close();\n }\n\n /**\n * Read the contract marker from the `_prisma_next_marker` store.\n *\n * Returns `null` when the marker store does not exist (fresh database\n * that hasn't been initialised yet) or when no marker record is present.\n *\n * Called by `IdbRuntimeImpl.verifyMarker()` to check that the live IDB\n * schema matches the contract.\n */\n async readMarker(): Promise<IdbMarkerRecord | null> {\n const db = await this.db;\n if (!db.objectStoreNames.contains(MARKER_STORE_NAME)) {\n return null;\n }\n return new Promise((resolve, reject) => {\n const tx = db.transaction(MARKER_STORE_NAME, \"readonly\");\n const store = tx.objectStore(MARKER_STORE_NAME);\n\n const appReq = store.get(\"app\");\n appReq.onsuccess = () => {\n const result = appReq.result as IdbMarkerRecord | undefined;\n resolve(result ?? null);\n };\n appReq.onerror = () => reject(appReq.error);\n\n tx.onerror = () => reject(tx.error);\n });\n }\n\n /**\n * Open a multi-store readwrite transaction and wrap it in an\n * {@link IdbTransactionScope}.\n *\n * Awaits the live `IDBDatabase` connection before opening the transaction.\n * Used by `withMutationScope()` in `client-idb` to run cross-store writes\n * atomically.\n */\n async transaction(storeNames: string[], mode: IDBTransactionMode = \"readwrite\"): Promise<IdbTransactionScope> {\n const db = await this.db;\n return createTransactionScope(db, storeNames, mode);\n }\n\n /**\n * Execute an IDB plan body and yield rows as an async iterable.\n *\n * Opens a new IDB transaction for each call, collects all rows inside the\n * transaction (collect-then-yield; see execute/index.ts for rationale),\n * and yields them after the transaction commits.\n *\n * Called by `IdbRuntimeImpl.runDriver()` in `@prisma-idb/runtime-idb`.\n */\n execute(plan: IdbPlanBody): AsyncIterable<Record<string, unknown>> {\n const dbPromise = this.db;\n return {\n /** Wait for commit before exposing materialized rows to the runtime. */\n [Symbol.asyncIterator]() {\n return (async function* () {\n const db = await dbPromise;\n const rows = await executeIdbPlan(db, plan);\n yield* rows;\n })();\n },\n };\n }\n}\n\n/**\n * Opens an IDB database and resolves once it is ready for use.\n *\n * Wraps the IDB event-based open API in a Promise. There is no `upgradeneeded`\n * handler: the driver does not own schema. Migrations are applied by\n * `openAndUpgrade` (target-idb), which opens the database at a specific\n * version and runs DDL inside the version-change transaction.\n *\n * When version is omitted the IDB spec opens the database at its current version\n * (or version 1 for a brand-new database). This is the correct runtime behaviour\n * per ADR 001: the migration runner owns version bumping; the runtime just connects\n * to whatever schema is already in place.\n */\nfunction openIdbDatabase(dbName: string, version?: number, factory: IDBFactory = indexedDB): Promise<IDBDatabase> {\n return new Promise<IDBDatabase>((resolve, reject) => {\n const req = factory.open(dbName, version);\n\n req.onerror = () => {\n reject(req.error);\n };\n\n req.onblocked = () => {\n reject(new Error(`IDB open blocked for \"${dbName}\": another connection is open with an older version.`));\n };\n\n req.onsuccess = () => {\n const db = req.result;\n // Multi-tab safety: when another tab opens this database at a higher\n // version, the IDB spec fires `versionchange` on every other open\n // connection. If we don't close, the new tab's open request hangs\n // indefinitely on `blocked`. Closing here releases the lock; the\n // application layer can listen for `db.onclose` to surface a\n // \"please reload\" toast if it cares.\n db.onversionchange = () => {\n db.close();\n };\n resolve(db);\n };\n });\n}\n","import type { RuntimeDriverDescriptor } from \"@prisma/orm-framework/components/execution\";\nimport { idbDriverDescriptorMeta } from \"../core/descriptor-meta\";\nimport { IdbRuntimeDriverInstance } from \"../core/idb-driver\";\n\nexport type {\n IdbPlanBody,\n IdbAtomicPlan,\n IdbBatchPlan,\n IdbAddPlan,\n IdbCursorScanPlan,\n IdbCountPlan,\n IdbKeysPlan,\n IdbKeyGetPlan,\n IdbGetAllPlan,\n IdbPutPlan,\n IdbUpdatePlan,\n IdbDeletePlan,\n IdbScanWritePlan,\n IdbKeyRangeDescriptor,\n IdbScanDirection,\n IdbRowFilter,\n IdbRowComparator,\n IdbMarkerRecord,\n} from \"../core/plan-body\";\nexport { MARKER_STORE_NAME } from \"../core/plan-body\";\nexport type { IdbRuntimeDriverInstance } from \"../core/idb-driver\";\nexport { IdbExecuteError } from \"../core/execute/error\";\nexport type { IdbExecuteErrorCode } from \"../core/execute/error\";\nexport type { IdbTransactionScope } from \"../core/transaction-scope\";\nexport { createTransactionScope } from \"../core/transaction-scope\";\n\n/**\n * Creates a runtime driver descriptor for IndexedDB.\n *\n * The returned descriptor's `create()` starts opening the named IDB database\n * in the background; the driver awaits the connection before its first query.\n *\n * @param dbName - The IDB database name to open.\n * @param version - The IDB version number. When omitted, IndexedDB opens the\n * database at its current version (version 1 for a new database). The\n * driver never runs migrations; `openAndUpgrade` (target-idb) bumps the version.\n *\n * @example\n * ```ts\n * const stack = createRuntimeStack({\n * target: idbRuntimeTargetDescriptor,\n * adapter: idbRuntimeAdapterDescriptor,\n * driver: createIDBRuntimeDriver(\"my-app\"),\n * });\n * ```\n */\nexport function createIDBRuntimeDriver(\n dbName: string,\n version?: number,\n options?: { readonly factory?: IDBFactory }\n): RuntimeDriverDescriptor<\"idb\", \"idb\", void, IdbRuntimeDriverInstance> {\n return {\n ...idbDriverDescriptorMeta,\n create(): IdbRuntimeDriverInstance {\n return new IdbRuntimeDriverInstance(dbName, version, options?.factory);\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-idb/driver-idb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Prisma 8 IDB family — driver (wraps window.indexedDB for the execution stack)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"./package.json": "./package.json"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@prisma/orm-framework": "8.0.0-rc.
|
|
32
|
+
"@prisma/orm-framework": "8.0.0-rc.18"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"fake-indexeddb": "^6.2.5",
|