@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
package/README.md CHANGED
@@ -26,6 +26,8 @@ const open = await db.orm.todo.where({ done: false }).orderBy({ title: "asc" }).
26
26
  | `@prisma-idb/client-idb/client` | `createIdbClient`, `createManagedIdbClient`: open an already-migrated database. |
27
27
  | `@prisma-idb/client-idb/orm` | `idbOrm`, the accessor types, and the `and`, `or` and `not` filter helpers. |
28
28
 
29
+ The `internal` subpath is for companion packages such as `sync-extension-idb`. Application code uses the entry points above. For nested writes, pass a relation callback to `create()` or `update()`; the client supplies the relation mutator.
30
+
29
31
  ## Documentation
30
32
 
31
33
  - [Quick Start](https://prisma-idb.dev/docs/prisma-8/getting-started)
@@ -1,5 +1,5 @@
1
- import { b as IdbContract } from "./idb-orm-MOH1S3ES.mjs";
2
- import { a as IdbClientOptions, i as IdbClient, n as ManagedIdbClientOptions, o as createIdbClient, r as createManagedIdbClient, t as ManagedIdbClient } from "./managed-client-CJD9lBx5.mjs";
1
+ import { l as IdbContract } from "./executor-CevtdBfz.mjs";
2
+ import { a as IdbClientOptions, i as IdbClient, n as ManagedIdbClientOptions, o as createIdbClient, r as createManagedIdbClient, t as ManagedIdbClient } from "./managed-client-B_xGXdjz.mjs";
3
3
  import { Contract } from "@prisma/orm-framework/contract/types";
4
4
  import { ContractSpace } from "@prisma/orm-framework/components/control";
5
5
  import { IdbExtensionSpace } from "@prisma-idb/family-idb/control";
@@ -75,22 +75,6 @@ interface AutoMigrateClientOptions<TContract extends IdbContract> {
75
75
  * ```
76
76
  */
77
77
  declare function createAutoMigratingIdbClient<TContract extends IdbContract>(options: AutoMigrateClientOptions<TContract>): Promise<IdbClient<TContract>>;
78
- /**
79
- * The migration loop. Exported for tests.
80
- *
81
- * Collects pending ops from the app space and every extension space, then
82
- * applies all of them in a single `upgradeneeded` transaction (one IDB
83
- * version bump total). The same transaction writes the marker of every
84
- * space that migrated, so schema changes and markers commit together.
85
- *
86
- * @internal Prefer {@link createAutoMigratingIdbClient}.
87
- */
88
- declare function autoMigrate(input: {
89
- readonly contractSpace: ContractSpace<Contract>;
90
- readonly dbName: string;
91
- readonly factory: IDBFactory;
92
- readonly extensions?: ReadonlyArray<IdbExtensionSpace>;
93
- }): Promise<void>;
94
78
  //#endregion
95
79
  //#region src/core/managed-auto-migrate.d.ts
96
80
  /**
@@ -107,5 +91,5 @@ declare function autoMigrate(input: {
107
91
  */
108
92
  declare function createManagedAutoIdbClient<TContract extends IdbContract>(options: AutoMigrateClientOptions<TContract>): ManagedIdbClient<IdbClient<TContract>>;
109
93
  //#endregion
110
- export { type AutoMigrateClientOptions, type IdbClient, type IdbClientOptions, type ManagedIdbClient, type ManagedIdbClientOptions, autoMigrate, createAutoMigratingIdbClient, createIdbClient, createManagedAutoIdbClient, createManagedIdbClient };
94
+ export { type AutoMigrateClientOptions, type IdbClient, type IdbClientOptions, type ManagedIdbClient, type ManagedIdbClientOptions, createAutoMigratingIdbClient, createIdbClient, createManagedAutoIdbClient, createManagedIdbClient };
111
95
  //# sourceMappingURL=client-auto.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-auto.d.mts","names":[],"sources":["../src/core/auto-migrate.ts","../src/core/managed-auto-migrate.ts"],"mappings":";;;;;;;;;;;;;;;;;;;UA4BiB,yBAAyB,kBAAkB;WACjD,eAAe,cAAc;WAC7B;;WAEA,UAAU;;;;;;;;;WASV,aAAa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6ChB,6BAA6B,kBAAkB,aACnE,SAAS,yBAAyB,aACjC,QAAQ,UAAU;;;;;;;;;;;iBAgCC,YAAY;WAKvB,eAAe,cAAc;WAC7B;WACA,SAAS;WACT,aAAa,cAAc;IAClC;;;;;;;;;;;;;;;iBClGY,2BAA2B,kBAAkB,aAC3D,SAAS,yBAAyB,aACjC,iBAAiB,UAAU"}
1
+ {"version":3,"file":"client-auto.d.mts","names":[],"sources":["../src/core/auto-migrate.ts","../src/core/managed-auto-migrate.ts"],"mappings":";;;;;;;;;;;;;;;;;;;UA4BiB,yBAAyB,kBAAkB;WACjD,eAAe,cAAc;WAC7B;;WAEA,UAAU;;;;;;;;;WASV,aAAa,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6ChB,6BAA6B,kBAAkB,aACnE,SAAS,yBAAyB,aACjC,QAAQ,UAAU;;;;;;;;;;;;;;;iBCzDL,2BAA2B,kBAAkB,aAC3D,SAAS,yBAAyB,aACjC,iBAAiB,UAAU"}
@@ -1,4 +1,4 @@
1
- import { n as createIdbClient, t as createManagedIdbClient } from "./managed-client-DofL09P8.mjs";
1
+ import { n as createIdbClient, t as createManagedIdbClient } from "./managed-client-Cp01irrm.mjs";
2
2
  import { isIdbDdlOp, openAndUpgrade, readMarker } from "@prisma-idb/target-idb/runtime";
3
3
  import { APP_SPACE_ID } from "@prisma/orm-framework/components/control";
4
4
  import { canonicalizeJson } from "@prisma/orm-framework/components/utils";
@@ -106,20 +106,7 @@ async function createAutoMigratingIdbClient(options) {
106
106
  */
107
107
  async function autoMigrate(input) {
108
108
  const { dbName, factory } = input;
109
- const spaces = [{
110
- spaceId: APP_SPACE_ID,
111
- contractSpace: input.contractSpace
112
- }, ...input.extensions ?? []];
113
- const seenSpaceIds = /* @__PURE__ */ new Set();
114
- for (const extension of input.extensions ?? []) {
115
- if (extension.spaceId === APP_SPACE_ID || seenSpaceIds.has(extension.spaceId)) throw new Error(`Invalid duplicate or reserved extension space ID: "${extension.spaceId}"`);
116
- seenSpaceIds.add(extension.spaceId);
117
- }
118
- for (const space of spaces) {
119
- const declaredHash = space.contractSpace.contractJson.storage.storageHash;
120
- const headHash = space.contractSpace.headRef.hash;
121
- if (declaredHash !== headHash) throw new Error(`Contract space "${space.spaceId}" is internally inconsistent: contractJson.storage.storageHash (${declaredHash}) does not match headRef.hash (${headHash}). The contract source likely changed without regenerating migrations/refs/head.json for this space — rebuild its migration chain.`);
122
- }
109
+ const spaces = resolveContractSpaces(input.contractSpace, input.extensions ?? []);
123
110
  const { currentVersion: initialVersion } = await openAndReadMarker(dbName, factory, APP_SPACE_ID);
124
111
  const pendingPerSpace = [];
125
112
  for (const space of spaces) {
@@ -154,6 +141,24 @@ async function autoMigrate(input) {
154
141
  }))
155
142
  });
156
143
  }
144
+ /** Validates bundled descriptors before the migration loop opens the database. */
145
+ function resolveContractSpaces(contractSpace, extensions) {
146
+ const spaces = [{
147
+ spaceId: APP_SPACE_ID,
148
+ contractSpace
149
+ }, ...extensions];
150
+ const seenSpaceIds = /* @__PURE__ */ new Set();
151
+ for (const extension of extensions) {
152
+ if (extension.spaceId === APP_SPACE_ID || seenSpaceIds.has(extension.spaceId)) throw new Error(`Invalid duplicate or reserved extension space ID: "${extension.spaceId}"`);
153
+ seenSpaceIds.add(extension.spaceId);
154
+ }
155
+ for (const space of spaces) {
156
+ const declaredHash = space.contractSpace.contractJson.storage.storageHash;
157
+ const headHash = space.contractSpace.headRef.hash;
158
+ if (declaredHash !== headHash) throw new Error(`Contract space "${space.spaceId}" is internally inconsistent: contractJson.storage.storageHash (${declaredHash}) does not match headRef.hash (${headHash}). The contract source likely changed without regenerating migrations/refs/head.json for this space — rebuild its migration chain.`);
159
+ }
160
+ return spaces;
161
+ }
157
162
  /**
158
163
  * Walk the migration chain from `markerHash` (or `null` for a fresh DB) to
159
164
  * `headHash`, collecting every pending package's ops in order.
@@ -251,6 +256,6 @@ function createManagedAutoIdbClient(options) {
251
256
  });
252
257
  }
253
258
  //#endregion
254
- export { autoMigrate, createAutoMigratingIdbClient, createIdbClient, createManagedAutoIdbClient, createManagedIdbClient };
259
+ export { createAutoMigratingIdbClient, createIdbClient, createManagedAutoIdbClient, createManagedIdbClient };
255
260
 
256
261
  //# sourceMappingURL=client-auto.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-auto.mjs","names":[],"sources":["../src/core/migration-hash.ts","../src/core/auto-migrate.ts","../src/core/managed-auto-migrate.ts"],"sourcesContent":["import type { MigrationPackage } from \"@prisma/orm-framework/components/control\";\nimport { canonicalizeJson } from \"@prisma/orm-framework/components/utils\";\n\n/** Hex-encode the bytes of a digest buffer. */\nfunction toHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/** SHA-256 → lowercase hex, via WebCrypto (`crypto.subtle`) — works in the browser. */\nasync function sha256Hex(input: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(input));\n return toHex(digest);\n}\n\n/**\n * Browser-safe re-implementation of `computeMigrationHash` from\n * `@prisma/orm-toolchain/migration-tools/hash`.\n *\n * The framework version hashes with Node's `node:crypto` `createHash`, which\n * does not exist in the browser — so importing it into the runtime auto-migrate\n * integrity check (PLAN Issue #23 / ADR 199) throws `createHash is not a\n * function` on every client init and breaks the whole app. This version is\n * **byte-identical** to the framework's: it reuses the same `canonicalizeJson`\n * and the same nested SHA-256/hex scheme — strip `migrationHash` from the\n * metadata, hash the canonicalized metadata and ops separately, then hash\n * the canonicalized pair of those two hashes, as bare lowercase hex (no\n * `sha256:` prefix — dropped repo-wide at rc.4). Reusing the\n * identical canonicalization + algorithm guarantees the result matches the\n * `migrationHash` the CLI recorded, so the integrity check stays meaningful.\n *\n * `crypto.subtle.digest` is async, hence the `Promise` return (the framework's\n * Node version is synchronous). Callers in the async migration path await it.\n */\nexport async function computeMigrationHash(\n metadata: MigrationPackage[\"metadata\"],\n ops: MigrationPackage[\"ops\"]\n): Promise<string> {\n // v0.12.0 strips only `migrationHash` before hashing (`hints`/`labels` were\n // removed from the on-disk manifest schema entirely).\n const stripped: Record<string, unknown> = { ...metadata };\n delete stripped[\"migrationHash\"];\n const inner = await Promise.all([sha256Hex(canonicalizeJson(stripped)), sha256Hex(canonicalizeJson(ops))]);\n return sha256Hex(canonicalizeJson(inner));\n}\n","import type { Contract } from \"@prisma/orm-framework/contract/types\";\nimport type { ContractSpace, MigrationPackage } from \"@prisma/orm-framework/components/control\";\nimport { APP_SPACE_ID } from \"@prisma/orm-framework/components/control\";\nimport type { IdbExtensionSpace } from \"@prisma-idb/family-idb/control\";\n// Browser-safe (WebCrypto) hash — the framework's `@prisma/orm-toolchain/migration-tools/hash`\n// uses `node:crypto` and throws in the browser (PLAN Issue #23 regression).\nimport { computeMigrationHash } from \"./migration-hash\";\n// Import from `./runtime` (not `./migration`) so `MigrationCLI` → `node:fs`\n// is not bundled into the browser client.\nimport { isIdbDdlOp, openAndUpgrade, readMarker, type IdbDdlOp } from \"@prisma-idb/target-idb/runtime\";\nimport { createIdbClient, type IdbClient } from \"./idb-client\";\nimport type { IdbContract } from \"./types\";\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Options for {@link createAutoMigratingIdbClient}.\n *\n * `contractSpace` is the bundled artefact produced at design time by\n * `prisma-idb migration contract-space`. It carries the canonical\n * contract JSON, the ordered list of migration packages, and the head\n * ref the runtime walks toward.\n *\n * `extensions` is an optional list of additional contract spaces contributed\n * by IDB extensions (e.g. the sync extension). Each extension space migrates\n * independently of the application space and writes its own marker row keyed\n * by the extension's `spaceId`.\n */\nexport interface AutoMigrateClientOptions<TContract extends IdbContract> {\n readonly contractSpace: ContractSpace<TContract>;\n readonly dbName: string;\n /** IDB factory override — primarily for tests. Defaults to `indexedDB`. */\n readonly factory?: IDBFactory;\n /**\n * Additional contract spaces from IDB extensions.\n *\n * Every space with pending work — the application space plus each\n * extension — is applied together in one `upgradeneeded` transaction,\n * which also writes every space's marker. Each space still gets its own\n * marker row in `_prisma_next_marker`.\n */\n readonly extensions?: ReadonlyArray<IdbExtensionSpace>;\n}\n\n/**\n * Create a typed IDB client, applying any pending migrations from the bundled\n * `contractSpace` first.\n *\n * **What runs**:\n *\n * 1. Open the database at the current local version, read the marker from\n * `_prisma_next_marker`. (Null for a fresh database)\n * 2. If the marker hash equals `contractSpace.headRef.hash`, the database\n * is already at the target — return the client immediately.\n * 3. Otherwise, walk `contractSpace.migrations` from the marker hash (or\n * `null` for fresh) to `headRef.hash`, collecting each pending\n * package's `ops` in chain order.\n * 4. If `extensions` are configured, repeat steps 1–3 for each extension\n * space, then combine every space's pending ops into ONE reopen at\n * `db.version + 1` so `upgradeneeded` fires once; apply every collected\n * op (all spaces) inside that single version-change transaction, which\n * also writes every migrated space's marker.\n * 5. Hand back the typed `IdbClient`.\n *\n * Every pending op is applied exactly as planned, destructive ones included.\n * The developer reviewed them when running `prisma-idb migration plan`,\n * which warns about any that delete data. Skipping some ops would leave the\n * database claiming a schema it doesn't have, and refusing them would stop\n * the app from opening for every user.\n *\n * **What does NOT run in the browser**:\n *\n * The planner does not ship to the browser. The differ does not run. Live-DB\n * schema introspection does not happen. All the planning was done once at\n * design time and is encoded in the bundled `ops.json` blobs inside\n * `contractSpace.migrations`.\n *\n * @example\n * ```ts\n * import { createAutoMigratingIdbClient } from '@prisma-idb/client-idb/client-auto';\n * import { contractSpace } from './prisma/contract-space.generated';\n *\n * const db = await createAutoMigratingIdbClient({ contractSpace, dbName: 'my-app' });\n * const users = await db.orm.users.all().toArray();\n * ```\n */\nexport async function createAutoMigratingIdbClient<TContract extends IdbContract>(\n options: AutoMigrateClientOptions<TContract>\n): Promise<IdbClient<TContract>> {\n const factory = options.factory ?? indexedDB;\n\n await autoMigrate({\n // The public `AutoMigrateClientOptions<TContract>` is generic over the\n // user's narrow IDB contract; the internal `autoMigrate` only consumes\n // chain-walking fields, so widen to `ContractSpace<Contract>` here.\n contractSpace: options.contractSpace as unknown as ContractSpace<Contract>,\n dbName: options.dbName,\n factory,\n ...(options.extensions !== undefined ? { extensions: options.extensions } : {}),\n });\n\n return createIdbClient({\n contract: options.contractSpace.contractJson,\n dbName: options.dbName,\n factory,\n });\n}\n\n// ── Core migration loop ──────────────────────────────────────────────────────\n\n/**\n * The migration loop. Exported for tests.\n *\n * Collects pending ops from the app space and every extension space, then\n * applies all of them in a single `upgradeneeded` transaction (one IDB\n * version bump total). The same transaction writes the marker of every\n * space that migrated, so schema changes and markers commit together.\n *\n * @internal Prefer {@link createAutoMigratingIdbClient}.\n */\nexport async function autoMigrate(input: {\n // `ContractSpace<Contract>` instead of `<unknown>` so the generic\n // constraint `TContract extends Contract` from the framework is satisfied.\n // The internal apply path only reads `headRef.hash` and `migrations`, so\n // the precise contract shape inside `contractJson` doesn't matter here.\n readonly contractSpace: ContractSpace<Contract>;\n readonly dbName: string;\n readonly factory: IDBFactory;\n readonly extensions?: ReadonlyArray<IdbExtensionSpace>;\n}): Promise<void> {\n const { dbName, factory } = input;\n\n // Order here only affects the collection loop below, not the final apply\n // order (see the combined-apply step, which re-sorts to match upstream\n // ADR 212's extension-first convention).\n const spaces: Array<{ spaceId: string; contractSpace: ContractSpace<Contract> }> = [\n { spaceId: APP_SPACE_ID, contractSpace: input.contractSpace },\n ...(input.extensions ?? []),\n ];\n\n // Reject reserved/duplicate extension space IDs before touching the\n // database. Without this, two extensions sharing a spaceId would combine\n // their pending DDL and the upgrade would try to create the same stores\n // twice; an extension using APP_SPACE_ID would have its marker write\n // silently overwrite (or be overwritten by) the app space's marker.\n const seenSpaceIds = new Set<string>();\n for (const extension of input.extensions ?? []) {\n if (extension.spaceId === APP_SPACE_ID || seenSpaceIds.has(extension.spaceId)) {\n throw new Error(`Invalid duplicate or reserved extension space ID: \"${extension.spaceId}\"`);\n }\n seenSpaceIds.add(extension.spaceId);\n }\n\n // Descriptor self-consistency (upstream ADR 212): a space's pinned headRef\n // must match the hash actually embedded in its contractJson. Catches an\n // extension author who edited the contract source without regenerating\n // migrations/refs/head.json — fails loudly here instead of surfacing as a\n // confusing chain-walk error deep inside walkChain.\n for (const space of spaces) {\n const declaredHash = space.contractSpace.contractJson.storage.storageHash;\n const headHash = space.contractSpace.headRef.hash;\n if (declaredHash !== headHash) {\n throw new Error(\n `Contract space \"${space.spaceId}\" is internally inconsistent: ` +\n `contractJson.storage.storageHash (${declaredHash}) does not match ` +\n `headRef.hash (${headHash}). The contract source likely changed without ` +\n \"regenerating migrations/refs/head.json for this space — rebuild its migration chain.\"\n );\n }\n }\n\n // Read current DB version once (all spaces share the same IDB database).\n const { currentVersion: initialVersion } = await openAndReadMarker(dbName, factory, APP_SPACE_ID);\n\n // Collect pending work per space without applying yet, so a broken chain in\n // any space fails before the database is touched.\n const pendingPerSpace: Array<{ spaceId: string; ops: IdbDdlOp[]; storageHash: string }> = [];\n\n for (const space of spaces) {\n const targetHash = space.contractSpace.headRef.hash;\n const { markerHash } = await openAndReadMarker(dbName, factory, space.spaceId);\n if (markerHash === targetHash) continue;\n\n const pendingOps = await walkChain({\n markerHash,\n headHash: targetHash,\n migrations: space.contractSpace.migrations,\n });\n // Push whenever the marker is behind `targetHash` — even if every package\n // walked had zero ops (a hash-only \"bridge\" migration, e.g. re-emitting\n // the contract under a new hashing algorithm with no structural change).\n // Gating on `pendingOps.length > 0` here would skip the marker write\n // below for that space, and since nothing ever changes on a later\n // retry either, the marker gets stuck at the old hash forever — the\n // space never converges to `targetHash`, even though there was never\n // any actual work to do.\n pendingPerSpace.push({ spaceId: space.spaceId, ops: pendingOps, storageHash: targetHash });\n }\n\n if (pendingPerSpace.length === 0) return;\n\n // Combine every pending space into ONE upgradeneeded transaction (one IDB\n // version bump total), which also writes every space's marker. A failure\n // partway through rolls back all spaces' schema changes and markers, and a\n // multi-tab user hits one versionchange/blocked cycle on cold start instead\n // of one per space.\n //\n // DDL op order is extensions (alphabetical by spaceId) first, app space\n // last, matching upstream ADR 212's convention. That's safe even though the\n // app space creates `_prisma_next_marker`: openAndUpgrade writes the\n // markers only after every op has run.\n const orderedPending = [...pendingPerSpace].sort((a, b) => {\n if (a.spaceId === APP_SPACE_ID) return 1;\n if (b.spaceId === APP_SPACE_ID) return -1;\n return a.spaceId.localeCompare(b.spaceId);\n });\n\n await openAndUpgrade({\n factory,\n dbName,\n targetVersion: initialVersion + 1,\n ops: orderedPending.flatMap((space) => space.ops),\n markers: orderedPending.map((space) => ({ space: space.spaceId, storageHash: space.storageHash })),\n });\n}\n\n/**\n * Walk the migration chain from `markerHash` (or `null` for a fresh DB) to\n * `headHash`, collecting every pending package's ops in order.\n *\n * Throws on chain discontinuity (no package whose `from === cursor`) so\n * misconfigured `contractSpace` inputs fail loudly rather than silently\n * leaving the DB at an intermediate state.\n */\nasync function walkChain(input: {\n readonly markerHash: string | null;\n readonly headHash: string;\n readonly migrations: readonly MigrationPackage[];\n}): Promise<IdbDdlOp[]> {\n const byFrom = new Map<string | null, MigrationPackage>();\n for (const pkg of input.migrations) {\n byFrom.set(pkg.metadata.from, pkg);\n }\n\n const pendingOps: IdbDdlOp[] = [];\n let cursor: string | null = input.markerHash;\n const visited = new Set<string | null>();\n\n while (cursor !== input.headHash) {\n if (visited.has(cursor)) {\n throw new Error(\n `Auto-migration chain contains a cycle at hash ${JSON.stringify(cursor)}. ` +\n \"Re-run `prisma-idb migration contract-space` to rebuild a valid chain.\"\n );\n }\n visited.add(cursor);\n const next = byFrom.get(cursor);\n if (!next) {\n throw new Error(\n `Auto-migration chain broken: no migration package with from === ${JSON.stringify(cursor)}. ` +\n \"Verify that contract-space.generated.ts is up to date by re-running \" +\n \"`prisma-idb migration contract-space`.\"\n );\n }\n const computedHash = await computeMigrationHash(next.metadata, next.ops);\n if (computedHash !== next.metadata.migrationHash) {\n throw new Error(\n `Migration package \"${next.dirName}\" failed integrity check: ` +\n `stored migrationHash ${next.metadata.migrationHash} does not match ` +\n `computed hash ${computedHash}. ` +\n \"The ops may have been edited after the package was generated. \" +\n \"Re-run `prisma-idb migration plan` to regenerate the package.\"\n );\n }\n for (const op of next.ops) {\n if (!isIdbDdlOp(op)) {\n throw new Error(`Non-IDB operation found in migration package ${next.dirName}: ${JSON.stringify(op)}`);\n }\n pendingOps.push(op);\n }\n cursor = next.metadata.to;\n }\n\n return pendingOps;\n}\n\n/**\n * Open the database at its current local version (no version arg), read the\n * marker for `spaceId`, then close the connection. Returns the current integer\n * version so the caller can compute `currentVersion + 1` for the upgrade re-open.\n */\nfunction openAndReadMarker(\n dbName: string,\n factory: IDBFactory,\n spaceId: string\n): Promise<{ currentVersion: number; markerHash: string | null }> {\n return new Promise((resolve, reject) => {\n let req: IDBOpenDBRequest;\n try {\n req = factory.open(dbName);\n } catch (err) {\n reject(err);\n return;\n }\n\n req.onsuccess = () => {\n const db = req.result;\n const currentVersion = db.version;\n void (async () => {\n try {\n const record = await readMarker(db, spaceId);\n resolve({ currentVersion, markerHash: record?.storageHash ?? null });\n } catch (err) {\n reject(err);\n } finally {\n db.close();\n }\n })();\n };\n req.onerror = () => {\n reject(req.error ?? new Error(`IDB open failed while reading marker for \"${dbName}\"`));\n };\n });\n}\n","/**\n * `createManagedAutoIdbClient` — `createManagedIdbClient` pre-wired to\n * `createAutoMigratingIdbClient`. Composing those two by hand means writing\n * `dbName` twice, into two independent option bags with nothing tying them\n * together — `createManagedIdbClient`'s `dbName` is used only by `reset()`'s\n * `deleteDatabase` call, so if it ever drifts from the factory's own\n * `dbName` (typo, copy-paste into a second app), `reset()` silently deletes\n * the wrong database (or nothing) while the real one keeps its data. This\n * reads `dbName` (and a `factory` override, if set) once and threads it to\n * both layers, so they can't diverge.\n */\n\nimport { createAutoMigratingIdbClient } from \"./auto-migrate\";\nimport type { AutoMigrateClientOptions } from \"./auto-migrate\";\nimport type { IdbClient } from \"./idb-client\";\nimport { createManagedIdbClient } from \"./managed-client\";\nimport type { ManagedIdbClient } from \"./managed-client\";\nimport type { IdbContract } from \"./types\";\n\n/**\n * @example\n * ```ts\n * import { createManagedAutoIdbClient } from '@prisma-idb/client-idb/client-auto';\n *\n * const db = createManagedAutoIdbClient({ contractSpace, dbName: 'my-app' });\n *\n * const client = await db.get();\n * // ... on logout:\n * await db.reset();\n * ```\n */\nexport function createManagedAutoIdbClient<TContract extends IdbContract>(\n options: AutoMigrateClientOptions<TContract>\n): ManagedIdbClient<IdbClient<TContract>> {\n return createManagedIdbClient(() => createAutoMigratingIdbClient(options), {\n dbName: options.dbName,\n ...(options.factory !== undefined ? { idbFactory: options.factory } : {}),\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-auto.mjs","names":[],"sources":["../src/core/migration-hash.ts","../src/core/auto-migrate.ts","../src/core/managed-auto-migrate.ts"],"sourcesContent":["import type { MigrationPackage } from \"@prisma/orm-framework/components/control\";\nimport { canonicalizeJson } from \"@prisma/orm-framework/components/utils\";\n\n/** Hex-encode the bytes of a digest buffer. */\nfunction toHex(buffer: ArrayBuffer): string {\n return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/** SHA-256 → lowercase hex, via WebCrypto (`crypto.subtle`) — works in the browser. */\nasync function sha256Hex(input: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(input));\n return toHex(digest);\n}\n\n/**\n * Browser-safe re-implementation of `computeMigrationHash` from\n * `@prisma/orm-toolchain/migration-tools/hash`.\n *\n * The framework version hashes with Node's `node:crypto` `createHash`, which\n * does not exist in the browser — so importing it into the runtime auto-migrate\n * integrity check (PLAN Issue #23 / ADR 199) throws `createHash is not a\n * function` on every client init and breaks the whole app. This version is\n * **byte-identical** to the framework's: it reuses the same `canonicalizeJson`\n * and the same nested SHA-256/hex scheme — strip `migrationHash` from the\n * metadata, hash the canonicalized metadata and ops separately, then hash\n * the canonicalized pair of those two hashes, as bare lowercase hex (no\n * `sha256:` prefix — dropped repo-wide at rc.4). Reusing the\n * identical canonicalization + algorithm guarantees the result matches the\n * `migrationHash` the CLI recorded, so the integrity check stays meaningful.\n *\n * `crypto.subtle.digest` is async, hence the `Promise` return (the framework's\n * Node version is synchronous). Callers in the async migration path await it.\n */\nexport async function computeMigrationHash(\n metadata: MigrationPackage[\"metadata\"],\n ops: MigrationPackage[\"ops\"]\n): Promise<string> {\n // v0.12.0 strips only `migrationHash` before hashing (`hints`/`labels` were\n // removed from the on-disk manifest schema entirely).\n const stripped: Record<string, unknown> = { ...metadata };\n delete stripped[\"migrationHash\"];\n const inner = await Promise.all([sha256Hex(canonicalizeJson(stripped)), sha256Hex(canonicalizeJson(ops))]);\n return sha256Hex(canonicalizeJson(inner));\n}\n","import type { Contract } from \"@prisma/orm-framework/contract/types\";\nimport type { ContractSpace, MigrationPackage } from \"@prisma/orm-framework/components/control\";\nimport { APP_SPACE_ID } from \"@prisma/orm-framework/components/control\";\nimport type { IdbExtensionSpace } from \"@prisma-idb/family-idb/control\";\n// Browser-safe (WebCrypto) hash — the framework's `@prisma/orm-toolchain/migration-tools/hash`\n// uses `node:crypto` and throws in the browser (PLAN Issue #23 regression).\nimport { computeMigrationHash } from \"./migration-hash\";\n// Import from `./runtime` (not `./migration`) so `MigrationCLI` → `node:fs`\n// is not bundled into the browser client.\nimport { isIdbDdlOp, openAndUpgrade, readMarker, type IdbDdlOp } from \"@prisma-idb/target-idb/runtime\";\nimport { createIdbClient, type IdbClient } from \"./idb-client\";\nimport type { IdbContract } from \"./types\";\n\n// ── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Options for {@link createAutoMigratingIdbClient}.\n *\n * `contractSpace` is the bundled artefact produced at design time by\n * `prisma-idb migration contract-space`. It carries the canonical\n * contract JSON, the ordered list of migration packages, and the head\n * ref the runtime walks toward.\n *\n * `extensions` is an optional list of additional contract spaces contributed\n * by IDB extensions (e.g. the sync extension). Each extension space migrates\n * independently of the application space and writes its own marker row keyed\n * by the extension's `spaceId`.\n */\nexport interface AutoMigrateClientOptions<TContract extends IdbContract> {\n readonly contractSpace: ContractSpace<TContract>;\n readonly dbName: string;\n /** IDB factory override — primarily for tests. Defaults to `indexedDB`. */\n readonly factory?: IDBFactory;\n /**\n * Additional contract spaces from IDB extensions.\n *\n * Every space with pending work — the application space plus each\n * extension — is applied together in one `upgradeneeded` transaction,\n * which also writes every space's marker. Each space still gets its own\n * marker row in `_prisma_next_marker`.\n */\n readonly extensions?: ReadonlyArray<IdbExtensionSpace>;\n}\n\n/**\n * Create a typed IDB client, applying any pending migrations from the bundled\n * `contractSpace` first.\n *\n * **What runs**:\n *\n * 1. Open the database at the current local version, read the marker from\n * `_prisma_next_marker`. (Null for a fresh database)\n * 2. If the marker hash equals `contractSpace.headRef.hash`, the database\n * is already at the target — return the client immediately.\n * 3. Otherwise, walk `contractSpace.migrations` from the marker hash (or\n * `null` for fresh) to `headRef.hash`, collecting each pending\n * package's `ops` in chain order.\n * 4. If `extensions` are configured, repeat steps 1–3 for each extension\n * space, then combine every space's pending ops into ONE reopen at\n * `db.version + 1` so `upgradeneeded` fires once; apply every collected\n * op (all spaces) inside that single version-change transaction, which\n * also writes every migrated space's marker.\n * 5. Hand back the typed `IdbClient`.\n *\n * Every pending op is applied exactly as planned, destructive ones included.\n * The developer reviewed them when running `prisma-idb migration plan`,\n * which warns about any that delete data. Skipping some ops would leave the\n * database claiming a schema it doesn't have, and refusing them would stop\n * the app from opening for every user.\n *\n * **What does NOT run in the browser**:\n *\n * The planner does not ship to the browser. The differ does not run. Live-DB\n * schema introspection does not happen. All the planning was done once at\n * design time and is encoded in the bundled `ops.json` blobs inside\n * `contractSpace.migrations`.\n *\n * @example\n * ```ts\n * import { createAutoMigratingIdbClient } from '@prisma-idb/client-idb/client-auto';\n * import { contractSpace } from './prisma/contract-space.generated';\n *\n * const db = await createAutoMigratingIdbClient({ contractSpace, dbName: 'my-app' });\n * const users = await db.orm.users.all().toArray();\n * ```\n */\nexport async function createAutoMigratingIdbClient<TContract extends IdbContract>(\n options: AutoMigrateClientOptions<TContract>\n): Promise<IdbClient<TContract>> {\n const factory = options.factory ?? indexedDB;\n\n await autoMigrate({\n // The public `AutoMigrateClientOptions<TContract>` is generic over the\n // user's narrow IDB contract; the internal `autoMigrate` only consumes\n // chain-walking fields, so widen to `ContractSpace<Contract>` here.\n contractSpace: options.contractSpace as unknown as ContractSpace<Contract>,\n dbName: options.dbName,\n factory,\n ...(options.extensions !== undefined ? { extensions: options.extensions } : {}),\n });\n\n return createIdbClient({\n contract: options.contractSpace.contractJson,\n dbName: options.dbName,\n factory,\n });\n}\n\n// ── Core migration loop ──────────────────────────────────────────────────────\n\n/**\n * The migration loop. Exported for tests.\n *\n * Collects pending ops from the app space and every extension space, then\n * applies all of them in a single `upgradeneeded` transaction (one IDB\n * version bump total). The same transaction writes the marker of every\n * space that migrated, so schema changes and markers commit together.\n *\n * @internal Prefer {@link createAutoMigratingIdbClient}.\n */\nexport async function autoMigrate(input: {\n // `ContractSpace<Contract>` instead of `<unknown>` so the generic\n // constraint `TContract extends Contract` from the framework is satisfied.\n // The internal apply path only reads `headRef.hash` and `migrations`, so\n // the precise contract shape inside `contractJson` doesn't matter here.\n readonly contractSpace: ContractSpace<Contract>;\n readonly dbName: string;\n readonly factory: IDBFactory;\n readonly extensions?: ReadonlyArray<IdbExtensionSpace>;\n}): Promise<void> {\n const { dbName, factory } = input;\n\n const spaces = resolveContractSpaces(input.contractSpace, input.extensions ?? []);\n\n // Read current DB version once (all spaces share the same IDB database).\n const { currentVersion: initialVersion } = await openAndReadMarker(dbName, factory, APP_SPACE_ID);\n\n // Collect pending work per space without applying yet, so a broken chain in\n // any space fails before the database is touched.\n const pendingPerSpace: Array<{ spaceId: string; ops: IdbDdlOp[]; storageHash: string }> = [];\n\n for (const space of spaces) {\n const targetHash = space.contractSpace.headRef.hash;\n const { markerHash } = await openAndReadMarker(dbName, factory, space.spaceId);\n if (markerHash === targetHash) continue;\n\n const pendingOps = await walkChain({\n markerHash,\n headHash: targetHash,\n migrations: space.contractSpace.migrations,\n });\n // Push whenever the marker is behind `targetHash` — even if every package\n // walked had zero ops (a hash-only \"bridge\" migration, e.g. re-emitting\n // the contract under a new hashing algorithm with no structural change).\n // Gating on `pendingOps.length > 0` here would skip the marker write\n // below for that space, and since nothing ever changes on a later\n // retry either, the marker gets stuck at the old hash forever — the\n // space never converges to `targetHash`, even though there was never\n // any actual work to do.\n pendingPerSpace.push({ spaceId: space.spaceId, ops: pendingOps, storageHash: targetHash });\n }\n\n if (pendingPerSpace.length === 0) return;\n\n // Combine every pending space into ONE upgradeneeded transaction (one IDB\n // version bump total), which also writes every space's marker. A failure\n // partway through rolls back all spaces' schema changes and markers, and a\n // multi-tab user hits one versionchange/blocked cycle on cold start instead\n // of one per space.\n //\n // DDL op order is extensions (alphabetical by spaceId) first, app space\n // last, matching upstream ADR 212's convention. That's safe even though the\n // app space creates `_prisma_next_marker`: openAndUpgrade writes the\n // markers only after every op has run.\n const orderedPending = [...pendingPerSpace].sort((a, b) => {\n if (a.spaceId === APP_SPACE_ID) return 1;\n if (b.spaceId === APP_SPACE_ID) return -1;\n return a.spaceId.localeCompare(b.spaceId);\n });\n\n await openAndUpgrade({\n factory,\n dbName,\n targetVersion: initialVersion + 1,\n ops: orderedPending.flatMap((space) => space.ops),\n markers: orderedPending.map((space) => ({ space: space.spaceId, storageHash: space.storageHash })),\n });\n}\n\n/** Validates bundled descriptors before the migration loop opens the database. */\nfunction resolveContractSpaces(\n contractSpace: ContractSpace<Contract>,\n extensions: ReadonlyArray<IdbExtensionSpace>\n): Array<{ spaceId: string; contractSpace: ContractSpace<Contract> }> {\n // Validate and collect app-first. The combined upgrade applies extensions\n // first, matching upstream ADR 212.\n const spaces: Array<{ spaceId: string; contractSpace: ContractSpace<Contract> }> = [\n { spaceId: APP_SPACE_ID, contractSpace },\n ...extensions,\n ];\n\n // Reject reserved/duplicate extension space IDs before touching the\n // database. Without this, two extensions sharing a spaceId would combine\n // their pending DDL and the upgrade would try to create the same stores\n // twice; an extension using APP_SPACE_ID would have its marker write\n // silently overwrite (or be overwritten by) the app space's marker.\n const seenSpaceIds = new Set<string>();\n for (const extension of extensions) {\n if (extension.spaceId === APP_SPACE_ID || seenSpaceIds.has(extension.spaceId)) {\n throw new Error(`Invalid duplicate or reserved extension space ID: \"${extension.spaceId}\"`);\n }\n seenSpaceIds.add(extension.spaceId);\n }\n\n // Descriptor self-consistency (upstream ADR 212): a space's pinned headRef\n // must match the hash actually embedded in its contractJson. Catches an\n // extension author who edited the contract source without regenerating\n // migrations/refs/head.json — fails loudly here instead of surfacing as a\n // confusing chain-walk error deep inside walkChain.\n for (const space of spaces) {\n const declaredHash = space.contractSpace.contractJson.storage.storageHash;\n const headHash = space.contractSpace.headRef.hash;\n if (declaredHash !== headHash) {\n throw new Error(\n `Contract space \"${space.spaceId}\" is internally inconsistent: ` +\n `contractJson.storage.storageHash (${declaredHash}) does not match ` +\n `headRef.hash (${headHash}). The contract source likely changed without ` +\n \"regenerating migrations/refs/head.json for this space — rebuild its migration chain.\"\n );\n }\n }\n\n return spaces;\n}\n\n/**\n * Walk the migration chain from `markerHash` (or `null` for a fresh DB) to\n * `headHash`, collecting every pending package's ops in order.\n *\n * Throws on chain discontinuity (no package whose `from === cursor`) so\n * misconfigured `contractSpace` inputs fail loudly rather than silently\n * leaving the DB at an intermediate state.\n */\nasync function walkChain(input: {\n readonly markerHash: string | null;\n readonly headHash: string;\n readonly migrations: readonly MigrationPackage[];\n}): Promise<IdbDdlOp[]> {\n const byFrom = new Map<string | null, MigrationPackage>();\n for (const pkg of input.migrations) {\n byFrom.set(pkg.metadata.from, pkg);\n }\n\n const pendingOps: IdbDdlOp[] = [];\n let cursor: string | null = input.markerHash;\n const visited = new Set<string | null>();\n\n while (cursor !== input.headHash) {\n if (visited.has(cursor)) {\n throw new Error(\n `Auto-migration chain contains a cycle at hash ${JSON.stringify(cursor)}. ` +\n \"Re-run `prisma-idb migration contract-space` to rebuild a valid chain.\"\n );\n }\n visited.add(cursor);\n const next = byFrom.get(cursor);\n if (!next) {\n throw new Error(\n `Auto-migration chain broken: no migration package with from === ${JSON.stringify(cursor)}. ` +\n \"Verify that contract-space.generated.ts is up to date by re-running \" +\n \"`prisma-idb migration contract-space`.\"\n );\n }\n const computedHash = await computeMigrationHash(next.metadata, next.ops);\n if (computedHash !== next.metadata.migrationHash) {\n throw new Error(\n `Migration package \"${next.dirName}\" failed integrity check: ` +\n `stored migrationHash ${next.metadata.migrationHash} does not match ` +\n `computed hash ${computedHash}. ` +\n \"The ops may have been edited after the package was generated. \" +\n \"Re-run `prisma-idb migration plan` to regenerate the package.\"\n );\n }\n for (const op of next.ops) {\n if (!isIdbDdlOp(op)) {\n throw new Error(`Non-IDB operation found in migration package ${next.dirName}: ${JSON.stringify(op)}`);\n }\n pendingOps.push(op);\n }\n cursor = next.metadata.to;\n }\n\n return pendingOps;\n}\n\n/**\n * Open the database at its current local version (no version arg), read the\n * marker for `spaceId`, then close the connection. Returns the current integer\n * version so the caller can compute `currentVersion + 1` for the upgrade re-open.\n */\nfunction openAndReadMarker(\n dbName: string,\n factory: IDBFactory,\n spaceId: string\n): Promise<{ currentVersion: number; markerHash: string | null }> {\n return new Promise((resolve, reject) => {\n let req: IDBOpenDBRequest;\n try {\n req = factory.open(dbName);\n } catch (err) {\n reject(err);\n return;\n }\n\n req.onsuccess = () => {\n const db = req.result;\n const currentVersion = db.version;\n void (async () => {\n try {\n const record = await readMarker(db, spaceId);\n resolve({ currentVersion, markerHash: record?.storageHash ?? null });\n } catch (err) {\n reject(err);\n } finally {\n db.close();\n }\n })();\n };\n req.onerror = () => {\n reject(req.error ?? new Error(`IDB open failed while reading marker for \"${dbName}\"`));\n };\n });\n}\n","/**\n * `createManagedAutoIdbClient` — `createManagedIdbClient` pre-wired to\n * `createAutoMigratingIdbClient`. Composing those two by hand means writing\n * `dbName` twice, into two independent option bags with nothing tying them\n * together — `createManagedIdbClient`'s `dbName` is used only by `reset()`'s\n * `deleteDatabase` call, so if it ever drifts from the factory's own\n * `dbName` (typo, copy-paste into a second app), `reset()` silently deletes\n * the wrong database (or nothing) while the real one keeps its data. This\n * reads `dbName` (and a `factory` override, if set) once and threads it to\n * both layers, so they can't diverge.\n */\n\nimport { createAutoMigratingIdbClient } from \"./auto-migrate\";\nimport type { AutoMigrateClientOptions } from \"./auto-migrate\";\nimport type { IdbClient } from \"./idb-client\";\nimport { createManagedIdbClient } from \"./managed-client\";\nimport type { ManagedIdbClient } from \"./managed-client\";\nimport type { IdbContract } from \"./types\";\n\n/**\n * @example\n * ```ts\n * import { createManagedAutoIdbClient } from '@prisma-idb/client-idb/client-auto';\n *\n * const db = createManagedAutoIdbClient({ contractSpace, dbName: 'my-app' });\n *\n * const client = await db.get();\n * // ... on logout:\n * await db.reset();\n * ```\n */\nexport function createManagedAutoIdbClient<TContract extends IdbContract>(\n options: AutoMigrateClientOptions<TContract>\n): ManagedIdbClient<IdbClient<TContract>> {\n return createManagedIdbClient(() => createAutoMigratingIdbClient(options), {\n dbName: options.dbName,\n ...(options.factory !== undefined ? { idbFactory: options.factory } : {}),\n });\n}\n"],"mappings":"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"}
package/dist/client.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as IdbClientOptions, i as IdbClient, n as ManagedIdbClientOptions, o as createIdbClient, r as createManagedIdbClient, t as ManagedIdbClient } from "./managed-client-CJD9lBx5.mjs";
1
+ import { a as IdbClientOptions, i as IdbClient, n as ManagedIdbClientOptions, o as createIdbClient, r as createManagedIdbClient, t as ManagedIdbClient } from "./managed-client-B_xGXdjz.mjs";
2
2
  export { type IdbClient, type IdbClientOptions, type ManagedIdbClient, type ManagedIdbClientOptions, createIdbClient, createManagedIdbClient };
package/dist/client.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as createIdbClient, t as createManagedIdbClient } from "./managed-client-DofL09P8.mjs";
1
+ import { n as createIdbClient, t as createManagedIdbClient } from "./managed-client-Cp01irrm.mjs";
2
2
  export { createIdbClient, createManagedIdbClient };
@@ -1,5 +1,5 @@
1
1
  import { AsyncIterableResult } from "@prisma/orm-framework/components/runtime";
2
- import { IdbFieldFilter, IdbFilterExpr, IdbNullCheckExpr, IdbQueryPlan } from "@prisma-idb/adapter-idb/runtime";
2
+ import { IdbQueryPlan } from "@prisma-idb/adapter-idb/runtime";
3
3
  import { Contract, ContractModelBase, ContractReferenceRelation } from "@prisma/orm-framework/contract/types";
4
4
  import { keyEquals, keyToken } from "@prisma-idb/target-idb/runtime";
5
5
  import { ExtractIdbFieldInputTypes, ExtractIdbFieldOutputTypes, IdbKeyPath, IdbStorage } from "@prisma-idb/target-idb/pack";
@@ -419,398 +419,5 @@ interface IdbQueryExecutor {
419
419
  query<Row>(plan: IdbQueryPlan<Row>): AsyncIterableResult<Row>;
420
420
  }
421
421
  //#endregion
422
- //#region src/core/model-accessor.d.ts
423
- /**
424
- * Operator surface available on every field. All return frozen AST nodes
425
- * — they are values, not statements, so chaining and logical combinators
426
- * compose naturally.
427
- */
428
- interface IdbFieldAccessor<T> {
429
- eq(value: T): IdbFieldFilter;
430
- neq(value: T): IdbFieldFilter;
431
- gt(value: T): IdbFieldFilter;
432
- lt(value: T): IdbFieldFilter;
433
- gte(value: T): IdbFieldFilter;
434
- lte(value: T): IdbFieldFilter;
435
- in(values: ReadonlyArray<T>): IdbFieldFilter;
436
- notIn(values: ReadonlyArray<T>): IdbFieldFilter;
437
- contains(sub: string): IdbFieldFilter;
438
- startsWith(sub: string): IdbFieldFilter;
439
- endsWith(sub: string): IdbFieldFilter;
440
- isNull(): IdbNullCheckExpr;
441
- isNotNull(): IdbNullCheckExpr;
442
- }
443
- /**
444
- * The accessor object handed to a `where(fn)` callback. Each key resolves
445
- * to an {@link IdbFieldAccessor} typed against the field's output type.
446
- *
447
- * The Proxy makes every string key resolve to an accessor at runtime; the
448
- * type narrows the visible surface to the model's declared fields so the
449
- * developer gets autocomplete.
450
- */
451
- type IdbModelAccessor<TContract, ModelName extends string> = { readonly [K in keyof DefaultModelRow<TContract, ModelName>]-?: IdbFieldAccessor<DefaultModelRow<TContract, ModelName>[K]>; };
452
- //#endregion
453
- //#region src/core/store-state.d.ts
454
- /**
455
- * One entry in {@link IdbAccessorState.includes} — describes how a single
456
- * `.include()`d relation should be materialised.
457
- *
458
- * - `collection`: load the related rows, applying the refined `state`
459
- * (its `filters` / `orderBy` / `skip` / `take`) to the child scan.
460
- * `state` is `emptyAccessorState()` for an unrefined `.include(rel)`.
461
- * - `scalar`: reduce the related rows to a single number (Phase 6.5 supports
462
- * `count`). The `state.filters` still constrain which children are counted.
463
- *
464
- * Mirrors the `IncludeExpr` / `IncludeScalar` split from
465
- * Prisma ORM's SQL client, collapsed to the two shapes IDB
466
- * needs (no SQL column mapping, no `combine()` branches).
467
- */
468
- type IncludeEntry = {
469
- readonly kind: "collection";
470
- readonly state: IdbAccessorState;
471
- } | {
472
- readonly kind: "scalar";
473
- readonly fn: "count";
474
- readonly state: IdbAccessorState;
475
- };
476
- /**
477
- * Marker returned by the child accessor's `.count()` terminal when it is
478
- * called inside an `include()` refinement callback. Carries the refined
479
- * child `state` so the relation loader counts only the matching children.
480
- *
481
- * Detected by {@link isIncludeScalar}; mirrors `IncludeScalar` from the
482
- * vendor `sql-orm-client/include-descriptors.ts`.
483
- */
484
- interface IdbIncludeScalar {
485
- readonly kind: "includeScalar";
486
- readonly fn: "count";
487
- readonly state: IdbAccessorState;
488
- }
489
- /**
490
- * Immutable state carried by each {@link IdbStoreAccessorImpl} node in the
491
- * builder chain.
492
- *
493
- * Every mutating method (`.where()`, `.take()`, etc.) returns a cloned
494
- * instance with an updated state rather than mutating in place, making the
495
- * accessor safe to reuse across multiple query branches.
496
- */
497
- interface IdbAccessorState {
498
- /**
499
- * Accumulated filter expressions. Multiple `.where()` calls compose
500
- * with AND semantics — the planner wraps them in an `andExpr` when
501
- * lowering. Each entry is already either an `IdbFieldFilter`, a
502
- * combinator, or a `null-check` node — never raw shorthand records.
503
- */
504
- readonly filters: ReadonlyArray<IdbFilterExpr>;
505
- /** Sort spec: field → direction. Applied as an in-memory comparator. */
506
- readonly orderBy?: Record<string, "asc" | "desc">;
507
- /** OFFSET (number of rows to skip). */
508
- readonly skip?: number;
509
- /** LIMIT (maximum number of rows to return). */
510
- readonly take?: number;
511
- /**
512
- * Relations that have been `.include()`d — keys are relation names, values
513
- * describe how to materialise each (collection vs scalar count, plus any
514
- * refinement state). See {@link IncludeEntry}.
515
- */
516
- readonly includes: Record<string, IncludeEntry>;
517
- /**
518
- * Fields kept by `.select()`. `undefined` means "all fields". When set,
519
- * the materialised rows are projected down to these fields (plus any
520
- * included relation keys) after the scan and relation loads complete.
521
- */
522
- readonly selectedFields?: ReadonlyArray<string>;
523
- }
524
- //#endregion
525
- //#region src/core/grouped-accessor.d.ts
526
- /**
527
- * One grouped-aggregate result row: the group-key fields picked from the model
528
- * row, intersected with the aggregate aliases.
529
- */
530
- 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>;
531
- /**
532
- * Grouped-aggregate builder returned by `accessor.groupBy(...)`.
533
- *
534
- * Port of the vendor `GroupedCollection` (`sql-orm-client/grouped-collection.ts`),
535
- * but purely in-memory: there is no SQL compilation, so `aggregate()`
536
- * materialises the matching rows, partitions them by the group-key fields, and
537
- * reduces each partition with the requested selectors.
538
- */
539
- interface IdbGroupedAccessor<TContract, ModelName extends string, Fields extends readonly string[]> {
540
- /**
541
- * Reduce each group to one row of `{ ...groupKeyFields, ...aggregates }`.
542
- *
543
- * @example
544
- * ```ts
545
- * const byUser = await db.posts
546
- * .where({ published: true })
547
- * .groupBy("authorId")
548
- * .aggregate((agg) => ({ count: agg.count(), totalViews: agg.sum("views") }));
549
- * // [{ authorId: "u1", count: 3, totalViews: 120 }, ...]
550
- * ```
551
- */
552
- aggregate<Spec extends IdbAggregateSpec>(fn: (agg: IdbAggregateBuilder<TContract, ModelName>) => Spec): Promise<Array<GroupedResultRow<TContract, ModelName, Fields, Spec>>>;
553
- }
554
- //#endregion
555
- //#region src/core/store-accessor.d.ts
556
- /** Callback form of `.where(fn)` — receives the typed model accessor proxy. */
557
- type WhereCallback<TContract, ModelName extends string> = (m: IdbModelAccessor<TContract, ModelName>) => IdbFilterExpr;
558
- /** Tuple of one-or-more field names of a model (for `select()` / `groupBy()`). */
559
- type FieldTuple<TContract, ModelName extends string> = readonly [keyof DefaultModelRow<TContract, ModelName> & string, ...(keyof DefaultModelRow<TContract, ModelName> & string)[]];
560
- /**
561
- * The child accessor handed to an `include()` refinement callback.
562
- *
563
- * Exposes the chainable narrowing methods (`where` / `orderBy` / `take` /
564
- * `skip`) plus the scalar `count()` reducer. Mirrors the vendor
565
- * `IncludeRefinementCollection`: chainable methods return the same refinement
566
- * surface so `count()` stays reachable after a `where()`, and `count()` returns
567
- * an {@link IdbIncludeScalar} marker rather than the async terminal `count()`
568
- * found on the top-level accessor.
569
- */
570
- interface IdbIncludeRefinementAccessor<TContract, ModelName extends string> {
571
- where(filter: WhereFilter<TContract, ModelName> | WhereCallback<TContract, ModelName>): IdbIncludeRefinementAccessor<TContract, ModelName>;
572
- orderBy(spec: OrderBySpec<TContract, ModelName>): IdbIncludeRefinementAccessor<TContract, ModelName>;
573
- take(n: number): IdbIncludeRefinementAccessor<TContract, ModelName>;
574
- skip(n: number): IdbIncludeRefinementAccessor<TContract, ModelName>;
575
- count(): IdbIncludeScalar;
576
- }
577
- /** Refinement callback type for a given relation key `K`. */
578
- type IncludeRefineFn<TContract, ModelName extends string, K extends string, R> = (collection: IdbIncludeRefinementAccessor<TContract, RelatedModelOf<TContract, ModelName, K>>) => R;
579
- /**
580
- * Immutable query-builder for a single IDB object store.
581
- *
582
- * Each method that narrows the query (`.where()`, `.take()`, etc.) returns a
583
- * new, independent accessor instance — the original is never mutated. This
584
- * mirrors the `MongoCollection` pattern from Prisma ORM's Mongo family.
585
- *
586
- * @template TContract - The full IDB contract (with or without type maps).
587
- * @template ModelName - The model (store) this accessor targets.
588
- * @template TIncludes - Tracks which relations have been included via
589
- * `.include()` calls, so the return type widens progressively.
590
- * @template TSelected - Field names kept by `.select()`. `never` (the
591
- * default) means "all fields"; otherwise the row narrows to these fields
592
- * (plus any included relations).
593
- */
594
- interface IdbStoreAccessor<TContract, ModelName extends string, TIncludes extends IncludeSpec<TContract, ModelName> = NoIncludes, TSelected extends string = never> {
595
- /**
596
- * Add a filter (ANDed with any previous `.where()` calls).
597
- *
598
- * Two forms:
599
- *
600
- * - **Shorthand**: `where({ field: value })` — multi-key shorthand
601
- * objects compose as AND. `null` values become null-checks rather
602
- * than literal-null equalities so absent fields match.
603
- * - **Callback**: `where((m) => m.field.op(value))` — receives the
604
- * typed model accessor proxy and returns an `IdbFilterExpr` built
605
- * via the operator surface. Combinators (`and`, `or`, `not` from
606
- * `@prisma-idb/client-idb/orm`) compose nodes.
607
- */
608
- where(filter: WhereFilter<TContract, ModelName> | WhereCallback<TContract, ModelName>): IdbStoreAccessor<TContract, ModelName, TIncludes, TSelected>;
609
- /** Set the sort order. Replaces any previous `.orderBy()` call. */
610
- orderBy(spec: OrderBySpec<TContract, ModelName>): IdbStoreAccessor<TContract, ModelName, TIncludes, TSelected>;
611
- /** Limit the number of rows returned. */
612
- take(n: number): IdbStoreAccessor<TContract, ModelName, TIncludes, TSelected>;
613
- /** Skip the first `n` rows (OFFSET). */
614
- skip(n: number): IdbStoreAccessor<TContract, ModelName, TIncludes, TSelected>;
615
- /**
616
- * Include a reference relation in the returned rows.
617
- *
618
- * The relation is loaded via a single batch cursor scan after the main
619
- * query — O(1) round trips to IDB per included relation regardless of
620
- * the number of parent rows. The return type gains the relation field
621
- * automatically.
622
- *
623
- * An optional refinement callback narrows the loaded relation:
624
- *
625
- * - return the (chained) collection to apply `where` / `orderBy` /
626
- * `take` / `skip` to the related rows (per-parent for `1:N`);
627
- * - return `collection.count()` to reduce a to-many relation to the
628
- * number of matching children (the field becomes a `number`).
629
- *
630
- * @example
631
- * ```ts
632
- * db.users.include("posts", (posts) => posts.where({ published: true }).take(5))
633
- * db.users.include("posts", (posts) => posts.count())
634
- * ```
635
- */
636
- 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>;
637
- include<K extends ReferenceRelKeys<TContract, ModelName>>(relation: K, refineFn: IncludeRefineFn<TContract, ModelName, K, IdbIncludeScalar>): IdbStoreAccessor<TContract, ModelName, TIncludes & Record<K, "scalar">, TSelected>;
638
- /**
639
- * Project the row down to a subset of scalar fields. Any previously
640
- * `.include()`d relations are preserved on the result; only the scalar
641
- * fields are narrowed.
642
- *
643
- * @example
644
- * ```ts
645
- * const summaries = await db.users.select("id", "email").all();
646
- * // typeof summaries[number] === { id: string; email: string }
647
- * ```
648
- */
649
- select<Fields extends FieldTuple<TContract, ModelName>>(...fields: Fields): IdbStoreAccessor<TContract, ModelName, TIncludes, Fields[number]>;
650
- /** Return all matching rows as an async iterable (also awaitable as `Row[]`). */
651
- all(): AsyncIterableResult<SelectedRow<TContract, ModelName, TIncludes, TSelected>>;
652
- /** Return the first matching row, or `null` if none match. */
653
- first(): Promise<SelectedRow<TContract, ModelName, TIncludes, TSelected> | null>;
654
- /**
655
- * Run an in-memory aggregate (count/sum/avg/min/max) over the rows matching
656
- * the accumulated `.where()` filter. Returns one result object keyed by the
657
- * aliases supplied in the spec.
658
- *
659
- * @example
660
- * ```ts
661
- * const stats = await db.posts.where({ published: true }).aggregate((agg) => ({
662
- * total: agg.count(),
663
- * avgViews: agg.avg("views"),
664
- * }));
665
- * ```
666
- */
667
- aggregate<Spec extends IdbAggregateSpec>(fn: (agg: IdbAggregateBuilder<TContract, ModelName>) => Spec): Promise<IdbAggregateResult<Spec>>;
668
- /**
669
- * Switch to grouped-aggregate mode. The returned {@link IdbGroupedAccessor}'s
670
- * `.aggregate(...)` terminal produces one row per group with the chosen key
671
- * fields plus the requested aggregates.
672
- *
673
- * @example
674
- * ```ts
675
- * const byUser = await db.posts
676
- * .where({ published: true })
677
- * .groupBy("authorId")
678
- * .aggregate((agg) => ({ count: agg.count(), totalViews: agg.sum("views") }));
679
- * ```
680
- */
681
- groupBy<Fields extends FieldTuple<TContract, ModelName>>(...fields: Fields): IdbGroupedAccessor<TContract, ModelName, Fields>;
682
- /**
683
- * Insert a record into the store and return the stored row.
684
- *
685
- * The primary key field is optional in `data` — pass it to use a
686
- * client-generated ID (`cuid`, `uuid`) or omit it for auto-increment stores.
687
- *
688
- * Relation fields accept a mutation callback:
689
- * `posts: (rel) => rel.create([...])` or `author: (rel) => rel.connect({ id })`.
690
- * When any relation callback is present, all writes are wrapped in a single
691
- * multi-store IDB transaction (requires IdbRuntime, not a plain executor).
692
- */
693
- create(data: MutationCreateInput<TContract, ModelName>): Promise<DefaultModelRow<TContract, ModelName>>;
694
- /** Look up a single row by primary key. Returns `null` if not found. */
695
- findUnique(key: KeyType<TContract, ModelName>): Promise<DefaultModelRow<TContract, ModelName> | null>;
696
- /** Delete the row with the given primary key. */
697
- delete(key: KeyType<TContract, ModelName>): Promise<void>;
698
- /**
699
- * Update the first row matching the accumulated `.where()` filter.
700
- * Returns the merged row, or `null` if no row matches.
701
- *
702
- * Relation fields accept `connect` or `disconnect` callbacks. When any
703
- * relation callback is present, all writes run in a single multi-store
704
- * IDB transaction (requires IdbRuntime).
705
- */
706
- update(patch: MutationUpdateInput<TContract, ModelName>): Promise<DefaultModelRow<TContract, ModelName> | null>;
707
- /**
708
- * Update all rows matching the accumulated `.where()` filter and return
709
- * them as an `AsyncIterableResult` (also awaitable as `Row[]`).
710
- */
711
- updateAll(patch: PatchInput<TContract, ModelName>): AsyncIterableResult<DefaultModelRow<TContract, ModelName>>;
712
- /**
713
- * Update all rows matching the accumulated `.where()` filter.
714
- * Returns the count of updated rows.
715
- */
716
- updateCount(patch: PatchInput<TContract, ModelName>): Promise<number>;
717
- /**
718
- * Insert or update a single record.
719
- *
720
- * - If a row matching `where` exists: shallow-merge `update` onto it and
721
- * return the merged row.
722
- * - If no matching row exists: insert `create` and return it.
723
- */
724
- upsert(args: {
725
- create: CreateInput<TContract, ModelName>;
726
- update: PatchInput<TContract, ModelName>;
727
- where: WhereFilter<TContract, ModelName>;
728
- }): Promise<DefaultModelRow<TContract, ModelName>>;
729
- /**
730
- * Insert multiple records in a single atomic transaction.
731
- * Returns all inserted rows as an `AsyncIterableResult`.
732
- */
733
- createAll(data: CreateInput<TContract, ModelName>[]): AsyncIterableResult<DefaultModelRow<TContract, ModelName>>;
734
- /**
735
- * Insert multiple records in a single atomic transaction.
736
- * Returns the count of inserted rows.
737
- */
738
- createCount(data: CreateInput<TContract, ModelName>[]): Promise<number>;
739
- /**
740
- * Delete all rows matching the accumulated `.where()` filter.
741
- * Returns the deleted rows as an `AsyncIterableResult`.
742
- */
743
- deleteAll(): AsyncIterableResult<DefaultModelRow<TContract, ModelName>>;
744
- /**
745
- * Delete all rows matching the accumulated `.where()` filter.
746
- * Returns the count of deleted rows.
747
- */
748
- deleteCount(): Promise<number>;
749
- /**
750
- * Count all rows matching the accumulated `.where()` filter.
751
- * With no filter, counts all rows in the store.
752
- *
753
- * **Note — `skip`/`take` are respected**: unlike Prisma's SQL `count()`,
754
- * which ignores pagination, this implementation reuses the same cursor-scan
755
- * plan as `all()`. That means `where(...).take(5).count()` returns at most 5,
756
- * not the total number of matching rows. Use `where(...).count()` without
757
- * `take`/`skip` when you need an unbounded total.
758
- */
759
- count(): Promise<number>;
760
- }
761
- //#endregion
762
- //#region src/core/idb-orm.d.ts
763
- /**
764
- * The ORM client object returned by {@link idbOrm}.
765
- *
766
- * Keys are the root keys from `contract.roots` (e.g. `"users"`, `"posts"`).
767
- * Each value is an {@link IdbStoreAccessor} targeting the corresponding model.
768
- *
769
- * @example
770
- * ```ts
771
- * const db = idbOrm({ contract, executor: runtime });
772
- * const users = await db.users.all(); // IdbStoreAccessor<..., "User">
773
- * ```
774
- */
775
- type IdbOrmClient<TContract extends Contract<IdbStorage>> = { readonly [K in string & keyof TContract["roots"]]: TContract["roots"][K] extends {
776
- model: infer ModelName extends string;
777
- } ? IdbStoreAccessor<TContract, ModelName> : never; };
778
- /**
779
- * Options for {@link idbOrm}.
780
- */
781
- interface IdbOrmOptions<TContract extends IdbContract> {
782
- /** The resolved IDB contract (with or without attached type maps). */
783
- readonly contract: TContract;
784
- /**
785
- * The query executor.
786
- *
787
- * Any object with a compatible `query()` signature satisfies this —
788
- * most commonly an `IdbRuntime` created via `createIdbRuntime()`.
789
- */
790
- readonly executor: IdbQueryExecutor;
791
- }
792
- /**
793
- * Create a typed IDB ORM client from a contract and executor.
794
- *
795
- * The client exposes one `IdbStoreAccessor` per entry in `contract.roots`.
796
- * Only roots-declared stores are accessible at the top level — other stores
797
- * can be reached via `.include()` on any accessor.
798
- *
799
- * @example
800
- * ```ts
801
- * import { idbOrm } from "@prisma-idb/client-idb/orm";
802
- * import { createIdbRuntime } from "@prisma-idb/runtime-idb/runtime";
803
- * import contract from "./prisma/idb-contract";
804
- *
805
- * const runtime = createIdbRuntime({ adapter, driver });
806
- * const db = idbOrm({ contract, executor: runtime });
807
- *
808
- * // Typed query builder:
809
- * const alice = await db.users.where({ email: "alice@example.com" }).first();
810
- * const postsWithAuthor = await db.posts.include("author").all();
811
- * ```
812
- */
813
- declare function idbOrm<TContract extends IdbContract>(options: IdbOrmOptions<TContract>): IdbOrmClient<TContract>;
814
- //#endregion
815
- export { ModelKeyPathOrdered as A, RelationMutationCreate as B, IdbRelationMutator as C, IncludedRow as D, IncludeSpec as E, OrderBySpec as F, extractKeyFromRow as G, SelectedRow as H, PatchInput as I, keyEquals as J, getKeyPath as K, ReferenceRelKeys as L, MutationUpdateInput as M, NoIncludes as N, KeyType as O, NumericFieldNames as P, RelatedModelOf as R, IdbRelationMutation as S, IncludeMarker as T, SortDirection as U, RelationMutationDisconnect as V, WhereFilter as W, keyToken as Y, IdbAggregateResult as _, IdbStoreAccessor as a, IdbContract as b, IdbGroupedAccessor as c, IdbModelAccessor as d, IdbQueryExecutor as f, IdbAggregateBuilder as g, DefaultModelRow as h, IdbIncludeRefinementAccessor as i, MutationCreateInput as j, ModelKeyPath as k, IdbIncludeScalar as l, CreateInput as m, IdbOrmOptions as n, WhereCallback as o, AggregateFn as p, getStoreName as q, idbOrm as r, GroupedResultRow as s, IdbOrmClient as t, IdbFieldAccessor as u, IdbAggregateSelector as v, IncludeFields as w, IdbKeyPath as x, IdbAggregateSpec as y, RelationMutationConnect as z };
816
- //# sourceMappingURL=idb-orm-MOH1S3ES.d.mts.map
422
+ export { RelationMutationCreate as A, NoIncludes as C, ReferenceRelKeys as D, PatchInput as E, extractKeyFromRow as F, getKeyPath as I, getStoreName as L, SelectedRow as M, SortDirection as N, RelatedModelOf as O, WhereFilter as P, keyEquals as R, MutationUpdateInput as S, OrderBySpec as T, IncludedRow as _, IdbAggregateBuilder as a, ModelKeyPathOrdered as b, IdbAggregateSpec as c, IdbRelationMutation as d, IdbRelationMutator as f, IncludeSpec as g, IncludeMarker as h, DefaultModelRow as i, RelationMutationDisconnect as j, RelationMutationConnect as k, IdbContract as l, IncludeFields as m, AggregateFn as n, IdbAggregateResult as o, IdbStorage as p, CreateInput as r, IdbAggregateSelector as s, IdbQueryExecutor as t, IdbKeyPath as u, KeyType as v, NumericFieldNames as w, MutationCreateInput as x, ModelKeyPath as y, keyToken as z };
423
+ //# sourceMappingURL=executor-CevtdBfz.d.mts.map