@prisma-idb/client-idb 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client-auto.d.mts +2 -2
- package/dist/client-auto.d.mts.map +1 -1
- package/dist/client-auto.mjs +44 -3
- package/dist/client-auto.mjs.map +1 -1
- package/dist/client.d.mts +3 -2
- package/dist/client.mjs +3 -2
- package/dist/decimal-identity-D_1J9lPN.d.mts +21 -0
- package/dist/decimal-identity-D_1J9lPN.d.mts.map +1 -0
- package/dist/internal.d.mts +5 -4
- package/dist/internal.d.mts.map +1 -1
- package/dist/internal.mjs +2 -2
- package/dist/{managed-client-M3xMUk0n.mjs → managed-client-DMk8ERfX.mjs} +12 -5
- package/dist/managed-client-DMk8ERfX.mjs.map +1 -0
- package/dist/{managed-client-DQlsxc4Z.d.mts → managed-client-eksFK4UF.d.mts} +32 -3
- package/dist/managed-client-eksFK4UF.d.mts.map +1 -0
- package/dist/{mutation-executor-inLFb0TE.mjs → mutation-executor-IuJmyWEG.mjs} +486 -105
- package/dist/mutation-executor-IuJmyWEG.mjs.map +1 -0
- package/dist/{executor-CIEHpa1j.d.mts → mutation-scope-CE3GMR4X.d.mts} +66 -22
- package/dist/mutation-scope-CE3GMR4X.d.mts.map +1 -0
- package/dist/{idb-orm-B1Br1C0F.mjs → orm-transaction-BGx1eukw.mjs} +357 -61
- package/dist/orm-transaction-BGx1eukw.mjs.map +1 -0
- package/dist/{idb-orm-6OuwkeDM.d.mts → orm-transaction-B_ZJ92uY.d.mts} +40 -7
- package/dist/orm-transaction-B_ZJ92uY.d.mts.map +1 -0
- package/dist/orm.d.mts +13 -4
- package/dist/orm.d.mts.map +1 -1
- package/dist/orm.mjs +3 -3
- package/package.json +9 -9
- package/dist/executor-CIEHpa1j.d.mts.map +0 -1
- package/dist/idb-orm-6OuwkeDM.d.mts.map +0 -1
- package/dist/idb-orm-B1Br1C0F.mjs.map +0 -1
- package/dist/managed-client-DQlsxc4Z.d.mts.map +0 -1
- package/dist/managed-client-M3xMUk0n.mjs.map +0 -1
- package/dist/mutation-executor-inLFb0TE.mjs.map +0 -1
- package/dist/mutation-scope-DrxFhek3.d.mts +0 -33
- package/dist/mutation-scope-DrxFhek3.d.mts.map +0 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as createMutationDefaultsCache, B as resolveDecimalKey, C as validateScalarFks, D as planQuery, E as lowerRows, F as combineFilterExprs, G as fieldValueToken, J as getStoreName, K as getKeyPath, M as assertValidRecord, N as assertEnumValues, O as applyUpdateDefaults, P as buildRowComparator, S as requireTransactionExecutor, T as lowerCount, U as buildCatalog, V as withDecimalIdentityExecutor, W as extractKeyFromRow, _ as hasEnforceableChildRelations, a as collectOnUpdateEnforcementStoreNames, b as makePlanMeta, c as executeBulkUpdateWithFkValidation, d as executeMutationPlan, f as executeNestedCreateMutation, g as executeScalarUpdateWithFkValidation, h as executeScalarCreateWithFkValidation, i as collectOnUpdateClosureStoreNames, k as completeCreateRecord, l as executeDeleteAllWithReferentialActions, m as executeScalarCreateAllWithFkValidation, n as applyReferentialActionsForRowOnUpdate, o as collectRelatedStoreNames, p as executeNestedUpdateMutation, q as getRelation, r as collectDeleteStoreNames, s as collectScalarFkStoreNames, u as executeDeleteWithReferentialActions, v as hasNestedMutationCallbacks, w as withMutationScope, x as readMutationRows, y as hasScalarFkFields } from "./mutation-executor-IuJmyWEG.mjs";
|
|
2
2
|
import { AsyncIterableResult } from "@prisma/orm-framework/components/runtime";
|
|
3
|
-
import { andExpr, fieldFilter, nullCheckExpr, shorthandToFilterExpr } from "@prisma-idb/adapter-idb/runtime";
|
|
4
3
|
import { domainModelsAtDefaultNamespace } from "@prisma/orm-framework/contract/types";
|
|
4
|
+
import { IdbExecuteError } from "@prisma-idb/driver-idb/runtime";
|
|
5
|
+
import { absentAsNull, isValidIdbKey } from "@prisma-idb/target-idb/runtime";
|
|
6
|
+
import { andExpr, fieldFilter, nullCheckExpr, shorthandToFilterExpr } from "@prisma-idb/adapter-idb/runtime";
|
|
5
7
|
//#region src/core/model-accessor.ts
|
|
6
8
|
/**
|
|
7
9
|
* Proxy-based typed accessor handed to `where(fn)` callbacks.
|
|
@@ -52,7 +54,9 @@ function createFieldAccessor(field) {
|
|
|
52
54
|
*/
|
|
53
55
|
function createModelAccessor() {
|
|
54
56
|
const cache = /* @__PURE__ */ new Map();
|
|
55
|
-
return new Proxy({}, {
|
|
57
|
+
return new Proxy({}, {
|
|
58
|
+
/** Reuse the field accessor within one filter callback so repeated reads share its operator surface. */
|
|
59
|
+
get(_target, prop) {
|
|
56
60
|
if (typeof prop !== "string") return void 0;
|
|
57
61
|
let acc = cache.get(prop);
|
|
58
62
|
if (acc === void 0) {
|
|
@@ -145,33 +149,43 @@ function isAggregateSelector(value) {
|
|
|
145
149
|
* - `sum`/`avg`/`min`/`max` → computed over the non-null numeric values of
|
|
146
150
|
* `field`; `null` when no row has a non-null value (matching Prisma's
|
|
147
151
|
* "aggregate of an empty set is null" and the vendor `coerceAggregateValue`
|
|
148
|
-
* null handling).
|
|
152
|
+
* null handling). Values must match the declared number codec.
|
|
149
153
|
*/
|
|
150
|
-
function reduceAggregate(fn, field, rows) {
|
|
154
|
+
function reduceAggregate(fn, field, rows, codecId) {
|
|
151
155
|
if (fn === "count") return rows.length;
|
|
152
156
|
if (field === void 0) return null;
|
|
153
157
|
const values = [];
|
|
154
158
|
for (const row of rows) {
|
|
155
159
|
const raw = row[field];
|
|
156
160
|
if (raw === null || raw === void 0) continue;
|
|
157
|
-
|
|
158
|
-
if (!Number.
|
|
161
|
+
if (typeof raw !== "number" || !Number.isFinite(raw)) throw new TypeError(`${fn}("${field}") requires finite number values`);
|
|
162
|
+
if (codecId === "idb/int32@1" && (!Number.isInteger(raw) || raw < -2147483648 || raw > 2147483647)) throw new TypeError(`${fn}("${field}") requires Int values in the signed 32-bit range`);
|
|
163
|
+
values.push(raw);
|
|
159
164
|
}
|
|
160
165
|
if (values.length === 0) return null;
|
|
161
166
|
switch (fn) {
|
|
162
|
-
case "sum": return values
|
|
163
|
-
case "avg": return values
|
|
164
|
-
case "min": return Math.min(
|
|
165
|
-
case "max": return Math.max(
|
|
167
|
+
case "sum": return sumValues(values, field, codecId === "idb/int32@1");
|
|
168
|
+
case "avg": return sumValues(values, field, codecId === "idb/int32@1") / values.length;
|
|
169
|
+
case "min": return values.reduce((acc, n) => Math.min(acc, n));
|
|
170
|
+
case "max": return values.reduce((acc, n) => Math.max(acc, n));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Reject overflow before a rounded or nonfinite intermediate total can reach the result. */
|
|
174
|
+
function sumValues(values, field, integer) {
|
|
175
|
+
let total = 0;
|
|
176
|
+
for (const value of values) {
|
|
177
|
+
total += value;
|
|
178
|
+
if (!Number.isFinite(total) || integer && !Number.isSafeInteger(total)) throw new RangeError(`Aggregate total for "${field}" exceeds the ${integer ? "safe integer" : "finite number"} range`);
|
|
166
179
|
}
|
|
180
|
+
return total;
|
|
167
181
|
}
|
|
168
182
|
/**
|
|
169
183
|
* Run every selector in an aggregate spec against `rows`, producing the
|
|
170
184
|
* result object keyed by the spec's aliases.
|
|
171
185
|
*/
|
|
172
|
-
function computeAggregateSpec(spec, rows) {
|
|
186
|
+
function computeAggregateSpec(spec, rows, fields = {}) {
|
|
173
187
|
const result = {};
|
|
174
|
-
for (const [alias, selector] of Object.entries(spec)) result[alias] = reduceAggregate(selector.fn, selector.field, rows);
|
|
188
|
+
for (const [alias, selector] of Object.entries(spec)) result[alias] = reduceAggregate(selector.fn, selector.field, rows, fields[selector.field ?? ""]?.codecId);
|
|
175
189
|
return result;
|
|
176
190
|
}
|
|
177
191
|
/**
|
|
@@ -189,14 +203,20 @@ function toAggregateRequests(spec) {
|
|
|
189
203
|
}
|
|
190
204
|
/**
|
|
191
205
|
* Validate a user-supplied aggregate spec: it must be non-empty and every
|
|
192
|
-
* value must be a real selector
|
|
206
|
+
* value must be a real selector over a declared scalar number field (except
|
|
207
|
+
* count, which has no field). Throws a descriptive error otherwise, so a
|
|
193
208
|
* typo (e.g. returning a plain object) fails loudly rather than silently
|
|
194
209
|
* producing `NaN`/`undefined` results.
|
|
195
210
|
*/
|
|
196
|
-
function assertValidAggregateSpec(spec, context) {
|
|
211
|
+
function assertValidAggregateSpec(spec, context, fields) {
|
|
197
212
|
const entries = Object.entries(spec);
|
|
198
213
|
if (entries.length === 0) throw new Error(`${context} requires at least one aggregation selector`);
|
|
199
|
-
for (const [alias, selector] of entries)
|
|
214
|
+
for (const [alias, selector] of entries) {
|
|
215
|
+
if (!isAggregateSelector(selector)) throw new Error(`${context} selector "${alias}" is invalid`);
|
|
216
|
+
if (selector.fn === "count") continue;
|
|
217
|
+
const field = typeof selector.field === "string" ? fields[selector.field] : void 0;
|
|
218
|
+
if (!field || field.collection || field.codecId !== "idb/int32@1" && field.codecId !== "idb/double@1") throw new TypeError(`${context} selector "${alias}" requires a declared number field (Int or Float)`);
|
|
219
|
+
}
|
|
200
220
|
}
|
|
201
221
|
//#endregion
|
|
202
222
|
//#region src/core/grouped-accessor.ts
|
|
@@ -215,9 +235,11 @@ function groupToken(value) {
|
|
|
215
235
|
}
|
|
216
236
|
/** Build a grouped accessor that reduces materialized rows from the source accessor. */
|
|
217
237
|
function createGroupedAccessor(init) {
|
|
218
|
-
return {
|
|
238
|
+
return {
|
|
239
|
+
/** Materialize matching rows, partition them by group key, and reduce each group. */
|
|
240
|
+
async aggregate(fn) {
|
|
219
241
|
const spec = fn(createAggregateBuilder());
|
|
220
|
-
assertValidAggregateSpec(spec, "groupBy().aggregate()");
|
|
242
|
+
assertValidAggregateSpec(spec, "groupBy().aggregate()", init.fields);
|
|
221
243
|
const ast = {
|
|
222
244
|
kind: "groupBy",
|
|
223
245
|
modelName: init.modelName,
|
|
@@ -243,11 +265,30 @@ function createGroupedAccessor(init) {
|
|
|
243
265
|
}
|
|
244
266
|
return Array.from(groups.values()).map((group) => ({
|
|
245
267
|
...group.key,
|
|
246
|
-
...computeAggregateSpec(spec, group.rows)
|
|
268
|
+
...computeAggregateSpec(spec, group.rows, init.fields)
|
|
247
269
|
}));
|
|
248
270
|
} };
|
|
249
271
|
}
|
|
250
272
|
//#endregion
|
|
273
|
+
//#region src/core/unique-lookup.ts
|
|
274
|
+
/**
|
|
275
|
+
* Copy the own enumerable properties of an upsert `where` once. Validation and
|
|
276
|
+
* filter parsing then see the same values even when a property is a getter.
|
|
277
|
+
* Non-object inputs pass through so `assertUniqueLookup` can reject them.
|
|
278
|
+
*/
|
|
279
|
+
function snapshotLookup(where) {
|
|
280
|
+
return typeof where === "object" && where !== null && !Array.isArray(where) ? Object.fromEntries(Object.entries(where)) : where;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Require equality on a complete native unique key before upsert can mutate.
|
|
284
|
+
* Null and other non-key values are absent from IndexedDB indexes, so even a
|
|
285
|
+
* declared unique index cannot make those filters identify at most one row.
|
|
286
|
+
*/
|
|
287
|
+
function assertUniqueLookup(catalog, where) {
|
|
288
|
+
const sources = [catalog.primaryKey, ...catalog.indexes];
|
|
289
|
+
if (!(typeof where === "object" && where !== null && !Array.isArray(where) && sources.some((source) => source.unique && !source.multiEntry && source.fields.length > 0 && source.fields.every((field) => Object.prototype.propertyIsEnumerable.call(where, field) && isValidIdbKey(where[field]))))) throw new Error(`Invalid upsert where for "${catalog.storeName}": provide a complete primary key or unique index with non-null IndexedDB key values`);
|
|
290
|
+
}
|
|
291
|
+
//#endregion
|
|
251
292
|
//#region src/core/relation-loader.ts
|
|
252
293
|
/**
|
|
253
294
|
* Batch-load a single named relation for all rows in `rows` and attach the
|
|
@@ -308,7 +349,7 @@ async function loadRelation(relName, entry, rows, contract, modelName, executor,
|
|
|
308
349
|
const joinWhere = andExpr(targetFields.map((field, i) => fieldFilter(field, "in", [...localTuples.values()].map((tuple) => tuple[i]))));
|
|
309
350
|
const where = refinedWhere === void 0 ? joinWhere : andExpr([joinWhere, refinedWhere]);
|
|
310
351
|
const request = { ...refinedWhere === void 0 ? {} : { where: refinedWhere } };
|
|
311
|
-
const catalog = buildCatalog(contract, relatedStoreName);
|
|
352
|
+
const catalog = buildCatalog(contract, relatedStoreName, relatedModelName);
|
|
312
353
|
const lowered = lowerRows(catalog, planQuery(catalog, { where }), planMeta, request, (row) => {
|
|
313
354
|
const token = tupleToken(row, targetFields);
|
|
314
355
|
return token !== null && localTuples.has(token);
|
|
@@ -317,10 +358,10 @@ async function loadRelation(relName, entry, rows, contract, modelName, executor,
|
|
|
317
358
|
meta: planMeta,
|
|
318
359
|
idbPlan: lowered.idbPlan
|
|
319
360
|
};
|
|
320
|
-
return attachRelatedRows(relName, entry, rows, lowered.finish(await executor.query(plan).toArray()), relation);
|
|
361
|
+
return attachRelatedRows(relName, entry, rows, lowered.finish(await executor.query(plan).toArray()), relation, catalog.fields);
|
|
321
362
|
}
|
|
322
363
|
/** Attach related rows by their join tuple, shaping each parent's collection independently. */
|
|
323
|
-
function attachRelatedRows(relName, entry, rows, relatedRows, relation) {
|
|
364
|
+
function attachRelatedRows(relName, entry, rows, relatedRows, relation, fields) {
|
|
324
365
|
const { cardinality, on: { localFields, targetFields } } = relation;
|
|
325
366
|
if (cardinality === "1:N") {
|
|
326
367
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -339,7 +380,7 @@ function attachRelatedRows(relName, entry, rows, relatedRows, relation) {
|
|
|
339
380
|
...row,
|
|
340
381
|
[relName]: groupFor(row).length
|
|
341
382
|
}));
|
|
342
|
-
const comparator = buildRowComparator(entry.state.orderBy);
|
|
383
|
+
const comparator = buildRowComparator(entry.state.orderBy, fields);
|
|
343
384
|
const skip = entry.state.skip ?? 0;
|
|
344
385
|
const take = entry.state.take;
|
|
345
386
|
return rows.map((row) => {
|
|
@@ -411,6 +452,7 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
411
452
|
this.#newGroupingKey = newGroupingKey ?? (() => `idb-op-${++_key}`);
|
|
412
453
|
this.#includeRefinementMode = includeRefinementMode;
|
|
413
454
|
}
|
|
455
|
+
/** Add a callback or shorthand predicate to a new accessor without mutating this one. */
|
|
414
456
|
where(filter) {
|
|
415
457
|
const expr = typeof filter === "function" ? filter(createModelAccessor()) : shorthandToFilterExpr(filter);
|
|
416
458
|
if (expr === void 0) return this.#clone({});
|
|
@@ -425,6 +467,7 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
425
467
|
skip(n) {
|
|
426
468
|
return this.#clone({ skip: n });
|
|
427
469
|
}
|
|
470
|
+
/** Add a collection or count refinement while preserving this accessor state. */
|
|
428
471
|
include(relation, refineFn) {
|
|
429
472
|
const entry = this.#resolveIncludeEntry(relation, refineFn);
|
|
430
473
|
const newState = mergeAccessorState(this.#state, { includes: {
|
|
@@ -433,9 +476,11 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
433
476
|
} });
|
|
434
477
|
return new IdbStoreAccessorImpl(this.#contract, this.#modelName, this.#executor, newState, this.#newGroupingKey, this.#includeRefinementMode);
|
|
435
478
|
}
|
|
479
|
+
/** Narrow scalar output fields while retaining included relations. */
|
|
436
480
|
select(...fields) {
|
|
437
481
|
return this.#clone({ selectedFields: fields });
|
|
438
482
|
}
|
|
483
|
+
/** Read matching rows, load includes, and project fields before yielding. */
|
|
439
484
|
all() {
|
|
440
485
|
const groupingKey = this.#newGroupingKey();
|
|
441
486
|
const buildScanPlan = this.#buildScanPlan.bind(this);
|
|
@@ -453,9 +498,11 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
453
498
|
async first() {
|
|
454
499
|
return this.take(1).all().first();
|
|
455
500
|
}
|
|
501
|
+
/** Reduce all filtered rows; query pagination does not constrain aggregates. */
|
|
456
502
|
async aggregate(fn) {
|
|
457
503
|
const spec = fn(createAggregateBuilder());
|
|
458
|
-
|
|
504
|
+
const { fields } = buildCatalog(this.#contract, this.#storeName, this.#modelName);
|
|
505
|
+
assertValidAggregateSpec(spec, "aggregate()", fields);
|
|
459
506
|
const combined = this.#combinedFilterExpr();
|
|
460
507
|
const ast = {
|
|
461
508
|
kind: "aggregate",
|
|
@@ -463,18 +510,21 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
463
510
|
aggregates: toAggregateRequests(spec),
|
|
464
511
|
...combined !== void 0 ? { where: combined } : {}
|
|
465
512
|
};
|
|
466
|
-
return computeAggregateSpec(spec, await this.#materialize(this.#newGroupingKey(), ast));
|
|
513
|
+
return computeAggregateSpec(spec, await this.#materialize(this.#newGroupingKey(), ast), fields);
|
|
467
514
|
}
|
|
515
|
+
/** Carry this filter into a grouped accessor whose materialization is owned here. */
|
|
468
516
|
groupBy(...fields) {
|
|
469
517
|
const combined = this.#combinedFilterExpr();
|
|
470
518
|
const materialize = (ast) => this.#materialize(this.#newGroupingKey(), ast);
|
|
471
519
|
return createGroupedAccessor({
|
|
472
520
|
modelName: this.#modelName,
|
|
473
521
|
by: fields,
|
|
522
|
+
fields: buildCatalog(this.#contract, this.#storeName, this.#modelName).fields,
|
|
474
523
|
where: combined,
|
|
475
524
|
materialize
|
|
476
525
|
});
|
|
477
526
|
}
|
|
527
|
+
/** Complete and validate one record, using a transaction when relations need checks. */
|
|
478
528
|
async create(data) {
|
|
479
529
|
const record = data;
|
|
480
530
|
if (hasNestedMutationCallbacks(this.#contract, this.#modelName, record)) return await executeNestedCreateMutation({
|
|
@@ -483,8 +533,9 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
483
533
|
modelName: this.#modelName,
|
|
484
534
|
data: record
|
|
485
535
|
});
|
|
486
|
-
const withDefaults =
|
|
536
|
+
const withDefaults = completeCreateRecord(this.#contract, this.#modelName, record, createMutationDefaultsCache());
|
|
487
537
|
assertEnumValues(this.#contract, this.#modelName, withDefaults);
|
|
538
|
+
assertValidRecord(this.#contract, this.#modelName, "create", withDefaults);
|
|
488
539
|
if (hasScalarFkFields(this.#contract, this.#modelName, withDefaults)) return await executeScalarCreateWithFkValidation({
|
|
489
540
|
executor: requireTransactionExecutor(this.#executor),
|
|
490
541
|
contract: this.#contract,
|
|
@@ -507,38 +558,29 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
507
558
|
record: withDefaults
|
|
508
559
|
}
|
|
509
560
|
};
|
|
510
|
-
|
|
511
|
-
return withDefaults;
|
|
561
|
+
const [row] = await this.#executor.query(plan).toArray();
|
|
562
|
+
return row ?? withDefaults;
|
|
512
563
|
}
|
|
564
|
+
/** Resolve a primary key numerically for Decimal fields while retaining its stored spelling. */
|
|
513
565
|
async findUnique(key) {
|
|
514
|
-
|
|
515
|
-
const meta = this.#planMeta(
|
|
566
|
+
if (!isValidIdbKey(key)) return null;
|
|
567
|
+
const meta = this.#planMeta(this.#newGroupingKey());
|
|
516
568
|
const ast = {
|
|
517
569
|
kind: "findUnique",
|
|
518
570
|
modelName: this.#modelName,
|
|
519
571
|
key
|
|
520
572
|
};
|
|
521
|
-
const
|
|
522
|
-
const fields = typeof keyPath === "string" ? [keyPath] : keyPath;
|
|
523
|
-
const values = typeof keyPath === "string" ? [key] : key;
|
|
524
|
-
const where = combineFilterExprs(fields.map((field, i) => ({
|
|
525
|
-
kind: "field",
|
|
526
|
-
field,
|
|
527
|
-
op: "eq",
|
|
528
|
-
value: values[i]
|
|
529
|
-
})));
|
|
530
|
-
const request = {
|
|
531
|
-
...where === void 0 ? {} : { where },
|
|
532
|
-
take: 1
|
|
533
|
-
};
|
|
534
|
-
const lowered = this.#lowerRows(meta, request);
|
|
535
|
-
const rows = await this.#executor.query({
|
|
573
|
+
const row = await resolveDecimalKey({ execute: (body) => this.#executor.query({
|
|
536
574
|
meta,
|
|
537
575
|
ast,
|
|
538
|
-
idbPlan:
|
|
539
|
-
|
|
540
|
-
|
|
576
|
+
idbPlan: {
|
|
577
|
+
...body,
|
|
578
|
+
meta
|
|
579
|
+
}
|
|
580
|
+
}).toArray() }, this.#contract, this.#modelName, key);
|
|
581
|
+
return row === void 0 ? null : absentAsNull(this.#contract, this.#modelName)(row);
|
|
541
582
|
}
|
|
583
|
+
/** Delete a keyed row with child actions when its relations require them. */
|
|
542
584
|
async delete(key) {
|
|
543
585
|
if (hasEnforceableChildRelations(this.#contract, this.#modelName)) {
|
|
544
586
|
await executeDeleteWithReferentialActions({
|
|
@@ -567,6 +609,7 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
567
609
|
};
|
|
568
610
|
await this.#executor.query(plan).toArray();
|
|
569
611
|
}
|
|
612
|
+
/** Update the first matching row with scalar or nested relation validation. */
|
|
570
613
|
async update(patch) {
|
|
571
614
|
const patchRecord = patch;
|
|
572
615
|
if (hasNestedMutationCallbacks(this.#contract, this.#modelName, patchRecord)) return await executeNestedUpdateMutation({
|
|
@@ -614,9 +657,12 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
614
657
|
async updateCount(patch) {
|
|
615
658
|
return (await this.updateAll(patch).toArray()).length;
|
|
616
659
|
}
|
|
660
|
+
/** Select and apply the create or update arm inside one transaction. */
|
|
617
661
|
async upsert(args) {
|
|
662
|
+
const where = snapshotLookup(args.where);
|
|
663
|
+
assertUniqueLookup(buildCatalog(this.#contract, this.#storeName, this.#modelName), where);
|
|
618
664
|
const keyPath = getKeyPath(this.#contract, this.#modelName);
|
|
619
|
-
const whereExpr = shorthandToFilterExpr(
|
|
665
|
+
const whereExpr = shorthandToFilterExpr(where);
|
|
620
666
|
const meta = this.#planMeta(this.#newGroupingKey());
|
|
621
667
|
const createRecord = args.create;
|
|
622
668
|
const patchRecord = args.update;
|
|
@@ -624,9 +670,11 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
624
670
|
const executionDefaults = this.#contract.execution?.mutations.defaults;
|
|
625
671
|
const exec = requireTransactionExecutor(this.#executor);
|
|
626
672
|
const effectivePatch = applyUpdateDefaults(executionDefaults, storeName, patchRecord, createMutationDefaultsCache());
|
|
627
|
-
const createWithDefaults =
|
|
673
|
+
const createWithDefaults = completeCreateRecord(this.#contract, this.#modelName, createRecord, createMutationDefaultsCache());
|
|
628
674
|
assertEnumValues(this.#contract, this.#modelName, effectivePatch);
|
|
629
675
|
assertEnumValues(this.#contract, this.#modelName, createWithDefaults);
|
|
676
|
+
assertValidRecord(this.#contract, this.#modelName, "update", effectivePatch);
|
|
677
|
+
assertValidRecord(this.#contract, this.#modelName, "create", createWithDefaults);
|
|
630
678
|
const { storeNames: onUpdateStoreNames } = collectOnUpdateEnforcementStoreNames(this.#contract, this.#modelName, effectivePatch);
|
|
631
679
|
const storeNames = [.../* @__PURE__ */ new Set([
|
|
632
680
|
storeName,
|
|
@@ -648,19 +696,20 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
648
696
|
await validateScalarFks(scope, this.#contract, this.#modelName, effectivePatch, existing);
|
|
649
697
|
const key = extractKeyFromRow(existing, keyPath);
|
|
650
698
|
await applyReferentialActionsForRowOnUpdate(scope, this.#contract, this.#modelName, existing, effectivePatch);
|
|
651
|
-
|
|
699
|
+
const rows = await scope.execute({
|
|
652
700
|
meta,
|
|
653
701
|
kind: "update",
|
|
654
702
|
storeName,
|
|
655
703
|
key,
|
|
656
704
|
patch: effectivePatch
|
|
657
|
-
})
|
|
705
|
+
});
|
|
706
|
+
return absentAsNull(this.#contract, this.#modelName)(rows[0] ?? existing);
|
|
658
707
|
});
|
|
659
708
|
}
|
|
709
|
+
/** Complete a batch with shared query defaults and insert it atomically. */
|
|
660
710
|
createAll(data) {
|
|
661
711
|
const defaultsCache = createMutationDefaultsCache();
|
|
662
|
-
const
|
|
663
|
-
const records = data.map((d) => applyCreateDefaults(executionDefaults, this.#storeName, d, defaultsCache));
|
|
712
|
+
const records = data.map((d) => completeCreateRecord(this.#contract, this.#modelName, d, defaultsCache));
|
|
664
713
|
if (records.some((row) => hasScalarFkFields(this.#contract, this.#modelName, row))) {
|
|
665
714
|
const executor = requireTransactionExecutor(this.#executor);
|
|
666
715
|
const contract = this.#contract;
|
|
@@ -700,7 +749,10 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
700
749
|
const contract = this.#contract;
|
|
701
750
|
const modelName = this.#modelName;
|
|
702
751
|
return new AsyncIterableResult((async function* () {
|
|
703
|
-
for (const record of records)
|
|
752
|
+
for (const record of records) {
|
|
753
|
+
assertEnumValues(contract, modelName, record);
|
|
754
|
+
assertValidRecord(contract, modelName, "create", record);
|
|
755
|
+
}
|
|
704
756
|
for await (const row of executorQuery(plan)) yield row;
|
|
705
757
|
})());
|
|
706
758
|
}
|
|
@@ -735,10 +787,12 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
735
787
|
async deleteCount() {
|
|
736
788
|
return (await this.deleteAll().toArray()).length;
|
|
737
789
|
}
|
|
790
|
+
/** Return an include-count descriptor during refinement, or execute a top-level count. */
|
|
738
791
|
count() {
|
|
739
792
|
if (this.#includeRefinementMode) return createIncludeScalar(this.#state);
|
|
740
793
|
return this.#countTerminal();
|
|
741
794
|
}
|
|
795
|
+
/** Count this filtered page through the planner, without applying select or include. */
|
|
742
796
|
async #countTerminal() {
|
|
743
797
|
const combined = this.#combinedFilterExpr();
|
|
744
798
|
const { skip, take } = this.#state;
|
|
@@ -748,7 +802,7 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
748
802
|
...take === void 0 ? {} : { take }
|
|
749
803
|
};
|
|
750
804
|
const meta = this.#planMeta(this.#newGroupingKey());
|
|
751
|
-
const catalog = buildCatalog(this.#contract, this.#storeName);
|
|
805
|
+
const catalog = buildCatalog(this.#contract, this.#storeName, this.#modelName);
|
|
752
806
|
const lowered = lowerCount(catalog, planQuery(catalog, request), meta, request);
|
|
753
807
|
const ast = {
|
|
754
808
|
kind: "count",
|
|
@@ -806,9 +860,10 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
806
860
|
return lowered.finish(rows);
|
|
807
861
|
}
|
|
808
862
|
#lowerRows(meta, request) {
|
|
809
|
-
const catalog = buildCatalog(this.#contract, this.#storeName);
|
|
863
|
+
const catalog = buildCatalog(this.#contract, this.#storeName, this.#modelName);
|
|
810
864
|
return lowerRows(catalog, planQuery(catalog, request), meta, request);
|
|
811
865
|
}
|
|
866
|
+
/** Pair the query intent with its planned driver operation and result shaping. */
|
|
812
867
|
#buildScanPlan(groupingKey) {
|
|
813
868
|
const combined = this.#combinedFilterExpr();
|
|
814
869
|
const meta = this.#planMeta(groupingKey);
|
|
@@ -839,6 +894,7 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
839
894
|
#combinedFilterExpr() {
|
|
840
895
|
return combineFilterExprs(this.#state.filters);
|
|
841
896
|
}
|
|
897
|
+
/** Batch-load each requested relation using the unprojected parent rows. */
|
|
842
898
|
async #applyIncludes(rows, groupingKey) {
|
|
843
899
|
const relNames = Object.keys(this.#state.includes);
|
|
844
900
|
if (relNames.length === 0) return rows;
|
|
@@ -864,6 +920,7 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
864
920
|
return out;
|
|
865
921
|
});
|
|
866
922
|
}
|
|
923
|
+
/** Attach the contract hash and grouping key shared by this operation. */
|
|
867
924
|
#planMeta(groupingKey) {
|
|
868
925
|
return {
|
|
869
926
|
target: "idb",
|
|
@@ -872,6 +929,7 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
872
929
|
annotations: { groupingKey }
|
|
873
930
|
};
|
|
874
931
|
}
|
|
932
|
+
/** Preserve shared runtime wiring while replacing only the accessor state. */
|
|
875
933
|
#clone(overrides) {
|
|
876
934
|
return new IdbStoreAccessorImpl(this.#contract, this.#modelName, this.#executor, mergeAccessorState(this.#state, overrides), this.#newGroupingKey, this.#includeRefinementMode);
|
|
877
935
|
}
|
|
@@ -891,7 +949,7 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
891
949
|
* import { createIdbRuntime } from "@prisma-idb/runtime-idb/runtime";
|
|
892
950
|
* import contract from "./prisma/idb-contract";
|
|
893
951
|
*
|
|
894
|
-
* const runtime = createIdbRuntime({ adapter, driver });
|
|
952
|
+
* const runtime = createIdbRuntime({ adapter, driver, contract });
|
|
895
953
|
* const db = idbOrm({ contract, executor: runtime });
|
|
896
954
|
*
|
|
897
955
|
* // Typed query builder:
|
|
@@ -900,7 +958,8 @@ var IdbStoreAccessorImpl = class IdbStoreAccessorImpl {
|
|
|
900
958
|
* ```
|
|
901
959
|
*/
|
|
902
960
|
function idbOrm(options) {
|
|
903
|
-
const { contract
|
|
961
|
+
const { contract } = options;
|
|
962
|
+
const executor = withDecimalIdentityExecutor(contract, options.executor);
|
|
904
963
|
let _key = 0;
|
|
905
964
|
const newGroupingKey = () => `idb-op-${++_key}`;
|
|
906
965
|
const client = {};
|
|
@@ -908,6 +967,243 @@ function idbOrm(options) {
|
|
|
908
967
|
return client;
|
|
909
968
|
}
|
|
910
969
|
//#endregion
|
|
911
|
-
|
|
970
|
+
//#region src/core/open-transaction.ts
|
|
971
|
+
/**
|
|
972
|
+
* An executor bound to one already-open IDB transaction.
|
|
973
|
+
*
|
|
974
|
+
* `db.transaction()` hands its callback ORM accessors built on this executor.
|
|
975
|
+
* Every query the accessors issue runs on the open transaction instead of
|
|
976
|
+
* opening a new one. Operations that normally open their own mutation scope
|
|
977
|
+
* (FK-checked writes, cascade deletes) get a view of the same transaction:
|
|
978
|
+
* `commit()` on the view does nothing, so an inner operation cannot commit the
|
|
979
|
+
* outer transaction, and `rollback()` aborts it.
|
|
980
|
+
*
|
|
981
|
+
* The executor also records how the transaction ended, so the runner can tell
|
|
982
|
+
* a rolled-back transaction from one that IndexedDB auto-committed early
|
|
983
|
+
* (ADR 005).
|
|
984
|
+
*/
|
|
985
|
+
function isTransactionInactive(error) {
|
|
986
|
+
return error instanceof IdbExecuteError && error.code === "TRANSACTION_INACTIVE";
|
|
987
|
+
}
|
|
988
|
+
/** Bind ORM operations to one scope and record aborts or early commits. */
|
|
989
|
+
var OpenTransaction = class {
|
|
990
|
+
#scope;
|
|
991
|
+
#storeNames;
|
|
992
|
+
#meta;
|
|
993
|
+
#state = "open";
|
|
994
|
+
#autoCommitCause;
|
|
995
|
+
#ended = false;
|
|
996
|
+
constructor(scope, storeNames, meta) {
|
|
997
|
+
this.#scope = scope;
|
|
998
|
+
this.#storeNames = new Set(storeNames);
|
|
999
|
+
this.#meta = meta;
|
|
1000
|
+
}
|
|
1001
|
+
/** Stop accepting operations. Called once the callback has settled. */
|
|
1002
|
+
end() {
|
|
1003
|
+
this.#ended = true;
|
|
1004
|
+
}
|
|
1005
|
+
/** The error of the first operation that found the transaction already finished, if any. */
|
|
1006
|
+
get autoCommitCause() {
|
|
1007
|
+
return this.#autoCommitCause;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Whether IndexedDB has already finished this transaction on its own, so a
|
|
1011
|
+
* rollback can no longer undo its writes. Probes the transaction when no
|
|
1012
|
+
* operation has failed yet, and records the probe error as the cause, so call
|
|
1013
|
+
* it before rolling back or committing.
|
|
1014
|
+
*/
|
|
1015
|
+
async hasAutoCommitted() {
|
|
1016
|
+
if (this.#state !== "open") return this.#state === "auto-committed";
|
|
1017
|
+
const [storeName] = this.#storeNames;
|
|
1018
|
+
try {
|
|
1019
|
+
await this.#scope.execute({
|
|
1020
|
+
meta: this.#meta,
|
|
1021
|
+
kind: "key-get",
|
|
1022
|
+
storeName,
|
|
1023
|
+
key: ""
|
|
1024
|
+
});
|
|
1025
|
+
return false;
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
if (!isTransactionInactive(error)) return false;
|
|
1028
|
+
this.#state = "auto-committed";
|
|
1029
|
+
this.#autoCommitCause = error;
|
|
1030
|
+
return true;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
/** Expose rows from the existing scope through the ORM query-result interface. */
|
|
1034
|
+
query(plan) {
|
|
1035
|
+
const run = executeMutationPlan.bind(null, this);
|
|
1036
|
+
return new AsyncIterableResult((async function* () {
|
|
1037
|
+
for (const row of await run(plan.idbPlan)) yield row;
|
|
1038
|
+
})());
|
|
1039
|
+
}
|
|
1040
|
+
/** Reuse the outer scope after verifying that the requested stores were declared. */
|
|
1041
|
+
async transaction(storeNames) {
|
|
1042
|
+
this.#assertNotEnded();
|
|
1043
|
+
try {
|
|
1044
|
+
this.#assertStoresListed(storeNames);
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
this.#recordFailure(error);
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
return this;
|
|
1050
|
+
}
|
|
1051
|
+
/** Run one operation in the scope and abort the transaction when it fails. */
|
|
1052
|
+
async execute(plan) {
|
|
1053
|
+
this.#assertNotEnded();
|
|
1054
|
+
try {
|
|
1055
|
+
this.#assertStoresListed([plan.storeName]);
|
|
1056
|
+
return await this.#scope.execute(plan);
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
this.#recordFailure(error);
|
|
1059
|
+
throw error;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
async commit() {}
|
|
1063
|
+
rollback() {
|
|
1064
|
+
this.#recordFailure(void 0);
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Wrap an ORM accessor so that any call that fails aborts the transaction.
|
|
1068
|
+
*
|
|
1069
|
+
* Some failures, such as enum validation, happen before an operation reaches
|
|
1070
|
+
* this executor. Without this wrapper the callback could catch them and
|
|
1071
|
+
* commit the writes made before. The wrapper covers errors thrown
|
|
1072
|
+
* synchronously, rejected promises, and rejections of a result that is
|
|
1073
|
+
* awaited directly (`AsyncIterableResult`). Objects the accessor returns,
|
|
1074
|
+
* such as `where()` builders, are wrapped too.
|
|
1075
|
+
*/
|
|
1076
|
+
abortOnFailure(target) {
|
|
1077
|
+
return new Proxy(target, { get: (object, property) => {
|
|
1078
|
+
const value = Reflect.get(object, property, object);
|
|
1079
|
+
if (typeof value !== "function") return value;
|
|
1080
|
+
if (property === "then") return this.#observeThen(object, value);
|
|
1081
|
+
return (...args) => {
|
|
1082
|
+
const result = this.#recordThrown(() => value.apply(object, args));
|
|
1083
|
+
if (result instanceof Promise) {
|
|
1084
|
+
result.catch((error) => this.#recordFailure(error));
|
|
1085
|
+
return result;
|
|
1086
|
+
}
|
|
1087
|
+
return typeof result === "object" && result !== null ? this.abortOnFailure(result) : result;
|
|
1088
|
+
};
|
|
1089
|
+
} });
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* `await result` passes its own rejection handler to `then()`, which turns the
|
|
1093
|
+
* rejection into a fulfilled promise. Record the failure before that handler runs.
|
|
1094
|
+
*/
|
|
1095
|
+
#observeThen(object, then) {
|
|
1096
|
+
return (onFulfilled, onRejected) => then.call(object, onFulfilled, (error) => {
|
|
1097
|
+
this.#recordFailure(error);
|
|
1098
|
+
if (typeof onRejected !== "function") throw error;
|
|
1099
|
+
return onRejected(error);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
/** Record synchronous accessor failures before propagating them to the callback. */
|
|
1103
|
+
#recordThrown(call) {
|
|
1104
|
+
try {
|
|
1105
|
+
return call();
|
|
1106
|
+
} catch (error) {
|
|
1107
|
+
this.#recordFailure(error);
|
|
1108
|
+
throw error;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
#assertNotEnded() {
|
|
1112
|
+
if (this.#ended) throw new Error("This transaction has ended. Do not use `tx` after the db.transaction() callback finishes.");
|
|
1113
|
+
}
|
|
1114
|
+
/** Reject undeclared stores before an operation can escape the transaction scope. */
|
|
1115
|
+
#assertStoresListed(storeNames) {
|
|
1116
|
+
const missing = storeNames.filter((name) => !this.#storeNames.has(name));
|
|
1117
|
+
if (missing.length > 0) throw new Error(`Stores ${missing.map((name) => `"${name}"`).join(", ")} are not part of this transaction. List the models that own them in the first argument of db.transaction().`);
|
|
1118
|
+
}
|
|
1119
|
+
/** Any failed operation aborts the whole transaction, even if the callback catches the error. */
|
|
1120
|
+
#recordFailure(error) {
|
|
1121
|
+
if (this.#state !== "open") return;
|
|
1122
|
+
if (isTransactionInactive(error)) {
|
|
1123
|
+
this.#state = "auto-committed";
|
|
1124
|
+
this.#autoCommitCause = error;
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
this.#state = "aborted";
|
|
1128
|
+
this.#scope.rollback();
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
//#endregion
|
|
1132
|
+
//#region src/core/orm-transaction.ts
|
|
1133
|
+
/**
|
|
1134
|
+
* Thrown when IndexedDB finished the transaction before the callback did, so
|
|
1135
|
+
* writes made before that point were committed and could not be rolled back.
|
|
1136
|
+
*
|
|
1137
|
+
* IndexedDB commits a transaction as soon as it has no pending request. That
|
|
1138
|
+
* happens when the callback awaits something other than a `tx` operation, such
|
|
1139
|
+
* as a timer or a network call (ADR 005). `cause` holds the original error.
|
|
1140
|
+
*/
|
|
1141
|
+
var IdbTransactionCommittedEarlyError = class extends Error {
|
|
1142
|
+
constructor(cause) {
|
|
1143
|
+
super("The transaction was committed before the db.transaction() callback finished, so writes made earlier in the callback were saved and not rolled back. Await only `tx` operations inside the callback; do not await timers or network calls.", { cause });
|
|
1144
|
+
this.name = "IdbTransactionCommittedEarlyError";
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
/**
|
|
1148
|
+
* Run `fn` inside one readwrite transaction over the models behind `rootKeys`.
|
|
1149
|
+
*
|
|
1150
|
+
* Besides the listed models, the transaction opens the stores their operations
|
|
1151
|
+
* need: parents for foreign-key checks, children for referential actions, and
|
|
1152
|
+
* related models for `include`. An operation that needs any other store fails
|
|
1153
|
+
* with an error that names it.
|
|
1154
|
+
*/
|
|
1155
|
+
async function runOrmTransaction(options, rootKeys, fn) {
|
|
1156
|
+
const { contract, executor } = options;
|
|
1157
|
+
if (rootKeys.length === 0) throw new Error("db.transaction() needs at least one model to open the transaction on.");
|
|
1158
|
+
const modelNames = rootKeys.map((key) => {
|
|
1159
|
+
const root = contract.roots[key];
|
|
1160
|
+
if (root === void 0) throw new Error(`db.transaction(): "${key}" is not a root of the contract.`);
|
|
1161
|
+
return root.model;
|
|
1162
|
+
});
|
|
1163
|
+
const storeNames = [...new Set(modelNames.flatMap((model) => storeNamesNeededBy(contract, model)))];
|
|
1164
|
+
const scope = await executor.transaction(storeNames, "readwrite");
|
|
1165
|
+
const open = new OpenTransaction(scope, storeNames, makePlanMeta(contract));
|
|
1166
|
+
let result;
|
|
1167
|
+
try {
|
|
1168
|
+
result = await fn(pickRoots(idbOrm({
|
|
1169
|
+
contract,
|
|
1170
|
+
executor: open
|
|
1171
|
+
}), rootKeys, open));
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
open.end();
|
|
1174
|
+
if (await open.hasAutoCommitted()) await failCommittedEarly(scope, error);
|
|
1175
|
+
scope.rollback();
|
|
1176
|
+
throw error;
|
|
1177
|
+
}
|
|
1178
|
+
open.end();
|
|
1179
|
+
if (await open.hasAutoCommitted()) await failCommittedEarly(scope, open.autoCommitCause);
|
|
1180
|
+
await scope.commit();
|
|
1181
|
+
return result;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Reports a transaction that IndexedDB committed before the callback ended. The writes
|
|
1185
|
+
* persisted, so the scope is committed rather than rolled back: a tracked scope only
|
|
1186
|
+
* announces its writes (for example, sync outbox notifications) when it commits.
|
|
1187
|
+
*/
|
|
1188
|
+
async function failCommittedEarly(scope, cause) {
|
|
1189
|
+
await scope.commit().catch(() => {});
|
|
1190
|
+
throw new IdbTransactionCommittedEarlyError(cause);
|
|
1191
|
+
}
|
|
1192
|
+
/** Collect stores used by relation reads, child actions, and cascading updates. */
|
|
1193
|
+
function storeNamesNeededBy(contract, modelName) {
|
|
1194
|
+
return [
|
|
1195
|
+
...collectRelatedStoreNames(contract, modelName),
|
|
1196
|
+
...collectDeleteStoreNames(contract, modelName),
|
|
1197
|
+
...collectOnUpdateClosureStoreNames(contract, modelName)
|
|
1198
|
+
];
|
|
1199
|
+
}
|
|
1200
|
+
/** Expose only the requested roots and abort the scope on any accessor failure. */
|
|
1201
|
+
function pickRoots(orm, rootKeys, open) {
|
|
1202
|
+
const picked = {};
|
|
1203
|
+
for (const key of rootKeys) picked[key] = open.abortOnFailure(orm[key]);
|
|
1204
|
+
return picked;
|
|
1205
|
+
}
|
|
1206
|
+
//#endregion
|
|
1207
|
+
export { runOrmTransaction as n, idbOrm as r, IdbTransactionCommittedEarlyError as t };
|
|
912
1208
|
|
|
913
|
-
//# sourceMappingURL=
|
|
1209
|
+
//# sourceMappingURL=orm-transaction-BGx1eukw.mjs.map
|