@prisma-idb/family-idb 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/prisma-idb.mjs +1 -1
- package/dist/{cli-Hf4zwIew.mjs → cli-JTFu1R3s.mjs} +9 -19
- package/dist/cli-JTFu1R3s.mjs.map +1 -0
- package/dist/exports/cli.mjs +1 -1
- package/dist/exports/contract-psl.d.mts.map +1 -1
- package/dist/exports/contract-psl.mjs +7 -15
- package/dist/exports/contract-psl.mjs.map +1 -1
- package/dist/exports/contract-ts.d.mts +18 -7
- package/dist/exports/contract-ts.d.mts.map +1 -1
- package/dist/exports/contract-ts.mjs +81 -20
- package/dist/exports/contract-ts.mjs.map +1 -1
- package/dist/exports/control.d.mts +4 -0
- package/dist/exports/control.d.mts.map +1 -1
- package/dist/exports/control.mjs +11 -1
- package/dist/exports/control.mjs.map +1 -1
- package/dist/{psl-interpreter-BBwZIGC3.mjs → psl-interpreter-BoPLi4c4.mjs} +173 -73
- package/dist/psl-interpreter-BoPLi4c4.mjs.map +1 -0
- package/dist/psl-interpreter-Vi1cBmhb.d.mts.map +1 -1
- package/package.json +5 -5
- package/dist/cli-Hf4zwIew.mjs.map +0 -1
- package/dist/psl-interpreter-BBwZIGC3.mjs.map +0 -1
package/dist/bin/prisma-idb.mjs
CHANGED
|
@@ -2,7 +2,6 @@ import { dirname, join, resolve } from "pathe";
|
|
|
2
2
|
import { createCli, defineCommand, defineCommandFamily, flag, loadConfig } from "@prisma/cli-engine";
|
|
3
3
|
import { ormConfigSection } from "@prisma/orm-toolchain/cli";
|
|
4
4
|
import { CliStructuredError, notOk, ok } from "@prisma/cli-engine/protocol";
|
|
5
|
-
import { finalizeConfig } from "@prisma/orm-toolchain/config-loader";
|
|
6
5
|
//#region src/cli/collecting-sink.ts
|
|
7
6
|
/**
|
|
8
7
|
* Buffers writes the way a real stream does: a write with no trailing
|
|
@@ -55,13 +54,6 @@ function normalizeError(error) {
|
|
|
55
54
|
if (CliStructuredError.is(error)) return error;
|
|
56
55
|
return new CliStructuredError("IDB-CLI.UNEXPECTED_ERROR", error instanceof Error ? error.message : String(error));
|
|
57
56
|
}
|
|
58
|
-
function finalizedConfigContext(ctx) {
|
|
59
|
-
if (ctx.config === void 0) return ctx;
|
|
60
|
-
return {
|
|
61
|
-
...ctx,
|
|
62
|
-
config: finalizeConfig(ctx.config, ctx.cwd)
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
57
|
/**
|
|
66
58
|
* These 3 commands are IDB-specific (fake-indexeddb, IdbMigrationPlanner) —
|
|
67
59
|
* pointing them at a non-IDB `prisma.config.ts` would fail confusingly deep
|
|
@@ -81,12 +73,11 @@ function requireIdbFamily(config) {
|
|
|
81
73
|
}
|
|
82
74
|
/**
|
|
83
75
|
* `defineCommand` pre-bound to the `orm` config section (see
|
|
84
|
-
* `config-section.ts`) with a uniform error boundary
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* once, means handlers always read absolute paths.
|
|
76
|
+
* `config-section.ts`) with a uniform error boundary, mirroring
|
|
77
|
+
* `@prisma/orm-toolchain`'s own `defineOrmCommand`. The section's schema
|
|
78
|
+
* resolves `contract.output`/`migrations.dir` against the config file that
|
|
79
|
+
* wrote them (and defaults `migrations.dir`), so handlers always read
|
|
80
|
+
* absolute paths without a separate finalize step.
|
|
90
81
|
*/
|
|
91
82
|
function defineIdbCommand(def) {
|
|
92
83
|
return defineCommand({
|
|
@@ -94,9 +85,8 @@ function defineIdbCommand(def) {
|
|
|
94
85
|
needs: { config: ormConfigSection },
|
|
95
86
|
handler: async (args, ctx) => {
|
|
96
87
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return await def.handler(args, finalized);
|
|
88
|
+
requireIdbFamily(ctx.config);
|
|
89
|
+
return await def.handler(args, ctx);
|
|
100
90
|
} catch (error) {
|
|
101
91
|
return notOk(normalizeError(error));
|
|
102
92
|
}
|
|
@@ -265,7 +255,7 @@ const idbCommandFamily = defineCommandFamily({
|
|
|
265
255
|
});
|
|
266
256
|
//#endregion
|
|
267
257
|
//#region package.json
|
|
268
|
-
var version = "0.
|
|
258
|
+
var version = "0.8.0";
|
|
269
259
|
//#endregion
|
|
270
260
|
//#region src/cli/cli.ts
|
|
271
261
|
const BIN_NAME = "prisma-idb";
|
|
@@ -339,4 +329,4 @@ async function runIdbCli(proc) {
|
|
|
339
329
|
//#endregion
|
|
340
330
|
export { idbCommandFamily as a, runIdbCli as i, BIN_NAME as n, createIdbCli as r, BIN_GROUPS as t };
|
|
341
331
|
|
|
342
|
-
//# sourceMappingURL=cli-
|
|
332
|
+
//# sourceMappingURL=cli-JTFu1R3s.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-JTFu1R3s.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 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\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, mirroring\n * `@prisma/orm-toolchain`'s own `defineOrmCommand`. The section's schema\n * resolves `contract.output`/`migrations.dir` against the config file that\n * wrote them (and defaults `migrations.dir`), so handlers always read\n * absolute paths without a separate finalize step.\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 requireIdbFamily(ctx.config);\n return await def.handler(args, ctx);\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-JTFu1R3s.mjs";
|
|
2
2
|
export { BIN_GROUPS, BIN_NAME, createIdbCli, idbCommandFamily, runIdbCli };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-psl.d.mts","names":[],"sources":["../../src/core/psl-provider.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"contract-psl.d.mts","names":[],"sources":["../../src/core/psl-provider.ts"],"mappings":";;;;;;;;;;;;;;;iBAsBgB,0BAA0B;UAgBzB;WACN;;;;;;;WAOA,aAAa;;;;;;;;;WASb,oBAAoB;;;;;;;;;;;;;;;;;;;;;;iBAuBf,kBAAkB,oBAAoB,UAAU,2BAA2B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as interpretPslDocumentToIdbContract } from "../psl-interpreter-
|
|
1
|
+
import { t as interpretPslDocumentToIdbContract } from "../psl-interpreter-BoPLi4c4.mjs";
|
|
2
2
|
import { notOk } from "@prisma/orm-framework/utils/result";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
|
-
import { buildSymbolTable,
|
|
4
|
+
import { buildSymbolTable, mapPslDiagnostics } from "@prisma/orm-framework/psl-parser";
|
|
5
5
|
import { withSeedDiagnostics } from "@prisma/orm-framework/psl-parser/interpret";
|
|
6
6
|
import { parse } from "@prisma/orm-framework/psl-parser/syntax";
|
|
7
7
|
import { basename, extname } from "pathe";
|
|
@@ -28,14 +28,6 @@ function defaultOutputFromSchemaPath(schemaPath) {
|
|
|
28
28
|
if (basename(base) === "schema") return `${base.slice(0, -6)}contract.json`;
|
|
29
29
|
return `${base}.json`;
|
|
30
30
|
}
|
|
31
|
-
function mapParseDiagnostics(diagnostics, sourceFile, sourceId) {
|
|
32
|
-
return diagnostics.map((d) => ({
|
|
33
|
-
code: d.code,
|
|
34
|
-
message: d.message,
|
|
35
|
-
sourceId,
|
|
36
|
-
span: rangeToPslSpan(d.range, sourceFile)
|
|
37
|
-
}));
|
|
38
|
-
}
|
|
39
31
|
/**
|
|
40
32
|
* Creates a `ContractConfig` that reads an IDB schema from a `.prisma` file.
|
|
41
33
|
*
|
|
@@ -78,13 +70,13 @@ function prismaIdbContract(schemaPath, options) {
|
|
|
78
70
|
});
|
|
79
71
|
}
|
|
80
72
|
if (options?.injectSchemaText) schema = options.injectSchemaText(schema);
|
|
81
|
-
const { document,
|
|
82
|
-
const { table, diagnostics: symbolDiagnostics } = buildSymbolTable({
|
|
83
|
-
document,
|
|
84
|
-
|
|
73
|
+
const { document, sources, diagnostics: parseDiagnostics } = parse(schema, schemaPath);
|
|
74
|
+
const { symbolTable: table, diagnostics: symbolDiagnostics } = buildSymbolTable({
|
|
75
|
+
documents: [document],
|
|
76
|
+
sources,
|
|
85
77
|
pslBlockDescriptors: {}
|
|
86
78
|
});
|
|
87
|
-
const seedDiagnostics = [...
|
|
79
|
+
const seedDiagnostics = [...mapPslDiagnostics(parseDiagnostics, sources), ...mapPslDiagnostics(symbolDiagnostics, sources)];
|
|
88
80
|
return withSeedDiagnostics(interpretPslDocumentToIdbContract(table, schemaPath, options?.projection !== void 0 ? { projection: options.projection } : void 0), seedDiagnostics);
|
|
89
81
|
}
|
|
90
82
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-psl.mjs","names":[],"sources":["../../src/core/psl-provider.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport type { ContractConfig
|
|
1
|
+
{"version":3,"file":"contract-psl.mjs","names":[],"sources":["../../src/core/psl-provider.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport type { ContractConfig } from \"@prisma/orm-framework/config/config-types\";\nimport { buildSymbolTable, mapPslDiagnostics } from \"@prisma/orm-framework/psl-parser\";\nimport { withSeedDiagnostics } from \"@prisma/orm-framework/psl-parser/interpret\";\nimport { parse } from \"@prisma/orm-framework/psl-parser/syntax\";\nimport { notOk } from \"@prisma/orm-framework/utils/result\";\nimport { extname, basename } from \"pathe\";\nimport type { ContractProjection } from \"./psl-interpreter\";\nimport { interpretPslDocumentToIdbContract } from \"./psl-interpreter\";\n\n/**\n * Removes `@idb.exclude`/`@@idb.exclude` from raw PSL text — the `idb`\n * namespace family-idb owns (`psl-interpreter.ts`'s `IDB_EXCLUDE_ATTR`).\n * A foreign PSL parser (e.g. the SQL family's) doesn't recognize this\n * namespace and hard-errors on it (`PSL_EXTENSION_NAMESPACE_NOT_COMPOSED`);\n * stripping it is the text-level equivalent of what family-idb itself\n * already does for these same fields under `projection: \"full\"`, where the\n * attribute is documented as a no-op. Use this when sharing a schema.prisma\n * authored for family-idb with a different family's schema loader — the\n * server always wants every field these attributes would otherwise hide\n * from the client.\n */\nexport function stripIdbExcludeAttributes(schema: string): string {\n return schema\n .replace(/^[ \\t]*@@idb\\.exclude\\b.*\\n?/gm, \"\") // model-level: drop the whole line, incl. trailing comments\n .replace(/[ \\t]*@idb\\.exclude\\b/g, \"\"); // field-level: drop just the token, keep the field\n}\n\nfunction defaultOutputFromSchemaPath(schemaPath: string): string {\n const ext = extname(schemaPath);\n if (ext.length === 0) return `${schemaPath}.json`;\n const base = schemaPath.slice(0, -ext.length);\n if (basename(base) === \"schema\") {\n return `${base.slice(0, -\"schema\".length)}contract.json`;\n }\n return `${base}.json`;\n}\n\nexport interface PrismaIdbContractOptions {\n readonly output?: string;\n /**\n * `\"client\"` projects out anything marked `@idb.exclude`/`@@idb.exclude`\n * (ADR 012). Pass a distinct `output` alongside this so the client and\n * full/server contracts don't collide on the same file.\n * @default \"full\"\n */\n readonly projection?: ContractProjection;\n /**\n * Runs on the raw schema text right after it's read, before parsing.\n * Lets a downstream package (e.g. `@prisma-idb/sync-server`) append\n * synthetic model/enum declarations — the appended text goes through the\n * exact same parse → interpret → hash pipeline as hand-authored PSL, so\n * `storageHash` reflects it correctly (it's computed *inside*\n * `interpretPslDocumentToIdbContract`, from whatever this returns).\n */\n readonly injectSchemaText?: (schema: string) => string;\n}\n\n/**\n * Creates a `ContractConfig` that reads an IDB schema from a `.prisma` file.\n *\n * Use this in `prisma.config.ts` as the `contract:` value when you prefer\n * PSL authoring over the TypeScript-first `defineContract()` helper.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@prisma-idb/family-idb/config-types';\n * import { prismaIdbContract } from '@prisma-idb/family-idb/contract-psl';\n *\n * export default defineConfig({\n * // ...\n * contract: prismaIdbContract('./src/prisma/schema.prisma'),\n * });\n * ```\n *\n * The emitted `contract.json` lands next to the schema file by default\n * (`schema.prisma` → `contract.json`). Override with `options.output`.\n */\nexport function prismaIdbContract(schemaPath: string, options?: PrismaIdbContractOptions): ContractConfig {\n return {\n source: {\n inputs: [schemaPath],\n load: async (context) => {\n const [absoluteSchemaPath] = context.resolvedInputs;\n if (absoluteSchemaPath === undefined) {\n throw new Error(\n \"prismaIdbContract: context.resolvedInputs is empty. The CLI config loader should populate it positional-matched with source.inputs.\"\n );\n }\n\n let schema: string;\n try {\n schema = await readFile(absoluteSchemaPath, \"utf-8\");\n } catch (error) {\n const message = String(error);\n return notOk({\n summary: `Failed to read Prisma schema at \"${schemaPath}\"`,\n diagnostics: [\n {\n code: \"PSL_SCHEMA_READ_FAILED\",\n message,\n sourceId: schemaPath,\n },\n ],\n });\n }\n\n if (options?.injectSchemaText) {\n schema = options.injectSchemaText(schema);\n }\n\n // The filename becomes each diagnostic's `sourceId`, so pass the\n // configured schema path to keep diagnostics pointing at the user's file.\n const { document, sources, diagnostics: parseDiagnostics } = parse(schema, schemaPath);\n const { symbolTable: table, diagnostics: symbolDiagnostics } = buildSymbolTable({\n documents: [document],\n sources,\n pslBlockDescriptors: {},\n });\n\n // Do not short-circuit on provider-level diagnostics — the fault-tolerant\n // parser/symbol-table still produce a usable table, and the interpreter\n // may surface its own diagnostics in the same response.\n const seedDiagnostics = [\n ...mapPslDiagnostics(parseDiagnostics, sources),\n ...mapPslDiagnostics(symbolDiagnostics, sources),\n ];\n\n const interpreted = withSeedDiagnostics(\n interpretPslDocumentToIdbContract(\n table,\n schemaPath,\n options?.projection !== undefined ? { projection: options.projection } : undefined\n ),\n seedDiagnostics\n );\n // Return the full Result so seed diagnostics (parse + symbol-table\n // findings) survive the success path — the config loader surfaces\n // them alongside the contract.\n return interpreted;\n },\n },\n output: options?.output ?? defaultOutputFromSchemaPath(schemaPath),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,0BAA0B,QAAwB;CAChE,OAAO,OACJ,QAAQ,kCAAkC,EAAE,CAAC,CAC7C,QAAQ,0BAA0B,EAAE;AACzC;AAEA,SAAS,4BAA4B,YAA4B;CAC/D,MAAM,MAAM,QAAQ,UAAU;CAC9B,IAAI,IAAI,WAAW,GAAG,OAAO,GAAG,WAAW;CAC3C,MAAM,OAAO,WAAW,MAAM,GAAG,CAAC,IAAI,MAAM;CAC5C,IAAI,SAAS,IAAI,MAAM,UACrB,OAAO,GAAG,KAAK,MAAM,GAAG,EAAgB,EAAE;CAE5C,OAAO,GAAG,KAAK;AACjB;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,kBAAkB,YAAoB,SAAoD;CACxG,OAAO;EACL,QAAQ;GACN,QAAQ,CAAC,UAAU;GACnB,MAAM,OAAO,YAAY;IACvB,MAAM,CAAC,sBAAsB,QAAQ;IACrC,IAAI,uBAAuB,KAAA,GACzB,MAAM,IAAI,MACR,qIACF;IAGF,IAAI;IACJ,IAAI;KACF,SAAS,MAAM,SAAS,oBAAoB,OAAO;IACrD,SAAS,OAAO;KACd,MAAM,UAAU,OAAO,KAAK;KAC5B,OAAO,MAAM;MACX,SAAS,oCAAoC,WAAW;MACxD,aAAa,CACX;OACE,MAAM;OACN;OACA,UAAU;MACZ,CACF;KACF,CAAC;IACH;IAEA,IAAI,SAAS,kBACX,SAAS,QAAQ,iBAAiB,MAAM;IAK1C,MAAM,EAAE,UAAU,SAAS,aAAa,qBAAqB,MAAM,QAAQ,UAAU;IACrF,MAAM,EAAE,aAAa,OAAO,aAAa,sBAAsB,iBAAiB;KAC9E,WAAW,CAAC,QAAQ;KACpB;KACA,qBAAqB,CAAC;IACxB,CAAC;IAKD,MAAM,kBAAkB,CACtB,GAAG,kBAAkB,kBAAkB,OAAO,GAC9C,GAAG,kBAAkB,mBAAmB,OAAO,CACjD;IAaA,OAXoB,oBAClB,kCACE,OACA,YACA,SAAS,eAAe,KAAA,IAAY,EAAE,YAAY,QAAQ,WAAW,IAAI,KAAA,CAC3E,GACA,eAKe;GACnB;EACF;EACA,QAAQ,SAAS,UAAU,4BAA4B,UAAU;CACnE;AACF"}
|
|
@@ -3,11 +3,15 @@ import { IdbKeyPath, IdbReferentialAction, IdbStorage } from "@prisma-idb/target
|
|
|
3
3
|
import { Contract } from "@prisma/orm-framework/contract/types";
|
|
4
4
|
//#region src/core/contract-builder.d.ts
|
|
5
5
|
type PrismaScalarType = "String" | "Int" | "Float" | "Boolean" | "DateTime" | "BigInt" | "Decimal" | "Json" | "Bytes";
|
|
6
|
+
type PrismaScalarFieldSpec = PrismaScalarType | `${PrismaScalarType}?`;
|
|
6
7
|
/**
|
|
7
8
|
* A field spec string: the Prisma scalar type name, optionally suffixed with
|
|
8
9
|
* `?` to indicate the field is nullable (e.g. `"String"`, `"Int?"`, `"DateTime?"`).
|
|
9
10
|
*/
|
|
10
|
-
type FieldSpec =
|
|
11
|
+
type FieldSpec<EnumName extends string = never> = PrismaScalarFieldSpec | EnumName | `${EnumName}?` | `${EnumName}[]`;
|
|
12
|
+
/** A named enum's ordered string values. Each value is also its member name. */
|
|
13
|
+
type EnumDef = readonly string[];
|
|
14
|
+
type EnumDefs = Readonly<Record<string, EnumDef>>;
|
|
11
15
|
type RelationDef = {
|
|
12
16
|
readonly to: string;
|
|
13
17
|
readonly cardinality: "1:1" | "1:N" | "N:1";
|
|
@@ -17,6 +21,12 @@ type RelationDef = {
|
|
|
17
21
|
};
|
|
18
22
|
readonly onDelete?: IdbReferentialAction;
|
|
19
23
|
readonly onUpdate?: IdbReferentialAction;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a to-one (`N:1` / `1:1`) relation can be absent. Defaults to
|
|
26
|
+
* `true` when any `on.local` field is nullable (`"String?"`), `false`
|
|
27
|
+
* otherwise. Ignored for `1:N`.
|
|
28
|
+
*/
|
|
29
|
+
readonly nullable?: boolean;
|
|
20
30
|
};
|
|
21
31
|
type IndexDef = {
|
|
22
32
|
/** A single field name, or an ordered list of field names for a compound (multi-field) index. */
|
|
@@ -24,12 +34,12 @@ type IndexDef = {
|
|
|
24
34
|
readonly unique?: boolean;
|
|
25
35
|
readonly multiEntry?: boolean;
|
|
26
36
|
};
|
|
27
|
-
type ModelDef = {
|
|
37
|
+
type ModelDef<EnumName extends string = never> = {
|
|
28
38
|
readonly store: string;
|
|
29
39
|
/** The primary key field, or an ordered list of fields for a compound primary key. */
|
|
30
40
|
readonly key: IdbKeyPath;
|
|
31
41
|
/** All scalar fields on the model. Use `"Type"` for non-nullable, `"Type?"` for nullable. */
|
|
32
|
-
readonly fields: Record<string, FieldSpec
|
|
42
|
+
readonly fields: Record<string, FieldSpec<EnumName>>;
|
|
33
43
|
readonly indexes?: Record<string, IndexDef>;
|
|
34
44
|
readonly relations?: Record<string, RelationDef>;
|
|
35
45
|
/**
|
|
@@ -50,7 +60,7 @@ type ModelDef = {
|
|
|
50
60
|
/** Server-only fields on an otherwise-synced model — dropped in client projection. See ADR 012. */
|
|
51
61
|
readonly excludeFields?: readonly string[];
|
|
52
62
|
};
|
|
53
|
-
type DefineContractInput = {
|
|
63
|
+
type DefineContractInput<TEnums extends EnumDefs = Record<never, never>> = {
|
|
54
64
|
/** Pass the default export of `@prisma-idb/family-idb/pack`. */
|
|
55
65
|
readonly family: {
|
|
56
66
|
readonly familyId: "idb";
|
|
@@ -61,7 +71,8 @@ type DefineContractInput = {
|
|
|
61
71
|
readonly targetId: string;
|
|
62
72
|
readonly id: string;
|
|
63
73
|
};
|
|
64
|
-
readonly
|
|
74
|
+
readonly enums?: TEnums;
|
|
75
|
+
readonly models: Record<string, ModelDef<Extract<keyof TEnums, string>>>;
|
|
65
76
|
};
|
|
66
77
|
type DefineContractOptions = {
|
|
67
78
|
/** @default "full" */
|
|
@@ -94,7 +105,7 @@ type DefineContractOptions = {
|
|
|
94
105
|
* });
|
|
95
106
|
* ```
|
|
96
107
|
*/
|
|
97
|
-
declare function defineContract(input: DefineContractInput
|
|
108
|
+
declare function defineContract<const TEnums extends EnumDefs = Record<never, never>>(input: DefineContractInput<TEnums>, options?: DefineContractOptions): Contract<IdbStorage>;
|
|
98
109
|
//#endregion
|
|
99
|
-
export { type ContractProjection, type DefineContractInput, type DefineContractOptions, type FieldSpec, type IndexDef, type ModelDef, type RelationDef, defineContract };
|
|
110
|
+
export { type ContractProjection, type DefineContractInput, type DefineContractOptions, type EnumDef, type EnumDefs, type FieldSpec, type IndexDef, type ModelDef, type RelationDef, defineContract };
|
|
100
111
|
//# sourceMappingURL=contract-ts.d.mts.map
|
|
@@ -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":";;;;KAwBK;KAEA,wBAAwB,sBAAsB;;;;;KAMvC,UAAU,mCACpB,wBAAwB,cAAc,iBAAiB;;KAG7C;KAEA,WAAW,SAAS,eAAe;KAgBnC;WACD;WACA;WACA;aACE;aACA;;WAEF,WAAW;WACX,WAAW;;;;;;WAMX;;KAGC;;WAED,SAAS;WACT;WACA;;KAGC,SAAS;WACV;;WAEA,KAAK;;WAEL,QAAQ,eAAe,UAAU;WACjC,UAAU,eAAe;WACzB,YAAY,eAAe;;;;;;;;;;;;;WAa3B,gBAAgB;;WAEhB;;WAEA;;KAGC,oBAAoB,eAAe,WAAW;;WAE/C;aAAmB;aAA0B;;;WAE7C;aAAmB;aAA2B;;WAC9C,QAAQ;WACR,QAAQ,eAAe,SAAS,cAAc;;KAG7C;;WAED,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0cR,qBAAqB,eAAe,WAAW,sBAC7D,OAAO,oBAAoB,SAC3B,UAAU,wBACT,SAAS"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as validateContract } from "../validate-dwKE5_5B.mjs";
|
|
2
|
-
import { i as warnDroppedRelation, n as isValidIdbKeyCodec, r as literalValueMatchesCodec } from "../psl-interpreter-
|
|
2
|
+
import { i as warnDroppedRelation, n as isValidIdbKeyCodec, r as literalValueMatchesCodec } from "../psl-interpreter-BoPLi4c4.mjs";
|
|
3
3
|
import { keyPathFields } from "@prisma-idb/target-idb/pack";
|
|
4
4
|
import { UNBOUND_DOMAIN_NAMESPACE_ID, crossRef } from "@prisma/orm-framework/contract/types";
|
|
5
5
|
import { computeProfileHash, computeStorageHash } from "@prisma/orm-framework/contract/hashing";
|
|
@@ -66,11 +66,21 @@ function projectModelsForClient(models) {
|
|
|
66
66
|
}
|
|
67
67
|
return result;
|
|
68
68
|
}
|
|
69
|
-
function
|
|
69
|
+
function parseFieldSpec(spec) {
|
|
70
|
+
const nullable = spec.endsWith("?");
|
|
71
|
+
const withoutOptional = nullable ? spec.slice(0, -1) : spec;
|
|
72
|
+
const many = withoutOptional.endsWith("[]");
|
|
73
|
+
return {
|
|
74
|
+
typeName: many ? withoutOptional.slice(0, -2) : withoutOptional,
|
|
75
|
+
nullable,
|
|
76
|
+
many
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function resolveFieldCodecId(def, fieldName, enums) {
|
|
70
80
|
const spec = def.fields[fieldName];
|
|
71
81
|
if (spec === void 0) return void 0;
|
|
72
|
-
const typeName =
|
|
73
|
-
return SCALAR_TO_CODEC_ID[typeName];
|
|
82
|
+
const { typeName } = parseFieldSpec(spec);
|
|
83
|
+
return typeName in enums ? SCALAR_TO_CODEC_ID.String : SCALAR_TO_CODEC_ID[typeName];
|
|
74
84
|
}
|
|
75
85
|
/**
|
|
76
86
|
* Mirrors `psl-interpreter.ts`'s `IDB_INVALID_KEY_TYPE`/`IDB_INVALID_INDEX_KEY_TYPE`
|
|
@@ -82,14 +92,14 @@ function resolveFieldCodecId(def, fieldName) {
|
|
|
82
92
|
* (a `multiEntry` index only makes sense over a `Json`-typed field holding an
|
|
83
93
|
* array — the one case this file can't statically validate either way).
|
|
84
94
|
*/
|
|
85
|
-
function validateModelKeyAndIndexes(modelName, def) {
|
|
95
|
+
function validateModelKeyAndIndexes(modelName, def, enums) {
|
|
86
96
|
const keyFields = keyPathFields(def.key);
|
|
87
97
|
if (keyFields.length === 0) throw new Error(`defineContract: model "${modelName}" "key" must name at least one field.`);
|
|
88
98
|
if (new Set(keyFields).size !== keyFields.length) throw new Error(`defineContract: model "${modelName}" "key" [${keyFields.join(", ")}] repeats a field name.`);
|
|
89
99
|
for (const keyField of keyFields) {
|
|
90
100
|
if (!(keyField in def.fields)) throw new Error(`defineContract: model "${modelName}" key field "${keyField}" is not declared in "fields".`);
|
|
91
|
-
if (def.fields[keyField]
|
|
92
|
-
const keyCodec = resolveFieldCodecId(def, keyField);
|
|
101
|
+
if (def.fields[keyField] !== void 0 && parseFieldSpec(def.fields[keyField]).nullable) throw new Error(`defineContract: model "${modelName}" key field "${keyField}" is nullable ("${def.fields[keyField]}") — the primary key cannot be nullable.`);
|
|
102
|
+
const keyCodec = resolveFieldCodecId(def, keyField, enums);
|
|
93
103
|
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
104
|
}
|
|
95
105
|
for (const [indexName, idx] of Object.entries(def.indexes ?? {})) {
|
|
@@ -100,15 +110,24 @@ function validateModelKeyAndIndexes(modelName, def) {
|
|
|
100
110
|
for (const indexField of indexFields) {
|
|
101
111
|
if (!(indexField in def.fields)) throw new Error(`defineContract: model "${modelName}" index "${indexName}" references field "${indexField}", which is not declared in "fields".`);
|
|
102
112
|
if (idx.multiEntry) continue;
|
|
103
|
-
const fieldCodec = resolveFieldCodecId(def, indexField);
|
|
113
|
+
const fieldCodec = resolveFieldCodecId(def, indexField, enums);
|
|
104
114
|
if (fieldCodec === void 0 || isValidIdbKeyCodec(fieldCodec)) continue;
|
|
105
115
|
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
116
|
}
|
|
107
117
|
}
|
|
108
118
|
for (const [fieldName, value] of Object.entries(def.fieldDefaults ?? {})) {
|
|
109
119
|
if (!(fieldName in def.fields)) throw new Error(`defineContract: model "${modelName}" fieldDefaults references field "${fieldName}", which is not declared in "fields".`);
|
|
110
|
-
const codec = resolveFieldCodecId(def, fieldName);
|
|
120
|
+
const codec = resolveFieldCodecId(def, fieldName, enums);
|
|
111
121
|
if (codec !== void 0 && !literalValueMatchesCodec(value, codec)) throw new Error(`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.`);
|
|
122
|
+
const spec = def.fields[fieldName];
|
|
123
|
+
if (spec !== void 0) {
|
|
124
|
+
const parsedSpec = parseFieldSpec(spec);
|
|
125
|
+
const enumValues = enums[parsedSpec.typeName];
|
|
126
|
+
if (enumValues !== void 0) {
|
|
127
|
+
if (parsedSpec.many) throw new Error(`defineContract: model "${modelName}" fieldDefaults["${fieldName}"] is an enum list; list defaults are not supported by IDB.`);
|
|
128
|
+
if (typeof value !== "string" || !enumValues.includes(value)) throw new Error(`defineContract: model "${modelName}" fieldDefaults["${fieldName}"] is not a declared value of enum "${parsedSpec.typeName}".`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
112
131
|
}
|
|
113
132
|
}
|
|
114
133
|
function sameFieldList(a, b) {
|
|
@@ -139,23 +158,57 @@ function validateNoConflictingRelationActions(models) {
|
|
|
139
158
|
}
|
|
140
159
|
}
|
|
141
160
|
}
|
|
142
|
-
function buildFields(fields) {
|
|
161
|
+
function buildFields(fields, enums, namespaceId) {
|
|
143
162
|
const result = {};
|
|
144
163
|
for (const [name, spec] of Object.entries(fields)) {
|
|
145
|
-
const nullable = spec
|
|
146
|
-
const
|
|
147
|
-
const codecId = SCALAR_TO_CODEC_ID[typeName];
|
|
164
|
+
const { typeName, nullable, many } = parseFieldSpec(spec);
|
|
165
|
+
const enumValues = enums[typeName];
|
|
166
|
+
const codecId = enumValues === void 0 ? SCALAR_TO_CODEC_ID[typeName] : SCALAR_TO_CODEC_ID.String;
|
|
148
167
|
if (codecId === void 0) throw new Error(`Unknown field type "${typeName}" for field "${name}"`);
|
|
168
|
+
if (many && enumValues === void 0) throw new Error(`Field "${name}" has list type "${spec}". Only enum fields can be lists.`);
|
|
169
|
+
if (many && nullable) throw new Error(`Field "${name}" has type "${spec}". A list field cannot be optional.`);
|
|
149
170
|
result[name] = {
|
|
150
171
|
nullable,
|
|
151
172
|
type: {
|
|
152
173
|
kind: "scalar",
|
|
153
174
|
codecId
|
|
154
|
-
}
|
|
175
|
+
},
|
|
176
|
+
...many ? { many: true } : {},
|
|
177
|
+
...enumValues !== void 0 ? { valueSet: {
|
|
178
|
+
plane: "domain",
|
|
179
|
+
entityKind: "enum",
|
|
180
|
+
namespaceId,
|
|
181
|
+
entityName: typeName
|
|
182
|
+
} } : {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
function buildEnums(enums) {
|
|
188
|
+
const result = {};
|
|
189
|
+
for (const [name, values] of Object.entries(enums)) {
|
|
190
|
+
if (name in SCALAR_TO_CODEC_ID) throw new Error(`defineContract: enum "${name}" conflicts with the built-in scalar type of the same name.`);
|
|
191
|
+
if (values.length === 0) throw new Error(`defineContract: enum "${name}" must declare at least one value.`);
|
|
192
|
+
if (new Set(values).size !== values.length) throw new Error(`defineContract: enum "${name}" repeats a value. Enum values must be unique.`);
|
|
193
|
+
result[name] = {
|
|
194
|
+
codecId: SCALAR_TO_CODEC_ID.String,
|
|
195
|
+
members: values.map((value) => ({
|
|
196
|
+
name: value,
|
|
197
|
+
value
|
|
198
|
+
}))
|
|
155
199
|
};
|
|
156
200
|
}
|
|
157
201
|
return result;
|
|
158
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Client projection: drop enums that no remaining field uses, so an enum that
|
|
205
|
+
* only excluded models or fields reference doesn't reach the client contract.
|
|
206
|
+
* Runs after `buildEnums` has validated the complete map.
|
|
207
|
+
*/
|
|
208
|
+
function keepReferencedEnums(enums, models) {
|
|
209
|
+
const referenced = new Set(Object.values(models).flatMap((def) => Object.values(def.fields).map((spec) => parseFieldSpec(spec).typeName)));
|
|
210
|
+
return Object.fromEntries(Object.entries(enums).filter(([name]) => referenced.has(name)));
|
|
211
|
+
}
|
|
159
212
|
function buildRoots(models) {
|
|
160
213
|
const roots = {};
|
|
161
214
|
for (const modelName of Object.keys(models)) {
|
|
@@ -180,7 +233,7 @@ function buildStores(models) {
|
|
|
180
233
|
}
|
|
181
234
|
return stores;
|
|
182
235
|
}
|
|
183
|
-
function buildModels(models) {
|
|
236
|
+
function buildModels(models, enums, namespaceId) {
|
|
184
237
|
const result = {};
|
|
185
238
|
for (const [modelName, def] of Object.entries(models)) {
|
|
186
239
|
const relations = {};
|
|
@@ -192,7 +245,8 @@ function buildModels(models) {
|
|
|
192
245
|
on: {
|
|
193
246
|
localFields: rel.on.local,
|
|
194
247
|
targetFields: rel.on.target
|
|
195
|
-
}
|
|
248
|
+
},
|
|
249
|
+
...rel.cardinality === "1:N" ? {} : { nullable: rel.nullable ?? rel.on.local.some((f) => def.fields[f]?.endsWith("?") === true) }
|
|
196
250
|
};
|
|
197
251
|
if (rel.onDelete !== void 0 || rel.onUpdate !== void 0) relationsStorage[relName] = {
|
|
198
252
|
...rel.onDelete !== void 0 ? { onDelete: rel.onDelete } : {},
|
|
@@ -206,7 +260,7 @@ function buildModels(models) {
|
|
|
206
260
|
...def.fieldDefaults && Object.keys(def.fieldDefaults).length > 0 ? { fieldDefaults: def.fieldDefaults } : {}
|
|
207
261
|
};
|
|
208
262
|
result[modelName] = {
|
|
209
|
-
fields: buildFields(def.fields),
|
|
263
|
+
fields: buildFields(def.fields, enums, namespaceId),
|
|
210
264
|
relations,
|
|
211
265
|
storage
|
|
212
266
|
};
|
|
@@ -241,8 +295,12 @@ function buildModels(models) {
|
|
|
241
295
|
* ```
|
|
242
296
|
*/
|
|
243
297
|
function defineContract(input, options) {
|
|
244
|
-
const
|
|
245
|
-
|
|
298
|
+
const projection = options?.projection ?? "full";
|
|
299
|
+
const enums = input.enums ?? {};
|
|
300
|
+
const models = projection === "client" ? projectModelsForClient(input.models) : input.models;
|
|
301
|
+
const validatedEnums = buildEnums(enums);
|
|
302
|
+
const domainEnums = projection === "client" ? keepReferencedEnums(validatedEnums, models) : validatedEnums;
|
|
303
|
+
for (const [modelName, def] of Object.entries(models)) validateModelKeyAndIndexes(modelName, def, enums);
|
|
246
304
|
validateNoConflictingRelationActions(models);
|
|
247
305
|
const stores = buildStores(models);
|
|
248
306
|
const capabilities = { idb: {
|
|
@@ -271,7 +329,10 @@ function defineContract(input, options) {
|
|
|
271
329
|
...storageBlock,
|
|
272
330
|
storageHash
|
|
273
331
|
};
|
|
274
|
-
const domain = { namespaces: { [ns]: {
|
|
332
|
+
const domain = { namespaces: { [ns]: {
|
|
333
|
+
models: buildModels(models, enums, ns),
|
|
334
|
+
...Object.keys(domainEnums).length > 0 ? { enum: domainEnums } : {}
|
|
335
|
+
} } };
|
|
275
336
|
const contract = {
|
|
276
337
|
target: "idb",
|
|
277
338
|
targetFamily: "idb",
|