@prisma-next-idb/client-idb 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Prisma IDB
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,95 @@
1
+ import { b as IdbContract } from "./idb-orm-B5H6xka-.mjs";
2
+ import { n as IdbClientOptions, r as createIdbClient, t as IdbClient } from "./idb-client-CiyW5_TQ.mjs";
3
+ import { Contract } from "@prisma-next/contract/types";
4
+ import { ContractSpace, MigrationOperationClass } from "@prisma-next/framework-components/control";
5
+
6
+ //#region src/core/auto-migrate.d.ts
7
+ /**
8
+ * Migration policy for the browser-side apply path.
9
+ *
10
+ * Two knobs:
11
+ *
12
+ * - `allowedOperationClasses`: filter applied to each op's `operationClass`.
13
+ * Defaults to `['additive', 'widening']`. Anything outside this set is
14
+ * dropped before the upgrade transaction opens.
15
+ * - `onDestructive`: what to do if the planner emitted a destructive op
16
+ * that the filter just dropped. `'refuse'` (default) throws so the user
17
+ * sees the situation; `'allow'` re-includes destructive ops.
18
+ *
19
+ * Default is **safe**: a contract change that drops a store will refuse to
20
+ * apply unless the developer opts in. A user's local IDB can hold months
21
+ * of accumulated state (drafts, offline queue, cached content) and the
22
+ * spec explicitly calls out the silent-data-loss risk if destructive ops
23
+ * apply on every page load. See `FEEDBACKS.md` §4.
24
+ */
25
+ interface MigrationPolicy {
26
+ readonly allowedOperationClasses?: readonly MigrationOperationClass[];
27
+ readonly onDestructive?: "refuse" | "allow";
28
+ }
29
+ /**
30
+ * Options for {@link createAutoMigratingIdbClient}.
31
+ *
32
+ * `contractSpace` is the bundled artefact produced at design time by
33
+ * `prisma-next-idb generate-contract-space`. It carries the canonical
34
+ * contract JSON, the ordered list of migration packages, and the head
35
+ * ref the runtime walks toward.
36
+ */
37
+ interface AutoMigrateClientOptions<TContract extends IdbContract> {
38
+ readonly contractSpace: ContractSpace<TContract>;
39
+ readonly dbName: string;
40
+ /** Migration policy. Defaults to safe (additive + widening only, refuse destructive). */
41
+ readonly policy?: MigrationPolicy;
42
+ /** IDB factory override — primarily for tests. Defaults to `indexedDB`. */
43
+ readonly factory?: IDBFactory;
44
+ }
45
+ /**
46
+ * Create a typed IDB client, applying any pending migrations from the bundled
47
+ * `contractSpace` first.
48
+ *
49
+ * **What runs**:
50
+ *
51
+ * 1. Open the database at the current local version, read the marker from
52
+ * `_prisma_next_marker`. (Null for a fresh database)
53
+ * 2. If the marker hash equals `contractSpace.headRef.hash`, the database
54
+ * is already at the target — return the client immediately.
55
+ * 3. Otherwise, walk `contractSpace.migrations` from the marker hash (or
56
+ * `null` for fresh) to `headRef.hash`, collecting each pending
57
+ * package's `ops` in chain order.
58
+ * 4. Apply the policy filter. Refuse if any destructive op was filtered
59
+ * out and `onDestructive === 'refuse'`.
60
+ * 5. Reopen at `db.version + 1` so `upgradeneeded` fires; apply every
61
+ * collected op inside the version-change transaction.
62
+ * 6. Write the marker to `headRef.hash` in a separate readwrite tx.
63
+ * 7. Hand back the typed `IdbClient`.
64
+ *
65
+ * **What does NOT run in the browser**:
66
+ *
67
+ * The planner does not ship to the browser. The differ does not run. Live-DB
68
+ * schema introspection does not happen. All the planning was done once at
69
+ * design time and is encoded in the bundled `ops.json` blobs inside
70
+ * `contractSpace.migrations`.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * import { createAutoMigratingIdbClient } from '@prisma-next-idb/client-idb/client-auto';
75
+ * import { contractSpace } from './prisma/contract-space.generated';
76
+ *
77
+ * const db = await createAutoMigratingIdbClient({ contractSpace, dbName: 'my-app' });
78
+ * const users = await db.orm.users.all().toArray();
79
+ * ```
80
+ */
81
+ declare function createAutoMigratingIdbClient<TContract extends IdbContract>(options: AutoMigrateClientOptions<TContract>): Promise<IdbClient<TContract>>;
82
+ /**
83
+ * The migration loop. Exported for tests.
84
+ *
85
+ * @internal Prefer {@link createAutoMigratingIdbClient}.
86
+ */
87
+ declare function autoMigrate(input: {
88
+ readonly contractSpace: ContractSpace<Contract>;
89
+ readonly dbName: string;
90
+ readonly policy: Required<MigrationPolicy>;
91
+ readonly factory: IDBFactory;
92
+ }): Promise<void>;
93
+ //#endregion
94
+ export { type AutoMigrateClientOptions, type IdbClient, type IdbClientOptions, type MigrationPolicy, autoMigrate, createAutoMigratingIdbClient, createIdbClient };
95
+ //# sourceMappingURL=client-auto.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-auto.d.mts","names":[],"sources":["../src/core/auto-migrate.ts"],"mappings":";;;;;;;;AAoCA;;;;;;;;AAEwB;AAkBxB;;;;;;;UApBiB,eAAA;EAAA,SACN,uBAAA,YAAmC,uBAAuB;EAAA,SAC1D,aAAA;AAAA;;;;;;;;;UAkBM,wBAAA,mBAA2C,WAAA;EAAA,SACjD,aAAA,EAAe,aAAA,CAAc,SAAA;EAAA,SAC7B,MAAA;EA2CW;EAAA,SAzCX,MAAA,GAAS,eAAA;EAyC8B;EAAA,SAvCvC,OAAA,GAAU,UAAA;AAAA;;;;;;;;;;;;;;;;AAyCS;AAkC9B;;;;;;;;;;;;;;;;;;;;iBApCsB,4BAAA,mBAA+C,WAAA,EACnE,OAAA,EAAS,wBAAA,CAAyB,SAAA,IACjC,OAAA,CAAQ,SAAA,CAAU,SAAA;;;AA2CV;;;iBATW,WAAA,CAAY,KAAA;EAAA,SAKvB,aAAA,EAAe,aAAA,CAAc,QAAA;EAAA,SAC7B,MAAA;EAAA,SACA,MAAA,EAAQ,QAAA,CAAS,eAAA;EAAA,SACjB,OAAA,EAAS,UAAA;AAAA,IAChB,OAAA"}
@@ -0,0 +1,197 @@
1
+ import { t as createIdbClient } from "./idb-client-BJ6OoA2W.mjs";
2
+ import { isIdbDdlOp, openAndUpgrade, readMarker } from "@prisma-next-idb/target-idb/runtime";
3
+ import { APP_SPACE_ID } from "@prisma-next/framework-components/control";
4
+ import { canonicalizeJson } from "@prisma-next/framework-components/utils";
5
+ //#region src/core/migration-hash.ts
6
+ /** Hex-encode the bytes of a digest buffer. */
7
+ function toHex(buffer) {
8
+ return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
9
+ }
10
+ /** SHA-256 → lowercase hex, via WebCrypto (`crypto.subtle`) — works in the browser. */
11
+ async function sha256Hex(input) {
12
+ return toHex(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input)));
13
+ }
14
+ /**
15
+ * Browser-safe re-implementation of `computeMigrationHash` from
16
+ * `@prisma-next/migration-tools/hash`.
17
+ *
18
+ * The framework version hashes with Node's `node:crypto` `createHash`, which
19
+ * does not exist in the browser — so importing it into the runtime auto-migrate
20
+ * integrity check (PLAN Issue #23 / ADR 199) throws `createHash is not a
21
+ * function` on every client init and breaks the whole app. This version is
22
+ * **byte-identical** to the framework's: it reuses the same `canonicalizeJson`
23
+ * and the same nested SHA-256/hex scheme — strip `migrationHash` from the
24
+ * metadata, hash the canonicalized metadata and ops separately, then hash
25
+ * the canonicalized pair of those two hashes, prefixed with `sha256:`. Reusing
26
+ * the identical canonicalization + algorithm guarantees the result matches the
27
+ * `migrationHash` the CLI recorded, so the integrity check stays meaningful.
28
+ *
29
+ * `crypto.subtle.digest` is async, hence the `Promise` return (the framework's
30
+ * Node version is synchronous). Callers in the async migration path await it.
31
+ */
32
+ async function computeMigrationHash(metadata, ops) {
33
+ const stripped = { ...metadata };
34
+ delete stripped["migrationHash"];
35
+ return `sha256:${await sha256Hex(canonicalizeJson(await Promise.all([sha256Hex(canonicalizeJson(stripped)), sha256Hex(canonicalizeJson(ops))])))}`;
36
+ }
37
+ //#endregion
38
+ //#region src/core/auto-migrate.ts
39
+ const SAFE_POLICY = {
40
+ allowedOperationClasses: ["additive", "widening"],
41
+ onDestructive: "refuse"
42
+ };
43
+ /**
44
+ * Create a typed IDB client, applying any pending migrations from the bundled
45
+ * `contractSpace` first.
46
+ *
47
+ * **What runs**:
48
+ *
49
+ * 1. Open the database at the current local version, read the marker from
50
+ * `_prisma_next_marker`. (Null for a fresh database)
51
+ * 2. If the marker hash equals `contractSpace.headRef.hash`, the database
52
+ * is already at the target — return the client immediately.
53
+ * 3. Otherwise, walk `contractSpace.migrations` from the marker hash (or
54
+ * `null` for fresh) to `headRef.hash`, collecting each pending
55
+ * package's `ops` in chain order.
56
+ * 4. Apply the policy filter. Refuse if any destructive op was filtered
57
+ * out and `onDestructive === 'refuse'`.
58
+ * 5. Reopen at `db.version + 1` so `upgradeneeded` fires; apply every
59
+ * collected op inside the version-change transaction.
60
+ * 6. Write the marker to `headRef.hash` in a separate readwrite tx.
61
+ * 7. Hand back the typed `IdbClient`.
62
+ *
63
+ * **What does NOT run in the browser**:
64
+ *
65
+ * The planner does not ship to the browser. The differ does not run. Live-DB
66
+ * schema introspection does not happen. All the planning was done once at
67
+ * design time and is encoded in the bundled `ops.json` blobs inside
68
+ * `contractSpace.migrations`.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * import { createAutoMigratingIdbClient } from '@prisma-next-idb/client-idb/client-auto';
73
+ * import { contractSpace } from './prisma/contract-space.generated';
74
+ *
75
+ * const db = await createAutoMigratingIdbClient({ contractSpace, dbName: 'my-app' });
76
+ * const users = await db.orm.users.all().toArray();
77
+ * ```
78
+ */
79
+ async function createAutoMigratingIdbClient(options) {
80
+ const factory = options.factory ?? indexedDB;
81
+ const policy = mergePolicy(options.policy);
82
+ await autoMigrate({
83
+ contractSpace: options.contractSpace,
84
+ dbName: options.dbName,
85
+ policy,
86
+ factory
87
+ });
88
+ return createIdbClient({
89
+ contract: options.contractSpace.contractJson,
90
+ dbName: options.dbName
91
+ });
92
+ }
93
+ function mergePolicy(p) {
94
+ return {
95
+ allowedOperationClasses: p?.allowedOperationClasses ?? SAFE_POLICY.allowedOperationClasses,
96
+ onDestructive: p?.onDestructive ?? SAFE_POLICY.onDestructive
97
+ };
98
+ }
99
+ /**
100
+ * The migration loop. Exported for tests.
101
+ *
102
+ * @internal Prefer {@link createAutoMigratingIdbClient}.
103
+ */
104
+ async function autoMigrate(input) {
105
+ const { contractSpace, dbName, policy, factory } = input;
106
+ const targetHash = contractSpace.headRef.hash;
107
+ const { currentVersion, markerHash } = await openAndReadMarker(dbName, factory);
108
+ if (markerHash === targetHash) return;
109
+ const { pendingOps, destructiveDropped } = await walkChain({
110
+ markerHash,
111
+ headHash: targetHash,
112
+ migrations: contractSpace.migrations,
113
+ policy
114
+ });
115
+ if (destructiveDropped > 0 && policy.onDestructive === "refuse") throw new Error(`Auto-migration refused: ${destructiveDropped} destructive operation(s) in the pending chain would drop user data. To allow them, pass \`policy: { onDestructive: 'allow' }\` to createAutoMigratingIdbClient. Per-tab persistent state (drafts, offline queue, cached content) will be lost when destructive ops apply silently — review the change before opting in.`);
116
+ if (pendingOps.length === 0) return;
117
+ await openAndUpgrade({
118
+ factory,
119
+ dbName,
120
+ targetVersion: currentVersion + 1,
121
+ ops: pendingOps,
122
+ marker: {
123
+ space: APP_SPACE_ID,
124
+ storageHash: targetHash
125
+ }
126
+ });
127
+ }
128
+ /**
129
+ * Walk the migration chain from `markerHash` (or `null` for a fresh DB) to
130
+ * `headHash`, collecting each pending package's ops in order. Applies the
131
+ * policy filter on each op as it's added; returns the count of destructive
132
+ * ops that were dropped so the caller can refuse if the policy demands.
133
+ *
134
+ * Throws on chain discontinuity (no package whose `from === cursor`) so
135
+ * misconfigured `contractSpace` inputs fail loudly rather than silently
136
+ * leaving the DB at an intermediate state.
137
+ */
138
+ async function walkChain(input) {
139
+ const byFrom = /* @__PURE__ */ new Map();
140
+ for (const pkg of input.migrations) byFrom.set(pkg.metadata.from, pkg);
141
+ const allowed = new Set(input.policy.allowedOperationClasses);
142
+ const pendingOps = [];
143
+ let destructiveDropped = 0;
144
+ let cursor = input.markerHash;
145
+ const visited = /* @__PURE__ */ new Set();
146
+ while (cursor !== input.headHash) {
147
+ if (visited.has(cursor)) throw new Error(`Auto-migration chain contains a cycle at hash ${JSON.stringify(cursor)}. Re-run \`prisma-next-idb generate-contract-space\` to rebuild a valid chain.`);
148
+ visited.add(cursor);
149
+ const next = byFrom.get(cursor);
150
+ if (!next) throw new Error(`Auto-migration chain broken: no migration package with from === ${JSON.stringify(cursor)}. Verify that contract-space.generated.ts is up to date by re-running \`prisma-next-idb generate-contract-space\`.`);
151
+ const computedHash = await computeMigrationHash(next.metadata, next.ops);
152
+ if (computedHash !== next.metadata.migrationHash) throw new Error(`Migration package "${next.dirName}" failed integrity check: stored migrationHash ${next.metadata.migrationHash} does not match computed hash ${computedHash}. The ops may have been edited after the package was generated. Re-run \`prisma-next migration plan\` to regenerate the package.`);
153
+ for (const op of next.ops) {
154
+ if (!isIdbDdlOp(op)) throw new Error(`Non-IDB operation found in migration package ${next.dirName}: ${JSON.stringify(op)}`);
155
+ if (allowed.has(op.operationClass)) pendingOps.push(op);
156
+ else if (op.operationClass === "destructive") if (input.policy.onDestructive === "allow") pendingOps.push(op);
157
+ else destructiveDropped += 1;
158
+ }
159
+ cursor = next.metadata.to;
160
+ }
161
+ return {
162
+ pendingOps,
163
+ destructiveDropped
164
+ };
165
+ }
166
+ /**
167
+ * Open the database at its current local version (no version arg), read the
168
+ * marker, then close the connection. Returns the current integer version so
169
+ * the caller can compute `currentVersion + 1` for the upgrade re-open.
170
+ */
171
+ function openAndReadMarker(dbName, factory) {
172
+ return new Promise((resolve) => {
173
+ const req = factory.open(dbName);
174
+ req.onsuccess = async () => {
175
+ const db = req.result;
176
+ const currentVersion = db.version;
177
+ try {
178
+ resolve({
179
+ currentVersion,
180
+ markerHash: (await readMarker(db, APP_SPACE_ID))?.storageHash ?? null
181
+ });
182
+ } finally {
183
+ db.close();
184
+ }
185
+ };
186
+ req.onerror = () => {
187
+ resolve({
188
+ currentVersion: 0,
189
+ markerHash: null
190
+ });
191
+ };
192
+ });
193
+ }
194
+ //#endregion
195
+ export { autoMigrate, createAutoMigratingIdbClient, createIdbClient };
196
+
197
+ //# sourceMappingURL=client-auto.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-auto.mjs","names":[],"sources":["../src/core/migration-hash.ts","../src/core/auto-migrate.ts"],"sourcesContent":["import type { MigrationPackage } from \"@prisma-next/framework-components/control\";\nimport { canonicalizeJson } from \"@prisma-next/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-next/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, prefixed with `sha256:`. Reusing\n * the 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 const outer = await sha256Hex(canonicalizeJson(inner));\n return `sha256:${outer}`;\n}\n","import type { Contract } from \"@prisma-next/contract/types\";\nimport type {\n ContractSpace,\n MigrationOperationClass,\n MigrationPackage,\n} from \"@prisma-next/framework-components/control\";\nimport { APP_SPACE_ID } from \"@prisma-next/framework-components/control\";\n// Browser-safe (WebCrypto) hash — the framework's `@prisma-next/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-next-idb/target-idb/runtime\";\nimport { createIdbClient, type IdbClient } from \"./idb-client\";\nimport type { IdbContract } from \"./types\";\n\n// ── Public policy types ──────────────────────────────────────────────────────\n\n/**\n * Migration policy for the browser-side apply path.\n *\n * Two knobs:\n *\n * - `allowedOperationClasses`: filter applied to each op's `operationClass`.\n * Defaults to `['additive', 'widening']`. Anything outside this set is\n * dropped before the upgrade transaction opens.\n * - `onDestructive`: what to do if the planner emitted a destructive op\n * that the filter just dropped. `'refuse'` (default) throws so the user\n * sees the situation; `'allow'` re-includes destructive ops.\n *\n * Default is **safe**: a contract change that drops a store will refuse to\n * apply unless the developer opts in. A user's local IDB can hold months\n * of accumulated state (drafts, offline queue, cached content) and the\n * spec explicitly calls out the silent-data-loss risk if destructive ops\n * apply on every page load. See `FEEDBACKS.md` §4.\n */\nexport interface MigrationPolicy {\n readonly allowedOperationClasses?: readonly MigrationOperationClass[];\n readonly onDestructive?: \"refuse\" | \"allow\";\n}\n\nconst SAFE_POLICY: Required<MigrationPolicy> = {\n allowedOperationClasses: [\"additive\", \"widening\"],\n onDestructive: \"refuse\",\n};\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-next-idb generate-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 */\nexport interface AutoMigrateClientOptions<TContract extends IdbContract> {\n readonly contractSpace: ContractSpace<TContract>;\n readonly dbName: string;\n /** Migration policy. Defaults to safe (additive + widening only, refuse destructive). */\n readonly policy?: MigrationPolicy;\n /** IDB factory override — primarily for tests. Defaults to `indexedDB`. */\n readonly factory?: IDBFactory;\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. Apply the policy filter. Refuse if any destructive op was filtered\n * out and `onDestructive === 'refuse'`.\n * 5. Reopen at `db.version + 1` so `upgradeneeded` fires; apply every\n * collected op inside the version-change transaction.\n * 6. Write the marker to `headRef.hash` in a separate readwrite tx.\n * 7. Hand back the typed `IdbClient`.\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-next-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 const policy = mergePolicy(options.policy);\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 policy,\n factory,\n });\n\n return createIdbClient({\n contract: options.contractSpace.contractJson,\n dbName: options.dbName,\n });\n}\n\nfunction mergePolicy(p?: MigrationPolicy): Required<MigrationPolicy> {\n return {\n allowedOperationClasses: p?.allowedOperationClasses ?? SAFE_POLICY.allowedOperationClasses,\n onDestructive: p?.onDestructive ?? SAFE_POLICY.onDestructive,\n };\n}\n\n// ── Core migration loop ──────────────────────────────────────────────────────\n\n/**\n * The migration loop. Exported for tests.\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 policy: Required<MigrationPolicy>;\n readonly factory: IDBFactory;\n}): Promise<void> {\n const { contractSpace, dbName, policy, factory } = input;\n const targetHash = contractSpace.headRef.hash;\n\n // 1 + 2: read current version and marker.\n const { currentVersion, markerHash } = await openAndReadMarker(dbName, factory);\n if (markerHash === targetHash) return;\n\n // 3: collect pending ops from chain walk.\n const { pendingOps, destructiveDropped } = await walkChain({\n markerHash,\n headHash: targetHash,\n migrations: contractSpace.migrations,\n policy,\n });\n\n // 4: refuse if destructive ops were dropped under refuse policy.\n if (destructiveDropped > 0 && policy.onDestructive === \"refuse\") {\n throw new Error(\n `Auto-migration refused: ${destructiveDropped} destructive operation(s) ` +\n \"in the pending chain would drop user data. To allow them, pass \" +\n \"`policy: { onDestructive: 'allow' }` to createAutoMigratingIdbClient. \" +\n \"Per-tab persistent state (drafts, offline queue, cached content) will \" +\n \"be lost when destructive ops apply silently — review the change before opting in.\"\n );\n }\n\n if (pendingOps.length === 0) return;\n\n // 5 + 6: apply ops in upgradeneeded, write marker afterwards.\n await openAndUpgrade({\n factory,\n dbName,\n targetVersion: currentVersion + 1,\n ops: pendingOps,\n marker: { space: APP_SPACE_ID, storageHash: targetHash },\n });\n}\n\ninterface WalkResult {\n readonly pendingOps: IdbDdlOp[];\n readonly destructiveDropped: number;\n}\n\n/**\n * Walk the migration chain from `markerHash` (or `null` for a fresh DB) to\n * `headHash`, collecting each pending package's ops in order. Applies the\n * policy filter on each op as it's added; returns the count of destructive\n * ops that were dropped so the caller can refuse if the policy demands.\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 readonly policy: Required<MigrationPolicy>;\n}): Promise<WalkResult> {\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 allowed = new Set(input.policy.allowedOperationClasses);\n const pendingOps: IdbDdlOp[] = [];\n let destructiveDropped = 0;\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-next-idb generate-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-next-idb generate-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-next 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 if (allowed.has(op.operationClass)) {\n pendingOps.push(op);\n } else if (op.operationClass === \"destructive\") {\n if (input.policy.onDestructive === \"allow\") {\n pendingOps.push(op);\n } else {\n destructiveDropped += 1;\n }\n }\n // Other classes filtered silently.\n }\n cursor = next.metadata.to;\n }\n\n return { pendingOps, destructiveDropped };\n}\n\n/**\n * Open the database at its current local version (no version arg), read the\n * marker, then close the connection. Returns the current integer version so\n * the caller can compute `currentVersion + 1` for the upgrade re-open.\n */\nfunction openAndReadMarker(\n dbName: string,\n factory: IDBFactory\n): Promise<{ currentVersion: number; markerHash: string | null }> {\n return new Promise((resolve) => {\n const req = factory.open(dbName);\n req.onsuccess = async () => {\n const db = req.result;\n const currentVersion = db.version;\n try {\n const record = await readMarker(db, APP_SPACE_ID);\n resolve({ currentVersion, markerHash: record?.storageHash ?? null });\n } finally {\n db.close();\n }\n };\n req.onerror = () => {\n // Open failed — treat as fresh install. Version 0 means the first\n // upgrade opens at version 1.\n resolve({ currentVersion: 0, markerHash: null });\n };\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { n as IdbClientOptions, r as createIdbClient, t as IdbClient } from "./idb-client-CiyW5_TQ.mjs";
2
+ export { type IdbClient, type IdbClientOptions, createIdbClient };
@@ -0,0 +1,2 @@
1
+ import { t as createIdbClient } from "./idb-client-BJ6OoA2W.mjs";
2
+ export { createIdbClient };
@@ -0,0 +1,54 @@
1
+ import { o as withMutationScope, t as idbOrm } from "./idb-orm-NHIZ3oNt.mjs";
2
+ import { IdbAdapter } from "@prisma-next-idb/adapter-idb/runtime";
3
+ import { createIDBRuntimeDriver } from "@prisma-next-idb/driver-idb/runtime";
4
+ import { createIdbRuntime } from "@prisma-next-idb/runtime-idb/runtime";
5
+ import { idbCodecLookup } from "@prisma-next-idb/target-idb/runtime";
6
+ //#region src/core/idb-client.ts
7
+ /**
8
+ * Creates a typed IDB client from a contract and a database name.
9
+ *
10
+ * Assembles the full runtime stack (driver → adapter → runtime → ORM) internally.
11
+ * Equivalent to `postgres({ contract, url })` in `@prisma-next/postgres/runtime`.
12
+ *
13
+ * The IDB database version is not exposed — it is managed by the migration runner
14
+ * per ADR 001. The driver opens at the current database version, which is correct
15
+ * for a runtime that only reads/writes (no DDL).
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { createIdbClient } from '@prisma-next-idb/client-idb/client';
20
+ * import contract from './contract';
21
+ *
22
+ * export const db = createIdbClient({ contract, dbName: 'my-app' });
23
+ *
24
+ * // Later:
25
+ * const users = await db.orm.users.all().toArray();
26
+ * ```
27
+ */
28
+ function createIdbClient(options) {
29
+ const driver = createIDBRuntimeDriver(options.dbName).create();
30
+ const runtime = createIdbRuntime({
31
+ adapter: new IdbAdapter(idbCodecLookup),
32
+ driver,
33
+ contract: options.contract,
34
+ ...options.middleware !== void 0 && options.middleware.length > 0 ? { middleware: options.middleware } : {}
35
+ });
36
+ return {
37
+ orm: idbOrm({
38
+ contract: options.contract,
39
+ executor: runtime
40
+ }),
41
+ withTransaction: (storeNames, fn) => withMutationScope(runtime, storeNames, fn),
42
+ verifyMarker: () => runtime.verifyMarker(),
43
+ async close() {
44
+ await runtime.close();
45
+ },
46
+ [Symbol.asyncDispose]() {
47
+ return this.close();
48
+ }
49
+ };
50
+ }
51
+ //#endregion
52
+ export { createIdbClient as t };
53
+
54
+ //# sourceMappingURL=idb-client-BJ6OoA2W.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idb-client-BJ6OoA2W.mjs","names":[],"sources":["../src/core/idb-client.ts"],"sourcesContent":["import { IdbAdapter } from \"@prisma-next-idb/adapter-idb/runtime\";\nimport { createIDBRuntimeDriver } from \"@prisma-next-idb/driver-idb/runtime\";\nimport type { IdbTransactionScope } from \"@prisma-next-idb/driver-idb/runtime\";\nimport type { IdbMiddleware } from \"@prisma-next-idb/runtime-idb/runtime\";\nimport { createIdbRuntime } from \"@prisma-next-idb/runtime-idb/runtime\";\nimport { idbCodecLookup } from \"@prisma-next-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 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-next/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-next-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(options.dbName).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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAgB,gBACd,SACsB;CACtB,MAAM,SAAS,uBAAuB,QAAQ,MAAM,CAAC,CAAC,OAAO;CAE7D,MAAM,UAAU,iBAAiB;EAC/B,SAAA,IAFkB,WAAW,cAEvB;EACN;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"}
@@ -0,0 +1,52 @@
1
+ import { b as IdbContract, t as IdbOrmClient } from "./idb-orm-B5H6xka-.mjs";
2
+ import { IdbTransactionScope } from "@prisma-next-idb/driver-idb/runtime";
3
+ import { IdbMiddleware } from "@prisma-next-idb/runtime-idb/runtime";
4
+
5
+ //#region src/core/idb-client.d.ts
6
+ interface IdbClientOptions<TContract extends IdbContract> {
7
+ readonly contract: TContract;
8
+ readonly dbName: string;
9
+ readonly middleware?: readonly IdbMiddleware[];
10
+ }
11
+ interface IdbClient<TContract extends IdbContract> {
12
+ readonly orm: IdbOrmClient<TContract>;
13
+ /**
14
+ * Run `fn` inside a single multi-store readwrite IDB transaction.
15
+ *
16
+ * Opens the transaction, passes an `IdbTransactionScope` to `fn`, then
17
+ * commits on success or rolls back on error. Equivalent to calling
18
+ * `withMutationScope(runtime, storeNames, fn)`.
19
+ *
20
+ * Useful from test harnesses and from any caller that has an `IdbClient`
21
+ * but not the raw runtime reference.
22
+ */
23
+ withTransaction<T>(storeNames: string[], fn: (scope: IdbTransactionScope) => Promise<T>): Promise<T>;
24
+ verifyMarker(): Promise<boolean>;
25
+ close(): Promise<void>;
26
+ [Symbol.asyncDispose](): Promise<void>;
27
+ }
28
+ /**
29
+ * Creates a typed IDB client from a contract and a database name.
30
+ *
31
+ * Assembles the full runtime stack (driver → adapter → runtime → ORM) internally.
32
+ * Equivalent to `postgres({ contract, url })` in `@prisma-next/postgres/runtime`.
33
+ *
34
+ * The IDB database version is not exposed — it is managed by the migration runner
35
+ * per ADR 001. The driver opens at the current database version, which is correct
36
+ * for a runtime that only reads/writes (no DDL).
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { createIdbClient } from '@prisma-next-idb/client-idb/client';
41
+ * import contract from './contract';
42
+ *
43
+ * export const db = createIdbClient({ contract, dbName: 'my-app' });
44
+ *
45
+ * // Later:
46
+ * const users = await db.orm.users.all().toArray();
47
+ * ```
48
+ */
49
+ declare function createIdbClient<TContract extends IdbContract>(options: IdbClientOptions<TContract>): IdbClient<TContract>;
50
+ //#endregion
51
+ export { IdbClientOptions as n, createIdbClient as r, IdbClient as t };
52
+ //# sourceMappingURL=idb-client-CiyW5_TQ.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idb-client-CiyW5_TQ.d.mts","names":[],"sources":["../src/core/idb-client.ts"],"mappings":";;;;;UAWiB,gBAAA,mBAAmC,WAAA;EAAA,SACzC,QAAA,EAAU,SAAA;EAAA,SACV,MAAA;EAAA,SAEA,UAAA,YAAsB,aAAA;AAAA;AAAA,UAGhB,SAAA,mBAA4B,WAAA;EAAA,SAClC,GAAA,EAAK,YAAA,CAAa,SAAA;EAJiB;;;;;;;;;;EAe5C,eAAA,IAAmB,UAAA,YAAsB,EAAA,GAAK,KAAA,EAAO,mBAAA,KAAwB,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;EAClG,YAAA,IAAgB,OAAA;EAChB,KAAA,IAAS,OAAA;EAAA,CACR,MAAA,CAAO,YAAA,KAAiB,OAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;iBAwBX,eAAA,mBAAkC,WAAA,EAChD,OAAA,EAAS,gBAAA,CAAiB,SAAA,IACzB,SAAA,CAAU,SAAA"}