@prisma-idb/client-idb 0.9.0 → 0.10.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.
Files changed (32) hide show
  1. package/README.md +2 -0
  2. package/dist/client-auto.d.mts +3 -19
  3. package/dist/client-auto.d.mts.map +1 -1
  4. package/dist/client-auto.mjs +21 -16
  5. package/dist/client-auto.mjs.map +1 -1
  6. package/dist/client.d.mts +1 -1
  7. package/dist/client.mjs +1 -1
  8. package/dist/{idb-orm-MOH1S3ES.d.mts → executor-CevtdBfz.d.mts} +3 -396
  9. package/dist/executor-CevtdBfz.d.mts.map +1 -0
  10. package/dist/idb-orm-DsfllvzD.mjs +1256 -0
  11. package/dist/idb-orm-DsfllvzD.mjs.map +1 -0
  12. package/dist/idb-orm-gEKHfG4H.d.mts +399 -0
  13. package/dist/idb-orm-gEKHfG4H.d.mts.map +1 -0
  14. package/dist/internal.d.mts +39 -0
  15. package/dist/internal.d.mts.map +1 -0
  16. package/dist/internal.mjs +2 -0
  17. package/dist/{managed-client-CJD9lBx5.d.mts → managed-client-B_xGXdjz.d.mts} +3 -2
  18. package/dist/managed-client-B_xGXdjz.d.mts.map +1 -0
  19. package/dist/{managed-client-DofL09P8.mjs → managed-client-Cp01irrm.mjs} +3 -2
  20. package/dist/{managed-client-DofL09P8.mjs.map → managed-client-Cp01irrm.mjs.map} +1 -1
  21. package/dist/{idb-orm-C8yXJp6R.mjs → mutation-executor-CD5n_kKT.mjs} +41 -1361
  22. package/dist/mutation-executor-CD5n_kKT.mjs.map +1 -0
  23. package/dist/mutation-scope-pOn0QzvV.d.mts +33 -0
  24. package/dist/mutation-scope-pOn0QzvV.d.mts.map +1 -0
  25. package/dist/orm.d.mts +4 -75
  26. package/dist/orm.d.mts.map +1 -1
  27. package/dist/orm.mjs +3 -2
  28. package/dist/orm.mjs.map +1 -1
  29. package/package.json +7 -6
  30. package/dist/idb-orm-C8yXJp6R.mjs.map +0 -1
  31. package/dist/idb-orm-MOH1S3ES.d.mts.map +0 -1
  32. package/dist/managed-client-CJD9lBx5.d.mts.map +0 -1
@@ -0,0 +1,399 @@
1
+ import { C as NoIncludes, D as ReferenceRelKeys, E as PatchInput, M as SelectedRow, O as RelatedModelOf, P as WhereFilter, S as MutationUpdateInput, T as OrderBySpec, a as IdbAggregateBuilder, c as IdbAggregateSpec, g as IncludeSpec, i as DefaultModelRow, l as IdbContract, o as IdbAggregateResult, p as IdbStorage, r as CreateInput, t as IdbQueryExecutor, v as KeyType, x as MutationCreateInput } from "./executor-CevtdBfz.mjs";
2
+ import { AsyncIterableResult } from "@prisma/orm-framework/components/runtime";
3
+ import { IdbFieldFilter, IdbFilterExpr, IdbNullCheckExpr } from "@prisma-idb/adapter-idb/runtime";
4
+ import { Contract } from "@prisma/orm-framework/contract/types";
5
+ //#region src/core/model-accessor.d.ts
6
+ /**
7
+ * Operator surface available on every field. All return frozen AST nodes
8
+ * — they are values, not statements, so chaining and logical combinators
9
+ * compose naturally.
10
+ */
11
+ interface IdbFieldAccessor<T> {
12
+ eq(value: T): IdbFieldFilter;
13
+ neq(value: T): IdbFieldFilter;
14
+ gt(value: T): IdbFieldFilter;
15
+ lt(value: T): IdbFieldFilter;
16
+ gte(value: T): IdbFieldFilter;
17
+ lte(value: T): IdbFieldFilter;
18
+ in(values: ReadonlyArray<T>): IdbFieldFilter;
19
+ notIn(values: ReadonlyArray<T>): IdbFieldFilter;
20
+ contains(sub: string): IdbFieldFilter;
21
+ startsWith(sub: string): IdbFieldFilter;
22
+ endsWith(sub: string): IdbFieldFilter;
23
+ isNull(): IdbNullCheckExpr;
24
+ isNotNull(): IdbNullCheckExpr;
25
+ }
26
+ /**
27
+ * The accessor object handed to a `where(fn)` callback. Each key resolves
28
+ * to an {@link IdbFieldAccessor} typed against the field's output type.
29
+ *
30
+ * The Proxy makes every string key resolve to an accessor at runtime; the
31
+ * type narrows the visible surface to the model's declared fields so the
32
+ * developer gets autocomplete.
33
+ */
34
+ type IdbModelAccessor<TContract, ModelName extends string> = { readonly [K in keyof DefaultModelRow<TContract, ModelName>]-?: IdbFieldAccessor<DefaultModelRow<TContract, ModelName>[K]>; };
35
+ //#endregion
36
+ //#region src/core/store-state.d.ts
37
+ /**
38
+ * One entry in {@link IdbAccessorState.includes} — describes how a single
39
+ * `.include()`d relation should be materialised.
40
+ *
41
+ * - `collection`: load the related rows, applying the refined `state`
42
+ * (its `filters` / `orderBy` / `skip` / `take`) to the child scan.
43
+ * `state` is `emptyAccessorState()` for an unrefined `.include(rel)`.
44
+ * - `scalar`: reduce the related rows to a single number (Phase 6.5 supports
45
+ * `count`). The `state.filters` still constrain which children are counted.
46
+ *
47
+ * Mirrors the `IncludeExpr` / `IncludeScalar` split from
48
+ * Prisma ORM's SQL client, collapsed to the two shapes IDB
49
+ * needs (no SQL column mapping, no `combine()` branches).
50
+ */
51
+ type IncludeEntry = {
52
+ readonly kind: "collection";
53
+ readonly state: IdbAccessorState;
54
+ } | {
55
+ readonly kind: "scalar";
56
+ readonly fn: "count";
57
+ readonly state: IdbAccessorState;
58
+ };
59
+ /**
60
+ * Marker returned by the child accessor's `.count()` terminal when it is
61
+ * called inside an `include()` refinement callback. Carries the refined
62
+ * child `state` so the relation loader counts only the matching children.
63
+ *
64
+ * Detected by {@link isIncludeScalar}; mirrors `IncludeScalar` from the
65
+ * vendor `sql-orm-client/include-descriptors.ts`.
66
+ */
67
+ interface IdbIncludeScalar {
68
+ readonly kind: "includeScalar";
69
+ readonly fn: "count";
70
+ readonly state: IdbAccessorState;
71
+ }
72
+ /**
73
+ * Immutable state carried by each {@link IdbStoreAccessorImpl} node in the
74
+ * builder chain.
75
+ *
76
+ * Every mutating method (`.where()`, `.take()`, etc.) returns a cloned
77
+ * instance with an updated state rather than mutating in place, making the
78
+ * accessor safe to reuse across multiple query branches.
79
+ */
80
+ interface IdbAccessorState {
81
+ /**
82
+ * Accumulated filter expressions. Multiple `.where()` calls compose
83
+ * with AND semantics — the planner wraps them in an `andExpr` when
84
+ * lowering. Each entry is already either an `IdbFieldFilter`, a
85
+ * combinator, or a `null-check` node — never raw shorthand records.
86
+ */
87
+ readonly filters: ReadonlyArray<IdbFilterExpr>;
88
+ /** Sort spec: field → direction. Applied as an in-memory comparator. */
89
+ readonly orderBy?: Record<string, "asc" | "desc">;
90
+ /** OFFSET (number of rows to skip). */
91
+ readonly skip?: number;
92
+ /** LIMIT (maximum number of rows to return). */
93
+ readonly take?: number;
94
+ /**
95
+ * Relations that have been `.include()`d — keys are relation names, values
96
+ * describe how to materialise each (collection vs scalar count, plus any
97
+ * refinement state). See {@link IncludeEntry}.
98
+ */
99
+ readonly includes: Record<string, IncludeEntry>;
100
+ /**
101
+ * Fields kept by `.select()`. `undefined` means "all fields". When set,
102
+ * the materialised rows are projected down to these fields (plus any
103
+ * included relation keys) after the scan and relation loads complete.
104
+ */
105
+ readonly selectedFields?: ReadonlyArray<string>;
106
+ }
107
+ //#endregion
108
+ //#region src/core/grouped-accessor.d.ts
109
+ /**
110
+ * One grouped-aggregate result row: the group-key fields picked from the model
111
+ * row, intersected with the aggregate aliases.
112
+ */
113
+ type GroupedResultRow<TContract, ModelName extends string, Fields extends readonly string[], Spec extends IdbAggregateSpec> = Pick<DefaultModelRow<TContract, ModelName>, Fields[number] & keyof DefaultModelRow<TContract, ModelName>> & IdbAggregateResult<Spec>;
114
+ /**
115
+ * Grouped-aggregate builder returned by `accessor.groupBy(...)`.
116
+ *
117
+ * Port of the vendor `GroupedCollection` (`sql-orm-client/grouped-collection.ts`),
118
+ * but purely in-memory: there is no SQL compilation, so `aggregate()`
119
+ * materialises the matching rows, partitions them by the group-key fields, and
120
+ * reduces each partition with the requested selectors.
121
+ */
122
+ interface IdbGroupedAccessor<TContract, ModelName extends string, Fields extends readonly string[]> {
123
+ /**
124
+ * Reduce each group to one row of `{ ...groupKeyFields, ...aggregates }`.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * const byUser = await db.posts
129
+ * .where({ published: true })
130
+ * .groupBy("authorId")
131
+ * .aggregate((agg) => ({ count: agg.count(), totalViews: agg.sum("views") }));
132
+ * // [{ authorId: "u1", count: 3, totalViews: 120 }, ...]
133
+ * ```
134
+ */
135
+ aggregate<Spec extends IdbAggregateSpec>(fn: (agg: IdbAggregateBuilder<TContract, ModelName>) => Spec): Promise<Array<GroupedResultRow<TContract, ModelName, Fields, Spec>>>;
136
+ }
137
+ //#endregion
138
+ //#region src/core/store-accessor.d.ts
139
+ /** Callback form of `.where(fn)` — receives the typed model accessor proxy. */
140
+ type WhereCallback<TContract, ModelName extends string> = (m: IdbModelAccessor<TContract, ModelName>) => IdbFilterExpr;
141
+ /** Tuple of one-or-more field names of a model (for `select()` / `groupBy()`). */
142
+ type FieldTuple<TContract, ModelName extends string> = readonly [keyof DefaultModelRow<TContract, ModelName> & string, ...(keyof DefaultModelRow<TContract, ModelName> & string)[]];
143
+ /**
144
+ * The child accessor handed to an `include()` refinement callback.
145
+ *
146
+ * Exposes the chainable narrowing methods (`where` / `orderBy` / `take` /
147
+ * `skip`) plus the scalar `count()` reducer. Mirrors the vendor
148
+ * `IncludeRefinementCollection`: chainable methods return the same refinement
149
+ * surface so `count()` stays reachable after a `where()`, and `count()` returns
150
+ * an {@link IdbIncludeScalar} marker rather than the async terminal `count()`
151
+ * found on the top-level accessor.
152
+ */
153
+ interface IdbIncludeRefinementAccessor<TContract, ModelName extends string> {
154
+ where(filter: WhereFilter<TContract, ModelName> | WhereCallback<TContract, ModelName>): IdbIncludeRefinementAccessor<TContract, ModelName>;
155
+ orderBy(spec: OrderBySpec<TContract, ModelName>): IdbIncludeRefinementAccessor<TContract, ModelName>;
156
+ take(n: number): IdbIncludeRefinementAccessor<TContract, ModelName>;
157
+ skip(n: number): IdbIncludeRefinementAccessor<TContract, ModelName>;
158
+ count(): IdbIncludeScalar;
159
+ }
160
+ /** Refinement callback type for a given relation key `K`. */
161
+ type IncludeRefineFn<TContract, ModelName extends string, K extends string, R> = (collection: IdbIncludeRefinementAccessor<TContract, RelatedModelOf<TContract, ModelName, K>>) => R;
162
+ /**
163
+ * Immutable query-builder for a single IDB object store.
164
+ *
165
+ * Each method that narrows the query (`.where()`, `.take()`, etc.) returns a
166
+ * new, independent accessor instance — the original is never mutated. This
167
+ * mirrors the `MongoCollection` pattern from Prisma ORM's Mongo family.
168
+ *
169
+ * @template TContract - The full IDB contract (with or without type maps).
170
+ * @template ModelName - The model (store) this accessor targets.
171
+ * @template TIncludes - Tracks which relations have been included via
172
+ * `.include()` calls, so the return type widens progressively.
173
+ * @template TSelected - Field names kept by `.select()`. `never` (the
174
+ * default) means "all fields"; otherwise the row narrows to these fields
175
+ * (plus any included relations).
176
+ */
177
+ interface IdbStoreAccessor<TContract, ModelName extends string, TIncludes extends IncludeSpec<TContract, ModelName> = NoIncludes, TSelected extends string = never> {
178
+ /**
179
+ * Add a filter (ANDed with any previous `.where()` calls).
180
+ *
181
+ * Two forms:
182
+ *
183
+ * - **Shorthand**: `where({ field: value })` — multi-key shorthand
184
+ * objects compose as AND. `null` values become null-checks rather
185
+ * than literal-null equalities so absent fields match.
186
+ * - **Callback**: `where((m) => m.field.op(value))` — receives the
187
+ * typed model accessor proxy and returns an `IdbFilterExpr` built
188
+ * via the operator surface. Combinators (`and`, `or`, `not` from
189
+ * `@prisma-idb/client-idb/orm`) compose nodes.
190
+ */
191
+ where(filter: WhereFilter<TContract, ModelName> | WhereCallback<TContract, ModelName>): IdbStoreAccessor<TContract, ModelName, TIncludes, TSelected>;
192
+ /** Set the sort order. Replaces any previous `.orderBy()` call. */
193
+ orderBy(spec: OrderBySpec<TContract, ModelName>): IdbStoreAccessor<TContract, ModelName, TIncludes, TSelected>;
194
+ /** Limit the number of rows returned. */
195
+ take(n: number): IdbStoreAccessor<TContract, ModelName, TIncludes, TSelected>;
196
+ /** Skip the first `n` rows (OFFSET). */
197
+ skip(n: number): IdbStoreAccessor<TContract, ModelName, TIncludes, TSelected>;
198
+ /**
199
+ * Include a reference relation in the returned rows.
200
+ *
201
+ * The relation is loaded via a single batch cursor scan after the main
202
+ * query — O(1) round trips to IDB per included relation regardless of
203
+ * the number of parent rows. The return type gains the relation field
204
+ * automatically.
205
+ *
206
+ * An optional refinement callback narrows the loaded relation:
207
+ *
208
+ * - return the (chained) collection to apply `where` / `orderBy` /
209
+ * `take` / `skip` to the related rows (per-parent for `1:N`);
210
+ * - return `collection.count()` to reduce a to-many relation to the
211
+ * number of matching children (the field becomes a `number`).
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * db.users.include("posts", (posts) => posts.where({ published: true }).take(5))
216
+ * db.users.include("posts", (posts) => posts.count())
217
+ * ```
218
+ */
219
+ include<K extends ReferenceRelKeys<TContract, ModelName>>(relation: K, refineFn?: IncludeRefineFn<TContract, ModelName, K, IdbIncludeRefinementAccessor<TContract, RelatedModelOf<TContract, ModelName, K>>>): IdbStoreAccessor<TContract, ModelName, TIncludes & Record<K, true>, TSelected>;
220
+ include<K extends ReferenceRelKeys<TContract, ModelName>>(relation: K, refineFn: IncludeRefineFn<TContract, ModelName, K, IdbIncludeScalar>): IdbStoreAccessor<TContract, ModelName, TIncludes & Record<K, "scalar">, TSelected>;
221
+ /**
222
+ * Project the row down to a subset of scalar fields. Any previously
223
+ * `.include()`d relations are preserved on the result; only the scalar
224
+ * fields are narrowed.
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * const summaries = await db.users.select("id", "email").all();
229
+ * // typeof summaries[number] === { id: string; email: string }
230
+ * ```
231
+ */
232
+ select<Fields extends FieldTuple<TContract, ModelName>>(...fields: Fields): IdbStoreAccessor<TContract, ModelName, TIncludes, Fields[number]>;
233
+ /** Return all matching rows as an async iterable (also awaitable as `Row[]`). */
234
+ all(): AsyncIterableResult<SelectedRow<TContract, ModelName, TIncludes, TSelected>>;
235
+ /** Return the first matching row, or `null` if none match. */
236
+ first(): Promise<SelectedRow<TContract, ModelName, TIncludes, TSelected> | null>;
237
+ /**
238
+ * Run an in-memory aggregate (count/sum/avg/min/max) over the rows matching
239
+ * the accumulated `.where()` filter. Returns one result object keyed by the
240
+ * aliases supplied in the spec.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * const stats = await db.posts.where({ published: true }).aggregate((agg) => ({
245
+ * total: agg.count(),
246
+ * avgViews: agg.avg("views"),
247
+ * }));
248
+ * ```
249
+ */
250
+ aggregate<Spec extends IdbAggregateSpec>(fn: (agg: IdbAggregateBuilder<TContract, ModelName>) => Spec): Promise<IdbAggregateResult<Spec>>;
251
+ /**
252
+ * Switch to grouped-aggregate mode. The returned {@link IdbGroupedAccessor}'s
253
+ * `.aggregate(...)` terminal produces one row per group with the chosen key
254
+ * fields plus the requested aggregates.
255
+ *
256
+ * @example
257
+ * ```ts
258
+ * const byUser = await db.posts
259
+ * .where({ published: true })
260
+ * .groupBy("authorId")
261
+ * .aggregate((agg) => ({ count: agg.count(), totalViews: agg.sum("views") }));
262
+ * ```
263
+ */
264
+ groupBy<Fields extends FieldTuple<TContract, ModelName>>(...fields: Fields): IdbGroupedAccessor<TContract, ModelName, Fields>;
265
+ /**
266
+ * Insert a record into the store and return the stored row.
267
+ *
268
+ * The primary key field is optional in `data` — pass it to use a
269
+ * client-generated ID (`cuid`, `uuid`) or omit it for auto-increment stores.
270
+ *
271
+ * Relation fields accept a mutation callback:
272
+ * `posts: (rel) => rel.create([...])` or `author: (rel) => rel.connect({ id })`.
273
+ * When any relation callback is present, all writes are wrapped in a single
274
+ * multi-store IDB transaction (requires IdbRuntime, not a plain executor).
275
+ */
276
+ create(data: MutationCreateInput<TContract, ModelName>): Promise<DefaultModelRow<TContract, ModelName>>;
277
+ /** Look up a single row by primary key. Returns `null` if not found. */
278
+ findUnique(key: KeyType<TContract, ModelName>): Promise<DefaultModelRow<TContract, ModelName> | null>;
279
+ /** Delete the row with the given primary key. */
280
+ delete(key: KeyType<TContract, ModelName>): Promise<void>;
281
+ /**
282
+ * Update the first row matching the accumulated `.where()` filter.
283
+ * Returns the merged row, or `null` if no row matches.
284
+ *
285
+ * Relation fields accept `connect` or `disconnect` callbacks. When any
286
+ * relation callback is present, all writes run in a single multi-store
287
+ * IDB transaction (requires IdbRuntime).
288
+ */
289
+ update(patch: MutationUpdateInput<TContract, ModelName>): Promise<DefaultModelRow<TContract, ModelName> | null>;
290
+ /**
291
+ * Update all rows matching the accumulated `.where()` filter and return
292
+ * them as an `AsyncIterableResult` (also awaitable as `Row[]`).
293
+ */
294
+ updateAll(patch: PatchInput<TContract, ModelName>): AsyncIterableResult<DefaultModelRow<TContract, ModelName>>;
295
+ /**
296
+ * Update all rows matching the accumulated `.where()` filter.
297
+ * Returns the count of updated rows.
298
+ */
299
+ updateCount(patch: PatchInput<TContract, ModelName>): Promise<number>;
300
+ /**
301
+ * Insert or update a single record.
302
+ *
303
+ * - If a row matching `where` exists: shallow-merge `update` onto it and
304
+ * return the merged row.
305
+ * - If no matching row exists: insert `create` and return it.
306
+ */
307
+ upsert(args: {
308
+ create: CreateInput<TContract, ModelName>;
309
+ update: PatchInput<TContract, ModelName>;
310
+ where: WhereFilter<TContract, ModelName>;
311
+ }): Promise<DefaultModelRow<TContract, ModelName>>;
312
+ /**
313
+ * Insert multiple records in a single atomic transaction.
314
+ * Returns all inserted rows as an `AsyncIterableResult`.
315
+ */
316
+ createAll(data: CreateInput<TContract, ModelName>[]): AsyncIterableResult<DefaultModelRow<TContract, ModelName>>;
317
+ /**
318
+ * Insert multiple records in a single atomic transaction.
319
+ * Returns the count of inserted rows.
320
+ */
321
+ createCount(data: CreateInput<TContract, ModelName>[]): Promise<number>;
322
+ /**
323
+ * Delete all rows matching the accumulated `.where()` filter.
324
+ * Returns the deleted rows as an `AsyncIterableResult`.
325
+ */
326
+ deleteAll(): AsyncIterableResult<DefaultModelRow<TContract, ModelName>>;
327
+ /**
328
+ * Delete all rows matching the accumulated `.where()` filter.
329
+ * Returns the count of deleted rows.
330
+ */
331
+ deleteCount(): Promise<number>;
332
+ /**
333
+ * Count all rows matching the accumulated `.where()` filter.
334
+ * With no filter, counts all rows in the store.
335
+ *
336
+ * **Note — `skip`/`take` are respected**: unlike Prisma's SQL `count()`,
337
+ * which ignores pagination, this implementation reuses the same cursor-scan
338
+ * plan as `all()`. That means `where(...).take(5).count()` returns at most 5,
339
+ * not the total number of matching rows. Use `where(...).count()` without
340
+ * `take`/`skip` when you need an unbounded total.
341
+ */
342
+ count(): Promise<number>;
343
+ }
344
+ //#endregion
345
+ //#region src/core/idb-orm.d.ts
346
+ /**
347
+ * The ORM client object returned by {@link idbOrm}.
348
+ *
349
+ * Keys are the root keys from `contract.roots` (e.g. `"users"`, `"posts"`).
350
+ * Each value is an {@link IdbStoreAccessor} targeting the corresponding model.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * const db = idbOrm({ contract, executor: runtime });
355
+ * const users = await db.users.all(); // IdbStoreAccessor<..., "User">
356
+ * ```
357
+ */
358
+ type IdbOrmClient<TContract extends Contract<IdbStorage>> = { readonly [K in string & keyof TContract["roots"]]: TContract["roots"][K] extends {
359
+ model: infer ModelName extends string;
360
+ } ? IdbStoreAccessor<TContract, ModelName> : never; };
361
+ /**
362
+ * Options for {@link idbOrm}.
363
+ */
364
+ interface IdbOrmOptions<TContract extends IdbContract> {
365
+ /** The resolved IDB contract (with or without attached type maps). */
366
+ readonly contract: TContract;
367
+ /**
368
+ * The query executor.
369
+ *
370
+ * Any object with a compatible `query()` signature satisfies this —
371
+ * most commonly an `IdbRuntime` created via `createIdbRuntime()`.
372
+ */
373
+ readonly executor: IdbQueryExecutor;
374
+ }
375
+ /**
376
+ * Create a typed IDB ORM client from a contract and executor.
377
+ *
378
+ * The client exposes one `IdbStoreAccessor` per entry in `contract.roots`.
379
+ * Only roots-declared stores are accessible at the top level — other stores
380
+ * can be reached via `.include()` on any accessor.
381
+ *
382
+ * @example
383
+ * ```ts
384
+ * import { idbOrm } from "@prisma-idb/client-idb/orm";
385
+ * import { createIdbRuntime } from "@prisma-idb/runtime-idb/runtime";
386
+ * import contract from "./prisma/idb-contract";
387
+ *
388
+ * const runtime = createIdbRuntime({ adapter, driver });
389
+ * const db = idbOrm({ contract, executor: runtime });
390
+ *
391
+ * // Typed query builder:
392
+ * const alice = await db.users.where({ email: "alice@example.com" }).first();
393
+ * const postsWithAuthor = await db.posts.include("author").all();
394
+ * ```
395
+ */
396
+ declare function idbOrm<TContract extends IdbContract>(options: IdbOrmOptions<TContract>): IdbOrmClient<TContract>;
397
+ //#endregion
398
+ export { IdbStoreAccessor as a, IdbGroupedAccessor as c, IdbModelAccessor as d, IdbIncludeRefinementAccessor as i, IdbIncludeScalar as l, IdbOrmOptions as n, WhereCallback as o, idbOrm as r, GroupedResultRow as s, IdbOrmClient as t, IdbFieldAccessor as u };
399
+ //# sourceMappingURL=idb-orm-gEKHfG4H.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idb-orm-gEKHfG4H.d.mts","names":[],"sources":["../src/core/model-accessor.ts","../src/core/store-state.ts","../src/core/grouped-accessor.ts","../src/core/store-accessor.ts","../src/core/idb-orm.ts"],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { l as IdbContract } from "./executor-CevtdBfz.mjs";
2
+ import "./mutation-scope-pOn0QzvV.mjs";
3
+ import { IdbFilterExpr } from "@prisma-idb/adapter-idb/runtime";
4
+ import { IdbTransactionScope } from "@prisma-idb/driver-idb/runtime";
5
+ //#region src/core/mutation-executor.d.ts
6
+ /**
7
+ * Transitively walks the `onDelete` cascade graph from `modelName`, collecting
8
+ * every store a recursive delete might touch. `cascade` edges are walked
9
+ * further (a cascaded child's own children may themselves cascade);
10
+ * `restrict`/`setNull`/`setDefault` edges still need their store added (each
11
+ * is read or written once) but don't recurse further, since none of them
12
+ * delete the child row. Guarded by a model-level visited set — required
13
+ * because IDB must declare every store a transaction might touch before it
14
+ * opens, so this static walk must terminate even on a self-referential model
15
+ * or a cycle of mutually-cascading models.
16
+ */
17
+ declare function collectDeleteStoreNames(contract: IdbContract, modelName: string): string[];
18
+ /**
19
+ * Enforces every `onDelete` action declared on `modelName`'s child relations
20
+ * against one specific `row` about to be deleted. `cascade` recurses into
21
+ * each matched child's own `onDelete` relations *before* deleting it — so a
22
+ * multi-hop chain (`User --cascade--> Post --cascade--> Comment`) is fully
23
+ * torn down, and a `restrict` several hops deep still aborts the whole
24
+ * transaction (recursing before deleting means the delete never happens if a
25
+ * deeper hop throws). `setNull`/`setDefault` are leaf actions: the child row
26
+ * survives, so recursion never continues past them.
27
+ *
28
+ * `visited` (keyed by `storeName::key`) guards against row-level cycles — two
29
+ * specific rows whose FKs point at each other through a self-referential or
30
+ * mutually-cascading relation graph. Callers should leave it at its default
31
+ * (a fresh `Set` per top-level call) so independent rows deleted in the same
32
+ * `deleteAll()` batch don't cross-suppress each other's cascades; it's only
33
+ * ever passed explicitly by this function's own recursive calls, to keep one
34
+ * shared guard across a single row's full recursive descent.
35
+ */
36
+ declare function applyReferentialActionsForRow(scope: IdbTransactionScope, contract: IdbContract, modelName: string, row: Record<string, unknown>, visited?: Set<string>): Promise<void>;
37
+ //#endregion
38
+ export { applyReferentialActionsForRow, collectDeleteStoreNames };
39
+ //# sourceMappingURL=internal.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.mts","names":[],"sources":["../src/core/mutation-executor.ts"],"mappings":";;;;;;;;;;;;;;;;iBAy3CgB,wBAAwB,UAAU,aAAa;;;;;;;;;;;;;;;;;;;iBAsCzC,8BACpB,OAAO,qBACP,UAAU,aACV,mBACA,KAAK,yBACL,UAAS,cACR"}
@@ -0,0 +1,2 @@
1
+ import { r as collectDeleteStoreNames, t as applyReferentialActionsForRow } from "./mutation-executor-CD5n_kKT.mjs";
2
+ export { applyReferentialActionsForRow, collectDeleteStoreNames };
@@ -1,4 +1,5 @@
1
- import { b as IdbContract, t as IdbOrmClient } from "./idb-orm-MOH1S3ES.mjs";
1
+ import { l as IdbContract } from "./executor-CevtdBfz.mjs";
2
+ import { t as IdbOrmClient } from "./idb-orm-gEKHfG4H.mjs";
2
3
  import { IdbTransactionScope } from "@prisma-idb/driver-idb/runtime";
3
4
  import { IdbMiddleware } from "@prisma-idb/runtime-idb/runtime";
4
5
  //#region src/core/idb-client.d.ts
@@ -112,4 +113,4 @@ declare function createManagedIdbClient<TClient extends {
112
113
  }>(open: () => Promise<TClient>, options: ManagedIdbClientOptions): ManagedIdbClient<TClient>;
113
114
  //#endregion
114
115
  export { IdbClientOptions as a, IdbClient as i, ManagedIdbClientOptions as n, createIdbClient as o, createManagedIdbClient as r, ManagedIdbClient as t };
115
- //# sourceMappingURL=managed-client-CJD9lBx5.d.mts.map
116
+ //# sourceMappingURL=managed-client-B_xGXdjz.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-client-B_xGXdjz.d.mts","names":[],"sources":["../src/core/idb-client.ts","../src/core/managed-client.ts"],"mappings":";;;;;UAWiB,iBAAiB,kBAAkB;WACzC,UAAU;WACV;;WAGA,UAAU;WACV,sBAAsB;;UAGhB,UAAU,kBAAkB;WAClC,KAAK,aAAa;;;;;;;;;;;EAW3B,gBAAgB,GAAG,sBAAsB,KAAK,OAAO,wBAAwB,QAAQ,KAAK,QAAQ;EAClG,gBAAgB;EAChB,SAAS;GACR,OAAO,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;iBAwBX,gBAAgB,kBAAkB,aAChD,SAAS,iBAAiB,aACzB,UAAU;;;;;;;;;;;;;;;;;UC9CI;;WAEN;;WAEA,aAAa;;UAGP,iBAAiB;;EAEhC,OAAO,QAAQ;;EAEf,SAAS;;;;;;;;;;;;EAYT,SAAS;;;;;;;;;;;;;;;;;;;;;iBAsBK,uBAAuB;EAAkB,SAAS;GAChE,YAAY,QAAQ,UACpB,SAAS,0BACR,iBAAiB"}
@@ -1,4 +1,5 @@
1
- import { c as withMutationScope, t as idbOrm } from "./idb-orm-C8yXJp6R.mjs";
1
+ import { y as withMutationScope } from "./mutation-executor-CD5n_kKT.mjs";
2
+ import { t as idbOrm } from "./idb-orm-DsfllvzD.mjs";
2
3
  import { IdbAdapter } from "@prisma-idb/adapter-idb/runtime";
3
4
  import { idbCodecLookup } from "@prisma-idb/target-idb/runtime";
4
5
  import { createIDBRuntimeDriver } from "@prisma-idb/driver-idb/runtime";
@@ -119,4 +120,4 @@ function createManagedIdbClient(open, options) {
119
120
  //#endregion
120
121
  export { createIdbClient as n, createManagedIdbClient as t };
121
122
 
122
- //# sourceMappingURL=managed-client-DofL09P8.mjs.map
123
+ //# sourceMappingURL=managed-client-Cp01irrm.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"managed-client-DofL09P8.mjs","names":[],"sources":["../src/core/idb-client.ts","../src/core/managed-client.ts"],"sourcesContent":["import { IdbAdapter } from \"@prisma-idb/adapter-idb/runtime\";\nimport { createIDBRuntimeDriver } from \"@prisma-idb/driver-idb/runtime\";\nimport type { IdbTransactionScope } from \"@prisma-idb/driver-idb/runtime\";\nimport type { IdbMiddleware } from \"@prisma-idb/runtime-idb/runtime\";\nimport { createIdbRuntime } from \"@prisma-idb/runtime-idb/runtime\";\nimport { idbCodecLookup } from \"@prisma-idb/target-idb/runtime\";\nimport { withMutationScope } from \"./mutation-scope\";\nimport { idbOrm } from \"./idb-orm\";\nimport type { IdbOrmClient } from \"./idb-orm\";\nimport type { IdbContract } from \"./types\";\n\nexport interface IdbClientOptions<TContract extends IdbContract> {\n readonly contract: TContract;\n readonly dbName: string;\n // No version — the migration runner owns the IDB version integer (ADR 001).\n /** IDB factory override — primarily for tests and custom browser realms. */\n readonly factory?: IDBFactory;\n readonly middleware?: readonly IdbMiddleware[];\n}\n\nexport interface IdbClient<TContract extends IdbContract> {\n readonly orm: IdbOrmClient<TContract>;\n /**\n * Run `fn` inside a single multi-store readwrite IDB transaction.\n *\n * Opens the transaction, passes an `IdbTransactionScope` to `fn`, then\n * commits on success or rolls back on error. Equivalent to calling\n * `withMutationScope(runtime, storeNames, fn)`.\n *\n * Useful from test harnesses and from any caller that has an `IdbClient`\n * but not the raw runtime reference.\n */\n withTransaction<T>(storeNames: string[], fn: (scope: IdbTransactionScope) => Promise<T>): Promise<T>;\n verifyMarker(): Promise<boolean>;\n close(): Promise<void>;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\n/**\n * Creates a typed IDB client from a contract and a database name.\n *\n * Assembles the full runtime stack (driver → adapter → runtime → ORM) internally.\n * Equivalent to `postgres({ contract, url })` in `@prisma/orm-postgres/runtime`.\n *\n * The IDB database version is not exposed — it is managed by the migration runner\n * per ADR 001. The driver opens at the current database version, which is correct\n * for a runtime that only reads/writes (no DDL).\n *\n * @example\n * ```ts\n * import { createIdbClient } from '@prisma-idb/client-idb/client';\n * import contract from './contract';\n *\n * export const db = createIdbClient({ contract, dbName: 'my-app' });\n *\n * // Later:\n * const users = await db.orm.users.all().toArray();\n * ```\n */\nexport function createIdbClient<TContract extends IdbContract>(\n options: IdbClientOptions<TContract>\n): IdbClient<TContract> {\n const driver = createIDBRuntimeDriver(\n options.dbName,\n undefined,\n options.factory !== undefined ? { factory: options.factory } : undefined\n ).create();\n const adapter = new IdbAdapter(idbCodecLookup);\n const runtime = createIdbRuntime({\n adapter,\n driver,\n contract: options.contract as Record<string, unknown>,\n ...(options.middleware !== undefined && options.middleware.length > 0 ? { middleware: options.middleware } : {}),\n });\n const orm = idbOrm({ contract: options.contract, executor: runtime });\n\n return {\n orm,\n withTransaction: <T>(storeNames: string[], fn: (scope: IdbTransactionScope) => Promise<T>) =>\n withMutationScope(runtime, storeNames, fn),\n verifyMarker: () => runtime.verifyMarker(),\n async close() {\n await runtime.close();\n },\n [Symbol.asyncDispose]() {\n return this.close();\n },\n };\n}\n","/**\n * `createManagedIdbClient` — a singleton, race-safe wrapper around any IDB\n * client factory (`createIdbClient`, `createAutoMigratingIdbClient`, or a\n * sync-extension factory like `createAutoMigratingSyncIdbClient` — anything\n * returning a `close(): Promise<void>`-shaped client).\n *\n * Every consumer of this package that wants a module-level singleton client\n * plus a \"wipe local data\" reset (e.g. on logout) needs the same race-safety:\n * `reset()` must never call `indexedDB.deleteDatabase` while a `get()` open\n * is still in flight, since a still-opening connection would block (or worse,\n * leave a stale handle once that open resolves after the delete). This was\n * previously hand-rolled per app with diverging correctness; see ADR-less\n * discussion in the kanban example's `db.ts` history.\n */\n\nexport interface ManagedIdbClientOptions {\n /** IDB database name — passed to `indexedDB.deleteDatabase` on `reset()`. */\n readonly dbName: string;\n /** IDB factory override — primarily for tests. Defaults to `indexedDB`. */\n readonly idbFactory?: IDBFactory;\n}\n\nexport interface ManagedIdbClient<TClient> {\n /** Opens the client on first call and caches it; concurrent calls share one open. */\n get(): Promise<TClient>;\n /** Closes the cached client, if any. Safe to call when nothing is open. */\n close(): Promise<void>;\n /**\n * Closes and deletes the database — waits out any `get()` open already in\n * flight first. `onblocked` (another tab still has the DB open) is NOT\n * terminal — per spec, the delete request stays pending, and still fires\n * `onsuccess` once every blocking connection closes — so this promise\n * stays pending through it too, settling only once `deleteDatabase`\n * actually reaches `onsuccess` or `onerror`. Neither resolving early\n * (claims success before the old data is actually gone) nor rejecting\n * early (reports failure on a delete that may still succeed) would be\n * accurate.\n */\n reset(): Promise<void>;\n}\n\n/**\n * Wraps `open` (a client factory, e.g. `() => createAutoMigratingIdbClient({...})`)\n * with a module-scoped singleton: `get()` opens once and caches the result,\n * `close()` releases it, `reset()` closes and wipes the underlying database.\n *\n * @example\n * ```ts\n * import { createManagedIdbClient } from '@prisma-idb/client-idb/client';\n *\n * const db = createManagedIdbClient(\n * () => createAutoMigratingIdbClient({ contractSpace, dbName: 'my-app' }),\n * { dbName: 'my-app' }\n * );\n *\n * const client = await db.get();\n * // ... on logout:\n * await db.reset();\n * ```\n */\nexport function createManagedIdbClient<TClient extends { close(): Promise<void> }>(\n open: () => Promise<TClient>,\n options: ManagedIdbClientOptions\n): ManagedIdbClient<TClient> {\n const { dbName, idbFactory = indexedDB } = options;\n\n let client: TClient | null = null;\n let clientPromise: Promise<TClient> | null = null;\n // Gate between get() and reset() — set synchronously (no `await` before\n // either reads/writes it) so the two never interleave: see module doc comment.\n let resetPromise: Promise<void> | null = null;\n\n async function get(): Promise<TClient> {\n if (resetPromise) await resetPromise;\n if (client) return client;\n clientPromise ??= open().catch((err: unknown) => {\n // Clear on rejection so a later get() call retries instead of\n // re-awaiting the same rejected promise forever.\n clientPromise = null;\n throw err;\n });\n\n const fresh = await clientPromise;\n client = fresh;\n return fresh;\n }\n\n async function close(): Promise<void> {\n if (!client) return;\n await client.close();\n client = null;\n clientPromise = null;\n }\n\n async function reset(): Promise<void> {\n if (resetPromise) return resetPromise;\n resetPromise = (async () => {\n if (clientPromise) await clientPromise.catch(() => {});\n await close();\n await new Promise<void>((resolve, reject) => {\n const req = idbFactory.deleteDatabase(dbName);\n req.onsuccess = () => resolve();\n req.onerror = () => reject(req.error as Error);\n // Deliberately no-op: `onblocked` only means the request is waiting\n // on another open connection, not that it failed — `onsuccess` (or\n // `onerror`) still fires later once that connection closes. Settling\n // here either way would misreport the outcome; see the `reset()`\n // doc comment above.\n req.onblocked = () => {};\n });\n })();\n try {\n await resetPromise;\n } finally {\n resetPromise = null;\n }\n }\n\n return { get, close, reset };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,SAAgB,gBACd,SACsB;CACtB,MAAM,SAAS,uBACb,QAAQ,QACR,KAAA,GACA,QAAQ,YAAY,KAAA,IAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,KAAA,CACjE,CAAC,CAAC,OAAO;CACT,MAAM,UAAU,IAAI,WAAW,cAAc;CAC7C,MAAM,UAAU,iBAAiB;EAC/B;EACA;EACA,UAAU,QAAQ;EAClB,GAAI,QAAQ,eAAe,KAAA,KAAa,QAAQ,WAAW,SAAS,IAAI,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;CAChH,CAAC;CAGD,OAAO;EACL,KAHU,OAAO;GAAE,UAAU,QAAQ;GAAU,UAAU;EAAQ,CAG/D;EACF,kBAAqB,YAAsB,OACzC,kBAAkB,SAAS,YAAY,EAAE;EAC3C,oBAAoB,QAAQ,aAAa;EACzC,MAAM,QAAQ;GACZ,MAAM,QAAQ,MAAM;EACtB;EACA,CAAC,OAAO,gBAAgB;GACtB,OAAO,KAAK,MAAM;EACpB;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;AC5BA,SAAgB,uBACd,MACA,SAC2B;CAC3B,MAAM,EAAE,QAAQ,aAAa,cAAc;CAE3C,IAAI,SAAyB;CAC7B,IAAI,gBAAyC;CAG7C,IAAI,eAAqC;CAEzC,eAAe,MAAwB;EACrC,IAAI,cAAc,MAAM;EACxB,IAAI,QAAQ,OAAO;EACnB,kBAAkB,KAAK,CAAC,CAAC,OAAO,QAAiB;GAG/C,gBAAgB;GAChB,MAAM;EACR,CAAC;EAED,MAAM,QAAQ,MAAM;EACpB,SAAS;EACT,OAAO;CACT;CAEA,eAAe,QAAuB;EACpC,IAAI,CAAC,QAAQ;EACb,MAAM,OAAO,MAAM;EACnB,SAAS;EACT,gBAAgB;CAClB;CAEA,eAAe,QAAuB;EACpC,IAAI,cAAc,OAAO;EACzB,gBAAgB,YAAY;GAC1B,IAAI,eAAe,MAAM,cAAc,YAAY,CAAC,CAAC;GACrD,MAAM,MAAM;GACZ,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,MAAM,MAAM,WAAW,eAAe,MAAM;IAC5C,IAAI,kBAAkB,QAAQ;IAC9B,IAAI,gBAAgB,OAAO,IAAI,KAAc;IAM7C,IAAI,kBAAkB,CAAC;GACzB,CAAC;EACH,EAAA,CAAG;EACH,IAAI;GACF,MAAM;EACR,UAAU;GACR,eAAe;EACjB;CACF;CAEA,OAAO;EAAE;EAAK;EAAO;CAAM;AAC7B"}
1
+ {"version":3,"file":"managed-client-Cp01irrm.mjs","names":[],"sources":["../src/core/idb-client.ts","../src/core/managed-client.ts"],"sourcesContent":["import { IdbAdapter } from \"@prisma-idb/adapter-idb/runtime\";\nimport { createIDBRuntimeDriver } from \"@prisma-idb/driver-idb/runtime\";\nimport type { IdbTransactionScope } from \"@prisma-idb/driver-idb/runtime\";\nimport type { IdbMiddleware } from \"@prisma-idb/runtime-idb/runtime\";\nimport { createIdbRuntime } from \"@prisma-idb/runtime-idb/runtime\";\nimport { idbCodecLookup } from \"@prisma-idb/target-idb/runtime\";\nimport { withMutationScope } from \"./mutation-scope\";\nimport { idbOrm } from \"./idb-orm\";\nimport type { IdbOrmClient } from \"./idb-orm\";\nimport type { IdbContract } from \"./types\";\n\nexport interface IdbClientOptions<TContract extends IdbContract> {\n readonly contract: TContract;\n readonly dbName: string;\n // No version — the migration runner owns the IDB version integer (ADR 001).\n /** IDB factory override — primarily for tests and custom browser realms. */\n readonly factory?: IDBFactory;\n readonly middleware?: readonly IdbMiddleware[];\n}\n\nexport interface IdbClient<TContract extends IdbContract> {\n readonly orm: IdbOrmClient<TContract>;\n /**\n * Run `fn` inside a single multi-store readwrite IDB transaction.\n *\n * Opens the transaction, passes an `IdbTransactionScope` to `fn`, then\n * commits on success or rolls back on error. Equivalent to calling\n * `withMutationScope(runtime, storeNames, fn)`.\n *\n * Useful from test harnesses and from any caller that has an `IdbClient`\n * but not the raw runtime reference.\n */\n withTransaction<T>(storeNames: string[], fn: (scope: IdbTransactionScope) => Promise<T>): Promise<T>;\n verifyMarker(): Promise<boolean>;\n close(): Promise<void>;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\n/**\n * Creates a typed IDB client from a contract and a database name.\n *\n * Assembles the full runtime stack (driver → adapter → runtime → ORM) internally.\n * Equivalent to `postgres({ contract, url })` in `@prisma/orm-postgres/runtime`.\n *\n * The IDB database version is not exposed — it is managed by the migration runner\n * per ADR 001. The driver opens at the current database version, which is correct\n * for a runtime that only reads/writes (no DDL).\n *\n * @example\n * ```ts\n * import { createIdbClient } from '@prisma-idb/client-idb/client';\n * import contract from './contract';\n *\n * export const db = createIdbClient({ contract, dbName: 'my-app' });\n *\n * // Later:\n * const users = await db.orm.users.all().toArray();\n * ```\n */\nexport function createIdbClient<TContract extends IdbContract>(\n options: IdbClientOptions<TContract>\n): IdbClient<TContract> {\n const driver = createIDBRuntimeDriver(\n options.dbName,\n undefined,\n options.factory !== undefined ? { factory: options.factory } : undefined\n ).create();\n const adapter = new IdbAdapter(idbCodecLookup);\n const runtime = createIdbRuntime({\n adapter,\n driver,\n contract: options.contract as Record<string, unknown>,\n ...(options.middleware !== undefined && options.middleware.length > 0 ? { middleware: options.middleware } : {}),\n });\n const orm = idbOrm({ contract: options.contract, executor: runtime });\n\n return {\n orm,\n withTransaction: <T>(storeNames: string[], fn: (scope: IdbTransactionScope) => Promise<T>) =>\n withMutationScope(runtime, storeNames, fn),\n verifyMarker: () => runtime.verifyMarker(),\n async close() {\n await runtime.close();\n },\n [Symbol.asyncDispose]() {\n return this.close();\n },\n };\n}\n","/**\n * `createManagedIdbClient` — a singleton, race-safe wrapper around any IDB\n * client factory (`createIdbClient`, `createAutoMigratingIdbClient`, or a\n * sync-extension factory like `createAutoMigratingSyncIdbClient` — anything\n * returning a `close(): Promise<void>`-shaped client).\n *\n * Every consumer of this package that wants a module-level singleton client\n * plus a \"wipe local data\" reset (e.g. on logout) needs the same race-safety:\n * `reset()` must never call `indexedDB.deleteDatabase` while a `get()` open\n * is still in flight, since a still-opening connection would block (or worse,\n * leave a stale handle once that open resolves after the delete). This was\n * previously hand-rolled per app with diverging correctness; see ADR-less\n * discussion in the kanban example's `db.ts` history.\n */\n\nexport interface ManagedIdbClientOptions {\n /** IDB database name — passed to `indexedDB.deleteDatabase` on `reset()`. */\n readonly dbName: string;\n /** IDB factory override — primarily for tests. Defaults to `indexedDB`. */\n readonly idbFactory?: IDBFactory;\n}\n\nexport interface ManagedIdbClient<TClient> {\n /** Opens the client on first call and caches it; concurrent calls share one open. */\n get(): Promise<TClient>;\n /** Closes the cached client, if any. Safe to call when nothing is open. */\n close(): Promise<void>;\n /**\n * Closes and deletes the database — waits out any `get()` open already in\n * flight first. `onblocked` (another tab still has the DB open) is NOT\n * terminal — per spec, the delete request stays pending, and still fires\n * `onsuccess` once every blocking connection closes — so this promise\n * stays pending through it too, settling only once `deleteDatabase`\n * actually reaches `onsuccess` or `onerror`. Neither resolving early\n * (claims success before the old data is actually gone) nor rejecting\n * early (reports failure on a delete that may still succeed) would be\n * accurate.\n */\n reset(): Promise<void>;\n}\n\n/**\n * Wraps `open` (a client factory, e.g. `() => createAutoMigratingIdbClient({...})`)\n * with a module-scoped singleton: `get()` opens once and caches the result,\n * `close()` releases it, `reset()` closes and wipes the underlying database.\n *\n * @example\n * ```ts\n * import { createManagedIdbClient } from '@prisma-idb/client-idb/client';\n *\n * const db = createManagedIdbClient(\n * () => createAutoMigratingIdbClient({ contractSpace, dbName: 'my-app' }),\n * { dbName: 'my-app' }\n * );\n *\n * const client = await db.get();\n * // ... on logout:\n * await db.reset();\n * ```\n */\nexport function createManagedIdbClient<TClient extends { close(): Promise<void> }>(\n open: () => Promise<TClient>,\n options: ManagedIdbClientOptions\n): ManagedIdbClient<TClient> {\n const { dbName, idbFactory = indexedDB } = options;\n\n let client: TClient | null = null;\n let clientPromise: Promise<TClient> | null = null;\n // Gate between get() and reset() — set synchronously (no `await` before\n // either reads/writes it) so the two never interleave: see module doc comment.\n let resetPromise: Promise<void> | null = null;\n\n async function get(): Promise<TClient> {\n if (resetPromise) await resetPromise;\n if (client) return client;\n clientPromise ??= open().catch((err: unknown) => {\n // Clear on rejection so a later get() call retries instead of\n // re-awaiting the same rejected promise forever.\n clientPromise = null;\n throw err;\n });\n\n const fresh = await clientPromise;\n client = fresh;\n return fresh;\n }\n\n async function close(): Promise<void> {\n if (!client) return;\n await client.close();\n client = null;\n clientPromise = null;\n }\n\n async function reset(): Promise<void> {\n if (resetPromise) return resetPromise;\n resetPromise = (async () => {\n if (clientPromise) await clientPromise.catch(() => {});\n await close();\n await new Promise<void>((resolve, reject) => {\n const req = idbFactory.deleteDatabase(dbName);\n req.onsuccess = () => resolve();\n req.onerror = () => reject(req.error as Error);\n // Deliberately no-op: `onblocked` only means the request is waiting\n // on another open connection, not that it failed — `onsuccess` (or\n // `onerror`) still fires later once that connection closes. Settling\n // here either way would misreport the outcome; see the `reset()`\n // doc comment above.\n req.onblocked = () => {};\n });\n })();\n try {\n await resetPromise;\n } finally {\n resetPromise = null;\n }\n }\n\n return { get, close, reset };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,SAAgB,gBACd,SACsB;CACtB,MAAM,SAAS,uBACb,QAAQ,QACR,KAAA,GACA,QAAQ,YAAY,KAAA,IAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,KAAA,CACjE,CAAC,CAAC,OAAO;CACT,MAAM,UAAU,IAAI,WAAW,cAAc;CAC7C,MAAM,UAAU,iBAAiB;EAC/B;EACA;EACA,UAAU,QAAQ;EAClB,GAAI,QAAQ,eAAe,KAAA,KAAa,QAAQ,WAAW,SAAS,IAAI,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;CAChH,CAAC;CAGD,OAAO;EACL,KAHU,OAAO;GAAE,UAAU,QAAQ;GAAU,UAAU;EAAQ,CAG/D;EACF,kBAAqB,YAAsB,OACzC,kBAAkB,SAAS,YAAY,EAAE;EAC3C,oBAAoB,QAAQ,aAAa;EACzC,MAAM,QAAQ;GACZ,MAAM,QAAQ,MAAM;EACtB;EACA,CAAC,OAAO,gBAAgB;GACtB,OAAO,KAAK,MAAM;EACpB;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;AC5BA,SAAgB,uBACd,MACA,SAC2B;CAC3B,MAAM,EAAE,QAAQ,aAAa,cAAc;CAE3C,IAAI,SAAyB;CAC7B,IAAI,gBAAyC;CAG7C,IAAI,eAAqC;CAEzC,eAAe,MAAwB;EACrC,IAAI,cAAc,MAAM;EACxB,IAAI,QAAQ,OAAO;EACnB,kBAAkB,KAAK,CAAC,CAAC,OAAO,QAAiB;GAG/C,gBAAgB;GAChB,MAAM;EACR,CAAC;EAED,MAAM,QAAQ,MAAM;EACpB,SAAS;EACT,OAAO;CACT;CAEA,eAAe,QAAuB;EACpC,IAAI,CAAC,QAAQ;EACb,MAAM,OAAO,MAAM;EACnB,SAAS;EACT,gBAAgB;CAClB;CAEA,eAAe,QAAuB;EACpC,IAAI,cAAc,OAAO;EACzB,gBAAgB,YAAY;GAC1B,IAAI,eAAe,MAAM,cAAc,YAAY,CAAC,CAAC;GACrD,MAAM,MAAM;GACZ,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,MAAM,MAAM,WAAW,eAAe,MAAM;IAC5C,IAAI,kBAAkB,QAAQ;IAC9B,IAAI,gBAAgB,OAAO,IAAI,KAAc;IAM7C,IAAI,kBAAkB,CAAC;GACzB,CAAC;EACH,EAAA,CAAG;EACH,IAAI;GACF,MAAM;EACR,UAAU;GACR,eAAe;EACjB;CACF;CAEA,OAAO;EAAE;EAAK;EAAO;CAAM;AAC7B"}