@prisma-idb/family-idb 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/bin/prisma-idb.d.mts +1 -0
  4. package/dist/bin/prisma-idb.mjs +13 -0
  5. package/dist/bin/prisma-idb.mjs.map +1 -0
  6. package/dist/chain-order-CaHXo5jz.mjs +44 -0
  7. package/dist/chain-order-CaHXo5jz.mjs.map +1 -0
  8. package/dist/cli-Dlaym_DD.mjs +342 -0
  9. package/dist/cli-Dlaym_DD.mjs.map +1 -0
  10. package/dist/contract-space-codegen--gfuyap2.mjs +128 -0
  11. package/dist/contract-space-codegen--gfuyap2.mjs.map +1 -0
  12. package/dist/exports/cli.d.mts +26 -0
  13. package/dist/exports/cli.d.mts.map +1 -0
  14. package/dist/exports/cli.mjs +2 -0
  15. package/dist/exports/config-types.d.mts +38 -0
  16. package/dist/exports/config-types.d.mts.map +1 -0
  17. package/dist/exports/config-types.mjs +66 -0
  18. package/dist/exports/config-types.mjs.map +1 -0
  19. package/dist/exports/contract-psl.d.mts +59 -0
  20. package/dist/exports/contract-psl.d.mts.map +1 -0
  21. package/dist/exports/contract-psl.mjs +97 -0
  22. package/dist/exports/contract-psl.mjs.map +1 -0
  23. package/dist/exports/contract-ts.d.mts +98 -0
  24. package/dist/exports/contract-ts.d.mts.map +1 -0
  25. package/dist/exports/contract-ts.mjs +275 -0
  26. package/dist/exports/contract-ts.mjs.map +1 -0
  27. package/dist/exports/control.d.mts +107 -0
  28. package/dist/exports/control.d.mts.map +1 -0
  29. package/dist/exports/control.mjs +573 -0
  30. package/dist/exports/control.mjs.map +1 -0
  31. package/dist/exports/pack.d.mts +10 -0
  32. package/dist/exports/pack.d.mts.map +1 -0
  33. package/dist/exports/pack.mjs +11 -0
  34. package/dist/exports/pack.mjs.map +1 -0
  35. package/dist/migration-plan-BWhd8BdV.mjs +330 -0
  36. package/dist/migration-plan-BWhd8BdV.mjs.map +1 -0
  37. package/dist/preflight-4jDCNvS0.mjs +119 -0
  38. package/dist/preflight-4jDCNvS0.mjs.map +1 -0
  39. package/dist/psl-interpreter-C0suaXu2.d.mts +45 -0
  40. package/dist/psl-interpreter-C0suaXu2.d.mts.map +1 -0
  41. package/dist/psl-interpreter-C6Q4okuO.mjs +809 -0
  42. package/dist/psl-interpreter-C6Q4okuO.mjs.map +1 -0
  43. package/dist/validate-BLnItEKs.mjs +48 -0
  44. package/dist/validate-BLnItEKs.mjs.map +1 -0
  45. package/package.json +64 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control.mjs","names":[],"sources":["../../src/core/emission.ts","../../src/core/schema-verify.ts","../../src/core/control-instance.ts","../../src/core/control-descriptor.ts","../../src/exports/control.ts"],"sourcesContent":["import type { Contract, ContractModel } from \"@prisma/orm-framework/contract/types\";\nimport type { EmissionSpi } from \"@prisma/orm-framework/components/emission\";\nimport type { IdbModelStorage, IdbStorage } from \"@prisma-idb/target-idb/pack\";\n\n// ── Serialization utilities ──────────────────────────────────────────────────\n// Adapted from Prisma ORM's domain type generation.\n// These utilities convert runtime JS values into TypeScript type-literal strings\n// that are spliced verbatim into the generated contract.d.ts file.\n\n/**\n * Converts a JavaScript primitive or plain object into a TypeScript type-literal\n * string. Arrays become `readonly [...]` tuples; objects become `{ readonly k: v }`\n * type literals; strings are single-quoted with escaping.\n */\nfunction serializeValue(value: unknown): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"string\") {\n const escaped = value.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n return `'${escaped}'`;\n }\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n if (typeof value === \"bigint\") return `${value}n`;\n if (Array.isArray(value)) {\n const items = value.map((v) => serializeValue(v)).join(\", \");\n return `readonly [${items}]`;\n }\n if (typeof value === \"object\") {\n const entries: string[] = [];\n for (const [k, v] of Object.entries(value)) {\n entries.push(`readonly ${serializeObjectKey(k)}: ${serializeValue(v)}`);\n }\n return `{ ${entries.join(\"; \")} }`;\n }\n return \"unknown\";\n}\n\n/**\n * Converts an object key to a safe TypeScript identifier. If the key is already a\n * valid identifier it is returned as-is; otherwise it is wrapped in single-quoted\n * string literal form.\n */\nfunction serializeObjectKey(key: string): string {\n if (/^[$A-Z_a-z][$\\w]*$/.test(key)) return key;\n return serializeValue(key);\n}\n\n// ── EmissionSpi ───────────────────────────────────────────────────────────────\n\n/**\n * IDB family emission plugin.\n *\n * Registered as `emission` on {@link IdbFamilyDescriptor}. The Prisma 8 emitter\n * calls these methods when generating `contract.d.ts` for any schema that targets the\n * IDB family. Each method returns a raw TypeScript source string that the emitter\n * splices into the generated file at the appropriate location.\n *\n * @see `generateContractDts` in `@prisma/orm-toolchain`.\n */\nexport const idbEmission = {\n id: \"idb\",\n\n /**\n * Serializes the full `storage` section of the contract into a TypeScript type\n * literal. The result is used as the first generic argument of `ContractType<...>`\n * in the generated `contract.d.ts`.\n *\n * For IDB the storage shape is:\n * ```ts\n * { readonly stores: { readonly <storeName>: { readonly keyPath: '...'; ... }; ... };\n * readonly storageHash: <storageHashTypeName> }\n * ```\n *\n * @param contract - The resolved Prisma contract.\n * @param storageHashTypeName - Emitter-provided name of the `StorageHash` type alias\n * to embed (e.g. `'StorageHash'`).\n */\n generateStorageType(contract: Contract, storageHashTypeName: string): string {\n const storage = contract.storage as IdbStorage;\n const stores: string[] = [];\n\n for (const [storeName, store] of Object.entries(storage.stores).sort(([a], [b]) => a.localeCompare(b))) {\n const storeParts: string[] = [`readonly keyPath: ${serializeValue(store.keyPath)}`];\n\n if (store.autoIncrement !== undefined) {\n storeParts.push(`readonly autoIncrement: ${store.autoIncrement}`);\n }\n\n const indexes = store.indexes ?? {};\n if (Object.keys(indexes).length > 0) {\n const indexEntries: string[] = [];\n for (const [indexName, index] of Object.entries(indexes).sort(([a], [b]) => a.localeCompare(b))) {\n const indexParts = [`readonly keyPath: ${serializeValue(index.keyPath)}`, `readonly unique: ${index.unique}`];\n if (index.multiEntry !== undefined) {\n indexParts.push(`readonly multiEntry: ${index.multiEntry}`);\n }\n indexEntries.push(`readonly ${serializeObjectKey(indexName)}: { ${indexParts.join(\"; \")} }`);\n }\n storeParts.push(`readonly indexes: { ${indexEntries.join(\"; \")} }`);\n } else {\n storeParts.push(`readonly indexes: Record<string, never>`);\n }\n\n stores.push(`readonly ${serializeObjectKey(storeName)}: { ${storeParts.join(\"; \")} }`);\n }\n\n const storesType = stores.length > 0 ? `{ ${stores.join(\"; \")} }` : \"Record<string, never>\";\n\n // v0.12.0: StorageBase now requires `namespaces`. Generate the literal type from\n // the contract's storage namespaces so the emitted storage type satisfies StorageBase.\n const nsEntries: string[] = [];\n for (const [nsId, ns] of Object.entries(\n (storage as unknown as { namespaces?: Record<string, { id: string; entries?: Record<string, unknown> }> })\n .namespaces ?? {}\n ).sort(([a], [b]) => a.localeCompare(b))) {\n const entriesObj = ns.entries ?? {};\n const entriesType = Object.keys(entriesObj).length === 0 ? \"Record<string, never>\" : serializeValue(entriesObj);\n nsEntries.push(\n `readonly ${serializeObjectKey(nsId)}: { readonly id: ${serializeValue(ns.id)}; readonly entries: ${entriesType} }`\n );\n }\n const namespacesType = nsEntries.length > 0 ? `{ ${nsEntries.join(\"; \")} }` : \"Record<string, never>\";\n\n return `{ readonly stores: ${storesType}; readonly namespaces: ${namespacesType}; readonly storageHash: ${storageHashTypeName} }`;\n },\n\n /**\n * Serializes the per-model storage metadata into a TypeScript type literal.\n * The result is used as the `storage` field of the model's entry in the\n * generated `models` type.\n *\n * For IDB, each model maps to exactly one object store:\n * ```ts\n * { readonly storeName: '<name>'; readonly keyPath: '<field>' }\n * ```\n *\n * @param _modelName - The Prisma model name (unused; provided for API symmetry).\n * @param model - The resolved `ContractModel` whose `storage` is cast to\n * {@link IdbModelStorage}.\n */\n generateModelStorageType(_modelName: string, model: ContractModel): string {\n const idbModel = model as ContractModel<IdbModelStorage>;\n return [\n `{ readonly storeName: ${serializeValue(idbModel.storage.storeName)}`,\n `readonly keyPath: ${serializeValue(idbModel.storage.keyPath)} }`,\n ].join(\"; \");\n },\n\n /**\n * Returns the `import type` lines prepended to `contract.d.ts` that bring\n * IDB-family types into scope.\n *\n * Imports {@link IdbContractWithTypeMaps} (the phantom wrapper used by\n * {@link getContractWrapper}) and {@link IdbTypeMaps} (used by\n * {@link getTypeMapsExpression}).\n */\n getFamilyImports(): string[] {\n return [\"import type { IdbContractWithTypeMaps, IdbTypeMaps } from '@prisma-idb/target-idb/pack';\"];\n },\n\n /**\n * Returns additional type alias declarations appended to `contract.d.ts`\n * after the codec and operation type aliases.\n *\n * For IDB this exports `LaneCodecTypes` (used by the generated client to\n * look up codec input/output pairs) as a simple alias of the assembled\n * `CodecTypes`. IDB has no query-operation types, so no further aliases\n * are needed.\n */\n getFamilyTypeAliases(): string {\n return \"export type LaneCodecTypes = CodecTypes;\";\n },\n\n /**\n * Returns the TypeScript expression for the `TypeMaps<...>` generic\n * instantiation written into `contract.d.ts`.\n *\n * IDB has no operation types or query-operation types, so the expression\n * uses the three-argument form of {@link IdbTypeMaps}.\n */\n getTypeMapsExpression(): string {\n return \"IdbTypeMaps<CodecTypes, FieldOutputTypes, FieldInputTypes>\";\n },\n\n /**\n * Returns the final `export type Contract = ...` declaration and convenience\n * re-exports derived from it.\n *\n * @param contractBaseName - Emitter-provided name of the base contract type\n * (always `'ContractBase'`).\n * @param typeMapsName - Emitter-provided name of the assembled TypeMaps type\n * (always `'TypeMaps'`).\n */\n getContractWrapper(contractBaseName: string, typeMapsName: string): string {\n return [\n `export type Contract = IdbContractWithTypeMaps<${contractBaseName}, ${typeMapsName}>;`,\n \"\",\n \"export type Stores = Contract['storage']['stores'];\",\n ].join(\"\\n\");\n },\n} as const satisfies EmissionSpi;\n","import {\n VERIFY_CODE_SCHEMA_FAILURE,\n type SchemaDiffIssue,\n type VerifyDatabaseSchemaResult,\n} from \"@prisma/orm-framework/components/control\";\nimport type { IdbContract } from \"./validate\";\nimport type { IdbIndexIR, IdbSchemaIR, IdbStoreIR } from \"./schema-ir\";\n\n/**\n * IDB's own presentation tree for `db verify` output. The framework's\n * `VerifyDatabaseSchemaResult` (0.16.0, TML-3028/#921/#992) dropped the\n * shared verification-tree vocabulary (`SchemaVerificationNode`) in favor of\n * a flat `SchemaDiffIssue[]` produced by a generic `DiffableNode` tree\n * differ. IDB's schema is only two levels deep (store → index), so adopting\n * that generic differ isn't worth the ceremony — this keeps the richer\n * pass/warn/fail tree as an IDB-local addition on top of the (still\n * satisfied) `schema.issues` contract.\n */\ninterface SchemaVerificationNode {\n readonly status: \"pass\" | \"warn\" | \"fail\";\n readonly kind: string;\n readonly name: string;\n readonly contractPath: string;\n readonly code: string;\n readonly message: string;\n readonly expected: unknown;\n readonly actual: unknown;\n readonly children: readonly SchemaVerificationNode[];\n}\n\n/** IDB's issue shape, extended with `path` so it satisfies `SchemaDiffIssue`. */\ntype SchemaIssue = SchemaDiffIssue & {\n readonly kind: string;\n readonly table: string;\n readonly column?: string;\n readonly indexOrConstraint?: string;\n readonly message: string;\n};\n\n/**\n * `verifyIdbSchema`'s actual return shape: a strict superset of\n * `VerifyDatabaseSchemaResult` that keeps the pass/warn/fail tree and node\n * counts as IDB-local additions. Assignable anywhere a plain\n * `VerifyDatabaseSchemaResult` is expected (see `control-instance.ts`).\n */\nexport interface IdbVerifyDatabaseSchemaResult extends Omit<VerifyDatabaseSchemaResult, \"schema\"> {\n readonly schema: {\n readonly issues: readonly SchemaIssue[];\n readonly warnings?: { readonly issues: readonly SchemaIssue[] };\n readonly root: SchemaVerificationNode;\n readonly counts: {\n readonly pass: number;\n readonly warn: number;\n readonly fail: number;\n readonly totalNodes: number;\n };\n };\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction passNode(\n kind: string,\n name: string,\n contractPath: string,\n children: readonly SchemaVerificationNode[] = []\n): SchemaVerificationNode {\n return {\n status: \"pass\",\n kind,\n name,\n contractPath,\n code: \"ok\",\n message: `${name} matches`,\n expected: null,\n actual: null,\n children,\n };\n}\n\nfunction failNode(\n kind: string,\n name: string,\n contractPath: string,\n code: string,\n message: string,\n expected: unknown,\n actual: unknown,\n children: readonly SchemaVerificationNode[] = []\n): SchemaVerificationNode {\n return {\n status: \"fail\",\n kind,\n name,\n contractPath,\n code,\n message,\n expected,\n actual,\n children,\n };\n}\n\nfunction warnNode(\n kind: string,\n name: string,\n contractPath: string,\n code: string,\n message: string,\n expected: unknown,\n actual: unknown,\n children: readonly SchemaVerificationNode[] = []\n): SchemaVerificationNode {\n return {\n status: \"warn\",\n kind,\n name,\n contractPath,\n code,\n message,\n expected,\n actual,\n children,\n };\n}\n\nfunction countStatuses(nodes: readonly SchemaVerificationNode[]): {\n pass: number;\n warn: number;\n fail: number;\n totalNodes: number;\n} {\n let pass = 0,\n warn = 0,\n fail = 0,\n totalNodes = 0;\n const stack = [...nodes];\n while (stack.length > 0) {\n const node = stack.pop()!;\n totalNodes++;\n if (node.status === \"pass\") pass++;\n else if (node.status === \"warn\") warn++;\n else fail++;\n stack.push(...node.children);\n }\n return { pass, warn, fail, totalNodes };\n}\n\n// ── Per-index verification ────────────────────────────────────────────────────\n\nfunction verifyIndex(\n indexName: string,\n contractIndex: { keyPath: string; unique: boolean; multiEntry?: boolean },\n actualIndex: IdbIndexIR | undefined,\n storeName: string,\n storePath: string,\n issues: SchemaIssue[]\n): SchemaVerificationNode {\n const indexPath = `${storePath}.indexes.${indexName}`;\n\n if (actualIndex === undefined) {\n const msg = `Index \"${indexName}\" defined in contract is missing from manifest schema`;\n issues.push({\n kind: \"missing_column\",\n table: storeName,\n column: indexName,\n message: msg,\n path: [storeName, indexName],\n });\n return failNode(\"index\", indexName, indexPath, \"missing_column\", msg, contractIndex, undefined);\n }\n\n const children: SchemaVerificationNode[] = [];\n\n // keyPath\n if (contractIndex.keyPath !== actualIndex.keyPath) {\n const msg = `Index \"${indexName}\" keyPath mismatch: expected \"${contractIndex.keyPath}\", got \"${actualIndex.keyPath}\"`;\n issues.push({\n kind: \"index_mismatch\",\n table: storeName,\n indexOrConstraint: indexName,\n message: msg,\n path: [storeName, indexName],\n });\n children.push(\n failNode(\n \"field\",\n \"keyPath\",\n `${indexPath}.keyPath`,\n \"index_mismatch\",\n msg,\n contractIndex.keyPath,\n actualIndex.keyPath\n )\n );\n } else {\n children.push(passNode(\"field\", \"keyPath\", `${indexPath}.keyPath`));\n }\n\n // unique — treat undefined as false to match how multiEntry is handled\n if ((contractIndex.unique ?? false) !== (actualIndex.unique ?? false)) {\n const msg = `Index \"${indexName}\" unique mismatch: expected ${contractIndex.unique}, got ${actualIndex.unique}`;\n issues.push({\n kind: \"index_mismatch\",\n table: storeName,\n indexOrConstraint: indexName,\n message: msg,\n path: [storeName, indexName],\n });\n children.push(\n failNode(\n \"field\",\n \"unique\",\n `${indexPath}.unique`,\n \"index_mismatch\",\n msg,\n contractIndex.unique,\n actualIndex.unique\n )\n );\n } else {\n children.push(passNode(\"field\", \"unique\", `${indexPath}.unique`));\n }\n\n // multiEntry (only check if contract has it set to true)\n const contractME = contractIndex.multiEntry ?? false;\n const actualME = actualIndex.multiEntry ?? false;\n if (contractME !== actualME) {\n const msg = `Index \"${indexName}\" multiEntry mismatch: expected ${contractME}, got ${actualME}`;\n issues.push({\n kind: \"index_mismatch\",\n table: storeName,\n indexOrConstraint: indexName,\n message: msg,\n path: [storeName, indexName],\n });\n children.push(\n failNode(\"field\", \"multiEntry\", `${indexPath}.multiEntry`, \"index_mismatch\", msg, contractME, actualME)\n );\n } else if (contractME) {\n children.push(passNode(\"field\", \"multiEntry\", `${indexPath}.multiEntry`));\n }\n\n // Extra indexes in manifest not in contract\n // (handled at store level — see verifyStore)\n\n const hasFail = children.some((c) => c.status === \"fail\");\n if (hasFail) {\n return failNode(\n \"index\",\n indexName,\n indexPath,\n \"index_mismatch\",\n `Index \"${indexName}\" has mismatches`,\n null,\n null,\n children\n );\n }\n return passNode(\"index\", indexName, indexPath, children);\n}\n\n// ── Per-store verification ────────────────────────────────────────────────────\n\nfunction verifyStore(\n storeName: string,\n contractStore: {\n keyPath: string;\n autoIncrement?: boolean;\n indexes?: Record<string, { keyPath: string; unique: boolean; multiEntry?: boolean }>;\n },\n actualStore: IdbStoreIR | undefined,\n strict: boolean,\n issues: SchemaIssue[]\n): SchemaVerificationNode {\n const storePath = `storage.stores.${storeName}`;\n\n if (actualStore === undefined) {\n const msg = `Object store \"${storeName}\" defined in contract is missing from manifest schema`;\n issues.push({ kind: \"missing_table\", table: storeName, message: msg, path: [storeName] });\n return failNode(\"collection\", storeName, storePath, \"missing_table\", msg, contractStore, undefined);\n }\n\n const children: SchemaVerificationNode[] = [];\n\n // keyPath\n if (contractStore.keyPath !== actualStore.keyPath) {\n const msg = `Store \"${storeName}\" keyPath mismatch: expected \"${contractStore.keyPath}\", got \"${actualStore.keyPath}\"`;\n issues.push({ kind: \"primary_key_mismatch\", table: storeName, message: msg, path: [storeName] });\n children.push(\n failNode(\n \"field\",\n \"keyPath\",\n `${storePath}.keyPath`,\n \"primary_key_mismatch\",\n msg,\n contractStore.keyPath,\n actualStore.keyPath\n )\n );\n } else {\n children.push(passNode(\"field\", \"keyPath\", `${storePath}.keyPath`));\n }\n\n // autoIncrement\n const contractAI = contractStore.autoIncrement ?? false;\n const actualAI = actualStore.autoIncrement ?? false;\n if (contractAI !== actualAI) {\n const msg = `Store \"${storeName}\" autoIncrement mismatch: expected ${contractAI}, got ${actualAI}`;\n issues.push({ kind: \"type_mismatch\", table: storeName, message: msg, path: [storeName] });\n children.push(\n failNode(\"field\", \"autoIncrement\", `${storePath}.autoIncrement`, \"type_mismatch\", msg, contractAI, actualAI)\n );\n } else if (contractAI) {\n children.push(passNode(\"field\", \"autoIncrement\", `${storePath}.autoIncrement`));\n }\n\n // indexes\n const contractIndexes = contractStore.indexes ?? {};\n const actualIndexes = actualStore.indexes ?? {};\n\n for (const [indexName, contractIndex] of Object.entries(contractIndexes)) {\n const actualIndex = actualIndexes[indexName];\n children.push(verifyIndex(indexName, contractIndex, actualIndex, storeName, storePath, issues));\n }\n\n // extra indexes in manifest (only relevant in strict mode)\n for (const indexName of Object.keys(actualIndexes)) {\n if (!(indexName in contractIndexes)) {\n const msg = `Index \"${indexName}\" exists in manifest but is not in contract`;\n if (strict) {\n issues.push({\n kind: \"extra_index\",\n table: storeName,\n indexOrConstraint: indexName,\n message: msg,\n path: [storeName, indexName],\n });\n children.push(\n failNode(\n \"index\",\n indexName,\n `${storePath}.indexes.${indexName}`,\n \"extra_index\",\n msg,\n undefined,\n actualIndexes[indexName]\n )\n );\n } else {\n children.push(\n warnNode(\n \"index\",\n indexName,\n `${storePath}.indexes.${indexName}`,\n \"extra_index\",\n msg,\n undefined,\n actualIndexes[indexName]\n )\n );\n }\n }\n }\n\n const hasFail = children.some((c) => c.status === \"fail\");\n if (hasFail) {\n return failNode(\n \"collection\",\n storeName,\n storePath,\n \"schema_mismatch\",\n `Store \"${storeName}\" has schema issues`,\n null,\n null,\n children\n );\n }\n const hasWarn = children.some((c) => c.status === \"warn\");\n if (hasWarn) {\n return {\n status: \"warn\",\n kind: \"collection\",\n name: storeName,\n contractPath: storePath,\n code: \"ok\",\n message: `Store \"${storeName}\" has warnings`,\n expected: null,\n actual: null,\n children,\n };\n }\n return passNode(\"collection\", storeName, storePath, children);\n}\n\n// ── Main entry point ──────────────────────────────────────────────────────────\n\n/**\n * Pure, synchronous schema verification for IndexedDB.\n *\n * Compares the stores and indexes defined in the contract against the manifest\n * schema (which represents the actual IndexedDB database). Returns a\n * {@link VerifyDatabaseSchemaResult} that callers can inspect for issues.\n *\n * Does not perform any I/O — all inputs must already be available.\n *\n * @param contract - Validated IDB contract (what we expect the schema to be).\n * @param schema - Schema IR from the manifest (what the schema actually is).\n * @param strict - When `true`, extra stores/indexes in the manifest are failures.\n * @param options - Optional context paths for metadata / error reporting.\n */\nexport function verifyIdbSchema(\n contract: IdbContract,\n schema: IdbSchemaIR,\n strict: boolean\n): IdbVerifyDatabaseSchemaResult {\n const start = Date.now();\n const issues: SchemaIssue[] = [];\n const storeNodes: SchemaVerificationNode[] = [];\n\n const contractStores = (\n contract.storage as {\n stores: Record<\n string,\n {\n keyPath: string;\n autoIncrement?: boolean;\n indexes?: Record<string, { keyPath: string; unique: boolean; multiEntry?: boolean }>;\n }\n >;\n }\n ).stores;\n const actualStores = schema.stores;\n\n // Verify each store defined in the contract.\n for (const [storeName, contractStore] of Object.entries(contractStores)) {\n const actualStore: IdbStoreIR | undefined = actualStores[storeName];\n storeNodes.push(verifyStore(storeName, contractStore, actualStore, strict, issues));\n }\n\n // Extra stores in manifest (only in strict mode as failures, otherwise warnings).\n for (const [storeName, actualStore] of Object.entries(actualStores)) {\n if (!(storeName in contractStores)) {\n const msg = `Object store \"${storeName}\" exists in manifest but is not in contract`;\n if (strict) {\n issues.push({ kind: \"extra_table\", table: storeName, message: msg, path: [storeName] });\n storeNodes.push(\n failNode(\"collection\", storeName, `storage.stores.${storeName}`, \"extra_table\", msg, undefined, actualStore)\n );\n } else {\n storeNodes.push(\n warnNode(\"collection\", storeName, `storage.stores.${storeName}`, \"extra_table\", msg, undefined, actualStore)\n );\n }\n }\n }\n\n const root: SchemaVerificationNode = {\n status: storeNodes.some((n) => n.status === \"fail\")\n ? \"fail\"\n : storeNodes.some((n) => n.status === \"warn\")\n ? \"warn\"\n : \"pass\",\n kind: \"root\",\n name: \"idb\",\n contractPath: \"storage.stores\",\n code: issues.length === 0 ? \"ok\" : \"schema_mismatch\",\n message: issues.length === 0 ? \"All stores and indexes match\" : `${issues.length} schema issue(s) found`,\n expected: null,\n actual: null,\n children: storeNodes,\n };\n\n // Count statuses including root.\n const allNodes = [root, ...storeNodes];\n const counts = countStatuses(allNodes);\n const ok = root.status !== \"fail\";\n\n const storageHash = (contract.storage as { storageHash: string }).storageHash;\n const profileHash = (contract as { profileHash?: string }).profileHash;\n\n // exactOptionalPropertyTypes: build optional sub-objects conditionally.\n const contractField =\n profileHash !== undefined ? ({ storageHash, profileHash } as const) : ({ storageHash } as const);\n\n const metaField = {\n strict,\n };\n\n const sharedResult = {\n summary: ok ? \"Schema verification passed\" : `Schema verification failed: ${issues.length} issue(s)`,\n contract: contractField,\n target: {\n expected: \"idb\",\n },\n schema: {\n issues,\n root,\n counts,\n },\n meta: metaField,\n timings: { total: Date.now() - start },\n };\n\n if (ok) {\n return { ok: true, ...sharedResult };\n }\n return { ok: false, code: VERIFY_CODE_SCHEMA_FAILURE, ...sharedResult };\n}\n","import type { Contract, ContractMarkerRecord, LedgerEntryRecord } from \"@prisma/orm-framework/contract/types\";\nimport type { TargetBoundComponentDescriptor } from \"@prisma/orm-framework/components/components\";\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n ControlStack,\n SignDatabaseResult,\n VerifyDatabaseResult,\n VerifyDatabaseSchemaResult,\n} from \"@prisma/orm-framework/components/control\";\nimport { VERIFY_CODE_TARGET_MISMATCH } from \"@prisma/orm-framework/components/control\";\nimport type { IdbSchemaIR } from \"./schema-ir\";\nimport { verifyIdbSchema } from \"./schema-verify\";\nimport { validateContract } from \"./validate\";\n\n// ── Structured refusal helpers ────────────────────────────────────────────────\n// IndexedDB only exists in the browser; the CLI runs in Node.js. The control\n// plane's read/write methods therefore return structured failures rather\n// than reading from any kind of file-backed shadow.\n//\n// The refusal code shares a prefix (`IDB-CLI-`) so callers can branch on the\n// family without parsing the summary text.\n\nconst REFUSAL_CODE = \"IDB-CLI-UNSUPPORTED\" as const;\n\n/**\n * Single-string refusal message baked into `summary` because the framework\n * `VerifyDatabaseResult` / `SignDatabaseResult` `meta` field is strict-shaped\n * (`{ contractPath, configPath? }`) and doesn't accept extra explanation\n * fields. The summary is the only free-form text field on every result.\n */\nfunction refusalSummary(action: \"verified\" | \"signed\"): string {\n return (\n `IndexedDB cannot be ${action} from the CLI. ` +\n \"IndexedDB only exists in the browser; the CLI runs in Node.js, so there \" +\n \"is no live database for it to inspect or update. Author migrations with \" +\n \"`prisma-idb migration plan`, validate the chain with \" +\n \"`prisma-idb migration preflight`, and let `createAutoMigratingIdbClient` apply \" +\n \"them the next time the app opens in a browser.\"\n );\n}\n\n// ── exactOptionalPropertyTypes helpers ───────────────────────────────────────\n\nfunction contractInfo(storageHash: string, profileHash: string | undefined) {\n return profileHash !== undefined ? ({ storageHash, profileHash } as const) : ({ storageHash } as const);\n}\n\nfunction verifyMeta(contractPath: string, configPath: string | undefined) {\n return configPath !== undefined ? ({ contractPath, configPath } as const) : ({ contractPath } as const);\n}\n\nfunction signMeta(contractPath: string, configPath: string | undefined) {\n return configPath !== undefined ? ({ contractPath, configPath } as const) : ({ contractPath } as const);\n}\n\n/** Fully-typed IDB control family instance returned by {@link createIdbFamilyInstance}. */\nexport type IdbControlFamilyInstance = ControlFamilyInstance<\"idb\", IdbSchemaIR>;\n\n/**\n * Creates an IDB control family instance for the given control stack.\n *\n * **CLI surface — refusals**: IndexedDB is a browser API, so the CLI cannot\n * read or write the live database. Every method that would normally talk to a\n * database (`verify`, `sign`, `readMarker`, `readAllMarkers`, `introspect`)\n * returns a structured refusal pointing the user at the contract-space\n * authoring + preflight workflow.\n *\n * The CLI-side `db init`, `db update`, and `db verify` commands therefore\n * surface a uniform `IDB-CLI-UNSUPPORTED` envelope rather than silently\n * succeeding.\n *\n * **Active surface**: `deserializeContract` (pure) and `verifySchema` (pure\n * function over an in-memory `IdbSchemaIR`) continue to work — neither\n * needs a live database.\n *\n * @param _stack - The assembled control stack (unused; IDB has no adapter/extension layer).\n */\nexport function createIdbFamilyInstance(_stack: ControlStack<\"idb\", string>): IdbControlFamilyInstance {\n return {\n familyId: \"idb\",\n\n // ── deserializeContract (active, pure) ─────────────────────────────────\n\n deserializeContract(contractJson: unknown): Contract {\n return validateContract(contractJson) as Contract;\n },\n\n // ── verify (CLI refusal) ───────────────────────────────────────────────\n\n async verify(options: {\n readonly driver: ControlDriverInstance<\"idb\", string>;\n readonly contract: unknown;\n readonly expectedTargetId: string;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<VerifyDatabaseResult> {\n const start = Date.now();\n const contract = validateContract(options.contract);\n const storageHash = (contract.storage as { storageHash: string }).storageHash;\n const profileHash = (contract as { profileHash?: string }).profileHash;\n\n if (options.expectedTargetId !== \"idb\") {\n return {\n ok: false,\n code: VERIFY_CODE_TARGET_MISMATCH,\n summary: `Target mismatch: expected \"idb\", got \"${options.expectedTargetId}\"`,\n contract: contractInfo(storageHash, profileHash),\n target: { expected: options.expectedTargetId, actual: \"idb\" },\n meta: verifyMeta(options.contractPath, options.configPath),\n timings: { total: Date.now() - start },\n };\n }\n\n return {\n ok: false,\n code: REFUSAL_CODE,\n summary: refusalSummary(\"verified\"),\n contract: contractInfo(storageHash, profileHash),\n target: { expected: \"idb\", actual: \"idb\" },\n meta: verifyMeta(options.contractPath, options.configPath),\n timings: { total: Date.now() - start },\n };\n },\n\n // ── verifySchema (active, pure) ────────────────────────────────────────\n\n verifySchema(options: {\n readonly contract: unknown;\n readonly schema: IdbSchemaIR;\n readonly strict: boolean;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<\"idb\", string>>;\n }): VerifyDatabaseSchemaResult {\n const contract = validateContract(options.contract);\n return verifyIdbSchema(contract, options.schema, options.strict);\n },\n\n // ── sign (CLI refusal) ──────────────────────────────────────────────────\n\n async sign(options: {\n readonly driver: ControlDriverInstance<\"idb\", string>;\n readonly contract: unknown;\n readonly contractPath: string;\n readonly configPath?: string;\n }): Promise<SignDatabaseResult> {\n const start = Date.now();\n const contract = validateContract(options.contract);\n const storageHash = (contract.storage as { storageHash: string }).storageHash;\n const profileHash = (contract as { profileHash?: string }).profileHash ?? \"\";\n\n return {\n ok: false,\n summary: refusalSummary(\"signed\"),\n contract: contractInfo(storageHash, profileHash),\n target: { expected: \"idb\", actual: \"idb\" },\n // `SignDatabaseResult.marker` is required; carry an \"untouched\"\n // record so callers don't crash on null-deref.\n marker: { created: false, updated: false },\n meta: signMeta(options.contractPath, options.configPath),\n timings: { total: Date.now() - start },\n };\n },\n\n // ── readMarker / readAllMarkers (CLI refusal: return null/empty) ───────\n // The framework typing requires `ContractMarkerRecord | null` here, not a\n // structured envelope. Returning `null` is the existing semantics for\n // \"no marker on file\"; we keep that contract so the CLI's downstream\n // logic (e.g. db init's \"create new marker\" path) treats IDB as\n // perpetually empty rather than erroring out of band.\n\n async readMarker(_options: {\n readonly driver: ControlDriverInstance<\"idb\", string>;\n readonly space: string;\n }): Promise<ContractMarkerRecord | null> {\n return null;\n },\n\n async readAllMarkers(_options: {\n readonly driver: ControlDriverInstance<\"idb\", string>;\n }): Promise<ReadonlyMap<string, ContractMarkerRecord>> {\n return new Map<string, ContractMarkerRecord>();\n },\n\n async readLedger(_options: {\n readonly driver: ControlDriverInstance<\"idb\", string>;\n readonly space?: string;\n }): Promise<readonly LedgerEntryRecord[]> {\n return [];\n },\n\n // ── introspect (CLI refusal: return empty schema) ──────────────────────\n // The framework typing requires `IdbSchemaIR` (not a result envelope), so\n // we return an empty schema. The structural refusal is communicated via\n // the sibling `verify`/`sign` methods that the CLI actually surfaces to\n // the user when running `db init` / `db update` / `db verify` against\n // an IDB project.\n\n async introspect(_options: {\n readonly driver: ControlDriverInstance<\"idb\", string>;\n readonly contract?: unknown;\n }): Promise<IdbSchemaIR> {\n return { stores: {} };\n },\n };\n}\n\nexport type { ControlDriverInstance, SignDatabaseResult, VerifyDatabaseResult, VerifyDatabaseSchemaResult };\nexport type { IdbSchemaIR } from \"./schema-ir\";\n","import type { ControlFamilyDescriptor, ControlStack } from \"@prisma/orm-framework/components/control\";\nimport { idbEmission } from \"./emission\";\nimport { type IdbControlFamilyInstance, createIdbFamilyInstance } from \"./control-instance\";\n\n/**\n * IDB family descriptor — the control-plane entry point for the IDB family.\n *\n * Registered in a Prisma 8 config file under the `family` key:\n * ```ts\n * import idb from '@prisma-idb/family-idb/control';\n * export default defineConfig({ family: idb, ... });\n * ```\n *\n * **Responsibilities:**\n * - Carries the {@link idbEmission} plugin used by `prisma contract emit`\n * to generate `contract.d.ts`.\n * - Acts as a factory: `create(stack)` returns an {@link IdbControlFamilyInstance}\n * with domain-action methods consumed by the CLI.\n */\nexport class IdbFamilyDescriptor implements ControlFamilyDescriptor<\"idb\", IdbControlFamilyInstance> {\n readonly kind = \"family\" as const;\n readonly id = \"idb\";\n readonly familyId = \"idb\" as const;\n readonly version = \"0.0.1\";\n readonly emission = idbEmission;\n\n /**\n * Creates an {@link IdbControlFamilyInstance} for the given control stack.\n *\n * The instance is a plain object whose methods implement the CLI domain\n * actions (validateContract, verify, sign, etc.).\n */\n create(stack: ControlStack<\"idb\", string>): IdbControlFamilyInstance {\n return createIdbFamilyInstance(stack);\n }\n}\n","import { IdbFamilyDescriptor } from \"../core/control-descriptor\";\n\nexport type { IdbControlFamilyInstance } from \"../core/control-instance\";\nexport type { IdbExtensionSpace } from \"../core/extension-space\";\nexport type { IdbSchemaIR, IdbStoreIR, IdbIndexIR } from \"../core/schema-ir\";\nexport type { IdbContract } from \"../core/validate\";\n\nexport default new IdbFamilyDescriptor();\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ //#region src/exports/pack.d.ts
2
+ declare const idbFamilyPack: {
3
+ readonly kind: "family";
4
+ readonly id: "idb";
5
+ readonly familyId: "idb";
6
+ readonly version: "0.0.1";
7
+ };
8
+ //#endregion
9
+ export { idbFamilyPack as default };
10
+ //# sourceMappingURL=pack.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.d.mts","names":[],"sources":["../../src/exports/pack.ts"],"mappings":";cAEM"}
@@ -0,0 +1,11 @@
1
+ //#region src/exports/pack.ts
2
+ const idbFamilyPack = {
3
+ kind: "family",
4
+ id: "idb",
5
+ familyId: "idb",
6
+ version: "0.0.1"
7
+ };
8
+ //#endregion
9
+ export { idbFamilyPack as default };
10
+
11
+ //# sourceMappingURL=pack.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.mjs","names":[],"sources":["../../src/exports/pack.ts"],"sourcesContent":["import type { FamilyPackRef } from \"@prisma/orm-framework/components/components\";\n\nconst idbFamilyPack = {\n kind: \"family\",\n id: \"idb\",\n familyId: \"idb\",\n version: \"0.0.1\",\n} as const satisfies FamilyPackRef<\"idb\">;\n\nexport default idbFamilyPack;\n"],"mappings":";AAEA,MAAM,gBAAgB;CACpB,MAAM;CACN,IAAI;CACJ,UAAU;CACV,SAAS;AACX"}
@@ -0,0 +1,330 @@
1
+ import { t as chainOrderByMetadata } from "./chain-order-CaHXo5jz.mjs";
2
+ import { copyFile, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
3
+ import { join, relative } from "pathe";
4
+ import { randomBytes } from "node:crypto";
5
+ import { IdbMigrationPlanner, contractToIdbSchema, renderMigrationTs } from "@prisma-idb/target-idb/migration";
6
+ import { keepInternalSpecifiers } from "@prisma/orm-framework/components/emission";
7
+ import { computeMigrationHash } from "@prisma/orm-toolchain/migration-tools/hash";
8
+ import { formatMigrationDirName } from "@prisma/orm-toolchain/migration-tools/io";
9
+ import { deriveProvidedInvariants } from "@prisma/orm-toolchain/migration-tools/invariants";
10
+ //#region src/core/migration-plan.ts
11
+ /**
12
+ * Plans the next migration package for an IDB project, auto-detecting
13
+ * whether this is the first ("greenfield"/baseline, `from: null`) or an
14
+ * incremental migration (diffed against the current head) by checking
15
+ * whether `<migrationsDir>/app/` (or `<migrationsDir>/` for an extension
16
+ * space) already contains any migration packages.
17
+ *
18
+ * This mirrors `prisma-idb migration plan`'s job — one command instead of
19
+ * a baseline/incremental split the caller has to pick correctly — but
20
+ * *not* its `from: null`-unless-ref default: that default exists there
21
+ * because a server database's on-disk chain can diverge from a live, separately
22
+ * migrated database (tracked via the `db` ref and advanced by migration apply).
23
+ * IDB has no such divergence — there is no
24
+ * `db update`/`migrate` step on the Node side at all (`db init`/`db
25
+ * update`/`db verify` are refusal-only for IDB), so the on-disk chain is
26
+ * the only state that will ever exist; the browser runtime applies it
27
+ * wholesale at open time. Trusting the on-disk chain as the auto-detected
28
+ * `from` is therefore the safety-equivalent of the framework's ref system
29
+ * for this target, not a corner-cut.
30
+ *
31
+ * Exit codes: 0 on success; 1 on a planning/consistency failure; 2 if
32
+ * `--name` was required (incremental mode) and not supplied.
33
+ */
34
+ async function migrationPlan(opts) {
35
+ const out = opts.out ?? ((line) => process.stdout.write(line));
36
+ const err = opts.err ?? ((line) => process.stderr.write(line));
37
+ const spaceId = opts.spaceId ?? "app";
38
+ if (spaceId === "snapshots") {
39
+ err("migration plan: spaceId \"snapshots\" is reserved for the contract-snapshot store and cannot be used.\n");
40
+ return 1;
41
+ }
42
+ const isAppSpace = spaceId === "app";
43
+ const targetDir = isAppSpace ? join(opts.migrationsDir, "app") : opts.migrationsDir;
44
+ const dirLabel = `${relative(process.cwd(), targetDir)}/`;
45
+ let existingDirs = [];
46
+ try {
47
+ existingDirs = (await readdir(targetDir, { withFileTypes: true })).filter((e) => e.isDirectory()).filter((e) => e.name !== "refs" && e.name !== "app" && e.name !== "snapshots").map((e) => e.name);
48
+ } catch (err_) {
49
+ if (err_.code !== "ENOENT") throw err_;
50
+ }
51
+ const ctx = {
52
+ ...opts,
53
+ out,
54
+ err,
55
+ spaceId,
56
+ isAppSpace,
57
+ targetDir,
58
+ dirLabel
59
+ };
60
+ if (existingDirs.length === 0) {
61
+ err(`migration plan: no existing migrations found under ${dirLabel} — generating a full baseline.\nIf you expected an incremental migration, check --migrations-dir / your config's migrations.dir.
62
+ `);
63
+ return planGreenfield(ctx);
64
+ }
65
+ return planIncremental(ctx, existingDirs);
66
+ }
67
+ async function planGreenfield(ctx) {
68
+ const name = ctx.name ?? "baseline";
69
+ let contractRaw;
70
+ let contractJson;
71
+ try {
72
+ contractRaw = await readFile(ctx.contractPath, "utf-8");
73
+ contractJson = JSON.parse(contractRaw);
74
+ } catch (err) {
75
+ if (err.code === "ENOENT") {
76
+ ctx.err(`migration plan: contract.json not found at ${ctx.contractPath}.\nRun \`prisma contract emit\` first to generate the contract file.
77
+ `);
78
+ return 1;
79
+ }
80
+ throw err;
81
+ }
82
+ const planResult = new IdbMigrationPlanner().plan({
83
+ contract: contractJson,
84
+ schema: null,
85
+ policy: { allowedOperationClasses: [
86
+ "additive",
87
+ "widening",
88
+ "destructive",
89
+ "data"
90
+ ] },
91
+ fromContract: null,
92
+ frameworkComponents: [],
93
+ spaceId: ctx.spaceId
94
+ });
95
+ if (planResult.kind === "failure") {
96
+ ctx.err("migration plan: migration planning failed:\n" + planResult.conflicts.map((c) => ` ${c.summary}`).join("\n") + "\n");
97
+ return 1;
98
+ }
99
+ const plan = planResult.plan;
100
+ const ops = ctx.isAppSpace ? plan.operations : plan.operations.filter((op) => !(op.kind === "createObjectStore" && op.storeName === "_prisma_next_marker"));
101
+ const toHash = plan.destination.storageHash;
102
+ const timestamp = /* @__PURE__ */ new Date();
103
+ const dirName = formatMigrationDirName(timestamp, name);
104
+ const providedInvariants = Array.from(deriveProvidedInvariants(ops));
105
+ const baseMetadata = {
106
+ from: null,
107
+ to: toHash,
108
+ providedInvariants,
109
+ createdAt: timestamp.toISOString()
110
+ };
111
+ const migrationHash = computeMigrationHash(baseMetadata, ops);
112
+ await writeMigrationPackage(ctx, {
113
+ dirName,
114
+ ops,
115
+ metadata: {
116
+ ...baseMetadata,
117
+ migrationHash
118
+ },
119
+ migrationTsContent: renderMigrationTs({
120
+ fromHash: null,
121
+ toHash,
122
+ ops
123
+ }),
124
+ contractRaw,
125
+ providedInvariants,
126
+ fromLabel: "null (fresh database — creates all stores from scratch)",
127
+ toHash,
128
+ logVerb: "Generated baseline migration"
129
+ });
130
+ return 0;
131
+ }
132
+ async function planIncremental(ctx, existingDirs) {
133
+ if (ctx.name === void 0) {
134
+ ctx.err("migration plan: --name <slug> is required when generating an incremental migration.\n");
135
+ return 2;
136
+ }
137
+ const packages = /* @__PURE__ */ new Map();
138
+ for (const dirName of existingDirs) {
139
+ const metaPath = join(ctx.targetDir, dirName, "migration.json");
140
+ let metaRaw;
141
+ try {
142
+ metaRaw = await readFile(metaPath, "utf-8");
143
+ } catch (err) {
144
+ ctx.err(`migration plan: cannot read ${metaPath} — ${err instanceof Error ? err.message : String(err)}\n`);
145
+ return 1;
146
+ }
147
+ let meta;
148
+ try {
149
+ meta = JSON.parse(metaRaw);
150
+ } catch (err) {
151
+ ctx.err(`migration plan: malformed ${metaPath} — ${err instanceof Error ? err.message : String(err)}\n`);
152
+ return 1;
153
+ }
154
+ packages.set(dirName, {
155
+ dirName,
156
+ metadata: meta
157
+ });
158
+ }
159
+ let ordered;
160
+ try {
161
+ ordered = chainOrderByMetadata(packages);
162
+ } catch (err) {
163
+ ctx.err(`migration plan: migration chain is broken — ${err instanceof Error ? err.message : String(err)}\n`);
164
+ return 1;
165
+ }
166
+ const head = ordered[ordered.length - 1];
167
+ if (head === void 0) {
168
+ ctx.err("migration plan: no valid migration packages could be read.\n");
169
+ return 1;
170
+ }
171
+ const headSnapshotPath = join(ctx.migrationsDir, "snapshots", head.metadata.to, "contract.json");
172
+ let fromContractJson;
173
+ try {
174
+ const fromContractRaw = await readFile(headSnapshotPath, "utf-8");
175
+ fromContractJson = JSON.parse(fromContractRaw);
176
+ } catch (err) {
177
+ if (err.code === "ENOENT") {
178
+ ctx.err(`migration plan: head migration ${head.dirName} is inconsistent.\n migration.json claims to: ${head.metadata.to}\n but no store entry exists at ${relative(process.cwd(), headSnapshotPath)}\nRegenerate the chain, or restore migrations/snapshots/ from source control.
179
+ `);
180
+ return 1;
181
+ }
182
+ throw err;
183
+ }
184
+ let contractRaw;
185
+ let contractJson;
186
+ try {
187
+ contractRaw = await readFile(ctx.contractPath, "utf-8");
188
+ contractJson = JSON.parse(contractRaw);
189
+ } catch (err) {
190
+ if (err.code === "ENOENT") {
191
+ ctx.err(`migration plan: contract.json not found at ${ctx.contractPath}.\nRun \`prisma contract emit\` first to generate the contract file.
192
+ `);
193
+ return 1;
194
+ }
195
+ throw err;
196
+ }
197
+ const fromSchema = contractToIdbSchema(fromContractJson);
198
+ const planResult = new IdbMigrationPlanner().plan({
199
+ contract: contractJson,
200
+ schema: fromSchema,
201
+ policy: { allowedOperationClasses: [
202
+ "additive",
203
+ "widening",
204
+ "destructive",
205
+ "data"
206
+ ] },
207
+ fromContract: fromContractJson,
208
+ frameworkComponents: [],
209
+ spaceId: ctx.spaceId
210
+ });
211
+ if (planResult.kind === "failure") {
212
+ ctx.err("migration plan: migration planning failed:\n" + planResult.conflicts.map((c) => ` ${c.summary}`).join("\n") + "\n");
213
+ return 1;
214
+ }
215
+ const plan = planResult.plan;
216
+ const ops = plan.operations;
217
+ const fromHash = head.metadata.to;
218
+ const toHash = plan.destination.storageHash;
219
+ if (fromHash === toHash) {
220
+ ctx.out("migration plan: contract is unchanged since the last migration — nothing to do.\n");
221
+ return 0;
222
+ }
223
+ const timestamp = /* @__PURE__ */ new Date();
224
+ const dirName = formatMigrationDirName(timestamp, ctx.name);
225
+ const providedInvariants = Array.from(deriveProvidedInvariants(ops));
226
+ const baseMetadata = {
227
+ from: fromHash,
228
+ to: toHash,
229
+ providedInvariants,
230
+ createdAt: timestamp.toISOString()
231
+ };
232
+ const migrationHash = computeMigrationHash(baseMetadata, ops);
233
+ await writeMigrationPackage(ctx, {
234
+ dirName,
235
+ ops,
236
+ metadata: {
237
+ ...baseMetadata,
238
+ migrationHash
239
+ },
240
+ migrationTsContent: plan.renderTypeScript(keepInternalSpecifiers),
241
+ contractRaw,
242
+ providedInvariants,
243
+ fromLabel: fromHash,
244
+ toHash,
245
+ logVerb: "Generated migration"
246
+ });
247
+ return 0;
248
+ }
249
+ async function writeMigrationPackage(ctx, input) {
250
+ const packageDir = join(ctx.targetDir, input.dirName);
251
+ await mkdir(packageDir, { recursive: true });
252
+ await writeFile(join(packageDir, "ops.json"), JSON.stringify(input.ops, null, 2), "utf-8");
253
+ await writeFile(join(packageDir, "migration.json"), JSON.stringify(input.metadata, null, 2), "utf-8");
254
+ await writeFile(join(packageDir, "migration.ts"), input.migrationTsContent, "utf-8");
255
+ const contractDtsPath = ctx.contractPath.replace(/\.json$/i, ".d.ts");
256
+ await writeContractSnapshot(ctx.migrationsDir, input.toHash, input.contractRaw, contractDtsPath, ctx.err);
257
+ let extensionSpaceNote = "";
258
+ if (!ctx.isAppSpace) {
259
+ const refsDir = join(ctx.migrationsDir, "refs");
260
+ await mkdir(refsDir, { recursive: true });
261
+ await writeFile(join(refsDir, "head.json"), JSON.stringify({
262
+ hash: input.toHash,
263
+ invariants: input.providedInvariants
264
+ }, null, 2), "utf-8");
265
+ extensionSpaceNote = `\nAlso pinned migrations/refs/head.json → ${input.toHash}\nexports/control.ts already JSON-imports migrations/refs/head.json, so it will pick up the new head automatically — just make sure it also imports the new migration package (migrations/${input.dirName}).\n`;
266
+ }
267
+ ctx.out(`${input.logVerb} at ${ctx.dirLabel}${input.dirName}\n from: ${input.fromLabel}\n to: ${input.toHash}\n ops: ${input.ops.length} operation${input.ops.length === 1 ? "" : "s"}\n` + (ctx.isAppSpace ? "\nNext: run `prisma-idb migration contract-space` to bundle into contract-space.generated.ts\n" : extensionSpaceNote));
268
+ }
269
+ /**
270
+ * Writes a contract snapshot into the shared content-addressed store at
271
+ * `<migrationsDir>/snapshots/<hash>/` (ADR 240). Write-if-absent on
272
+ * `contract.json`: our contract emission is deterministic (the hash is
273
+ * derived from these same bytes), so an existing `contract.json` for `hash`
274
+ * is trusted as-is without a byte comparison. A first write goes to a temp
275
+ * directory then renames into place, so a concurrent or interrupted write
276
+ * can't leave a partial entry visible under its real hash.
277
+ *
278
+ * Both `contract.json` and `contract.d.ts` are written for layout parity
279
+ * with the Postgres side's real, vendor-CLI-produced store — nothing in
280
+ * this package reads the `.d.ts` back today; only `contract.json` is a read
281
+ * dependency for the next `migration plan`. If an earlier run left an entry
282
+ * with only `contract.json` (the source `.d.ts` didn't exist yet at the
283
+ * time), a later call for the same hash backfills `contract.d.ts` in place
284
+ * instead of leaving the entry permanently partial.
285
+ */
286
+ async function writeContractSnapshot(migrationsDir, hash, contractRaw, contractDtsPath, err) {
287
+ const snapshotsDir = join(migrationsDir, "snapshots");
288
+ const entryDir = join(snapshotsDir, hash);
289
+ try {
290
+ await readFile(join(entryDir, "contract.json"), "utf-8");
291
+ try {
292
+ await readFile(join(entryDir, "contract.d.ts"), "utf-8");
293
+ } catch (dtsCheckErr) {
294
+ if (dtsCheckErr.code !== "ENOENT") throw dtsCheckErr;
295
+ try {
296
+ await copyFile(contractDtsPath, join(entryDir, "contract.d.ts"));
297
+ } catch (dtsErr) {
298
+ if (dtsErr.code !== "ENOENT") throw dtsErr;
299
+ }
300
+ }
301
+ return;
302
+ } catch (err_) {
303
+ if (err_.code !== "ENOENT") throw err_;
304
+ }
305
+ const tmpDir = join(snapshotsDir, `.tmp-${randomBytes(8).toString("hex")}`);
306
+ await mkdir(tmpDir, { recursive: true });
307
+ try {
308
+ await writeFile(join(tmpDir, "contract.json"), contractRaw, "utf-8");
309
+ try {
310
+ await copyFile(contractDtsPath, join(tmpDir, "contract.d.ts"));
311
+ } catch (dtsErr) {
312
+ if (dtsErr.code !== "ENOENT") throw dtsErr;
313
+ err(`Warning: contract.d.ts not found at ${contractDtsPath}.\nThe snapshot store entry will hold only contract.json.
314
+ Run \`prisma contract emit\` first, then re-run this command.
315
+
316
+ `);
317
+ }
318
+ await rename(tmpDir, entryDir);
319
+ } catch (writeErr) {
320
+ await rm(tmpDir, {
321
+ recursive: true,
322
+ force: true
323
+ });
324
+ throw writeErr;
325
+ }
326
+ }
327
+ //#endregion
328
+ export { migrationPlan };
329
+
330
+ //# sourceMappingURL=migration-plan-BWhd8BdV.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-plan-BWhd8BdV.mjs","names":[],"sources":["../src/core/migration-plan.ts"],"sourcesContent":["import { copyFile, mkdir, readdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { randomBytes } from \"node:crypto\";\nimport type { Contract } from \"@prisma/orm-framework/contract/types\";\nimport type { IdbMigrationPlanWithAuthoring } from \"@prisma-idb/target-idb/migration\";\nimport { IdbMigrationPlanner, contractToIdbSchema, renderMigrationTs } from \"@prisma-idb/target-idb/migration\";\nimport { keepInternalSpecifiers } from \"@prisma/orm-framework/components/emission\";\nimport { computeMigrationHash } from \"@prisma/orm-toolchain/migration-tools/hash\";\nimport { formatMigrationDirName } from \"@prisma/orm-toolchain/migration-tools/io\";\nimport { deriveProvidedInvariants } from \"@prisma/orm-toolchain/migration-tools/invariants\";\nimport { join, relative } from \"pathe\";\nimport { chainOrderByMetadata } from \"./chain-order\";\n\nexport interface MigrationPlanOptions {\n readonly migrationsDir: string;\n readonly contractPath: string;\n /**\n * Directory slug. Defaults to `\"baseline\"` when this turns out to be a\n * fresh (greenfield) space. **Required** when a prior migration package\n * is found (incremental mode) — this function returns exit code 2 if\n * omitted in that case, since which mode applies isn't known until the\n * on-disk chain has been inspected.\n */\n readonly name?: string;\n /** Contract-space identifier. Defaults to `\"app\"`. See ADR 212. */\n readonly spaceId?: string;\n /** Output sinks. Default to `process.stdout`/`process.stderr` so every\n * existing caller (commander bin, tests) is unaffected; a CLI-engine\n * command handler passes collecting sinks instead so nothing reaches\n * the real stdout/stderr streams. */\n readonly out?: (line: string) => void;\n readonly err?: (line: string) => void;\n}\n\n/**\n * Plans the next migration package for an IDB project, auto-detecting\n * whether this is the first (\"greenfield\"/baseline, `from: null`) or an\n * incremental migration (diffed against the current head) by checking\n * whether `<migrationsDir>/app/` (or `<migrationsDir>/` for an extension\n * space) already contains any migration packages.\n *\n * This mirrors `prisma-idb migration plan`'s job — one command instead of\n * a baseline/incremental split the caller has to pick correctly — but\n * *not* its `from: null`-unless-ref default: that default exists there\n * because a server database's on-disk chain can diverge from a live, separately\n * migrated database (tracked via the `db` ref and advanced by migration apply).\n * IDB has no such divergence — there is no\n * `db update`/`migrate` step on the Node side at all (`db init`/`db\n * update`/`db verify` are refusal-only for IDB), so the on-disk chain is\n * the only state that will ever exist; the browser runtime applies it\n * wholesale at open time. Trusting the on-disk chain as the auto-detected\n * `from` is therefore the safety-equivalent of the framework's ref system\n * for this target, not a corner-cut.\n *\n * Exit codes: 0 on success; 1 on a planning/consistency failure; 2 if\n * `--name` was required (incremental mode) and not supplied.\n */\nexport async function migrationPlan(opts: MigrationPlanOptions): Promise<number> {\n const out = opts.out ?? ((line: string) => process.stdout.write(line));\n const err = opts.err ?? ((line: string) => process.stderr.write(line));\n const spaceId = opts.spaceId ?? \"app\";\n if (spaceId === \"snapshots\") {\n err('migration plan: spaceId \"snapshots\" is reserved for the contract-snapshot store and cannot be used.\\n');\n return 1;\n }\n const isAppSpace = spaceId === \"app\";\n const targetDir = isAppSpace ? join(opts.migrationsDir, \"app\") : opts.migrationsDir;\n // Relative-to-cwd display path — reflects wherever `migrationsDir` actually\n // resolved to (config-driven, an override, or the framework convention),\n // not a hardcoded \"migrations/...\" literal that would go stale for any\n // project not using the default layout.\n const dirLabel = `${relative(process.cwd(), targetDir)}/`;\n\n let existingDirs: string[] = [];\n try {\n const entries = await readdir(targetDir, { withFileTypes: true });\n existingDirs = entries\n .filter((e) => e.isDirectory())\n // `refs/` holds the pinned head ref, not a migration package. `app/`\n // is a sibling space's directory when an extension space happens to\n // share `migrationsDir` with the app space (non-standard, but\n // defensive here — see ADR 212). `snapshots/` is the shared\n // content-addressed contract store (ADR 240) — an extension space's\n // targetDir is migrationsDir itself, so the store sits as a sibling\n // of its migration package dirs and must not be mistaken for one.\n .filter((e) => e.name !== \"refs\" && e.name !== \"app\" && e.name !== \"snapshots\")\n .map((e) => e.name);\n } catch (err_) {\n if ((err_ as NodeJS.ErrnoException).code !== \"ENOENT\") throw err_;\n // targetDir doesn't exist yet — expected for a fresh project.\n }\n\n const ctx: SharedCtx = { ...opts, out, err, spaceId, isAppSpace, targetDir, dirLabel };\n\n if (existingDirs.length === 0) {\n err(\n `migration plan: no existing migrations found under ${dirLabel} — generating a full baseline.\\n` +\n \"If you expected an incremental migration, check --migrations-dir / your config's migrations.dir.\\n\"\n );\n return planGreenfield(ctx);\n }\n\n return planIncremental(ctx, existingDirs);\n}\n\ninterface SharedCtx extends MigrationPlanOptions {\n readonly out: (line: string) => void;\n readonly err: (line: string) => void;\n readonly spaceId: string;\n readonly isAppSpace: boolean;\n readonly targetDir: string;\n readonly dirLabel: string;\n}\n\nasync function planGreenfield(ctx: SharedCtx): Promise<number> {\n const name = ctx.name ?? \"baseline\";\n\n let contractRaw: string;\n let contractJson: unknown;\n try {\n contractRaw = await readFile(ctx.contractPath, \"utf-8\");\n contractJson = JSON.parse(contractRaw);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n ctx.err(\n `migration plan: contract.json not found at ${ctx.contractPath}.\\n` +\n \"Run `prisma contract emit` first to generate the contract file.\\n\"\n );\n return 1;\n }\n throw err;\n }\n\n const planner = new IdbMigrationPlanner();\n const planResult = planner.plan({\n contract: contractJson,\n schema: null,\n policy: { allowedOperationClasses: [\"additive\", \"widening\", \"destructive\", \"data\"] },\n fromContract: null,\n frameworkComponents: [],\n spaceId: ctx.spaceId,\n });\n\n if (planResult.kind === \"failure\") {\n ctx.err(\n \"migration plan: migration planning failed:\\n\" +\n planResult.conflicts.map((c) => ` ${c.summary}`).join(\"\\n\") +\n \"\\n\"\n );\n return 1;\n }\n\n const plan = planResult.plan as IdbMigrationPlanWithAuthoring;\n // The planner unconditionally prepends `_prisma_next_marker` creation for\n // `fromContract: null`, regardless of spaceId — the app space's own\n // baseline creates that store; an extension space applying its DDL in\n // the same combined transaction (ADR 011) must not try to recreate it.\n const ops = ctx.isAppSpace\n ? plan.operations\n : plan.operations.filter(\n (op) => !(op.kind === \"createObjectStore\" && (op as { storeName?: string }).storeName === \"_prisma_next_marker\")\n );\n const toHash = plan.destination.storageHash;\n\n const timestamp = new Date();\n const dirName = formatMigrationDirName(timestamp, name);\n const providedInvariants = Array.from(deriveProvidedInvariants(ops));\n const baseMetadata = {\n from: null as string | null,\n to: toHash,\n providedInvariants,\n createdAt: timestamp.toISOString(),\n };\n const migrationHash = computeMigrationHash(\n baseMetadata,\n ops as unknown as Parameters<typeof computeMigrationHash>[1]\n );\n const metadata = { ...baseMetadata, migrationHash };\n\n await writeMigrationPackage(ctx, {\n dirName,\n ops,\n metadata,\n migrationTsContent: renderMigrationTs({ fromHash: null, toHash, ops }),\n contractRaw,\n providedInvariants,\n fromLabel: \"null (fresh database — creates all stores from scratch)\",\n toHash,\n logVerb: \"Generated baseline migration\",\n });\n\n return 0;\n}\n\nasync function planIncremental(ctx: SharedCtx, existingDirs: readonly string[]): Promise<number> {\n if (ctx.name === undefined) {\n ctx.err(\"migration plan: --name <slug> is required when generating an incremental migration.\\n\");\n return 2;\n }\n\n const packages = new Map<string, { dirName: string; metadata: { from: string | null; to: string } }>();\n for (const dirName of existingDirs) {\n const metaPath = join(ctx.targetDir, dirName, \"migration.json\");\n let metaRaw: string;\n try {\n metaRaw = await readFile(metaPath, \"utf-8\");\n } catch (err) {\n ctx.err(`migration plan: cannot read ${metaPath} — ${err instanceof Error ? err.message : String(err)}\\n`);\n return 1;\n }\n let meta: { from: string | null; to: string };\n try {\n meta = JSON.parse(metaRaw) as { from: string | null; to: string };\n } catch (err) {\n ctx.err(`migration plan: malformed ${metaPath} — ${err instanceof Error ? err.message : String(err)}\\n`);\n return 1;\n }\n packages.set(dirName, { dirName, metadata: meta });\n }\n\n let ordered: { dirName: string; metadata: { from: string | null; to: string } }[];\n try {\n ordered = chainOrderByMetadata(packages);\n } catch (err) {\n ctx.err(`migration plan: migration chain is broken — ${err instanceof Error ? err.message : String(err)}\\n`);\n return 1;\n }\n\n const head = ordered[ordered.length - 1];\n if (head === undefined) {\n ctx.err(\"migration plan: no valid migration packages could be read.\\n\");\n return 1;\n }\n\n const headSnapshotPath = join(ctx.migrationsDir, \"snapshots\", head.metadata.to, \"contract.json\");\n let fromContractJson: unknown;\n try {\n const fromContractRaw = await readFile(headSnapshotPath, \"utf-8\");\n fromContractJson = JSON.parse(fromContractRaw);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n ctx.err(\n `migration plan: head migration ${head.dirName} is inconsistent.\\n` +\n ` migration.json claims to: ${head.metadata.to}\\n` +\n ` but no store entry exists at ${relative(process.cwd(), headSnapshotPath)}\\n` +\n \"Regenerate the chain, or restore migrations/snapshots/ from source control.\\n\"\n );\n return 1;\n }\n throw err;\n }\n\n let contractRaw: string;\n let contractJson: unknown;\n try {\n contractRaw = await readFile(ctx.contractPath, \"utf-8\");\n contractJson = JSON.parse(contractRaw);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n ctx.err(\n `migration plan: contract.json not found at ${ctx.contractPath}.\\n` +\n \"Run `prisma contract emit` first to generate the contract file.\\n\"\n );\n return 1;\n }\n throw err;\n }\n\n // fromContract is never null here, so the planner never prepends the\n // _prisma_next_marker op — no stripping needed, unlike the greenfield path.\n const fromSchema = contractToIdbSchema(fromContractJson);\n const planner = new IdbMigrationPlanner();\n const planResult = planner.plan({\n contract: contractJson,\n schema: fromSchema,\n policy: { allowedOperationClasses: [\"additive\", \"widening\", \"destructive\", \"data\"] },\n fromContract: fromContractJson as Contract,\n frameworkComponents: [],\n spaceId: ctx.spaceId,\n });\n\n if (planResult.kind === \"failure\") {\n ctx.err(\n \"migration plan: migration planning failed:\\n\" +\n planResult.conflicts.map((c) => ` ${c.summary}`).join(\"\\n\") +\n \"\\n\"\n );\n return 1;\n }\n\n const plan = planResult.plan as IdbMigrationPlanWithAuthoring;\n const ops = plan.operations;\n const fromHash = head.metadata.to;\n const toHash = plan.destination.storageHash;\n\n if (fromHash === toHash) {\n ctx.out(\"migration plan: contract is unchanged since the last migration — nothing to do.\\n\");\n return 0;\n }\n\n const timestamp = new Date();\n const dirName = formatMigrationDirName(timestamp, ctx.name);\n const providedInvariants = Array.from(deriveProvidedInvariants(ops));\n const baseMetadata = { from: fromHash, to: toHash, providedInvariants, createdAt: timestamp.toISOString() };\n const migrationHash = computeMigrationHash(\n baseMetadata,\n ops as unknown as Parameters<typeof computeMigrationHash>[1]\n );\n const metadata = { ...baseMetadata, migrationHash };\n\n await writeMigrationPackage(ctx, {\n dirName,\n ops,\n metadata,\n migrationTsContent: plan.renderTypeScript(keepInternalSpecifiers),\n contractRaw,\n providedInvariants,\n fromLabel: fromHash,\n toHash,\n logVerb: \"Generated migration\",\n });\n\n return 0;\n}\n\ninterface WritePackageInput {\n readonly dirName: string;\n readonly ops: readonly unknown[];\n readonly metadata: { readonly from: string | null; readonly to: string; readonly migrationHash: string };\n readonly migrationTsContent: string;\n readonly contractRaw: string;\n readonly providedInvariants: readonly string[];\n readonly fromLabel: string;\n readonly toHash: string;\n readonly logVerb: string;\n}\n\nasync function writeMigrationPackage(ctx: SharedCtx, input: WritePackageInput): Promise<void> {\n const packageDir = join(ctx.targetDir, input.dirName);\n await mkdir(packageDir, { recursive: true });\n\n await writeFile(join(packageDir, \"ops.json\"), JSON.stringify(input.ops, null, 2), \"utf-8\");\n await writeFile(join(packageDir, \"migration.json\"), JSON.stringify(input.metadata, null, 2), \"utf-8\");\n await writeFile(join(packageDir, \"migration.ts\"), input.migrationTsContent, \"utf-8\");\n\n const contractDtsPath = ctx.contractPath.replace(/\\.json$/i, \".d.ts\");\n await writeContractSnapshot(ctx.migrationsDir, input.toHash, input.contractRaw, contractDtsPath, ctx.err);\n\n let extensionSpaceNote = \"\";\n if (!ctx.isAppSpace) {\n const refsDir = join(ctx.migrationsDir, \"refs\");\n await mkdir(refsDir, { recursive: true });\n await writeFile(\n join(refsDir, \"head.json\"),\n JSON.stringify({ hash: input.toHash, invariants: input.providedInvariants }, null, 2),\n \"utf-8\"\n );\n extensionSpaceNote =\n `\\nAlso pinned migrations/refs/head.json → ${input.toHash}\\n` +\n \"exports/control.ts already JSON-imports migrations/refs/head.json, so it will pick up the new head \" +\n `automatically — just make sure it also imports the new migration package (migrations/${input.dirName}).\\n`;\n }\n\n ctx.out(\n `${input.logVerb} at ${ctx.dirLabel}${input.dirName}\\n` +\n ` from: ${input.fromLabel}\\n` +\n ` to: ${input.toHash}\\n` +\n ` ops: ${input.ops.length} operation${input.ops.length === 1 ? \"\" : \"s\"}\\n` +\n (ctx.isAppSpace\n ? \"\\nNext: run `prisma-idb migration contract-space` to bundle into contract-space.generated.ts\\n\"\n : extensionSpaceNote)\n );\n}\n\n/**\n * Writes a contract snapshot into the shared content-addressed store at\n * `<migrationsDir>/snapshots/<hash>/` (ADR 240). Write-if-absent on\n * `contract.json`: our contract emission is deterministic (the hash is\n * derived from these same bytes), so an existing `contract.json` for `hash`\n * is trusted as-is without a byte comparison. A first write goes to a temp\n * directory then renames into place, so a concurrent or interrupted write\n * can't leave a partial entry visible under its real hash.\n *\n * Both `contract.json` and `contract.d.ts` are written for layout parity\n * with the Postgres side's real, vendor-CLI-produced store — nothing in\n * this package reads the `.d.ts` back today; only `contract.json` is a read\n * dependency for the next `migration plan`. If an earlier run left an entry\n * with only `contract.json` (the source `.d.ts` didn't exist yet at the\n * time), a later call for the same hash backfills `contract.d.ts` in place\n * instead of leaving the entry permanently partial.\n */\nasync function writeContractSnapshot(\n migrationsDir: string,\n hash: string,\n contractRaw: string,\n contractDtsPath: string,\n err: (line: string) => void\n): Promise<void> {\n const snapshotsDir = join(migrationsDir, \"snapshots\");\n const entryDir = join(snapshotsDir, hash);\n\n try {\n await readFile(join(entryDir, \"contract.json\"), \"utf-8\");\n // contract.json (the read dependency) is already present — write-if-absent.\n // Still backfill contract.d.ts if an earlier run skipped it (source .d.ts\n // didn't exist yet at the time), so the entry doesn't stay permanently\n // partial once a `contract emit` produces one.\n try {\n await readFile(join(entryDir, \"contract.d.ts\"), \"utf-8\");\n } catch (dtsCheckErr) {\n if ((dtsCheckErr as NodeJS.ErrnoException).code !== \"ENOENT\") throw dtsCheckErr;\n try {\n await copyFile(contractDtsPath, join(entryDir, \"contract.d.ts\"));\n } catch (dtsErr) {\n if ((dtsErr as NodeJS.ErrnoException).code !== \"ENOENT\") throw dtsErr;\n }\n }\n return;\n } catch (err_) {\n if ((err_ as NodeJS.ErrnoException).code !== \"ENOENT\") throw err_;\n }\n\n const tmpDir = join(snapshotsDir, `.tmp-${randomBytes(8).toString(\"hex\")}`);\n await mkdir(tmpDir, { recursive: true });\n try {\n await writeFile(join(tmpDir, \"contract.json\"), contractRaw, \"utf-8\");\n try {\n await copyFile(contractDtsPath, join(tmpDir, \"contract.d.ts\"));\n } catch (dtsErr) {\n if ((dtsErr as NodeJS.ErrnoException).code !== \"ENOENT\") throw dtsErr;\n err(\n `Warning: contract.d.ts not found at ${contractDtsPath}.\\n` +\n \"The snapshot store entry will hold only contract.json.\\n\" +\n \"Run `prisma contract emit` first, then re-run this command.\\n\\n\"\n );\n }\n await rename(tmpDir, entryDir);\n } catch (writeErr) {\n await rm(tmpDir, { recursive: true, force: true });\n throw writeErr;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import { t as chainOrderByMetadata } from "./chain-order-CaHXo5jz.mjs";
2
+ import { readFile, readdir } from "node:fs/promises";
3
+ import { join } from "pathe";
4
+ import { applyOneDdlOp, isIdbDdlOp } from "@prisma-idb/target-idb/migration";
5
+ import { computeMigrationHash } from "@prisma/orm-toolchain/migration-tools/hash";
6
+ import { IDBFactory } from "fake-indexeddb";
7
+ //#region src/core/preflight.ts
8
+ /**
9
+ * Walk every migration package under `<migrationsDir>/app/` in chain order,
10
+ * applying each package's `ops.json` against a fresh `fake-indexeddb`
11
+ * instance inside an `upgradeneeded` callback. Reports per-step status to
12
+ * stdout and the final outcome via exit code.
13
+ *
14
+ * **Why this exists**: a dedicated preflight command avoids three failure modes
15
+ * of inlining this into `db update`: (a) `fake-indexeddb` disagrees with real
16
+ * browsers on some edge cases and is not a reliable production oracle; (b) it
17
+ * would substitute a runtime check for proper test coverage; (c) it would ship
18
+ * a test-only package on a production path. Running it in CI gives the gate
19
+ * without the workflow tax on every author.
20
+ *
21
+ * **Scope vs runtime**: this command catches "the chain doesn't apply
22
+ * cleanly" — a structural issue. It does NOT catch "the chain produces
23
+ * the wrong schema" (that's `verifySchema` against the head's contract,
24
+ * resolvable via `migrations/snapshots/<hash>/contract.json` since Phase
25
+ * 8.9 — still deferred to a follow-up).
26
+ *
27
+ * Exit codes: 0 on full chain success; 1 on any failure.
28
+ */
29
+ async function runPreflight(opts) {
30
+ const out = opts.out ?? ((line) => process.stdout.write(line));
31
+ const err = opts.err ?? ((line) => process.stderr.write(line));
32
+ const migrationsDir = opts.migrationsDir;
33
+ const packages = await loadPackages(join(migrationsDir, "app"));
34
+ if (packages.length === 0) {
35
+ out("No migration packages found. Nothing to preflight.\n");
36
+ return 0;
37
+ }
38
+ out(`Preflighting ${packages.length} migration(s) against fake-indexeddb…\n`);
39
+ const factory = new IDBFactory();
40
+ const dbName = "__preflight__";
41
+ let currentVersion = 0;
42
+ for (const pkg of packages) {
43
+ out(` ${pkg.dirName} … `);
44
+ try {
45
+ currentVersion += 1;
46
+ await applyPackage({
47
+ factory,
48
+ dbName,
49
+ targetVersion: currentVersion,
50
+ ops: pkg.ops
51
+ });
52
+ out("ok\n");
53
+ } catch (applyErr) {
54
+ out("FAILED\n");
55
+ err(` ${applyErr instanceof Error ? applyErr.message : String(applyErr)}\n`);
56
+ err("\nPreflight failed.\n");
57
+ return 1;
58
+ }
59
+ }
60
+ out("\nPreflight passed: every migration in the chain applies cleanly.\n");
61
+ return 0;
62
+ }
63
+ async function loadPackages(appDir) {
64
+ let dirs;
65
+ try {
66
+ dirs = (await readdir(appDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
67
+ } catch (err) {
68
+ if (err.code === "ENOENT") return [];
69
+ throw err;
70
+ }
71
+ const unordered = /* @__PURE__ */ new Map();
72
+ for (const dirName of dirs) {
73
+ const metaRaw = await readFile(join(appDir, dirName, "migration.json"), "utf-8");
74
+ const opsRaw = await readFile(join(appDir, dirName, "ops.json"), "utf-8");
75
+ const metadata = JSON.parse(metaRaw);
76
+ const opsParsed = JSON.parse(opsRaw);
77
+ const ops = [];
78
+ for (const op of opsParsed) {
79
+ if (!isIdbDdlOp(op)) throw new Error(`Non-IDB op found in ${dirName}/ops.json: ${JSON.stringify(op)}`);
80
+ ops.push(op);
81
+ }
82
+ const computedHash = computeMigrationHash(metadata, ops);
83
+ if (computedHash !== metadata.migrationHash) throw new Error(`Migration hash mismatch in ${dirName}: stored migrationHash ${metadata.migrationHash} does not match computed hash ${computedHash}`);
84
+ unordered.set(dirName, {
85
+ dirName,
86
+ metadata,
87
+ ops
88
+ });
89
+ }
90
+ return chainOrderByMetadata(unordered);
91
+ }
92
+ function applyPackage(input) {
93
+ return new Promise((resolve, reject) => {
94
+ const req = input.factory.open(input.dbName, input.targetVersion);
95
+ req.onupgradeneeded = (event) => {
96
+ const target = event.target;
97
+ const db = target.result;
98
+ const tx = target.transaction;
99
+ if (tx === null) {
100
+ reject(/* @__PURE__ */ new Error("upgradeneeded fired with null version-change transaction"));
101
+ return;
102
+ }
103
+ try {
104
+ for (const op of input.ops) applyOneDdlOp(db, tx, op);
105
+ } catch (err) {
106
+ reject(err);
107
+ }
108
+ };
109
+ req.onsuccess = () => {
110
+ req.result.close();
111
+ resolve();
112
+ };
113
+ req.onerror = () => reject(req.error ?? /* @__PURE__ */ new Error("preflight open request failed"));
114
+ });
115
+ }
116
+ //#endregion
117
+ export { runPreflight };
118
+
119
+ //# sourceMappingURL=preflight-4jDCNvS0.mjs.map