@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 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
@@ -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
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"control.d.mts","names":[],"sources":["../src/exports/control.ts"],"mappings":";;;;;;;;;;;;cAaM,4BAA4B;WACvB;WACA;EAEH,MAAM,MAAM,yBAChB,cACA,+BACC;aAAmB,MAAM;;EAItB,SAAS;;cAGX,4BAA4B,sCAAsC"}
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
  }
@@ -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;CAEpB,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"}
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"}
@@ -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
  *
@@ -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;;;KCpSrC,MAAM;UAIM;;;;;;;;EAQf,QAAQ,MAAM,gBAAgB,QAAQ;;;;;;;EAQtC,UAAU;;;;;;;EAQV;;;;;;;;iBAoFc,uBACd,IAAI,aACJ,sBACA,OAAM,qBACL;;;cCxIU,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;;;;;;;;;;;KCrEhC;;;;;;;;cAqBC,wBAAwB;WAC1B,MAAM;WACN;WACA;;WAEA;;WAEA;WACS;EAGhB,YAAA;aACW,MAAM;aACN;aACA;aACA;KAEX;;;;;;;;;;;;;;;;;;;;;;;;iBCMY,uBACd,gBACA,kBACA;WAAqB,UAAU;IAC9B,4CAA4C"}
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
- throw err;
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
- const putReq = store.put(merged);
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) => reject(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, reject);
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 = () => reject(new IdbExecuteError({
465
- code: "TRANSACTION_ABORTED",
466
- planKind: "batch"
467
- }, "IDB transaction scope aborted"));
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 rows = [];
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
- for (const row of opRows) rows.push(row);
492
- resolve(rows);
493
- }, reject);
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 { [Symbol.asyncIterator]() {
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
  })();
@@ -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.11.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.12"
32
+ "@prisma/orm-framework": "8.0.0-rc.18"
33
33
  },
34
34
  "devDependencies": {
35
35
  "fake-indexeddb": "^6.2.5",