@prisma-idb/family-idb 0.0.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -16
- package/dist/bin/prisma-idb.mjs +1 -1
- package/dist/{cli-Dlaym_DD.mjs → cli-Hf4zwIew.mjs} +4 -4
- package/dist/cli-Hf4zwIew.mjs.map +1 -0
- package/dist/exports/cli.mjs +1 -1
- package/dist/exports/contract-psl.d.mts +1 -1
- package/dist/exports/contract-psl.mjs +1 -1
- package/dist/exports/contract-ts.d.mts +6 -4
- package/dist/exports/contract-ts.d.mts.map +1 -1
- package/dist/exports/contract-ts.mjs +31 -14
- package/dist/exports/contract-ts.mjs.map +1 -1
- package/dist/exports/control.d.mts +11 -3
- package/dist/exports/control.d.mts.map +1 -1
- package/dist/exports/control.mjs +6 -5
- package/dist/exports/control.mjs.map +1 -1
- package/dist/{migration-plan-BWhd8BdV.mjs → migration-plan-BwakybVZ.mjs} +8 -2
- package/dist/migration-plan-BwakybVZ.mjs.map +1 -0
- package/dist/{psl-interpreter-C6Q4okuO.mjs → psl-interpreter-BBwZIGC3.mjs} +62 -44
- package/dist/psl-interpreter-BBwZIGC3.mjs.map +1 -0
- package/dist/{psl-interpreter-C0suaXu2.d.mts → psl-interpreter-Vi1cBmhb.d.mts} +5 -3
- package/dist/psl-interpreter-Vi1cBmhb.d.mts.map +1 -0
- package/dist/{validate-BLnItEKs.mjs → validate-dwKE5_5B.mjs} +16 -3
- package/dist/validate-dwKE5_5B.mjs.map +1 -0
- package/package.json +5 -5
- package/dist/cli-Dlaym_DD.mjs.map +0 -1
- package/dist/migration-plan-BWhd8BdV.mjs.map +0 -1
- package/dist/psl-interpreter-C0suaXu2.d.mts.map +0 -1
- package/dist/psl-interpreter-C6Q4okuO.mjs.map +0 -1
- package/dist/validate-BLnItEKs.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
# `@prisma-idb/family-idb`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The IndexedDB family for Prisma 8, used at build time. It reads your Prisma schema into an IndexedDB contract, plugs IndexedDB into the `prisma` CLI, and provides the `prisma-idb` CLI for migrations.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install --save-dev @prisma-idb/family-idb
|
|
7
|
+
```
|
|
6
8
|
|
|
7
|
-
##
|
|
9
|
+
## The `prisma-idb` CLI
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
family-idb ← you are here (CLI / config / entry point)
|
|
11
|
-
client-idb (ORM query builder)
|
|
12
|
-
runtime-idb (RuntimeCore)
|
|
13
|
-
adapter-idb (query AST → IDB plan)
|
|
14
|
-
driver-idb (window.indexedDB wrapper)
|
|
15
|
-
target-idb (identity + migrations)
|
|
16
|
-
```
|
|
11
|
+
IndexedDB only exists in the browser, so three migration jobs need their own commands:
|
|
17
12
|
|
|
18
|
-
|
|
13
|
+
| Command | Does |
|
|
14
|
+
| ------------------------------------- | ------------------------------------------------------------------------------- |
|
|
15
|
+
| `prisma-idb migration plan` | Plans the next migration from the newest one on disk. Warns if it deletes data. |
|
|
16
|
+
| `prisma-idb migration contract-space` | Bundles the migrations into a module the browser imports. |
|
|
17
|
+
| `prisma-idb migration preflight` | Applies every migration to an in-memory IndexedDB to check that it runs. |
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
All three read `prisma.config.ts`. See [Migrations](https://prisma-idb.dev/docs/prisma-8/migrations).
|
|
20
|
+
|
|
21
|
+
## Entry points
|
|
22
|
+
|
|
23
|
+
| Import | Contains |
|
|
24
|
+
| ------------------------------------- | -------------------------------------------------------------------- |
|
|
25
|
+
| `@prisma-idb/family-idb/control` | The family descriptor for `prisma.config.ts`. |
|
|
26
|
+
| `@prisma-idb/family-idb/config-types` | `defineConfig` for `prisma.config.ts`. |
|
|
27
|
+
| `@prisma-idb/family-idb/contract-psl` | `prismaIdbContract(path, { projection })`: reads a `.prisma` schema. |
|
|
28
|
+
| `@prisma-idb/family-idb/contract-ts` | `defineContract()`: write a contract in TypeScript instead. |
|
|
29
|
+
| `@prisma-idb/family-idb/cli` | The `prisma-idb` commands, for embedding in another CLI. |
|
|
23
30
|
|
|
24
31
|
## Documentation
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
- [Quick Start](https://prisma-idb.dev/docs/prisma-8/getting-started)
|
|
34
|
+
- [Schema & Config](https://prisma-idb.dev/docs/prisma-8/schema)
|
|
35
|
+
- [Migrations](https://prisma-idb.dev/docs/prisma-8/migrations)
|
|
27
36
|
|
|
28
37
|
## License
|
|
29
38
|
|
package/dist/bin/prisma-idb.mjs
CHANGED
|
@@ -196,7 +196,7 @@ const commands = {
|
|
|
196
196
|
placeholder: "slug"
|
|
197
197
|
}),
|
|
198
198
|
space: flag.string({
|
|
199
|
-
brief: "Contract-space id (default: \"app\").
|
|
199
|
+
brief: "Contract-space id (default: \"app\"). Only extension authors set this.",
|
|
200
200
|
placeholder: "id"
|
|
201
201
|
}),
|
|
202
202
|
contract: flag.string({
|
|
@@ -211,7 +211,7 @@ const commands = {
|
|
|
211
211
|
handler: async (args, ctx) => {
|
|
212
212
|
const contractPath = requireContractPath(ctx.config, ctx.cwd, args.flags.contract);
|
|
213
213
|
const migrationsDir = resolveMigrationsDir(ctx.config, ctx.cwd, args.flags.migrationsDir);
|
|
214
|
-
const { migrationPlan } = await import("./migration-plan-
|
|
214
|
+
const { migrationPlan } = await import("./migration-plan-BwakybVZ.mjs");
|
|
215
215
|
const sink = createCollectingSink();
|
|
216
216
|
const exitCode = await migrationPlan({
|
|
217
217
|
migrationsDir,
|
|
@@ -265,7 +265,7 @@ const idbCommandFamily = defineCommandFamily({
|
|
|
265
265
|
});
|
|
266
266
|
//#endregion
|
|
267
267
|
//#region package.json
|
|
268
|
-
var version = "0.
|
|
268
|
+
var version = "0.7.0";
|
|
269
269
|
//#endregion
|
|
270
270
|
//#region src/cli/cli.ts
|
|
271
271
|
const BIN_NAME = "prisma-idb";
|
|
@@ -339,4 +339,4 @@ async function runIdbCli(proc) {
|
|
|
339
339
|
//#endregion
|
|
340
340
|
export { idbCommandFamily as a, runIdbCli as i, BIN_NAME as n, createIdbCli as r, BIN_GROUPS as t };
|
|
341
341
|
|
|
342
|
-
//# sourceMappingURL=cli-
|
|
342
|
+
//# sourceMappingURL=cli-Hf4zwIew.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-Hf4zwIew.mjs","names":["packageJson.version"],"sources":["../src/cli/collecting-sink.ts","../src/cli/define-command.ts","../src/cli/migration/paths.ts","../src/cli/migration/contract-space.ts","../src/cli/migration/plan.ts","../src/cli/migration/preflight.ts","../src/cli/family.ts","../package.json","../src/cli/cli.ts"],"sourcesContent":["import type { Presentations } from \"@prisma/cli-engine\";\n\n/**\n * Collects a wrapped core function's `out`/`err` sink writes so a command\n * handler can present them through the engine instead of writing to the\n * real `process.stdout`/`process.stderr` directly (which would corrupt\n * `--json` mode and be invisible to `createTestCli`'s in-memory harness).\n */\nexport interface CollectingSink {\n readonly out: (line: string) => void;\n readonly err: (line: string) => void;\n /** Every line written to either sink, in order, newline-split. */\n lines(): readonly string[];\n /**\n * `lines()` joined back into text — the full collected log (progress\n * AND error detail, not just the `err`-only parts), for use as a\n * `CliStructuredError`'s `summary` on a nonzero exit. A wrapped function\n * writes its error explanation to `err` only after already writing\n * per-step progress to `out` (e.g. preflight's `\"<dir> … \"` / `\"FAILED\"`\n * pair) — using only the `err` writes would silently drop that context.\n */\n fullText(): string;\n}\n\n/**\n * Buffers writes the way a real stream does: a write with no trailing\n * newline (e.g. the wrapped functions' `out(\" ${dirName} … \")` followed\n * later by a separate `out(\"ok\\n\")` call) stays pending and joins with\n * whatever arrives next, rather than becoming its own line. Splitting each\n * `out`/`err` call independently — the first cut of this — silently broke\n * exactly this pattern in `preflight.ts`'s per-package progress line.\n */\nexport function createCollectingSink(): CollectingSink {\n const completedLines: string[] = [];\n let buffer = \"\";\n\n function push(text: string): void {\n buffer += text;\n const parts = buffer.split(\"\\n\");\n buffer = parts.pop() ?? \"\";\n for (const part of parts) {\n if (part.length > 0) completedLines.push(part);\n }\n }\n\n const lines = (): readonly string[] => (buffer.length > 0 ? [...completedLines, buffer] : completedLines);\n\n return {\n out: (text) => push(text),\n err: (text) => push(text),\n lines,\n fullText: () => lines().join(\"\\n\").trim(),\n };\n}\n\n/**\n * Renders collected lines as a `drawing` block (verbatim, no reflow) in\n * human mode; `stdout` stays empty since these commands carry no separate\n * machine-pipeable data channel beyond the human log itself.\n */\nexport function presentationsFromSink(sink: CollectingSink, exitCode: number): Presentations {\n const lines = sink.lines();\n return {\n human: () => (lines.length === 0 ? [] : [{ kind: \"drawing\", lines }]),\n stdout: () => [],\n json: () => ({ exitCode, lines }),\n next: () => [],\n };\n}\n","import type { PrismaNextConfig } from \"@prisma/orm-toolchain/config-loader\";\nimport { finalizeConfig } from \"@prisma/orm-toolchain/config-loader\";\nimport type {\n ArgsSpec,\n CommandDefinition,\n FlagSpec,\n Handler,\n HelpSpec,\n PositionalSpec,\n SpawnDeclarations,\n} from \"@prisma/cli-engine\";\nimport { defineCommand } from \"@prisma/cli-engine\";\nimport { CliStructuredError, notOk } from \"@prisma/cli-engine/protocol\";\nimport { ormConfigSection } from \"./config-section\";\n\nfunction normalizeError(error: unknown): CliStructuredError {\n if (CliStructuredError.is(error)) return error;\n return new CliStructuredError(\"IDB-CLI.UNEXPECTED_ERROR\", error instanceof Error ? error.message : String(error));\n}\n\nfunction finalizedConfigContext<TCtx extends { readonly cwd: string; readonly config: unknown }>(ctx: TCtx): TCtx {\n if (ctx.config === undefined) return ctx;\n return { ...ctx, config: finalizeConfig(ctx.config as PrismaNextConfig, ctx.cwd) };\n}\n\n/**\n * These 3 commands are IDB-specific (fake-indexeddb, IdbMigrationPlanner) —\n * pointing them at a non-IDB `prisma.config.ts` would fail confusingly deep\n * inside that logic rather than with a clear, actionable error. `orm`'s own\n * config section is family-agnostic by design (ADR 150), so this guard is\n * this shell's responsibility, not the section's.\n */\nfunction requireIdbFamily(config: PrismaNextConfig): void {\n const familyId = config.family?.familyId;\n if (familyId !== \"idb\") {\n throw new CliStructuredError(\n \"IDB-CLI.FAMILY_MISMATCH\",\n `config family is \"${familyId ?? \"unknown\"}\", expected \"idb\".`,\n {\n why: \"This CLI's migration commands operate on IDB-family projects only.\",\n nextActions: [\n {\n kind: \"edit-file\",\n label:\n \"Point --config at your browser-side prisma.config.ts (the one using @prisma-idb/family-idb/control)\",\n },\n ],\n }\n );\n }\n}\n\n/**\n * `defineCommand` pre-bound to the `orm` config section (see\n * `config-section.ts`) with a uniform error boundary and path\n * finalization, mirroring `@prisma/orm-toolchain`'s own `defineOrmCommand`\n * (`vendor/prisma/packages/1-framework/3-tooling/cli/src/orm/define-command.ts`):\n * the engine's config loader hands a command `contract.output`/\n * `migrations.dir` exactly as authored (usually relative); finalizing here,\n * once, means handlers always read absolute paths.\n */\nexport function defineIdbCommand<\n TFlags extends Record<string, FlagSpec<unknown>> = Record<never, FlagSpec<unknown>>,\n TPositionals extends Record<string, PositionalSpec<unknown>> = Record<never, PositionalSpec<unknown>>,\n TCode extends number = never,\n>(\n def: {\n readonly help: HelpSpec;\n readonly args?: ArgsSpec<TFlags, TPositionals>;\n readonly exitCodes?: Readonly<Record<TCode, string>>;\n readonly handler: Handler<TFlags, TPositionals, PrismaNextConfig, TCode, false, false>;\n } & SpawnDeclarations\n): CommandDefinition<TFlags, TPositionals, PrismaNextConfig, TCode, false, false> {\n return defineCommand<TFlags, TPositionals, PrismaNextConfig, TCode, false, false>({\n ...def,\n needs: { config: ormConfigSection },\n handler: async (args, ctx) => {\n try {\n const finalized = finalizedConfigContext(ctx);\n requireIdbFamily(finalized.config);\n return await def.handler(args, finalized);\n } catch (error) {\n return notOk(normalizeError(error));\n }\n },\n });\n}\n","import type { PrismaNextConfig } from \"@prisma/orm-toolchain/config-loader\";\nimport { CliStructuredError } from \"@prisma/cli-engine/protocol\";\nimport { resolve } from \"pathe\";\n\n/**\n * Resolves the contract/migrations paths a command needs from the already\n * finalized (absolute-paths) `orm` config section, with CLI flag overrides\n * winning when supplied. `ctx.config` arrives finalized by\n * `defineIdbCommand` (see `../define-command.ts`), so `config.contract.output`\n * and `config.migrations.dir` are already absolute — only an explicit\n * `--contract`/`--migrations-dir` flag (relative to `cwd`) needs resolving\n * here.\n */\nexport function requireContractPath(config: PrismaNextConfig, cwd: string, override: string | undefined): string {\n if (override !== undefined) return resolve(cwd, override);\n const output = config.contract?.output;\n if (output === undefined) {\n throw new CliStructuredError(\n \"IDB-CLI.CONTRACT_PATH_MISSING\",\n \"config.contract.output is required to resolve the contract path.\",\n {\n why: \"Neither --contract nor prisma.config.ts's orm.contract.output supplied a path.\",\n nextActions: [\n { kind: \"edit-file\", label: \"Set contract.output in prisma.config.ts, or pass --contract explicitly\" },\n ],\n }\n );\n }\n return output;\n}\n\n/**\n * `finalizeConfig` always resolves `migrations.dir` to an absolute path\n * (defaulting to `<configDir>/migrations` when unset); the fallback here is\n * defensive, not the expected path.\n */\nexport function resolveMigrationsDir(config: PrismaNextConfig, cwd: string, override: string | undefined): string {\n if (override !== undefined) return resolve(cwd, override);\n return config.migrations?.dir ?? resolve(cwd, \"migrations\");\n}\n","import { dirname, join, resolve } from \"pathe\";\nimport { flag } from \"@prisma/cli-engine\";\nimport { CliStructuredError, notOk, ok } from \"@prisma/cli-engine/protocol\";\nimport { createCollectingSink, presentationsFromSink } from \"../collecting-sink\";\nimport { defineIdbCommand } from \"../define-command\";\nimport { requireContractPath, resolveMigrationsDir } from \"./paths\";\n\nexport const migrationContractSpaceCommand = defineIdbCommand({\n help: {\n summary: \"Regenerate contract-space.generated.ts from migrations/app/\",\n description:\n \"Reads every migration package under <migrationsDir>/app/, validates the\\n\" +\n \"chain's connectivity, and emits a generated TypeScript module that bundles\\n\" +\n \"them into a ContractSpace.\",\n examples: [\"migration contract-space\", \"migration contract-space --out src/lib/prisma/contract-space.ts\"],\n },\n args: {\n flags: {\n contract: flag.string({\n brief: \"Path to contract.json (default: orm.contract.output)\",\n placeholder: \"path\",\n }),\n migrationsDir: flag.string({\n brief: \"Path to the migrations root (default: orm.migrations.dir)\",\n placeholder: \"path\",\n }),\n out: flag.string({\n brief: \"Output file path (default: colocated with the resolved contract.json)\",\n placeholder: \"path\",\n }),\n },\n },\n handler: async (args, ctx) => {\n const contractPath = requireContractPath(ctx.config, ctx.cwd, args.flags.contract);\n const migrationsDir = resolveMigrationsDir(ctx.config, ctx.cwd, args.flags.migrationsDir);\n const outPath =\n args.flags.out !== undefined\n ? resolve(ctx.cwd, args.flags.out)\n : join(dirname(contractPath), \"contract-space.generated.ts\");\n\n const { generateContractSpace } = await import(\"../../core/contract-space-codegen\");\n const sink = createCollectingSink();\n const exitCode = await generateContractSpace({\n migrationsDir,\n contractPath,\n outPath,\n out: sink.out,\n err: sink.err,\n });\n\n if (exitCode !== 0) {\n return notOk(\n new CliStructuredError(\"IDB-CLI.CONTRACT_SPACE_FAILED\", sink.fullText() || \"migration contract-space failed.\")\n );\n }\n return ok(ctx.present({ data: { exitCode, outPath } }, presentationsFromSink(sink, exitCode)));\n },\n});\n","import { flag } from \"@prisma/cli-engine\";\nimport { CliStructuredError, notOk, ok } from \"@prisma/cli-engine/protocol\";\nimport { createCollectingSink, presentationsFromSink } from \"../collecting-sink\";\nimport { defineIdbCommand } from \"../define-command\";\nimport { requireContractPath, resolveMigrationsDir } from \"./paths\";\n\nexport const migrationPlanCommand = defineIdbCommand({\n help: {\n summary: \"Plan the next IDB migration package (auto-detects baseline vs. incremental)\",\n description:\n \"Compares the emitted contract against the latest on-disk migration state and\\n\" +\n \"produces a new migration package with the required operations. Offline —\\n\" +\n \"never opens IndexedDB (there is no live database on the Node side to consult).\",\n examples: [\"migration plan\", \"migration plan --name add-posts\", \"migration plan --space idb-sync\"],\n },\n args: {\n flags: {\n name: flag.string({\n brief: 'Directory slug (default: \"baseline\" for a fresh space; required otherwise)',\n placeholder: \"slug\",\n }),\n space: flag.string({\n brief: 'Contract-space id (default: \"app\"). Only extension authors set this.',\n placeholder: \"id\",\n }),\n contract: flag.string({\n brief: \"Path to contract.json (default: orm.contract.output)\",\n placeholder: \"path\",\n }),\n migrationsDir: flag.string({\n brief: \"Path to the migrations root (default: orm.migrations.dir)\",\n placeholder: \"path\",\n }),\n },\n },\n handler: async (args, ctx) => {\n const contractPath = requireContractPath(ctx.config, ctx.cwd, args.flags.contract);\n const migrationsDir = resolveMigrationsDir(ctx.config, ctx.cwd, args.flags.migrationsDir);\n\n const { migrationPlan } = await import(\"../../core/migration-plan\");\n const sink = createCollectingSink();\n const exitCode = await migrationPlan({\n migrationsDir,\n contractPath,\n out: sink.out,\n err: sink.err,\n ...(args.flags.name !== undefined && { name: args.flags.name }),\n ...(args.flags.space !== undefined && { spaceId: args.flags.space }),\n });\n\n if (exitCode === 2) {\n return notOk(\n new CliStructuredError(\n \"IDB-CLI.MIGRATION_NAME_REQUIRED\",\n sink.fullText() || \"--name <slug> is required when generating an incremental migration.\"\n )\n );\n }\n if (exitCode !== 0) {\n return notOk(\n new CliStructuredError(\"IDB-CLI.MIGRATION_PLAN_FAILED\", sink.fullText() || \"migration plan failed.\")\n );\n }\n return ok(ctx.present({ data: { exitCode } }, presentationsFromSink(sink, exitCode)));\n },\n});\n","import { flag } from \"@prisma/cli-engine\";\nimport { CliStructuredError, notOk, ok } from \"@prisma/cli-engine/protocol\";\nimport { createCollectingSink, presentationsFromSink } from \"../collecting-sink\";\nimport { defineIdbCommand } from \"../define-command\";\nimport { resolveMigrationsDir } from \"./paths\";\n\nexport const migrationPreflightCommand = defineIdbCommand({\n help: {\n summary: \"Validate the migration chain against fake-indexeddb\",\n description:\n \"Walks every migration package under <migrationsDir>/app/ in chain order,\\n\" +\n \"applying each package's ops.json against a fresh fake-indexeddb instance.\\n\" +\n \"Catches 'the chain doesn't apply cleanly' — not schema drift.\",\n examples: [\"migration preflight\"],\n },\n args: {\n flags: {\n migrationsDir: flag.string({\n brief: \"Path to the migrations root (default: orm.migrations.dir)\",\n placeholder: \"path\",\n }),\n },\n },\n handler: async (args, ctx) => {\n const migrationsDir = resolveMigrationsDir(ctx.config, ctx.cwd, args.flags.migrationsDir);\n\n const { runPreflight } = await import(\"../../core/preflight\");\n const sink = createCollectingSink();\n const exitCode = await runPreflight({ migrationsDir, out: sink.out, err: sink.err });\n\n if (exitCode !== 0) {\n return notOk(new CliStructuredError(\"IDB-CLI.PREFLIGHT_FAILED\", sink.fullText() || \"Preflight failed.\"));\n }\n return ok(ctx.present({ data: { exitCode } }, presentationsFromSink(sink, exitCode)));\n },\n});\n","import type { AnyCommand } from \"@prisma/cli-engine\";\nimport { defineCommandFamily } from \"@prisma/cli-engine\";\nimport { ormConfigSection } from \"./config-section\";\nimport { migrationContractSpaceCommand } from \"./migration/contract-space\";\nimport { migrationPlanCommand } from \"./migration/plan\";\nimport { migrationPreflightCommand } from \"./migration/preflight\";\n\nconst commands: Readonly<Record<string, AnyCommand>> = {\n \"migration plan\": migrationPlanCommand,\n \"migration contract-space\": migrationContractSpaceCommand,\n \"migration preflight\": migrationPreflightCommand,\n};\n\n/**\n * The unit this package contributes to a `@prisma/cli-engine` shell: its\n * config section (reused from the ORM family, see `config-section.ts`) and\n * its 3 commands. These have no generic equivalent — the real `prisma` CLI's\n * `migration new`/`plan`/etc. work against a live database or a SQL/Mongo\n * on-disk model; ours works against `fake-indexeddb` and a hand-rolled\n * contract-space convention.\n */\nexport const idbCommandFamily = defineCommandFamily({\n configSection: ormConfigSection,\n commands,\n});\n","","import type { Cli, HostProcess, Runtime } from \"@prisma/cli-engine\";\nimport { createCli, loadConfig } from \"@prisma/cli-engine\";\nimport { CliStructuredError } from \"@prisma/cli-engine/protocol\";\nimport packageJson from \"../../package.json\" with { type: \"json\" };\nimport { idbCommandFamily } from \"./family\";\n\nexport const BIN_NAME = \"prisma-idb\";\n\nexport const BIN_GROUPS = {\n migration: {\n brief: \"On-disk migration management commands\",\n description:\n \"Plan, bundle, and validate IDB migration packages. Migrations are\\n\" +\n \"contract-to-contract edges stored as versioned directories under migrations/.\",\n },\n} as const;\n\nexport function createIdbCli(): Cli {\n return createCli({\n name: BIN_NAME,\n version: packageJson.version,\n commandFamilies: [idbCommandFamily],\n groups: BIN_GROUPS,\n commands: idbCommandFamily.commands,\n help: {\n tagline: \"IDB-target tooling for Prisma 8\",\n description: \"Migration authoring for IndexedDB projects — no live database on the Node side to consult.\",\n },\n });\n}\n\n/** Everything environmental the engine is given, adapted from the host process once. */\nexport function runtimeFromProcess(proc: HostProcess): Runtime {\n return {\n stdout: { write: (text) => void proc.stdout.write(text) },\n stderr: { write: (text) => void proc.stderr.write(text) },\n stdin: proc.stdin,\n cwd: proc.cwd(),\n env: proc.env,\n isTty: {\n stdin: proc.stdin.isTTY === true,\n stdout: proc.stdout.isTTY === true,\n stderr: proc.stderr.isTTY === true,\n },\n host: {\n runtime: { name: \"node\", version: proc.version },\n platform: proc.platform,\n arch: proc.arch,\n },\n exit: (code) => proc.exit(code),\n onSignal: (callback) => {\n const onInterrupt = () => callback(\"SIGINT\");\n const onTerminate = () => callback(\"SIGTERM\");\n proc.on(\"SIGINT\", onInterrupt);\n proc.on(\"SIGTERM\", onTerminate);\n return () => {\n proc.off(\"SIGINT\", onInterrupt);\n proc.off(\"SIGTERM\", onTerminate);\n };\n },\n loadConfig: (configPath) => loadConfig(proc.cwd(), configPath),\n // Never reached: no command here declares `credentials`/`managesCredentials`,\n // so ctx.api is never constructed — required on Runtime regardless.\n managementApi: { baseUrl: \"https://api.prisma.io\" },\n };\n}\n\nconst STARTUP_FAILURE_EXIT_CODE = 1;\n\nfunction reportStartupFailure(proc: HostProcess, error: unknown): number {\n const normalized = CliStructuredError.is(error)\n ? error\n : new CliStructuredError(\"IDB-CLI.STARTUP_FAILURE\", error instanceof Error ? error.message : String(error));\n proc.stderr.write(`✘ [${normalized.code}] ${normalized.message}\\n`);\n return STARTUP_FAILURE_EXIT_CODE;\n}\n\n/** Parses, executes and settles one invocation; returns the exit code. */\nexport async function runIdbCli(proc: HostProcess): Promise<number> {\n try {\n return await createIdbCli().run(proc.argv.slice(2), runtimeFromProcess(proc));\n } catch (error) {\n return reportStartupFailure(proc, error);\n }\n}\n"],"mappings":"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"}
|
package/dist/exports/cli.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as idbCommandFamily, i as runIdbCli, n as BIN_NAME, r as createIdbCli, t as BIN_GROUPS } from "../cli-
|
|
1
|
+
import { a as idbCommandFamily, i as runIdbCli, n as BIN_NAME, r as createIdbCli, t as BIN_GROUPS } from "../cli-Hf4zwIew.mjs";
|
|
2
2
|
export { BIN_GROUPS, BIN_NAME, createIdbCli, idbCommandFamily, runIdbCli };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as InterpretPslOptions, r as interpretPslDocumentToIdbContract, t as ContractProjection } from "../psl-interpreter-
|
|
1
|
+
import { n as InterpretPslOptions, r as interpretPslDocumentToIdbContract, t as ContractProjection } from "../psl-interpreter-Vi1cBmhb.mjs";
|
|
2
2
|
import { ContractConfig } from "@prisma/orm-framework/config/config-types";
|
|
3
3
|
//#region src/core/psl-provider.d.ts
|
|
4
4
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as interpretPslDocumentToIdbContract } from "../psl-interpreter-
|
|
1
|
+
import { t as interpretPslDocumentToIdbContract } from "../psl-interpreter-BBwZIGC3.mjs";
|
|
2
2
|
import { notOk } from "@prisma/orm-framework/utils/result";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { buildSymbolTable, rangeToPslSpan } from "@prisma/orm-framework/psl-parser";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as ContractProjection } from "../psl-interpreter-
|
|
1
|
+
import { t as ContractProjection } from "../psl-interpreter-Vi1cBmhb.mjs";
|
|
2
|
+
import { IdbKeyPath, IdbReferentialAction, IdbStorage } from "@prisma-idb/target-idb/pack";
|
|
2
3
|
import { Contract } from "@prisma/orm-framework/contract/types";
|
|
3
|
-
import { IdbReferentialAction, IdbStorage } from "@prisma-idb/target-idb/pack";
|
|
4
4
|
//#region src/core/contract-builder.d.ts
|
|
5
5
|
type PrismaScalarType = "String" | "Int" | "Float" | "Boolean" | "DateTime" | "BigInt" | "Decimal" | "Json" | "Bytes";
|
|
6
6
|
/**
|
|
@@ -19,13 +19,15 @@ type RelationDef = {
|
|
|
19
19
|
readonly onUpdate?: IdbReferentialAction;
|
|
20
20
|
};
|
|
21
21
|
type IndexDef = {
|
|
22
|
-
|
|
22
|
+
/** A single field name, or an ordered list of field names for a compound (multi-field) index. */
|
|
23
|
+
readonly keyPath: IdbKeyPath;
|
|
23
24
|
readonly unique?: boolean;
|
|
24
25
|
readonly multiEntry?: boolean;
|
|
25
26
|
};
|
|
26
27
|
type ModelDef = {
|
|
27
28
|
readonly store: string;
|
|
28
|
-
|
|
29
|
+
/** The primary key field, or an ordered list of fields for a compound primary key. */
|
|
30
|
+
readonly key: IdbKeyPath;
|
|
29
31
|
/** All scalar fields on the model. Use `"Type"` for non-nullable, `"Type?"` for nullable. */
|
|
30
32
|
readonly fields: Record<string, FieldSpec>;
|
|
31
33
|
readonly indexes?: Record<string, IndexDef>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-ts.d.mts","names":[],"sources":["../../src/core/contract-builder.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"contract-ts.d.mts","names":[],"sources":["../../src/core/contract-builder.ts"],"mappings":";;;;KAkBK;;;;;KAMO,YAAY,sBAAsB;KAgBlC;WACD;WACA;WACA;aACE;aACA;;WAEF,WAAW;WACX,WAAW;;KAGV;;WAED,SAAS;WACT;WACA;;KAGC;WACD;;WAEA,KAAK;;WAEL,QAAQ,eAAe;WACvB,UAAU,eAAe;WACzB,YAAY,eAAe;;;;;;;;;;;;;WAa3B,gBAAgB;;WAEhB;;WAEA;;KAGC;;WAED;aAAmB;aAA0B;;;WAE7C;aAAmB;aAA2B;;WAC9C,QAAQ,eAAe;;KAGtB;;WAED,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmWR,eAAe,OAAO,qBAAqB,UAAU,wBAAwB,SAAS"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { t as validateContract } from "../validate-
|
|
2
|
-
import { i as warnDroppedRelation, n as isValidIdbKeyCodec, r as literalValueMatchesCodec } from "../psl-interpreter-
|
|
1
|
+
import { t as validateContract } from "../validate-dwKE5_5B.mjs";
|
|
2
|
+
import { i as warnDroppedRelation, n as isValidIdbKeyCodec, r as literalValueMatchesCodec } from "../psl-interpreter-BBwZIGC3.mjs";
|
|
3
|
+
import { keyPathFields } from "@prisma-idb/target-idb/pack";
|
|
3
4
|
import { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from "@prisma/orm-framework/contract/types";
|
|
4
5
|
import { computeProfileHash, computeStorageHash } from "@prisma/orm-framework/contract/hashing";
|
|
5
6
|
//#region src/core/contract-builder.ts
|
|
@@ -28,12 +29,17 @@ function projectModelsForClient(models) {
|
|
|
28
29
|
for (const [modelName, def] of Object.entries(models)) {
|
|
29
30
|
if (excludedModelNames.has(modelName)) continue;
|
|
30
31
|
const excludedFields = new Set(def.excludeFields ?? []);
|
|
31
|
-
|
|
32
|
+
const keyFields = keyPathFields(def.key);
|
|
33
|
+
const excludedKeyField = keyFields.find((f) => excludedFields.has(f));
|
|
34
|
+
if (excludedKeyField !== void 0) throw new Error(`defineContract: model "${modelName}" excludes its own key field "${excludedKeyField}" — the client contract needs a primary key for every included model.`);
|
|
32
35
|
for (const excludedField of excludedFields) {
|
|
33
|
-
if (excludedField
|
|
36
|
+
if (keyFields.includes(excludedField)) continue;
|
|
34
37
|
if (!(excludedField in def.fields)) throw new Error(`defineContract: model "${modelName}" excludeFields references unknown field "${excludedField}" — it is not declared in "fields".`);
|
|
35
38
|
}
|
|
36
|
-
for (const [indexName, idx] of Object.entries(def.indexes ?? {}))
|
|
39
|
+
for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {
|
|
40
|
+
const excludedIndexField = keyPathFields(idx.keyPath).find((f) => excludedFields.has(f));
|
|
41
|
+
if (excludedIndexField !== void 0) throw new Error(`defineContract: model "${modelName}" index "${indexName}" references excluded field "${excludedIndexField}". Remove the index or the exclusion.`);
|
|
42
|
+
}
|
|
37
43
|
const relations = {};
|
|
38
44
|
for (const [relName, rel] of Object.entries(def.relations ?? {})) {
|
|
39
45
|
if (excludedModelNames.has(rel.to)) {
|
|
@@ -77,16 +83,27 @@ function resolveFieldCodecId(def, fieldName) {
|
|
|
77
83
|
* array — the one case this file can't statically validate either way).
|
|
78
84
|
*/
|
|
79
85
|
function validateModelKeyAndIndexes(modelName, def) {
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
const keyFields = keyPathFields(def.key);
|
|
87
|
+
if (keyFields.length === 0) throw new Error(`defineContract: model "${modelName}" "key" must name at least one field.`);
|
|
88
|
+
if (new Set(keyFields).size !== keyFields.length) throw new Error(`defineContract: model "${modelName}" "key" [${keyFields.join(", ")}] repeats a field name.`);
|
|
89
|
+
for (const keyField of keyFields) {
|
|
90
|
+
if (!(keyField in def.fields)) throw new Error(`defineContract: model "${modelName}" key field "${keyField}" is not declared in "fields".`);
|
|
91
|
+
if (def.fields[keyField]?.endsWith("?")) throw new Error(`defineContract: model "${modelName}" key field "${keyField}" is nullable ("${def.fields[keyField]}") — the primary key cannot be nullable.`);
|
|
92
|
+
const keyCodec = resolveFieldCodecId(def, keyField);
|
|
93
|
+
if (keyCodec !== void 0 && !isValidIdbKeyCodec(keyCodec)) throw new Error(`defineContract: model "${modelName}" key field "${keyField}" has type "${keyCodec}", which IndexedDB cannot use as a key. Every write would throw (DataError extracting the primary key). Use String, Int, Float, DateTime, Decimal, or Bytes instead.`);
|
|
94
|
+
}
|
|
84
95
|
for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {
|
|
85
|
-
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
96
|
+
const indexFields = keyPathFields(idx.keyPath);
|
|
97
|
+
if (indexFields.length === 0) throw new Error(`defineContract: model "${modelName}" index "${indexName}" "keyPath" must name at least one field.`);
|
|
98
|
+
if (new Set(indexFields).size !== indexFields.length) throw new Error(`defineContract: model "${modelName}" index "${indexName}" "keyPath" [${indexFields.join(", ")}] repeats a field name.`);
|
|
99
|
+
if (idx.multiEntry && indexFields.length > 1) throw new Error(`defineContract: model "${modelName}" index "${indexName}" combines "multiEntry: true" with a compound "keyPath" [${indexFields.join(", ")}] — IndexedDB rejects this combination (InvalidAccessError). multiEntry only applies to a single array-valued field.`);
|
|
100
|
+
for (const indexField of indexFields) {
|
|
101
|
+
if (!(indexField in def.fields)) throw new Error(`defineContract: model "${modelName}" index "${indexName}" references field "${indexField}", which is not declared in "fields".`);
|
|
102
|
+
if (idx.multiEntry) continue;
|
|
103
|
+
const fieldCodec = resolveFieldCodecId(def, indexField);
|
|
104
|
+
if (fieldCodec === void 0 || isValidIdbKeyCodec(fieldCodec)) continue;
|
|
105
|
+
throw new Error(`defineContract: model "${modelName}" index "${indexName}" is keyed on "${indexField}" (type "${fieldCodec}"), which IndexedDB cannot use as an index key. Records are silently omitted from the index on write, and any query against it throws at runtime. Use String, Int, Float, DateTime, Decimal, or Bytes instead.`);
|
|
106
|
+
}
|
|
90
107
|
}
|
|
91
108
|
for (const [fieldName, value] of Object.entries(def.fieldDefaults ?? {})) {
|
|
92
109
|
if (!(fieldName in def.fields)) throw new Error(`defineContract: model "${modelName}" fieldDefaults references field "${fieldName}", which is not declared in "fields".`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-ts.mjs","names":[],"sources":["../../src/core/contract-builder.ts"],"sourcesContent":["import { computeProfileHash, computeStorageHash } from \"@prisma/orm-framework/contract/hashing\";\nimport type { ApplicationDomain, Contract, ContractField, CrossReference } from \"@prisma/orm-framework/contract/types\";\nimport { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from \"@prisma/orm-framework/contract/types\";\nimport type {\n IdbIndexDefinition,\n IdbModelStorage,\n IdbReferentialAction,\n IdbStorage,\n IdbStoreDefinition,\n} from \"@prisma-idb/target-idb/pack\";\nimport type { ContractProjection } from \"./psl-interpreter\";\nimport { isValidIdbKeyCodec, literalValueMatchesCodec, warnDroppedRelation } from \"./psl-interpreter\";\nimport { validateContract } from \"./validate\";\n\n// ── Field type system ─────────────────────────────────────────────────────────\n\ntype PrismaScalarType = \"String\" | \"Int\" | \"Float\" | \"Boolean\" | \"DateTime\" | \"BigInt\" | \"Decimal\" | \"Json\" | \"Bytes\";\n\n/**\n * A field spec string: the Prisma scalar type name, optionally suffixed with\n * `?` to indicate the field is nullable (e.g. `\"String\"`, `\"Int?\"`, `\"DateTime?\"`).\n */\nexport type FieldSpec = PrismaScalarType | `${PrismaScalarType}?`;\n\nconst SCALAR_TO_CODEC_ID: Record<PrismaScalarType, string> = {\n String: \"idb/string@1\",\n Int: \"idb/int32@1\",\n Float: \"idb/double@1\",\n Boolean: \"idb/bool@1\",\n DateTime: \"idb/date@1\",\n BigInt: \"idb/bigint@1\",\n Decimal: \"idb/decimal@1\",\n Json: \"idb/json@1\",\n Bytes: \"idb/bytes@1\",\n};\n\n// ── Input types ───────────────────────────────────────────────────────────────\n\nexport type RelationDef = {\n readonly to: string;\n readonly cardinality: \"1:1\" | \"1:N\" | \"N:1\";\n readonly on: {\n readonly local: readonly string[];\n readonly target: readonly string[];\n };\n readonly onDelete?: IdbReferentialAction;\n readonly onUpdate?: IdbReferentialAction;\n};\n\nexport type IndexDef = {\n readonly keyPath: string;\n readonly unique?: boolean;\n readonly multiEntry?: boolean;\n};\n\nexport type ModelDef = {\n readonly store: string;\n readonly key: string;\n /** All scalar fields on the model. Use `\"Type\"` for non-nullable, `\"Type?\"` for nullable. */\n readonly fields: Record<string, FieldSpec>;\n readonly indexes?: Record<string, IndexDef>;\n readonly relations?: Record<string, RelationDef>;\n /**\n * Literal default values, keyed by field name — feeds the `setDefault`\n * referential action (a child's FK field is reset to its own declared\n * default when its parent is deleted/updated with `onDelete`/`onUpdate:\n * \"setDefault\"`). A sibling map to `fields`, deliberately not folded into\n * `FieldSpec` (a bare type string) to avoid widening that type.\n *\n * Does NOT feed `create()`'s default-filling — the TS-DSL authoring path\n * has no `execution.mutations.defaults` support at all yet (a separate,\n * larger, pre-existing gap). Use the PSL authoring path's `@default(...)`\n * if you need create-time defaults; this map only backs `setDefault`.\n */\n readonly fieldDefaults?: Record<string, string | number | boolean>;\n /** Server-only model — dropped when `defineContract` runs with `{ projection: \"client\" }`. See ADR 012. */\n readonly exclude?: boolean;\n /** Server-only fields on an otherwise-synced model — dropped in client projection. See ADR 012. */\n readonly excludeFields?: readonly string[];\n};\n\nexport type DefineContractInput = {\n /** Pass the default export of `@prisma-idb/family-idb/pack`. */\n readonly family: { readonly familyId: \"idb\"; readonly id: string };\n /** Pass the default export of `@prisma-idb/target-idb/pack`. */\n readonly target: { readonly targetId: string; readonly id: string };\n readonly models: Record<string, ModelDef>;\n};\n\nexport type DefineContractOptions = {\n /** @default \"full\" */\n readonly projection?: ContractProjection;\n};\n\n/**\n * Drops `exclude: true` models and `excludeFields` entries for the client\n * projection (ADR 012). Any relation a survivor still has to an excluded\n * model is dropped too (ADR 013) — for every cardinality, required or not;\n * the model itself is never excluded as a result (see ADR 013 §\"Why we\n * don't cascade on requiredness\"). The underlying FK scalar field (if any)\n * is kept, orphaned but inert.\n */\nfunction projectModelsForClient(models: Record<string, ModelDef>): Record<string, ModelDef> {\n const excludedModelNames = new Set(\n Object.entries(models)\n .filter(([, def]) => def.exclude === true)\n .map(([name]) => name)\n );\n\n const result: Record<string, ModelDef> = {};\n\n for (const [modelName, def] of Object.entries(models)) {\n if (excludedModelNames.has(modelName)) continue;\n\n const excludedFields = new Set(def.excludeFields ?? []);\n\n if (excludedFields.has(def.key)) {\n throw new Error(\n `defineContract: model \"${modelName}\" excludes its own key field \"${def.key}\" — the client contract needs a primary key for every included model.`\n );\n }\n\n for (const excludedField of excludedFields) {\n if (excludedField === def.key) continue;\n if (!(excludedField in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" excludeFields references unknown field \"${excludedField}\" — it is not declared in \"fields\".`\n );\n }\n }\n\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n if (excludedFields.has(idx.keyPath)) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" references excluded field \"${idx.keyPath}\". Remove the index or the exclusion.`\n );\n }\n }\n\n const relations: Record<string, RelationDef> = {};\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n if (excludedModelNames.has(rel.to)) {\n warnDroppedRelation(modelName, relName, rel.to);\n continue;\n }\n const excludedLocalField = rel.on.local.find((f) => excludedFields.has(f));\n if (excludedLocalField !== undefined) {\n throw new Error(\n `defineContract: model \"${modelName}\" field \"${excludedLocalField}\" backs relation \"${relName}\" and cannot be excluded independently — field-level FK exclusion isn't supported (ADR 013 only handles relations pointing at a whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`\n );\n }\n const targetExcludedFields = new Set(models[rel.to]?.excludeFields ?? []);\n const excludedTargetField = rel.on.target.find((f) => targetExcludedFields.has(f));\n if (excludedTargetField !== undefined) {\n throw new Error(\n `defineContract: model \"${modelName}\" relation \"${relName}\" references excluded field \"${rel.to}.${excludedTargetField}\" — field-level FK exclusion isn't supported (ADR 013 only handles relations pointing at a whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`\n );\n }\n relations[relName] = rel;\n }\n\n const fields: Record<string, FieldSpec> = {};\n for (const [fieldName, spec] of Object.entries(def.fields)) {\n if (excludedFields.has(fieldName)) continue;\n fields[fieldName] = spec;\n }\n\n result[modelName] = { ...def, fields, relations };\n }\n\n return result;\n}\n\n// ── Validate key/index field types against IDB's valid-key algorithm ──────────\n\nfunction resolveFieldCodecId(def: ModelDef, fieldName: string): string | undefined {\n const spec = def.fields[fieldName];\n if (spec === undefined) return undefined;\n const typeName = (spec.endsWith(\"?\") ? spec.slice(0, -1) : spec) as PrismaScalarType;\n return SCALAR_TO_CODEC_ID[typeName];\n}\n\n/**\n * Mirrors `psl-interpreter.ts`'s `IDB_INVALID_KEY_TYPE`/`IDB_INVALID_INDEX_KEY_TYPE`\n * diagnostics for the TS authoring surface, which has no diagnostics array —\n * this DSL fails fast via `throw`, matching every other check in this file.\n *\n * `multiEntry` indexes are skipped: their key-validity depends on the\n * *elements* of an array value, which this scalar-type system can't express\n * (a `multiEntry` index only makes sense over a `Json`-typed field holding an\n * array — the one case this file can't statically validate either way).\n */\nfunction validateModelKeyAndIndexes(modelName: string, def: ModelDef): void {\n if (!(def.key in def.fields)) {\n throw new Error(`defineContract: model \"${modelName}\" key field \"${def.key}\" is not declared in \"fields\".`);\n }\n if (def.fields[def.key]?.endsWith(\"?\")) {\n throw new Error(\n `defineContract: model \"${modelName}\" key field \"${def.key}\" is nullable (\"${def.fields[def.key]}\") — the primary key cannot be nullable.`\n );\n }\n const keyCodec = resolveFieldCodecId(def, def.key);\n if (keyCodec !== undefined && !isValidIdbKeyCodec(keyCodec)) {\n throw new Error(\n `defineContract: model \"${modelName}\" key field \"${def.key}\" has type \"${keyCodec}\", which IndexedDB cannot use as a key. Every write would throw (DataError extracting the primary key). Use String, Int, Float, DateTime, Decimal, or Bytes instead.`\n );\n }\n\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n if (!(idx.keyPath in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" references field \"${idx.keyPath}\", which is not declared in \"fields\".`\n );\n }\n if (idx.multiEntry) continue;\n const fieldCodec = resolveFieldCodecId(def, idx.keyPath);\n if (fieldCodec === undefined || isValidIdbKeyCodec(fieldCodec)) continue;\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" is keyed on \"${idx.keyPath}\" (type \"${fieldCodec}\"), which IndexedDB cannot use as an index key. Records are silently omitted from the index on write, and any query against it throws at runtime. Use String, Int, Float, DateTime, Decimal, or Bytes instead.`\n );\n }\n\n for (const [fieldName, value] of Object.entries(def.fieldDefaults ?? {})) {\n if (!(fieldName in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" fieldDefaults references field \"${fieldName}\", which is not declared in \"fields\".`\n );\n }\n // Mirrors PSL's `literalValueMatchesCodec` check on `@default(...)` — a\n // literal default's JS type must match the field's declared type, and\n // only String/Int/Float/Decimal/Boolean fields accept a literal default\n // at all (matching `ModelDef.fieldDefaults`'s own `string | number |\n // boolean` value type).\n const codec = resolveFieldCodecId(def, fieldName);\n if (codec !== undefined && !literalValueMatchesCodec(value, codec)) {\n throw new Error(\n `defineContract: model \"${modelName}\" fieldDefaults[\"${fieldName}\"] is a ${typeof value} value, but field \"${fieldName}\" has type \"${def.fields[fieldName]}\" — the literal default's JS type must match the field's declared type.`\n );\n }\n }\n}\n\n// ── Validate no conflicting reciprocal relation-action declarations ───────────\n\nfunction sameFieldList(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((field, index) => field === b[index]);\n}\n\n/**\n * `onDelete`/`onUpdate` may be declared on either side of a relation — unlike\n * PSL (which only permits it on the FK-owning/`N:1` side), TS-DSL relation\n * entries are self-contained and don't require a paired reciprocal\n * declaration at all (see the `posts`-only test below). But if BOTH a\n * relation and its reciprocal declare the *same* kind of action, only the\n * side enforcement actually walks from is ever read at runtime\n * (`getReferentialActionForRelation` in `client-idb/src/core/mutation-executor.ts`\n * checks the direct side first and only falls back to the inverse when the\n * direct side is silent) — so the other side's declaration would be silently\n * ignored. That's a footgun, not a legitimate \"pick one\" scenario, so it's\n * rejected here instead.\n */\nfunction validateNoConflictingRelationActions(models: Record<string, ModelDef>): void {\n for (const [modelName, def] of Object.entries(models)) {\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n const relatedDef = models[rel.to];\n if (!relatedDef) continue;\n for (const [inverseRelName, inverseRel] of Object.entries(relatedDef.relations ?? {})) {\n if (inverseRel.to !== modelName) continue;\n // Mirrors the reciprocal-match predicate in `getReferentialActionForRelation`\n // (client-idb/src/core/mutation-executor.ts) — keep these in sync. Matching on\n // fields (not just `to`) is required: two distinct relations to the same model\n // (e.g. `Message.sender` / `Message.recipient` both -> `User`) are not reciprocals\n // of each other and must not be flagged.\n if (!sameFieldList(inverseRel.on.local, rel.on.target)) continue;\n if (!sameFieldList(inverseRel.on.target, rel.on.local)) continue;\n\n for (const kind of [\"onDelete\", \"onUpdate\"] as const) {\n if (rel[kind] !== undefined && inverseRel[kind] !== undefined) {\n throw new Error(\n `defineContract: relation \"${modelName}.${relName}\" and its reciprocal \"${rel.to}.${inverseRelName}\" ` +\n `both declare \"${kind}\". Only one side is ever read at runtime (whichever side enforcement's cascade ` +\n `walk starts from — the \"1:N\" side, or the non-owning \"1:1\" side); the other declaration would be ` +\n `silently ignored. Declare \"${kind}\" on one side only.`\n );\n }\n }\n }\n }\n }\n}\n\n// ── Helper: build ContractField entries from field specs ──────────────────────\n\nfunction buildFields(fields: Record<string, FieldSpec>): Record<string, ContractField> {\n const result: Record<string, ContractField> = {};\n for (const [name, spec] of Object.entries(fields)) {\n const nullable = spec.endsWith(\"?\");\n const typeName = (nullable ? spec.slice(0, -1) : spec) as PrismaScalarType;\n const codecId = SCALAR_TO_CODEC_ID[typeName];\n if (codecId === undefined) {\n throw new Error(`Unknown field type \"${typeName}\" for field \"${name}\"`);\n }\n result[name] = { nullable, type: { kind: \"scalar\" as const, codecId } };\n }\n return result;\n}\n\n// ── Helper: derive the `roots` map (storeName → model CrossReference) ─────────\n\nfunction buildRoots(models: Record<string, ModelDef>): Record<string, CrossReference> {\n const roots: Record<string, CrossReference> = {};\n for (const modelName of Object.keys(models)) {\n const def = models[modelName]!;\n // v0.12.0: roots values are CrossReference `{ namespace, model }`, not bare\n // model-name strings. IDB has a single (unbound) namespace.\n roots[def.store] = crossRef(modelName);\n }\n return roots;\n}\n\n// ── Helper: derive storage.stores from model definitions ─────────────────────\n\nfunction buildStores(models: Record<string, ModelDef>): Record<string, IdbStoreDefinition> {\n const stores: Record<string, IdbStoreDefinition> = {};\n for (const def of Object.values(models)) {\n const indexes: Record<string, IdbIndexDefinition> = {};\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n indexes[indexName] = {\n keyPath: idx.keyPath,\n unique: idx.unique ?? false,\n ...(idx.multiEntry !== undefined ? { multiEntry: idx.multiEntry } : {}),\n };\n }\n stores[def.store] = {\n keyPath: def.key,\n ...(Object.keys(indexes).length > 0 ? { indexes } : {}),\n };\n }\n return stores;\n}\n\n// ── Helper: build the models section ─────────────────────────────────────────\n\ntype ContractModelEntry = {\n readonly fields: Record<string, ContractField>;\n readonly relations: Record<\n string,\n {\n readonly to: CrossReference;\n readonly cardinality: \"1:1\" | \"1:N\" | \"N:1\";\n readonly on: { readonly localFields: readonly string[]; readonly targetFields: readonly string[] };\n }\n >;\n readonly storage: IdbModelStorage;\n};\n\nfunction buildModels(models: Record<string, ModelDef>): Record<string, ContractModelEntry> {\n const result: Record<string, ContractModelEntry> = {};\n for (const [modelName, def] of Object.entries(models)) {\n const relations: ContractModelEntry[\"relations\"] = {};\n const relationsStorage: Record<string, { onDelete?: IdbReferentialAction; onUpdate?: IdbReferentialAction }> = {};\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n relations[relName] = {\n // v0.12.0: relation `to` is a CrossReference, not a bare model-name string.\n to: crossRef(rel.to),\n cardinality: rel.cardinality,\n on: { localFields: rel.on.local, targetFields: rel.on.target },\n };\n if (rel.onDelete !== undefined || rel.onUpdate !== undefined) {\n relationsStorage[relName] = {\n ...(rel.onDelete !== undefined ? { onDelete: rel.onDelete } : {}),\n ...(rel.onUpdate !== undefined ? { onUpdate: rel.onUpdate } : {}),\n };\n }\n }\n const storage: IdbModelStorage = {\n storeName: def.store,\n keyPath: def.key,\n ...(Object.keys(relationsStorage).length > 0 ? { relations: relationsStorage } : {}),\n ...(def.fieldDefaults && Object.keys(def.fieldDefaults).length > 0 ? { fieldDefaults: def.fieldDefaults } : {}),\n };\n result[modelName] = { fields: buildFields(def.fields), relations, storage };\n }\n return result;\n}\n\n// ── defineContract ────────────────────────────────────────────────────────────\n\n/**\n * Builds a typed IDB contract from a developer-friendly model definition.\n *\n * This is the TypeScript-first (no-emit) authoring path per ADR 006. The\n * returned contract object can be passed directly to `createIdbClient()` or\n * to `typescriptContract()` for config-file usage.\n *\n * @example\n * ```ts\n * import { defineContract } from '@prisma-idb/family-idb/contract-ts';\n * import idbFamily from '@prisma-idb/family-idb/pack';\n * import idbTarget from '@prisma-idb/target-idb/pack';\n *\n * export default defineContract({\n * family: idbFamily,\n * target: idbTarget,\n * models: {\n * User: {\n * store: 'users',\n * key: 'id',\n * fields: { id: 'String', name: 'String?', email: 'String' },\n * indexes: { byEmail: { keyPath: 'email', unique: true } },\n * },\n * },\n * });\n * ```\n */\nexport function defineContract(input: DefineContractInput, options?: DefineContractOptions): Contract<IdbStorage> {\n const projection: ContractProjection = options?.projection ?? \"full\";\n const models = projection === \"client\" ? projectModelsForClient(input.models) : input.models;\n\n for (const [modelName, def] of Object.entries(models)) {\n validateModelKeyAndIndexes(modelName, def);\n }\n validateNoConflictingRelationActions(models);\n\n const stores = buildStores(models);\n\n // Mirror the capability surface that `prisma contract emit` writes\n // into the JSON contract — keeps the two authoring paths byte-equivalent\n // for the capabilities block. See ARCHITECTURE.md § \"Key type: capabilities\".\n const capabilities = {\n idb: {\n ddlOnlyInUpgrade: true,\n transactionalDDL: true,\n },\n };\n\n // v0.12.0 (ADR 221): both planes are namespace-keyed. IDB has no namespace\n // concept, so everything lives under the single unbound namespace.\n const ns = UNBOUND_DOMAIN_NAMESPACE_ID;\n const storageBlock = {\n stores,\n namespaces: { [ns]: { id: ns, entries: {} } },\n };\n\n const storageHash = computeStorageHash({\n target: \"idb\",\n targetFamily: \"idb\",\n storage: storageBlock,\n });\n\n const profileHash = computeProfileHash({\n target: \"idb\",\n targetFamily: \"idb\",\n capabilities,\n });\n\n const storage: IdbStorage = { ...storageBlock, storageHash };\n\n // v0.12.0: models live under `domain.namespaces.<ns>.models`, not a top-level\n // `models` field.\n const domain = {\n namespaces: { [ns]: { models: buildModels(models) } },\n } as unknown as ApplicationDomain;\n\n const contract: Contract<IdbStorage> = {\n target: \"idb\",\n targetFamily: \"idb\",\n roots: buildRoots(models),\n domain,\n storage,\n capabilities,\n extensions: {},\n meta: {},\n profileHash,\n };\n\n validateContract(contract);\n\n return contract;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"contract-ts.mjs","names":[],"sources":["../../src/core/contract-builder.ts"],"sourcesContent":["import { computeProfileHash, computeStorageHash } from \"@prisma/orm-framework/contract/hashing\";\nimport type { ApplicationDomain, Contract, ContractField, CrossReference } from \"@prisma/orm-framework/contract/types\";\nimport { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from \"@prisma/orm-framework/contract/types\";\nimport type {\n IdbIndexDefinition,\n IdbKeyPath,\n IdbModelStorage,\n IdbReferentialAction,\n IdbStorage,\n IdbStoreDefinition,\n} from \"@prisma-idb/target-idb/pack\";\nimport { keyPathFields } from \"@prisma-idb/target-idb/pack\";\nimport type { ContractProjection } from \"./psl-interpreter\";\nimport { isValidIdbKeyCodec, literalValueMatchesCodec, warnDroppedRelation } from \"./psl-interpreter\";\nimport { validateContract } from \"./validate\";\n\n// ── Field type system ─────────────────────────────────────────────────────────\n\ntype PrismaScalarType = \"String\" | \"Int\" | \"Float\" | \"Boolean\" | \"DateTime\" | \"BigInt\" | \"Decimal\" | \"Json\" | \"Bytes\";\n\n/**\n * A field spec string: the Prisma scalar type name, optionally suffixed with\n * `?` to indicate the field is nullable (e.g. `\"String\"`, `\"Int?\"`, `\"DateTime?\"`).\n */\nexport type FieldSpec = PrismaScalarType | `${PrismaScalarType}?`;\n\nconst SCALAR_TO_CODEC_ID: Record<PrismaScalarType, string> = {\n String: \"idb/string@1\",\n Int: \"idb/int32@1\",\n Float: \"idb/double@1\",\n Boolean: \"idb/bool@1\",\n DateTime: \"idb/date@1\",\n BigInt: \"idb/bigint@1\",\n Decimal: \"idb/decimal@1\",\n Json: \"idb/json@1\",\n Bytes: \"idb/bytes@1\",\n};\n\n// ── Input types ───────────────────────────────────────────────────────────────\n\nexport type RelationDef = {\n readonly to: string;\n readonly cardinality: \"1:1\" | \"1:N\" | \"N:1\";\n readonly on: {\n readonly local: readonly string[];\n readonly target: readonly string[];\n };\n readonly onDelete?: IdbReferentialAction;\n readonly onUpdate?: IdbReferentialAction;\n};\n\nexport type IndexDef = {\n /** A single field name, or an ordered list of field names for a compound (multi-field) index. */\n readonly keyPath: IdbKeyPath;\n readonly unique?: boolean;\n readonly multiEntry?: boolean;\n};\n\nexport type ModelDef = {\n readonly store: string;\n /** The primary key field, or an ordered list of fields for a compound primary key. */\n readonly key: IdbKeyPath;\n /** All scalar fields on the model. Use `\"Type\"` for non-nullable, `\"Type?\"` for nullable. */\n readonly fields: Record<string, FieldSpec>;\n readonly indexes?: Record<string, IndexDef>;\n readonly relations?: Record<string, RelationDef>;\n /**\n * Literal default values, keyed by field name — feeds the `setDefault`\n * referential action (a child's FK field is reset to its own declared\n * default when its parent is deleted/updated with `onDelete`/`onUpdate:\n * \"setDefault\"`). A sibling map to `fields`, deliberately not folded into\n * `FieldSpec` (a bare type string) to avoid widening that type.\n *\n * Does NOT feed `create()`'s default-filling — the TS-DSL authoring path\n * has no `execution.mutations.defaults` support at all yet (a separate,\n * larger, pre-existing gap). Use the PSL authoring path's `@default(...)`\n * if you need create-time defaults; this map only backs `setDefault`.\n */\n readonly fieldDefaults?: Record<string, string | number | boolean>;\n /** Server-only model — dropped when `defineContract` runs with `{ projection: \"client\" }`. See ADR 012. */\n readonly exclude?: boolean;\n /** Server-only fields on an otherwise-synced model — dropped in client projection. See ADR 012. */\n readonly excludeFields?: readonly string[];\n};\n\nexport type DefineContractInput = {\n /** Pass the default export of `@prisma-idb/family-idb/pack`. */\n readonly family: { readonly familyId: \"idb\"; readonly id: string };\n /** Pass the default export of `@prisma-idb/target-idb/pack`. */\n readonly target: { readonly targetId: string; readonly id: string };\n readonly models: Record<string, ModelDef>;\n};\n\nexport type DefineContractOptions = {\n /** @default \"full\" */\n readonly projection?: ContractProjection;\n};\n\n/**\n * Drops `exclude: true` models and `excludeFields` entries for the client\n * projection (ADR 012). Any relation a survivor still has to an excluded\n * model is dropped too (ADR 013) — for every cardinality, required or not;\n * the model itself is never excluded as a result (see ADR 013 §\"Why we\n * don't cascade on requiredness\"). The underlying FK scalar field (if any)\n * is kept, orphaned but inert.\n */\nfunction projectModelsForClient(models: Record<string, ModelDef>): Record<string, ModelDef> {\n const excludedModelNames = new Set(\n Object.entries(models)\n .filter(([, def]) => def.exclude === true)\n .map(([name]) => name)\n );\n\n const result: Record<string, ModelDef> = {};\n\n for (const [modelName, def] of Object.entries(models)) {\n if (excludedModelNames.has(modelName)) continue;\n\n const excludedFields = new Set(def.excludeFields ?? []);\n const keyFields = keyPathFields(def.key);\n\n const excludedKeyField = keyFields.find((f) => excludedFields.has(f));\n if (excludedKeyField !== undefined) {\n throw new Error(\n `defineContract: model \"${modelName}\" excludes its own key field \"${excludedKeyField}\" — the client contract needs a primary key for every included model.`\n );\n }\n\n for (const excludedField of excludedFields) {\n if (keyFields.includes(excludedField)) continue;\n if (!(excludedField in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" excludeFields references unknown field \"${excludedField}\" — it is not declared in \"fields\".`\n );\n }\n }\n\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n const excludedIndexField = keyPathFields(idx.keyPath).find((f) => excludedFields.has(f));\n if (excludedIndexField !== undefined) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" references excluded field \"${excludedIndexField}\". Remove the index or the exclusion.`\n );\n }\n }\n\n const relations: Record<string, RelationDef> = {};\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n if (excludedModelNames.has(rel.to)) {\n warnDroppedRelation(modelName, relName, rel.to);\n continue;\n }\n const excludedLocalField = rel.on.local.find((f) => excludedFields.has(f));\n if (excludedLocalField !== undefined) {\n throw new Error(\n `defineContract: model \"${modelName}\" field \"${excludedLocalField}\" backs relation \"${relName}\" and cannot be excluded independently — field-level FK exclusion isn't supported (ADR 013 only handles relations pointing at a whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`\n );\n }\n const targetExcludedFields = new Set(models[rel.to]?.excludeFields ?? []);\n const excludedTargetField = rel.on.target.find((f) => targetExcludedFields.has(f));\n if (excludedTargetField !== undefined) {\n throw new Error(\n `defineContract: model \"${modelName}\" relation \"${relName}\" references excluded field \"${rel.to}.${excludedTargetField}\" — field-level FK exclusion isn't supported (ADR 013 only handles relations pointing at a whole-model @@idb.exclude). Exclude the whole model instead, or remove the exclusion.`\n );\n }\n relations[relName] = rel;\n }\n\n const fields: Record<string, FieldSpec> = {};\n for (const [fieldName, spec] of Object.entries(def.fields)) {\n if (excludedFields.has(fieldName)) continue;\n fields[fieldName] = spec;\n }\n\n result[modelName] = { ...def, fields, relations };\n }\n\n return result;\n}\n\n// ── Validate key/index field types against IDB's valid-key algorithm ──────────\n\nfunction resolveFieldCodecId(def: ModelDef, fieldName: string): string | undefined {\n const spec = def.fields[fieldName];\n if (spec === undefined) return undefined;\n const typeName = (spec.endsWith(\"?\") ? spec.slice(0, -1) : spec) as PrismaScalarType;\n return SCALAR_TO_CODEC_ID[typeName];\n}\n\n/**\n * Mirrors `psl-interpreter.ts`'s `IDB_INVALID_KEY_TYPE`/`IDB_INVALID_INDEX_KEY_TYPE`\n * diagnostics for the TS authoring surface, which has no diagnostics array —\n * this DSL fails fast via `throw`, matching every other check in this file.\n *\n * `multiEntry` indexes are skipped: their key-validity depends on the\n * *elements* of an array value, which this scalar-type system can't express\n * (a `multiEntry` index only makes sense over a `Json`-typed field holding an\n * array — the one case this file can't statically validate either way).\n */\nfunction validateModelKeyAndIndexes(modelName: string, def: ModelDef): void {\n const keyFields = keyPathFields(def.key);\n if (keyFields.length === 0) {\n throw new Error(`defineContract: model \"${modelName}\" \"key\" must name at least one field.`);\n }\n if (new Set(keyFields).size !== keyFields.length) {\n throw new Error(`defineContract: model \"${modelName}\" \"key\" [${keyFields.join(\", \")}] repeats a field name.`);\n }\n\n for (const keyField of keyFields) {\n if (!(keyField in def.fields)) {\n throw new Error(`defineContract: model \"${modelName}\" key field \"${keyField}\" is not declared in \"fields\".`);\n }\n if (def.fields[keyField]?.endsWith(\"?\")) {\n throw new Error(\n `defineContract: model \"${modelName}\" key field \"${keyField}\" is nullable (\"${def.fields[keyField]}\") — the primary key cannot be nullable.`\n );\n }\n const keyCodec = resolveFieldCodecId(def, keyField);\n if (keyCodec !== undefined && !isValidIdbKeyCodec(keyCodec)) {\n throw new Error(\n `defineContract: model \"${modelName}\" key field \"${keyField}\" has type \"${keyCodec}\", which IndexedDB cannot use as a key. Every write would throw (DataError extracting the primary key). Use String, Int, Float, DateTime, Decimal, or Bytes instead.`\n );\n }\n }\n\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n const indexFields = keyPathFields(idx.keyPath);\n if (indexFields.length === 0) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" \"keyPath\" must name at least one field.`\n );\n }\n if (new Set(indexFields).size !== indexFields.length) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" \"keyPath\" [${indexFields.join(\", \")}] repeats a field name.`\n );\n }\n if (idx.multiEntry && indexFields.length > 1) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" combines \"multiEntry: true\" with a compound \"keyPath\" [${indexFields.join(\", \")}] — IndexedDB rejects this combination (InvalidAccessError). multiEntry only applies to a single array-valued field.`\n );\n }\n for (const indexField of indexFields) {\n if (!(indexField in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" references field \"${indexField}\", which is not declared in \"fields\".`\n );\n }\n if (idx.multiEntry) continue;\n const fieldCodec = resolveFieldCodecId(def, indexField);\n if (fieldCodec === undefined || isValidIdbKeyCodec(fieldCodec)) continue;\n throw new Error(\n `defineContract: model \"${modelName}\" index \"${indexName}\" is keyed on \"${indexField}\" (type \"${fieldCodec}\"), which IndexedDB cannot use as an index key. Records are silently omitted from the index on write, and any query against it throws at runtime. Use String, Int, Float, DateTime, Decimal, or Bytes instead.`\n );\n }\n }\n\n for (const [fieldName, value] of Object.entries(def.fieldDefaults ?? {})) {\n if (!(fieldName in def.fields)) {\n throw new Error(\n `defineContract: model \"${modelName}\" fieldDefaults references field \"${fieldName}\", which is not declared in \"fields\".`\n );\n }\n // Mirrors PSL's `literalValueMatchesCodec` check on `@default(...)` — a\n // literal default's JS type must match the field's declared type, and\n // only String/Int/Float/Decimal/Boolean fields accept a literal default\n // at all (matching `ModelDef.fieldDefaults`'s own `string | number |\n // boolean` value type).\n const codec = resolveFieldCodecId(def, fieldName);\n if (codec !== undefined && !literalValueMatchesCodec(value, codec)) {\n throw new Error(\n `defineContract: model \"${modelName}\" fieldDefaults[\"${fieldName}\"] is a ${typeof value} value, but field \"${fieldName}\" has type \"${def.fields[fieldName]}\" — the literal default's JS type must match the field's declared type.`\n );\n }\n }\n}\n\n// ── Validate no conflicting reciprocal relation-action declarations ───────────\n\nfunction sameFieldList(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((field, index) => field === b[index]);\n}\n\n/**\n * `onDelete`/`onUpdate` may be declared on either side of a relation — unlike\n * PSL (which only permits it on the FK-owning/`N:1` side), TS-DSL relation\n * entries are self-contained and don't require a paired reciprocal\n * declaration at all (see the `posts`-only test below). But if BOTH a\n * relation and its reciprocal declare the *same* kind of action, only the\n * side enforcement actually walks from is ever read at runtime\n * (`getReferentialActionForRelation` in `client-idb/src/core/mutation-executor.ts`\n * checks the direct side first and only falls back to the inverse when the\n * direct side is silent) — so the other side's declaration would be silently\n * ignored. That's a footgun, not a legitimate \"pick one\" scenario, so it's\n * rejected here instead.\n */\nfunction validateNoConflictingRelationActions(models: Record<string, ModelDef>): void {\n for (const [modelName, def] of Object.entries(models)) {\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n const relatedDef = models[rel.to];\n if (!relatedDef) continue;\n for (const [inverseRelName, inverseRel] of Object.entries(relatedDef.relations ?? {})) {\n if (inverseRel.to !== modelName) continue;\n // Mirrors the reciprocal-match predicate in `getReferentialActionForRelation`\n // (client-idb/src/core/mutation-executor.ts) — keep these in sync. Matching on\n // fields (not just `to`) is required: two distinct relations to the same model\n // (e.g. `Message.sender` / `Message.recipient` both -> `User`) are not reciprocals\n // of each other and must not be flagged.\n if (!sameFieldList(inverseRel.on.local, rel.on.target)) continue;\n if (!sameFieldList(inverseRel.on.target, rel.on.local)) continue;\n\n for (const kind of [\"onDelete\", \"onUpdate\"] as const) {\n if (rel[kind] !== undefined && inverseRel[kind] !== undefined) {\n throw new Error(\n `defineContract: relation \"${modelName}.${relName}\" and its reciprocal \"${rel.to}.${inverseRelName}\" ` +\n `both declare \"${kind}\". Only one side is ever read at runtime (whichever side enforcement's cascade ` +\n `walk starts from — the \"1:N\" side, or the non-owning \"1:1\" side); the other declaration would be ` +\n `silently ignored. Declare \"${kind}\" on one side only.`\n );\n }\n }\n }\n }\n }\n}\n\n// ── Helper: build ContractField entries from field specs ──────────────────────\n\nfunction buildFields(fields: Record<string, FieldSpec>): Record<string, ContractField> {\n const result: Record<string, ContractField> = {};\n for (const [name, spec] of Object.entries(fields)) {\n const nullable = spec.endsWith(\"?\");\n const typeName = (nullable ? spec.slice(0, -1) : spec) as PrismaScalarType;\n const codecId = SCALAR_TO_CODEC_ID[typeName];\n if (codecId === undefined) {\n throw new Error(`Unknown field type \"${typeName}\" for field \"${name}\"`);\n }\n result[name] = { nullable, type: { kind: \"scalar\" as const, codecId } };\n }\n return result;\n}\n\n// ── Helper: derive the `roots` map (storeName → model CrossReference) ─────────\n\nfunction buildRoots(models: Record<string, ModelDef>): Record<string, CrossReference> {\n const roots: Record<string, CrossReference> = {};\n for (const modelName of Object.keys(models)) {\n const def = models[modelName]!;\n // v0.12.0: roots values are CrossReference `{ namespace, model }`, not bare\n // model-name strings. IDB has a single (unbound) namespace.\n roots[def.store] = crossRef(modelName);\n }\n return roots;\n}\n\n// ── Helper: derive storage.stores from model definitions ─────────────────────\n\nfunction buildStores(models: Record<string, ModelDef>): Record<string, IdbStoreDefinition> {\n const stores: Record<string, IdbStoreDefinition> = {};\n for (const def of Object.values(models)) {\n const indexes: Record<string, IdbIndexDefinition> = {};\n for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {\n indexes[indexName] = {\n keyPath: idx.keyPath,\n unique: idx.unique ?? false,\n ...(idx.multiEntry !== undefined ? { multiEntry: idx.multiEntry } : {}),\n };\n }\n stores[def.store] = {\n keyPath: def.key,\n ...(Object.keys(indexes).length > 0 ? { indexes } : {}),\n };\n }\n return stores;\n}\n\n// ── Helper: build the models section ─────────────────────────────────────────\n\ntype ContractModelEntry = {\n readonly fields: Record<string, ContractField>;\n readonly relations: Record<\n string,\n {\n readonly to: CrossReference;\n readonly cardinality: \"1:1\" | \"1:N\" | \"N:1\";\n readonly on: { readonly localFields: readonly string[]; readonly targetFields: readonly string[] };\n }\n >;\n readonly storage: IdbModelStorage;\n};\n\nfunction buildModels(models: Record<string, ModelDef>): Record<string, ContractModelEntry> {\n const result: Record<string, ContractModelEntry> = {};\n for (const [modelName, def] of Object.entries(models)) {\n const relations: ContractModelEntry[\"relations\"] = {};\n const relationsStorage: Record<string, { onDelete?: IdbReferentialAction; onUpdate?: IdbReferentialAction }> = {};\n for (const [relName, rel] of Object.entries(def.relations ?? {})) {\n relations[relName] = {\n // v0.12.0: relation `to` is a CrossReference, not a bare model-name string.\n to: crossRef(rel.to),\n cardinality: rel.cardinality,\n on: { localFields: rel.on.local, targetFields: rel.on.target },\n };\n if (rel.onDelete !== undefined || rel.onUpdate !== undefined) {\n relationsStorage[relName] = {\n ...(rel.onDelete !== undefined ? { onDelete: rel.onDelete } : {}),\n ...(rel.onUpdate !== undefined ? { onUpdate: rel.onUpdate } : {}),\n };\n }\n }\n const storage: IdbModelStorage = {\n storeName: def.store,\n keyPath: def.key,\n ...(Object.keys(relationsStorage).length > 0 ? { relations: relationsStorage } : {}),\n ...(def.fieldDefaults && Object.keys(def.fieldDefaults).length > 0 ? { fieldDefaults: def.fieldDefaults } : {}),\n };\n result[modelName] = { fields: buildFields(def.fields), relations, storage };\n }\n return result;\n}\n\n// ── defineContract ────────────────────────────────────────────────────────────\n\n/**\n * Builds a typed IDB contract from a developer-friendly model definition.\n *\n * This is the TypeScript-first (no-emit) authoring path per ADR 006. The\n * returned contract object can be passed directly to `createIdbClient()` or\n * to `typescriptContract()` for config-file usage.\n *\n * @example\n * ```ts\n * import { defineContract } from '@prisma-idb/family-idb/contract-ts';\n * import idbFamily from '@prisma-idb/family-idb/pack';\n * import idbTarget from '@prisma-idb/target-idb/pack';\n *\n * export default defineContract({\n * family: idbFamily,\n * target: idbTarget,\n * models: {\n * User: {\n * store: 'users',\n * key: 'id',\n * fields: { id: 'String', name: 'String?', email: 'String' },\n * indexes: { byEmail: { keyPath: 'email', unique: true } },\n * },\n * },\n * });\n * ```\n */\nexport function defineContract(input: DefineContractInput, options?: DefineContractOptions): Contract<IdbStorage> {\n const projection: ContractProjection = options?.projection ?? \"full\";\n const models = projection === \"client\" ? projectModelsForClient(input.models) : input.models;\n\n for (const [modelName, def] of Object.entries(models)) {\n validateModelKeyAndIndexes(modelName, def);\n }\n validateNoConflictingRelationActions(models);\n\n const stores = buildStores(models);\n\n // Mirror the capability surface that `prisma contract emit` writes\n // into the JSON contract — keeps the two authoring paths byte-equivalent\n // for the capabilities block.\n const capabilities = {\n idb: {\n ddlOnlyInUpgrade: true,\n transactionalDDL: true,\n },\n };\n\n // v0.12.0 (ADR 221): both planes are namespace-keyed. IDB has no namespace\n // concept, so everything lives under the single unbound namespace.\n const ns = UNBOUND_DOMAIN_NAMESPACE_ID;\n const storageBlock = {\n stores,\n namespaces: { [ns]: { id: ns, entries: {} } },\n };\n\n const storageHash = computeStorageHash({\n target: \"idb\",\n targetFamily: \"idb\",\n storage: storageBlock,\n });\n\n const profileHash = computeProfileHash({\n target: \"idb\",\n targetFamily: \"idb\",\n capabilities,\n });\n\n const storage: IdbStorage = { ...storageBlock, storageHash };\n\n // v0.12.0: models live under `domain.namespaces.<ns>.models`, not a top-level\n // `models` field.\n const domain = {\n namespaces: { [ns]: { models: buildModels(models) } },\n } as unknown as ApplicationDomain;\n\n const contract: Contract<IdbStorage> = {\n target: \"idb\",\n targetFamily: \"idb\",\n roots: buildRoots(models),\n domain,\n storage,\n capabilities,\n extensions: {},\n meta: {},\n profileHash,\n };\n\n validateContract(contract);\n\n return contract;\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ContractSpace, ControlFamilyDescriptor, ControlFamilyInstance, ControlStack } from "@prisma/orm-framework/components/control";
|
|
2
|
-
import { Contract } from "@prisma/orm-framework/contract/types";
|
|
3
2
|
import { IdbStorage } from "@prisma-idb/target-idb/pack";
|
|
3
|
+
import { Contract } from "@prisma/orm-framework/contract/types";
|
|
4
4
|
//#region src/core/schema-ir.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* IndexedDB schema IR — the in-memory representation of the object store
|
|
@@ -10,13 +10,21 @@ import { IdbStorage } from "@prisma-idb/target-idb/pack";
|
|
|
10
10
|
* is independent of any versioned contract hash so it can be used as a
|
|
11
11
|
* stable internal representation.
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* A store or index `keyPath`: a single field name, or (for a compound
|
|
15
|
+
* primary key / compound secondary index) an ordered list of field names.
|
|
16
|
+
* Mirrors `IdbKeyPath` from `@prisma-idb/target-idb/pack` — duplicated
|
|
17
|
+
* rather than imported, matching this file's existing independence from any
|
|
18
|
+
* versioned contract type (see the module doc comment above).
|
|
19
|
+
*/
|
|
20
|
+
type IdbKeyPathIR = string | readonly string[];
|
|
13
21
|
type IdbIndexIR = {
|
|
14
|
-
readonly keyPath:
|
|
22
|
+
readonly keyPath: IdbKeyPathIR;
|
|
15
23
|
readonly unique: boolean;
|
|
16
24
|
readonly multiEntry?: boolean;
|
|
17
25
|
};
|
|
18
26
|
type IdbStoreIR = {
|
|
19
|
-
readonly keyPath:
|
|
27
|
+
readonly keyPath: IdbKeyPathIR;
|
|
20
28
|
readonly autoIncrement?: boolean;
|
|
21
29
|
readonly indexes?: Record<string, IdbIndexIR>;
|
|
22
30
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control.d.mts","names":[],"sources":["../../src/core/schema-ir.ts","../../src/core/control-instance.ts","../../src/core/control-descriptor.ts","../../src/core/extension-space.ts","../../src/core/validate.ts","../../src/exports/control.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"control.d.mts","names":[],"sources":["../../src/core/schema-ir.ts","../../src/core/control-instance.ts","../../src/core/control-descriptor.ts","../../src/core/extension-space.ts","../../src/core/validate.ts","../../src/exports/control.ts"],"mappings":";;;;;;;;;;;;;;;;;;;KAgBY;KAEA;WACD,SAAS;WACT;WACA;;KAGC;WACD,SAAS;WACT;WACA,UAAU,eAAe;;KAGxB;WACD,QAAQ,eAAe;;;;;KC0BtB,2BAA2B,6BAA6B;;;;;;;;;;;;;;;;;;cCtCvD,+BAA+B,+BAA+B;WAChE;WACA;WACA;WACA;WACA;;;;;;;;;;;;;;;EAQT,OAAO,OAAO,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;UCT7B;;WAEN;;WAEA,eAAe;;;;;KCPd,cAAc,SAAS"}
|
package/dist/exports/control.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { t as validateContract } from "../validate-
|
|
1
|
+
import { t as validateContract } from "../validate-dwKE5_5B.mjs";
|
|
2
2
|
import { VERIFY_CODE_SCHEMA_FAILURE, VERIFY_CODE_TARGET_MISMATCH } from "@prisma/orm-framework/components/control";
|
|
3
|
+
import { keyPathEquals } from "@prisma-idb/target-idb/pack";
|
|
3
4
|
//#region src/core/emission.ts
|
|
4
5
|
/**
|
|
5
6
|
* Converts a JavaScript primitive or plain object into a TypeScript type-literal
|
|
@@ -224,8 +225,8 @@ function verifyIndex(indexName, contractIndex, actualIndex, storeName, storePath
|
|
|
224
225
|
return failNode("index", indexName, indexPath, "missing_column", msg, contractIndex, void 0);
|
|
225
226
|
}
|
|
226
227
|
const children = [];
|
|
227
|
-
if (contractIndex.keyPath
|
|
228
|
-
const msg = `Index "${indexName}" keyPath mismatch: expected
|
|
228
|
+
if (!keyPathEquals(contractIndex.keyPath, actualIndex.keyPath)) {
|
|
229
|
+
const msg = `Index "${indexName}" keyPath mismatch: expected ${JSON.stringify(contractIndex.keyPath)}, got ${JSON.stringify(actualIndex.keyPath)}`;
|
|
229
230
|
issues.push({
|
|
230
231
|
kind: "index_mismatch",
|
|
231
232
|
table: storeName,
|
|
@@ -275,8 +276,8 @@ function verifyStore(storeName, contractStore, actualStore, strict, issues) {
|
|
|
275
276
|
return failNode("collection", storeName, storePath, "missing_table", msg, contractStore, void 0);
|
|
276
277
|
}
|
|
277
278
|
const children = [];
|
|
278
|
-
if (contractStore.keyPath
|
|
279
|
-
const msg = `Store "${storeName}" keyPath mismatch: expected
|
|
279
|
+
if (!keyPathEquals(contractStore.keyPath, actualStore.keyPath)) {
|
|
280
|
+
const msg = `Store "${storeName}" keyPath mismatch: expected ${JSON.stringify(contractStore.keyPath)}, got ${JSON.stringify(actualStore.keyPath)}`;
|
|
280
281
|
issues.push({
|
|
281
282
|
kind: "primary_key_mismatch",
|
|
282
283
|
table: storeName,
|