@prisma-idb/driver-idb 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/runtime.d.mts +62 -14
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +148 -35
- package/dist/runtime.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,12 @@ npm install --save-dev @prisma-idb/driver-idb
|
|
|
10
10
|
|
|
11
11
|
Part of [Prisma 8 IDB](https://prisma-idb.dev/docs/prisma-8), a typed IndexedDB client for Prisma 8. Start with the [Quick Start](https://prisma-idb.dev/docs/prisma-8/getting-started). For how the packages fit together, see [ARCHITECTURE.md](https://github.com/prisma-idb/prisma-idb/blob/main/packages/prisma-orm/docs/ARCHITECTURE.md).
|
|
12
12
|
|
|
13
|
+
## Update identity
|
|
14
|
+
|
|
15
|
+
`update` and `scan-write` preserve the existing primary key. Changing an inline key rejects with `IdbExecuteError` code `PRIMARY_KEY_CHANGE_UNSUPPORTED` and aborts the transaction. Repeating an equivalent key is allowed, including compound, date and binary keys.
|
|
16
|
+
|
|
17
|
+
Primary keys are immutable. Delete the row and create it again only after handling dependent records. Restrictive relations can block the delete, and cascading relations can delete dependent records. See [ADR 020](../docs/adrs/ADR%20020%20-%20Primary%20Keys%20Are%20Immutable.md) for the reasons.
|
|
18
|
+
|
|
13
19
|
## License
|
|
14
20
|
|
|
15
21
|
MIT
|
package/dist/runtime.d.mts
CHANGED
|
@@ -40,6 +40,38 @@ interface IdbMarkerRecord {
|
|
|
40
40
|
readonly appTag?: string | null;
|
|
41
41
|
readonly meta?: Record<string, unknown>;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* A key range as plain data. The driver builds the matching `IDBKeyRange` when it runs the plan, so a
|
|
45
|
+
* plan's `range` fields never hold an `IDBKeyRange`. (`IdbDeletePlan.key` is the one exception: it still
|
|
46
|
+
* takes an `IDBKeyRange`.) Each variant maps to the `IDBKeyRange` factory of the same name:
|
|
47
|
+
*
|
|
48
|
+
* - `only`: exactly `key`.
|
|
49
|
+
* - `lower`: keys at or above `key`; above only when `open` is true.
|
|
50
|
+
* - `upper`: keys at or below `key`; below only when `open` is true.
|
|
51
|
+
* - `bound`: keys from `lower` to `upper`. Each end is closed unless `lowerOpen` or `upperOpen` is true.
|
|
52
|
+
*
|
|
53
|
+
* For a compound index, the keys are arrays.
|
|
54
|
+
*/
|
|
55
|
+
type IdbKeyRangeDescriptor = {
|
|
56
|
+
readonly kind: "only";
|
|
57
|
+
readonly key: IDBValidKey;
|
|
58
|
+
} | {
|
|
59
|
+
readonly kind: "lower";
|
|
60
|
+
readonly key: IDBValidKey;
|
|
61
|
+
readonly open?: boolean;
|
|
62
|
+
} | {
|
|
63
|
+
readonly kind: "upper";
|
|
64
|
+
readonly key: IDBValidKey;
|
|
65
|
+
readonly open?: boolean;
|
|
66
|
+
} | {
|
|
67
|
+
readonly kind: "bound";
|
|
68
|
+
readonly lower: IDBValidKey;
|
|
69
|
+
readonly upper: IDBValidKey;
|
|
70
|
+
readonly lowerOpen?: boolean;
|
|
71
|
+
readonly upperOpen?: boolean;
|
|
72
|
+
};
|
|
73
|
+
/** The order a cursor walks keys in: ascending (`next`) or descending (`prev`). */
|
|
74
|
+
type IdbScanDirection = "next" | "prev";
|
|
43
75
|
/**
|
|
44
76
|
* In-memory predicate applied to each row while iterating a cursor.
|
|
45
77
|
* Returns true if the row should be included in the result set.
|
|
@@ -59,13 +91,18 @@ type IdbRowComparator = (a: Record<string, unknown>, b: Record<string, unknown>)
|
|
|
59
91
|
*
|
|
60
92
|
* When `indexName` is set, the cursor iterates the named secondary index
|
|
61
93
|
* instead of the primary key order.
|
|
94
|
+
*
|
|
95
|
+
* Without a `comparator`, rows come back in cursor order (shaped by `indexName`
|
|
96
|
+
* and `direction`), and the cursor stops as soon as `take` rows are collected.
|
|
97
|
+
* That is how a caller gets `orderBy` + `take` without reading the whole range.
|
|
98
|
+
* With a `comparator`, the cursor reads the whole range before sorting.
|
|
62
99
|
*/
|
|
63
100
|
interface IdbCursorScanPlan extends ExecutionPlan {
|
|
64
101
|
readonly kind: "cursor-scan";
|
|
65
102
|
readonly storeName: string;
|
|
66
103
|
readonly indexName?: string;
|
|
67
|
-
readonly range?:
|
|
68
|
-
readonly direction?:
|
|
104
|
+
readonly range?: IdbKeyRangeDescriptor;
|
|
105
|
+
readonly direction?: IdbScanDirection;
|
|
69
106
|
readonly filter?: IdbRowFilter;
|
|
70
107
|
readonly comparator?: IdbRowComparator;
|
|
71
108
|
readonly skip?: number;
|
|
@@ -82,16 +119,22 @@ interface IdbKeyGetPlan extends ExecutionPlan {
|
|
|
82
119
|
readonly key: IDBValidKey;
|
|
83
120
|
}
|
|
84
121
|
/**
|
|
85
|
-
*
|
|
122
|
+
* Bulk read via `getAll`, in key order. Cheaper than a cursor when no row
|
|
123
|
+
* filter, skip or early stop is needed.
|
|
124
|
+
*
|
|
125
|
+
* - Without `indexName`, reads the store; with it, reads through that index.
|
|
126
|
+
* - Without `range`, reads everything.
|
|
127
|
+
* - `count` caps the number of rows. `count: 0` returns no rows.
|
|
86
128
|
*
|
|
87
|
-
* Used for `findUnique` on `@@unique` fields and index-
|
|
88
|
-
*
|
|
129
|
+
* Used for `findUnique` on `@@unique` fields and for index-backed `findMany`
|
|
130
|
+
* on `@@index` fields.
|
|
89
131
|
*/
|
|
90
|
-
interface
|
|
91
|
-
readonly kind: "
|
|
132
|
+
interface IdbGetAllPlan extends ExecutionPlan {
|
|
133
|
+
readonly kind: "get-all";
|
|
92
134
|
readonly storeName: string;
|
|
93
|
-
readonly indexName
|
|
94
|
-
readonly range
|
|
135
|
+
readonly indexName?: string;
|
|
136
|
+
readonly range?: IdbKeyRangeDescriptor;
|
|
137
|
+
readonly count?: number;
|
|
95
138
|
}
|
|
96
139
|
/**
|
|
97
140
|
* Insert a single record via `store.add(record[, key])`.
|
|
@@ -171,10 +214,15 @@ interface IdbDeletePlan extends ExecutionPlan {
|
|
|
171
214
|
*
|
|
172
215
|
* With `take`, the cursor stops after the first `take` matches (no
|
|
173
216
|
* `cursor.continue()` call after the limit is reached).
|
|
217
|
+
*
|
|
218
|
+
* `indexName` and `range` narrow the rows the cursor visits; `filter` still
|
|
219
|
+
* applies to each of them.
|
|
174
220
|
*/
|
|
175
221
|
interface IdbScanWritePlan extends ExecutionPlan {
|
|
176
222
|
readonly kind: "scan-write";
|
|
177
223
|
readonly storeName: string;
|
|
224
|
+
readonly indexName?: string;
|
|
225
|
+
readonly range?: IdbKeyRangeDescriptor;
|
|
178
226
|
readonly filter?: IdbRowFilter;
|
|
179
227
|
readonly take?: number;
|
|
180
228
|
readonly write: "put-merged" | "delete";
|
|
@@ -195,7 +243,7 @@ interface IdbCountPlan extends ExecutionPlan {
|
|
|
195
243
|
readonly kind: "count";
|
|
196
244
|
readonly storeName: string;
|
|
197
245
|
readonly indexName?: string;
|
|
198
|
-
readonly range?:
|
|
246
|
+
readonly range?: IdbKeyRangeDescriptor;
|
|
199
247
|
}
|
|
200
248
|
/**
|
|
201
249
|
* Key-only read via `getKey(range)` / `getAllKeys(range, count)` — returns
|
|
@@ -217,13 +265,13 @@ interface IdbKeysPlan extends ExecutionPlan {
|
|
|
217
265
|
readonly kind: "keys";
|
|
218
266
|
readonly storeName: string;
|
|
219
267
|
readonly indexName?: string;
|
|
220
|
-
readonly range?:
|
|
268
|
+
readonly range?: IdbKeyRangeDescriptor;
|
|
221
269
|
readonly take?: number;
|
|
222
270
|
}
|
|
223
271
|
/**
|
|
224
272
|
* All single-store atomic op types — valid both standalone and inside a batch.
|
|
225
273
|
*/
|
|
226
|
-
type IdbAtomicPlan = IdbCursorScanPlan | IdbKeyGetPlan |
|
|
274
|
+
type IdbAtomicPlan = IdbCursorScanPlan | IdbKeyGetPlan | IdbGetAllPlan | IdbCountPlan | IdbKeysPlan | IdbAddPlan | IdbPutPlan | IdbUpdatePlan | IdbDeletePlan | IdbScanWritePlan;
|
|
227
275
|
/**
|
|
228
276
|
* Multi-op atomic batch executed inside a single IDB transaction.
|
|
229
277
|
*
|
|
@@ -334,7 +382,7 @@ declare class IdbRuntimeDriverInstance implements RuntimeDriverInstance<"idb", "
|
|
|
334
382
|
* handle specific failure modes (e.g. retry on TRANSACTION_ABORTED) can
|
|
335
383
|
* branch on `error.code` without string-matching the message.
|
|
336
384
|
*/
|
|
337
|
-
type IdbExecuteErrorCode = "STORE_NOT_FOUND" | "KEY_GET_FAILED" | "
|
|
385
|
+
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";
|
|
338
386
|
/**
|
|
339
387
|
* Structured error raised by the IDB driver's `execute()` path.
|
|
340
388
|
*
|
|
@@ -384,5 +432,5 @@ declare function createIDBRuntimeDriver(dbName: string, version?: number, option
|
|
|
384
432
|
readonly factory?: IDBFactory;
|
|
385
433
|
}): RuntimeDriverDescriptor<"idb", "idb", void, IdbRuntimeDriverInstance>;
|
|
386
434
|
//#endregion
|
|
387
|
-
export { type IdbAddPlan, type IdbAtomicPlan, type IdbBatchPlan, type IdbCountPlan, type IdbCursorScanPlan, type IdbDeletePlan, IdbExecuteError, type IdbExecuteErrorCode, type
|
|
435
|
+
export { type IdbAddPlan, type IdbAtomicPlan, type IdbBatchPlan, type IdbCountPlan, type IdbCursorScanPlan, type IdbDeletePlan, IdbExecuteError, type IdbExecuteErrorCode, type IdbGetAllPlan, type IdbKeyGetPlan, type IdbKeyRangeDescriptor, type IdbKeysPlan, type IdbMarkerRecord, type IdbPlanBody, type IdbPutPlan, type IdbRowComparator, type IdbRowFilter, type IdbRuntimeDriverInstance, type IdbScanDirection, type IdbScanWritePlan, type IdbTransactionScope, type IdbUpdatePlan, MARKER_STORE_NAME, createIDBRuntimeDriver, createTransactionScope };
|
|
388
436
|
//# sourceMappingURL=runtime.d.mts.map
|
package/dist/runtime.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/core/plan-body.ts","../src/core/transaction-scope.ts","../src/core/idb-driver.ts","../src/core/execute/error.ts","../src/exports/runtime.ts"],"mappings":";;;;;;;;;;;;;cAca;;;;;;;;;;;;;;UAeI;;WAEN;;WAEA;;WAEA;;WAEA,YAAY;WACZ;WACA;WACA;WACA;WACA,OAAO
|
|
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;;;;;;;;iBA8Fc,uBACd,IAAI,aACJ,sBACA,OAAM,qBACL;;;cClJU,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;;;;;;;;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
|
@@ -45,6 +45,63 @@ function transactionInactiveError(planKind, storeName, cause) {
|
|
|
45
45
|
}, `IDB transaction is no longer active when running "${planKind}" on store "${storeName}". IndexedDB transactions auto-commit as soon as the microtask queue drains with no pending request, so code between two operations must only \`await\` IDB-request-resolving promises (no fetch, timers or other async work inside a transaction). See ADR 005/007. Cause: ${String(cause)}`);
|
|
46
46
|
}
|
|
47
47
|
//#endregion
|
|
48
|
+
//#region src/core/execute/key-identity.ts
|
|
49
|
+
/**
|
|
50
|
+
* A string that is equal for two `IDBValidKey`s exactly when IndexedDB considers the keys equal.
|
|
51
|
+
*
|
|
52
|
+
* `Set` compares arrays, dates and binary keys by reference, so it cannot track primary keys directly.
|
|
53
|
+
* Each key type gets its own prefix so, for example, the string "1" and the number 1 stay distinct.
|
|
54
|
+
*
|
|
55
|
+
* Dates and binary keys are detected by their internal slots, not `instanceof`, because a key can come
|
|
56
|
+
* from another realm (for example an iframe), where its constructors differ from this realm's.
|
|
57
|
+
*/
|
|
58
|
+
function keyIdentity(key) {
|
|
59
|
+
if (typeof key === "number") return `n:${Object.is(key, -0) ? 0 : key}`;
|
|
60
|
+
if (typeof key === "string") return `s:${JSON.stringify(key)}`;
|
|
61
|
+
if (Array.isArray(key)) return `[${key.map(keyIdentity).join(",")}]`;
|
|
62
|
+
if (ArrayBuffer.isView(key)) return binaryIdentity(new Uint8Array(key.buffer, key.byteOffset, key.byteLength));
|
|
63
|
+
if (isArrayBuffer(key)) return binaryIdentity(new Uint8Array(key));
|
|
64
|
+
return `d:${Date.prototype.getTime.call(key)}`;
|
|
65
|
+
}
|
|
66
|
+
/** The `byteLength` getter throws unless its receiver has an `[[ArrayBufferData]]` slot, in any realm. */
|
|
67
|
+
const arrayBufferByteLength = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get;
|
|
68
|
+
function isArrayBuffer(value) {
|
|
69
|
+
try {
|
|
70
|
+
arrayBufferByteLength.call(value);
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function binaryIdentity(bytes) {
|
|
77
|
+
return `b:${bytes.join(".")}`;
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/core/execute/key-range.ts
|
|
81
|
+
/**
|
|
82
|
+
* Builds the `IDBKeyRange` a plan's range descriptor stands for.
|
|
83
|
+
*
|
|
84
|
+
* The `range` fields of a plan carry descriptors, not `IDBKeyRange` objects, so that part of a plan stays
|
|
85
|
+
* plain data that callers can build, hash and log without touching IndexedDB. Each descriptor maps to the `IDBKeyRange` factory of the same
|
|
86
|
+
* name, so the factory's own validation applies: an invalid key, or `bound` with `lower` above `upper`,
|
|
87
|
+
* throws `DataError`.
|
|
88
|
+
*/
|
|
89
|
+
function toIdbKeyRange(range) {
|
|
90
|
+
switch (range.kind) {
|
|
91
|
+
case "only": return IDBKeyRange.only(range.key);
|
|
92
|
+
case "lower": return IDBKeyRange.lowerBound(range.key, range.open);
|
|
93
|
+
case "upper": return IDBKeyRange.upperBound(range.key, range.open);
|
|
94
|
+
case "bound": return IDBKeyRange.bound(range.lower, range.upper, range.lowerOpen, range.upperOpen);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* `toIdbKeyRange` for an optional range. `undefined` means "no restriction", which IndexedDB expresses
|
|
99
|
+
* as an absent query.
|
|
100
|
+
*/
|
|
101
|
+
function toOptionalIdbKeyRange(range) {
|
|
102
|
+
return range === void 0 ? void 0 : toIdbKeyRange(range);
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
48
105
|
//#region src/core/execute/ops.ts
|
|
49
106
|
/**
|
|
50
107
|
* The error for a failed request: `IDB <what> failed on <target>: <cause>`.
|
|
@@ -63,6 +120,28 @@ function storeTarget(plan) {
|
|
|
63
120
|
const index = plan.indexName !== void 0 ? ` (index "${plan.indexName}")` : "";
|
|
64
121
|
return `store "${plan.storeName}"${index}`;
|
|
65
122
|
}
|
|
123
|
+
/** Route synchronous event-handler failures through the same channel as request errors. */
|
|
124
|
+
function guardCallback(plan, code, what, onError, run) {
|
|
125
|
+
return () => {
|
|
126
|
+
try {
|
|
127
|
+
run();
|
|
128
|
+
} catch (cause) {
|
|
129
|
+
onError(cause instanceof IdbExecuteError ? cause : opError(code, plan, what, cause));
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Updates preserve identity. A key move needs referential and sync delete/create semantics. */
|
|
134
|
+
function assertUnchangedPrimaryKey(store, plan, key, row) {
|
|
135
|
+
const keyPath = store.keyPath;
|
|
136
|
+
if (keyPath == null) return;
|
|
137
|
+
const extract = (path) => path === "" ? row : path.split(".").reduce((value, field) => value !== null && typeof value === "object" ? value[field] : void 0, row);
|
|
138
|
+
const nextKey = typeof keyPath === "string" ? extract(keyPath) : keyPath.map(extract);
|
|
139
|
+
if (indexedDB.cmp(key, nextKey) !== 0) throw new IdbExecuteError({
|
|
140
|
+
code: "PRIMARY_KEY_CHANGE_UNSUPPORTED",
|
|
141
|
+
planKind: plan.kind,
|
|
142
|
+
storeName: plan.storeName
|
|
143
|
+
}, `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.`);
|
|
144
|
+
}
|
|
66
145
|
/**
|
|
67
146
|
* Dispatch an atomic plan to its IDB operation within a live transaction.
|
|
68
147
|
*
|
|
@@ -75,13 +154,13 @@ function executeOpInTx(store, plan, onComplete, onError) {
|
|
|
75
154
|
dispatchOp(store, plan, onComplete, onError);
|
|
76
155
|
} catch (err) {
|
|
77
156
|
if (isTransactionInactiveError(err)) return onError(transactionInactiveError(plan.kind, plan.storeName, err));
|
|
78
|
-
|
|
157
|
+
onError(err);
|
|
79
158
|
}
|
|
80
159
|
}
|
|
81
160
|
function dispatchOp(store, plan, onComplete, onError) {
|
|
82
161
|
switch (plan.kind) {
|
|
83
162
|
case "key-get": return execKeyGet(store, plan, onComplete, onError);
|
|
84
|
-
case "
|
|
163
|
+
case "get-all": return execGetAll(store, plan, onComplete, onError);
|
|
85
164
|
case "cursor-scan": return execCursorScan(store, plan, onComplete, onError);
|
|
86
165
|
case "count": return execCount(store, plan, onComplete, onError);
|
|
87
166
|
case "keys": return execKeys(store, plan, onComplete, onError);
|
|
@@ -105,22 +184,25 @@ function execKeyGet(store, plan, onComplete, onError) {
|
|
|
105
184
|
};
|
|
106
185
|
req.onerror = () => onError(opError("KEY_GET_FAILED", plan, "key-get", req.error));
|
|
107
186
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
187
|
+
function execGetAll(store, plan, onComplete, onError) {
|
|
188
|
+
if (plan.count === 0) return onComplete([]);
|
|
189
|
+
const req = (plan.indexName !== void 0 ? store.index(plan.indexName) : store).getAll(toOptionalIdbKeyRange(plan.range), plan.count);
|
|
110
190
|
req.onsuccess = () => onComplete(req.result);
|
|
111
|
-
req.onerror = () => onError(opError("
|
|
191
|
+
req.onerror = () => onError(opError("GET_ALL_FAILED", plan, "get-all", req.error, storeTarget(plan)));
|
|
112
192
|
}
|
|
113
193
|
function execCount(store, plan, onComplete, onError) {
|
|
114
194
|
const source = plan.indexName !== void 0 ? store.index(plan.indexName) : store;
|
|
115
|
-
const
|
|
195
|
+
const range = toOptionalIdbKeyRange(plan.range);
|
|
196
|
+
const req = range !== void 0 ? source.count(range) : source.count();
|
|
116
197
|
req.onsuccess = () => onComplete([{ count: req.result }]);
|
|
117
198
|
req.onerror = () => onError(opError("COUNT_FAILED", plan, "count", req.error, storeTarget(plan)));
|
|
118
199
|
}
|
|
119
200
|
function execKeys(store, plan, onComplete, onError) {
|
|
120
201
|
const source = plan.indexName !== void 0 ? store.index(plan.indexName) : store;
|
|
121
202
|
const fail = (cause) => onError(opError("KEYS_FAILED", plan, "keys read", cause, storeTarget(plan)));
|
|
122
|
-
|
|
123
|
-
|
|
203
|
+
const range = toOptionalIdbKeyRange(plan.range);
|
|
204
|
+
if (plan.take === 1 && range !== void 0) {
|
|
205
|
+
const req = source.getKey(range);
|
|
124
206
|
req.onsuccess = () => onComplete(req.result === void 0 ? [] : [{ key: req.result }]);
|
|
125
207
|
req.onerror = () => fail(req.error);
|
|
126
208
|
return;
|
|
@@ -129,7 +211,7 @@ function execKeys(store, plan, onComplete, onError) {
|
|
|
129
211
|
onComplete([]);
|
|
130
212
|
return;
|
|
131
213
|
}
|
|
132
|
-
const req = source.getAllKeys(
|
|
214
|
+
const req = source.getAllKeys(range, plan.take);
|
|
133
215
|
req.onsuccess = () => onComplete(req.result.map((key) => ({ key })));
|
|
134
216
|
req.onerror = () => fail(req.error);
|
|
135
217
|
}
|
|
@@ -138,10 +220,11 @@ function execCursorScan(store, plan, onComplete, onError) {
|
|
|
138
220
|
const filter = plan.filter;
|
|
139
221
|
const skip = plan.skip;
|
|
140
222
|
const take = plan.take;
|
|
141
|
-
|
|
223
|
+
if (take === 0) return onComplete([]);
|
|
224
|
+
const req = (plan.indexName !== void 0 ? store.index(plan.indexName) : store).openCursor(toOptionalIdbKeyRange(plan.range) ?? null, plan.direction ?? "next");
|
|
142
225
|
const collected = [];
|
|
143
226
|
let skippedCount = 0;
|
|
144
|
-
req.onsuccess = () => {
|
|
227
|
+
req.onsuccess = guardCallback(plan, "CURSOR_SCAN_FAILED", "cursor-scan callback", onError, () => {
|
|
145
228
|
const cursor = req.result;
|
|
146
229
|
if (cursor === null) {
|
|
147
230
|
if (comparator !== void 0) {
|
|
@@ -164,7 +247,7 @@ function execCursorScan(store, plan, onComplete, onError) {
|
|
|
164
247
|
}
|
|
165
248
|
}
|
|
166
249
|
cursor.continue();
|
|
167
|
-
};
|
|
250
|
+
});
|
|
168
251
|
req.onerror = () => onError(opError("CURSOR_SCAN_FAILED", plan, "cursor-scan", req.error));
|
|
169
252
|
}
|
|
170
253
|
/**
|
|
@@ -192,21 +275,22 @@ function execPut(store, plan, onComplete, onError) {
|
|
|
192
275
|
}
|
|
193
276
|
function execUpdate(store, plan, onComplete, onError) {
|
|
194
277
|
const getReq = store.get(plan.key);
|
|
195
|
-
getReq.onsuccess = () => {
|
|
278
|
+
getReq.onsuccess = guardCallback(plan, "PUT_FAILED", "update (put phase)", onError, () => {
|
|
196
279
|
const merged = {
|
|
197
280
|
...getReq.result ?? {},
|
|
198
281
|
...plan.patch
|
|
199
282
|
};
|
|
200
|
-
|
|
283
|
+
assertUnchangedPrimaryKey(store, plan, plan.key, merged);
|
|
284
|
+
const putReq = store.keyPath === null ? store.put(merged, plan.key) : store.put(merged);
|
|
201
285
|
putReq.onsuccess = () => onComplete([merged]);
|
|
202
286
|
putReq.onerror = () => onError(opError("PUT_FAILED", plan, "update (put phase)", putReq.error));
|
|
203
|
-
};
|
|
287
|
+
});
|
|
204
288
|
getReq.onerror = () => onError(opError("KEY_GET_FAILED", plan, "update (get phase)", getReq.error));
|
|
205
289
|
}
|
|
206
290
|
function execDelete(store, plan, onComplete, onError) {
|
|
207
291
|
const req = store.openCursor(plan.key);
|
|
208
292
|
const collected = [];
|
|
209
|
-
req.onsuccess = () => {
|
|
293
|
+
req.onsuccess = guardCallback(plan, "DELETE_FAILED", "delete callback", onError, () => {
|
|
210
294
|
const cursor = req.result;
|
|
211
295
|
if (cursor === null) {
|
|
212
296
|
onComplete(collected);
|
|
@@ -214,15 +298,17 @@ function execDelete(store, plan, onComplete, onError) {
|
|
|
214
298
|
}
|
|
215
299
|
collected.push(cursor.value);
|
|
216
300
|
const delReq = cursor.delete();
|
|
217
|
-
delReq.onsuccess = () => cursor.continue();
|
|
301
|
+
delReq.onsuccess = guardCallback(plan, "DELETE_FAILED", "delete callback", onError, () => cursor.continue());
|
|
218
302
|
delReq.onerror = () => onError(opError("DELETE_FAILED", plan, "delete", delReq.error));
|
|
219
|
-
};
|
|
303
|
+
});
|
|
220
304
|
req.onerror = () => onError(opError("DELETE_FAILED", plan, "delete", req.error));
|
|
221
305
|
}
|
|
222
306
|
function execScanWrite(store, plan, onComplete, onError) {
|
|
223
|
-
|
|
307
|
+
if (plan.take === 0) return onComplete([]);
|
|
308
|
+
const req = (plan.indexName !== void 0 ? store.index(plan.indexName) : store).openCursor(toOptionalIdbKeyRange(plan.range) ?? null, "next");
|
|
224
309
|
const collected = [];
|
|
225
|
-
|
|
310
|
+
const writtenKeys = plan.indexName !== void 0 && plan.write === "put-merged" ? /* @__PURE__ */ new Set() : void 0;
|
|
311
|
+
req.onsuccess = guardCallback(plan, "PUT_FAILED", "scan-write callback", onError, () => {
|
|
226
312
|
const cursor = req.result;
|
|
227
313
|
if (cursor === null) {
|
|
228
314
|
onComplete(collected);
|
|
@@ -233,34 +319,40 @@ function execScanWrite(store, plan, onComplete, onError) {
|
|
|
233
319
|
cursor.continue();
|
|
234
320
|
return;
|
|
235
321
|
}
|
|
322
|
+
if (writtenKeys !== void 0 && writtenKeys.has(keyIdentity(cursor.primaryKey))) {
|
|
323
|
+
cursor.continue();
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
236
326
|
if (plan.write === "delete") {
|
|
237
327
|
collected.push(row);
|
|
238
328
|
const delReq = cursor.delete();
|
|
239
|
-
delReq.onsuccess = () => {
|
|
329
|
+
delReq.onsuccess = guardCallback(plan, "PUT_FAILED", "scan-write callback", onError, () => {
|
|
240
330
|
if (plan.take !== void 0 && collected.length >= plan.take) {
|
|
241
331
|
onComplete(collected);
|
|
242
332
|
return;
|
|
243
333
|
}
|
|
244
334
|
cursor.continue();
|
|
245
|
-
};
|
|
335
|
+
});
|
|
246
336
|
delReq.onerror = () => onError(opError("DELETE_FAILED", plan, "scan-write (delete)", delReq.error));
|
|
247
337
|
} else {
|
|
248
338
|
const merged = {
|
|
249
339
|
...row,
|
|
250
340
|
...plan.patch
|
|
251
341
|
};
|
|
342
|
+
assertUnchangedPrimaryKey(store, plan, cursor.primaryKey, merged);
|
|
252
343
|
const updReq = cursor.update(merged);
|
|
253
|
-
updReq.onsuccess = () => {
|
|
344
|
+
updReq.onsuccess = guardCallback(plan, "PUT_FAILED", "scan-write callback", onError, () => {
|
|
345
|
+
writtenKeys?.add(keyIdentity(cursor.primaryKey));
|
|
254
346
|
collected.push(merged);
|
|
255
347
|
if (plan.take !== void 0 && collected.length >= plan.take) {
|
|
256
348
|
onComplete(collected);
|
|
257
349
|
return;
|
|
258
350
|
}
|
|
259
351
|
cursor.continue();
|
|
260
|
-
};
|
|
352
|
+
});
|
|
261
353
|
updReq.onerror = () => onError(opError("PUT_FAILED", plan, "scan-write (put-merged)", updReq.error));
|
|
262
354
|
}
|
|
263
|
-
};
|
|
355
|
+
});
|
|
264
356
|
req.onerror = () => onError(opError("CURSOR_SCAN_FAILED", plan, "scan-write cursor", req.error));
|
|
265
357
|
}
|
|
266
358
|
//#endregion
|
|
@@ -305,7 +397,12 @@ function executeAtomicPlan(db, plan) {
|
|
|
305
397
|
}, `IDB transaction aborted on store "${plan.storeName}"`));
|
|
306
398
|
executeOpInTx(tx.objectStore(plan.storeName), plan, (opRows) => {
|
|
307
399
|
for (const row of opRows) rows.push(row);
|
|
308
|
-
}, (err) =>
|
|
400
|
+
}, (err) => {
|
|
401
|
+
reject(err);
|
|
402
|
+
try {
|
|
403
|
+
tx.abort();
|
|
404
|
+
} catch {}
|
|
405
|
+
});
|
|
309
406
|
});
|
|
310
407
|
}
|
|
311
408
|
function executeBatchPlan(db, plan) {
|
|
@@ -333,7 +430,12 @@ function executeBatchPlan(db, plan) {
|
|
|
333
430
|
code: "TRANSACTION_ABORTED",
|
|
334
431
|
planKind: "batch"
|
|
335
432
|
}, "IDB batch transaction was aborted"));
|
|
336
|
-
runOpsSequentially(tx, plan.ops, 0, rows,
|
|
433
|
+
runOpsSequentially(tx, plan.ops, 0, rows, (err) => {
|
|
434
|
+
reject(err);
|
|
435
|
+
try {
|
|
436
|
+
tx.abort();
|
|
437
|
+
} catch {}
|
|
438
|
+
});
|
|
337
439
|
});
|
|
338
440
|
}
|
|
339
441
|
/**
|
|
@@ -384,6 +486,7 @@ const MARKER_STORE_NAME = "_prisma_next_marker";
|
|
|
384
486
|
var IdbTransactionScopeImpl = class {
|
|
385
487
|
#tx;
|
|
386
488
|
#onComplete;
|
|
489
|
+
#pending = /* @__PURE__ */ new Set();
|
|
387
490
|
constructor(tx) {
|
|
388
491
|
this.#tx = tx;
|
|
389
492
|
this.#onComplete = new Promise((resolve, reject) => {
|
|
@@ -393,10 +496,15 @@ var IdbTransactionScopeImpl = class {
|
|
|
393
496
|
planKind: "batch",
|
|
394
497
|
cause: tx.error
|
|
395
498
|
}, `IDB transaction scope failed: ${String(tx.error)}`));
|
|
396
|
-
tx.onabort = () =>
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
499
|
+
tx.onabort = () => {
|
|
500
|
+
const error = new IdbExecuteError({
|
|
501
|
+
code: "TRANSACTION_ABORTED",
|
|
502
|
+
planKind: "batch",
|
|
503
|
+
cause: tx.error
|
|
504
|
+
}, "IDB transaction scope aborted");
|
|
505
|
+
for (const fail of this.#pending) fail(error);
|
|
506
|
+
reject(error);
|
|
507
|
+
};
|
|
400
508
|
});
|
|
401
509
|
this.#onComplete.catch(() => {});
|
|
402
510
|
}
|
|
@@ -418,11 +526,16 @@ var IdbTransactionScopeImpl = class {
|
|
|
418
526
|
}, `IDB transaction scope: store "${plan.storeName}" not found`));
|
|
419
527
|
return;
|
|
420
528
|
}
|
|
421
|
-
const
|
|
529
|
+
const fail = (error) => {
|
|
530
|
+
this.#pending.delete(fail);
|
|
531
|
+
reject(error);
|
|
532
|
+
this.rollback();
|
|
533
|
+
};
|
|
534
|
+
this.#pending.add(fail);
|
|
422
535
|
executeOpInTx(store, plan, (opRows) => {
|
|
423
|
-
|
|
424
|
-
resolve(
|
|
425
|
-
},
|
|
536
|
+
this.#pending.delete(fail);
|
|
537
|
+
resolve(opRows);
|
|
538
|
+
}, fail);
|
|
426
539
|
});
|
|
427
540
|
}
|
|
428
541
|
commit() {
|
package/dist/runtime.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.mjs","names":["unknown","#tx","#onComplete"],"sources":["../src/core/execute/error.ts","../src/core/execute/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 | \"INDEX_GET_FAILED\" // store.index(name).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 * 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 IdbIndexGetPlan,\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\";\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 \"index-get\":\n return execIndexGet(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 execIndexGet(store: IDBObjectStore, plan: IdbIndexGetPlan, onComplete: OnComplete, onError: OnError): void {\n const req = store.index(plan.indexName).getAll(plan.range);\n req.onsuccess = () => onComplete(req.result as Row[]);\n req.onerror = () =>\n onError(opError(\"INDEX_GET_FAILED\", plan, \"index-get\", req.error, `\"${plan.storeName}\"/\"${plan.indexName}\"`));\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 req = plan.range !== undefined ? source.count(plan.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\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 && plan.range !== undefined) {\n const req = source.getKey(plan.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(plan.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 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(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 // Cursor must be opened on a readwrite transaction (enforced by planTxMode).\n const req = store.openCursor(null, \"next\");\n const collected: Row[] = [];\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 (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 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, index-get, 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 * 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 */\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?: IDBKeyRange; // restrict the cursor's key range\n readonly direction?: IDBCursorDirection; // \"next\" | \"nextunique\" | \"prev\" | \"prevunique\"\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 * Index-based range lookup.\n *\n * Used for `findUnique` on `@@unique` fields and index-accelerated\n * `findMany` on `@@index` fields.\n */\nexport interface IdbIndexGetPlan extends ExecutionPlan {\n readonly kind: \"index-get\";\n readonly storeName: string;\n readonly indexName: string;\n readonly range: IDBKeyRange;\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 */\nexport interface IdbScanWritePlan extends ExecutionPlan {\n readonly kind: \"scan-write\";\n readonly storeName: string;\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?: IDBKeyRange; // 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?: IDBKeyRange; // 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 | IdbIndexGetPlan\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 IdbIndexGetPlan,\n IdbPutPlan,\n IdbUpdatePlan,\n IdbDeletePlan,\n IdbScanWritePlan,\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\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/** 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 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\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 = 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\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 = 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\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\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\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) => {\n reject(err);\n try {\n tx.abort();\n } catch {\n /* Already finished. */\n }\n }\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, (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 * 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 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 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 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 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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"}
|