@prisma-next/target-sqlite 0.15.0 → 0.16.0-dev.10

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 (86) hide show
  1. package/dist/{codec-ids-BfPkjMmk.d.mts → codec-ids-Dof8fZfe.d.mts} +1 -2
  2. package/dist/codec-ids-Dof8fZfe.d.mts.map +1 -0
  3. package/dist/codec-ids.d.mts +1 -1
  4. package/dist/{codec-types-izdPhp_9.d.mts → codec-types-CcFkbJ3o.d.mts} +3 -4
  5. package/dist/codec-types-CcFkbJ3o.d.mts.map +1 -0
  6. package/dist/codec-types.d.mts +2 -2
  7. package/dist/{codecs-BGeJavlQ.d.mts → codecs-DGgo--O0.d.mts} +2 -3
  8. package/dist/codecs-DGgo--O0.d.mts.map +1 -0
  9. package/dist/codecs.d.mts +1 -1
  10. package/dist/contract-free.d.mts +1 -2
  11. package/dist/contract-free.d.mts.map +1 -1
  12. package/dist/control-tables.d.mts.map +1 -1
  13. package/dist/control.d.mts +0 -1
  14. package/dist/control.d.mts.map +1 -1
  15. package/dist/control.mjs +14 -14
  16. package/dist/control.mjs.map +1 -1
  17. package/dist/ddl.d.mts +1 -1
  18. package/dist/default-normalizer.d.mts +0 -1
  19. package/dist/default-normalizer.d.mts.map +1 -1
  20. package/dist/{descriptor-meta-D1fap-5t.mjs → descriptor-meta-BPrgV878.mjs} +12 -6
  21. package/dist/descriptor-meta-BPrgV878.mjs.map +1 -0
  22. package/dist/migration.d.mts +2 -3
  23. package/dist/migration.d.mts.map +1 -1
  24. package/dist/migration.mjs +2 -2
  25. package/dist/native-type-normalizer.d.mts.map +1 -1
  26. package/dist/{nodes-VzaaeUTb.d.mts → nodes-COZjF41I.d.mts} +1 -2
  27. package/dist/nodes-COZjF41I.d.mts.map +1 -0
  28. package/dist/{op-factory-call-Do03QgD9.mjs → op-factory-call-8pTnIHhR.mjs} +8 -8
  29. package/dist/op-factory-call-8pTnIHhR.mjs.map +1 -0
  30. package/dist/op-factory-call.d.mts +2 -3
  31. package/dist/op-factory-call.d.mts.map +1 -1
  32. package/dist/op-factory-call.mjs +1 -1
  33. package/dist/pack.d.mts +41 -2
  34. package/dist/pack.d.mts.map +1 -1
  35. package/dist/pack.mjs +1 -1
  36. package/dist/{planner-produced-sqlite-migration-CeGjNfd2.mjs → planner-produced-sqlite-migration-CWmvnJi4.mjs} +21 -11
  37. package/dist/planner-produced-sqlite-migration-CWmvnJi4.mjs.map +1 -0
  38. package/dist/{planner-produced-sqlite-migration-o1qUzwLk.d.mts → planner-produced-sqlite-migration-fd_Jw-mL.d.mts} +3 -4
  39. package/dist/planner-produced-sqlite-migration-fd_Jw-mL.d.mts.map +1 -0
  40. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  41. package/dist/planner-produced-sqlite-migration.mjs +1 -1
  42. package/dist/{planner-D2hMk3oC.mjs → planner-sc4nqpzL.mjs} +19 -59
  43. package/dist/planner-sc4nqpzL.mjs.map +1 -0
  44. package/dist/planner-target-details-xR6UfIcz.d.mts.map +1 -1
  45. package/dist/planner.d.mts +7 -2
  46. package/dist/planner.d.mts.map +1 -1
  47. package/dist/planner.mjs +1 -1
  48. package/dist/render-ops.d.mts +0 -1
  49. package/dist/render-ops.d.mts.map +1 -1
  50. package/dist/runtime.d.mts +1 -2
  51. package/dist/runtime.d.mts.map +1 -1
  52. package/dist/{shared-C6sM-wel.d.mts → shared-DLUM4Ae2.d.mts} +1 -3
  53. package/dist/shared-DLUM4Ae2.d.mts.map +1 -0
  54. package/dist/sql-utils.d.mts.map +1 -1
  55. package/dist/{sqlite-contract-view-J32K82Gi.d.mts → sqlite-contract-view-CMgE0L4W.d.mts} +4 -3
  56. package/dist/sqlite-contract-view-CMgE0L4W.d.mts.map +1 -0
  57. package/dist/{sqlite-migration-BYbzskpE.d.mts → sqlite-migration-CBXynqeE.d.mts} +8 -8
  58. package/dist/sqlite-migration-CBXynqeE.d.mts.map +1 -0
  59. package/dist/{sqlite-migration-C9bD6XDl.mjs → sqlite-migration-CLXPhPxF.mjs} +11 -10
  60. package/dist/sqlite-migration-CLXPhPxF.mjs.map +1 -0
  61. package/package.json +19 -19
  62. package/src/core/authoring.ts +11 -5
  63. package/src/core/errors.ts +12 -8
  64. package/src/core/migrations/issue-planner.ts +18 -62
  65. package/src/core/migrations/op-factory-call.ts +1 -1
  66. package/src/core/migrations/operations/shared.ts +3 -5
  67. package/src/core/migrations/operations/tables.ts +3 -2
  68. package/src/core/migrations/planner-produced-sqlite-migration.ts +4 -0
  69. package/src/core/migrations/planner-strategies.ts +4 -3
  70. package/src/core/migrations/planner.ts +11 -2
  71. package/src/core/migrations/render-typescript.ts +20 -8
  72. package/src/core/migrations/runner.ts +13 -12
  73. package/src/core/migrations/sqlite-migration.ts +5 -4
  74. package/dist/codec-ids-BfPkjMmk.d.mts.map +0 -1
  75. package/dist/codec-types-izdPhp_9.d.mts.map +0 -1
  76. package/dist/codecs-BGeJavlQ.d.mts.map +0 -1
  77. package/dist/descriptor-meta-D1fap-5t.mjs.map +0 -1
  78. package/dist/nodes-VzaaeUTb.d.mts.map +0 -1
  79. package/dist/op-factory-call-Do03QgD9.mjs.map +0 -1
  80. package/dist/planner-D2hMk3oC.mjs.map +0 -1
  81. package/dist/planner-produced-sqlite-migration-CeGjNfd2.mjs.map +0 -1
  82. package/dist/planner-produced-sqlite-migration-o1qUzwLk.d.mts.map +0 -1
  83. package/dist/shared-C6sM-wel.d.mts.map +0 -1
  84. package/dist/sqlite-contract-view-J32K82Gi.d.mts.map +0 -1
  85. package/dist/sqlite-migration-BYbzskpE.d.mts.map +0 -1
  86. package/dist/sqlite-migration-C9bD6XDl.mjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { a as DropColumnCall, l as RecreateTableCall, n as CreateIndexCall, o as DropIndexCall, r as CreateTableCall, s as DropTableCall, t as AddColumnCall } from "./op-factory-call-Do03QgD9.mjs";
1
+ import { a as DropColumnCall, l as RecreateTableCall, n as CreateIndexCall, o as DropIndexCall, r as CreateTableCall, s as DropTableCall, t as AddColumnCall } from "./op-factory-call-8pTnIHhR.mjs";
2
2
  import { t as SqliteContractView } from "./sqlite-contract-view-B8qeCczc.mjs";
3
3
  import { blindCast } from "@prisma-next/utils/casts";
4
4
  import { Migration } from "@prisma-next/family-sql/migration";
@@ -17,8 +17,9 @@ import { CliStructuredError } from "@prisma-next/errors/control";
17
17
  * The `operation` argument is required so every throw site names the operation
18
18
  * that actually failed; a new operation cannot inherit a misattributed message.
19
19
  *
20
- * Distinct from `PN-MIG-2001` (placeholder not filled) because the missing
21
- * input is the stack itself, not the per-operation contract.
20
+ * Distinct from `MIGRATION.UNFILLED_PLACEHOLDER` (placeholder not filled)
21
+ * because the missing input is the stack itself, not the per-operation
22
+ * contract.
22
23
  *
23
24
  * Lives in `@prisma-next/target-sqlite/errors` rather than the shared
24
25
  * framework migration errors module because the failure is target-specific:
@@ -26,8 +27,7 @@ import { CliStructuredError } from "@prisma-next/errors/control";
26
27
  * adapter, the SQLite-target stack) only exists in this package.
27
28
  */
28
29
  function errorSqliteMigrationStackMissing(operation) {
29
- return new CliStructuredError("2008", `SqliteMigration.${operation} requires a control adapter`, {
30
- domain: "MIG",
30
+ return new CliStructuredError("MIGRATION.SQLITE_CONTROL_STACK_MISSING", `SqliteMigration.${operation} requires a control adapter`, {
31
31
  why: `SqliteMigration.${operation} was invoked on an instance constructed without a ControlStack, so the stored controlAdapter is undefined and the operation cannot lower its DDL node.`,
32
32
  fix: "Construct the migration via the migration CLI entrypoint (which assembles a ControlStack from the loaded prisma-next.config.ts), or pass a ControlStack containing a SQLite adapter to the migration constructor in test fixtures.",
33
33
  meta: { operation }
@@ -49,8 +49,9 @@ function errorSqliteMigrationStackMissing(operation) {
49
49
  * through every call.
50
50
  *
51
51
  * Binds the framework base's `Start` / `End` contract generics so a subclass
52
- * that assigns its `start-contract.json` / `end-contract.json` imports gets
53
- * fully-typed view accessors: `this.endContract` is a `SqliteContractView<End>`
52
+ * that assigns its snapshot-store `contract.json` imports to
53
+ * `startContractJson` / `endContractJson` gets fully-typed view accessors:
54
+ * `this.endContract` is a `SqliteContractView<End>`
54
55
  * (sole namespace unwrapped to the root — `this.endContract.table.<name>`),
55
56
  * built lazily from the JSON fields via the shared `MigrationContractViews`
56
57
  * helper. Mirrors `MongoMigration`'s view getters; the framework base derives
@@ -62,7 +63,7 @@ var SqliteMigration = class extends Migration {
62
63
  * Materialized SQLite control adapter, created once per migration
63
64
  * instance from the injected stack. `undefined` only when the migration
64
65
  * was instantiated without a stack (test fixtures); `controlAdapterFor`
65
- * throws a PN-MIG-2008 in that case to surface the misuse.
66
+ * throws a MIGRATION.SQLITE_CONTROL_STACK_MISSING in that case to surface the misuse.
66
67
  */
67
68
  controlAdapter;
68
69
  #endView = new MigrationContractViews(this, "SqliteMigration", (json) => SqliteContractView.fromJson(json));
@@ -72,7 +73,7 @@ var SqliteMigration = class extends Migration {
72
73
  this.controlAdapter = stack?.adapter ? blindCast(stack.adapter.create(stack)) : void 0;
73
74
  }
74
75
  /**
75
- * Returns the materialized control adapter, or throws a PN-MIG-2008 naming
76
+ * Returns the materialized control adapter, or throws a MIGRATION.SQLITE_CONTROL_STACK_MISSING naming
76
77
  * `operation` when the migration was constructed without a `ControlStack`.
77
78
  * Single home for the null-check that every DDL/DML method shares.
78
79
  */
@@ -120,4 +121,4 @@ var SqliteMigration = class extends Migration {
120
121
  //#endregion
121
122
  export { SqliteMigration as t };
122
123
 
123
- //# sourceMappingURL=sqlite-migration-C9bD6XDl.mjs.map
124
+ //# sourceMappingURL=sqlite-migration-CLXPhPxF.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-migration-CLXPhPxF.mjs","names":["SqlMigration","#endView","#startView"],"sources":["../src/core/errors.ts","../src/core/migrations/sqlite-migration.ts"],"sourcesContent":["import { CliStructuredError } from '@prisma-next/errors/control';\n\n/**\n * A `SqliteMigration` operation that needs the materialized control adapter\n * — named by `operation` (e.g. `createTable`, `dropColumn`, `recreateTable`) —\n * was invoked, but the migration was constructed without a `ControlStack`.\n * Concrete authoring usage always goes through the migration CLI entrypoint,\n * which assembles a stack from the loaded `prisma-next.config.ts`; reaching this\n * error means a test fixture or ad-hoc consumer instantiated `SqliteMigration`\n * with the no-arg form (legal for `operations` / `describe` introspection only).\n *\n * The `operation` argument is required so every throw site names the operation\n * that actually failed; a new operation cannot inherit a misattributed message.\n *\n * Distinct from `MIGRATION.UNFILLED_PLACEHOLDER` (placeholder not filled)\n * because the missing input is the stack itself, not the per-operation\n * contract.\n *\n * Lives in `@prisma-next/target-sqlite/errors` rather than the shared\n * framework migration errors module because the failure is target-specific:\n * the contract it talks about (`SqliteMigration`, the SQLite control\n * adapter, the SQLite-target stack) only exists in this package.\n */\nexport function errorSqliteMigrationStackMissing(operation: string): CliStructuredError {\n return new CliStructuredError(\n 'MIGRATION.SQLITE_CONTROL_STACK_MISSING',\n `SqliteMigration.${operation} requires a control adapter`,\n {\n why: `SqliteMigration.${operation} was invoked on an instance constructed without a ControlStack, so the stored controlAdapter is undefined and the operation cannot lower its DDL node.`,\n fix: 'Construct the migration via the migration CLI entrypoint (which assembles a ControlStack from the loaded prisma-next.config.ts), or pass a ControlStack containing a SQLite adapter to the migration constructor in test fixtures.',\n meta: { operation },\n },\n );\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type {\n MigrationOperationClass,\n SqlMigrationPlanOperation,\n} from '@prisma-next/family-sql/control';\nimport type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';\nimport { Migration as SqlMigration } from '@prisma-next/family-sql/migration';\nimport type { ControlStack } from '@prisma-next/framework-components/control';\nimport { MigrationContractViews } from '@prisma-next/migration-tools/migration';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { errorSqliteMigrationStackMissing } from '../errors';\nimport { SqliteContractView } from '../sqlite-contract-view';\nimport {\n AddColumnCall,\n CreateIndexCall,\n CreateTableCall,\n DropColumnCall,\n DropIndexCall,\n DropTableCall,\n RecreateTableCall,\n} from './op-factory-call';\nimport type { SqliteColumnSpec, SqliteIndexSpec, SqliteTableSpec } from './operations/shared';\nimport type { SqlitePlanTargetDetails } from './planner-target-details';\n\ntype Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;\n\n/**\n * Target-owned base class for SQLite migrations. Fixes the `SqlMigration`\n * generic to `SqlitePlanTargetDetails` and the abstract `targetId` to the\n * SQLite literal, so both user-authored migrations and renderer-generated\n * scaffolds can extend `SqliteMigration` directly without redeclaring\n * target-local identity.\n *\n * The constructor materializes a single SQLite `SqlControlAdapter` from\n * `stack.adapter.create(stack)` and stores it; the protected instance methods\n * forward to the corresponding `*Call` with that stored adapter, so user\n * migrations can write `this.createTable({...})` without threading the adapter\n * through every call.\n *\n * Binds the framework base's `Start` / `End` contract generics so a subclass\n * that assigns its snapshot-store `contract.json` imports to\n * `startContractJson` / `endContractJson` gets fully-typed view accessors:\n * `this.endContract` is a `SqliteContractView<End>`\n * (sole namespace unwrapped to the root — `this.endContract.table.<name>`),\n * built lazily from the JSON fields via the shared `MigrationContractViews`\n * helper. Mirrors `MongoMigration`'s view getters; the framework base derives\n * `describe()` from the same JSON.\n */\nexport abstract class SqliteMigration<\n Start extends Contract<SqlStorage> = Contract<SqlStorage>,\n End extends Contract<SqlStorage> = Contract<SqlStorage>,\n> extends SqlMigration<SqlitePlanTargetDetails, 'sqlite', Start, End> {\n readonly targetId = 'sqlite' as const;\n\n /**\n * Materialized SQLite control adapter, created once per migration\n * instance from the injected stack. `undefined` only when the migration\n * was instantiated without a stack (test fixtures); `controlAdapterFor`\n * throws a MIGRATION.SQLITE_CONTROL_STACK_MISSING in that case to surface the misuse.\n */\n protected readonly controlAdapter: SqlControlAdapter<'sqlite'> | undefined;\n\n #endView = new MigrationContractViews<SqliteContractView<End>>(this, 'SqliteMigration', (json) =>\n SqliteContractView.fromJson<End>(json),\n );\n #startView = new MigrationContractViews<SqliteContractView<Start>>(\n this,\n 'SqliteMigration',\n (json) => SqliteContractView.fromJson<Start>(json),\n );\n\n constructor(stack?: ControlStack<'sql', 'sqlite'>) {\n super(stack);\n this.controlAdapter = stack?.adapter\n ? blindCast<\n SqlControlAdapter<'sqlite'>,\n 'The SQLite descriptor create() returns SqlControlAdapter<sqlite>; typed as wider ControlAdapterInstance at the framework boundary'\n >(stack.adapter.create(stack))\n : undefined;\n }\n\n /**\n * Returns the materialized control adapter, or throws a MIGRATION.SQLITE_CONTROL_STACK_MISSING naming\n * `operation` when the migration was constructed without a `ControlStack`.\n * Single home for the null-check that every DDL/DML method shares.\n */\n private controlAdapterFor(operation: string): SqlControlAdapter<'sqlite'> {\n if (!this.controlAdapter) {\n throw errorSqliteMigrationStackMissing(operation);\n }\n return this.controlAdapter;\n }\n\n /**\n * The typed SQLite view over this migration's end-state contract — sole\n * namespace unwrapped to the root, so `this.endContract.table.<name>` etc.\n * Throws if no `endContractJson` was provided.\n */\n get endContract(): SqliteContractView<End> {\n return this.#endView.endContract;\n }\n\n /**\n * The typed SQLite view over this migration's start-state contract, or\n * `null` for a baseline migration (no `startContractJson`).\n */\n get startContract(): SqliteContractView<Start> | null {\n return this.#startView.startContract;\n }\n\n protected createTable(options: {\n readonly table: string;\n readonly ifNotExists?: boolean;\n readonly columns: readonly DdlColumn[];\n readonly constraints?: readonly DdlTableConstraint[];\n }): Promise<Op> {\n return new CreateTableCall(options.table, options.columns, options.constraints).toOp(\n this.controlAdapterFor('createTable'),\n );\n }\n\n protected dropTable(options: { readonly table: string }): Promise<Op> {\n return new DropTableCall(options.table).toOp(this.controlAdapterFor('dropTable'));\n }\n\n protected addColumn(options: {\n readonly table: string;\n readonly column: SqliteColumnSpec;\n }): Promise<Op> {\n return new AddColumnCall(options.table, options.column).toOp(\n this.controlAdapterFor('addColumn'),\n );\n }\n\n protected dropColumn(options: { readonly table: string; readonly column: string }): Promise<Op> {\n return new DropColumnCall(options.table, options.column).toOp(\n this.controlAdapterFor('dropColumn'),\n );\n }\n\n protected createIndex(options: {\n readonly table: string;\n readonly index: string;\n readonly columns: readonly string[];\n }): Promise<Op> {\n return new CreateIndexCall(options.table, options.index, options.columns).toOp(\n this.controlAdapterFor('createIndex'),\n );\n }\n\n protected dropIndex(options: { readonly table: string; readonly index: string }): Promise<Op> {\n return new DropIndexCall(options.table, options.index).toOp(\n this.controlAdapterFor('dropIndex'),\n );\n }\n\n protected recreateTable(options: {\n readonly tableName: string;\n readonly contractTable: SqliteTableSpec;\n readonly schemaColumnNames: readonly string[];\n readonly indexes: readonly SqliteIndexSpec[];\n readonly summary: string;\n readonly postchecks: readonly { readonly description: string; readonly sql: string }[];\n readonly operationClass: MigrationOperationClass;\n }): Promise<Op> {\n return new RecreateTableCall(options).toOp(this.controlAdapterFor('recreateTable'));\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,iCAAiC,WAAuC;CACtF,OAAO,IAAI,mBACT,0CACA,mBAAmB,UAAU,8BAC7B;EACE,KAAK,mBAAmB,UAAU;EAClC,KAAK;EACL,MAAM,EAAE,UAAU;CACpB,CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;ACiBA,IAAsB,kBAAtB,cAGUA,UAA4D;CACpE,WAAoB;;;;;;;CAQpB;CAEA,WAAW,IAAI,uBAAgD,MAAM,oBAAoB,SACvF,mBAAmB,SAAc,IAAI,CACvC;CACA,aAAa,IAAI,uBACf,MACA,oBACC,SAAS,mBAAmB,SAAgB,IAAI,CACnD;CAEA,YAAY,OAAuC;EACjD,MAAM,KAAK;EACX,KAAK,iBAAiB,OAAO,UACzB,UAGE,MAAM,QAAQ,OAAO,KAAK,CAAC,IAC7B,KAAA;CACN;;;;;;CAOA,kBAA0B,WAAgD;EACxE,IAAI,CAAC,KAAK,gBACR,MAAM,iCAAiC,SAAS;EAElD,OAAO,KAAK;CACd;;;;;;CAOA,IAAI,cAAuC;EACzC,OAAO,KAAKC,SAAS;CACvB;;;;;CAMA,IAAI,gBAAkD;EACpD,OAAO,KAAKC,WAAW;CACzB;CAEA,YAAsB,SAKN;EACd,OAAO,IAAI,gBAAgB,QAAQ,OAAO,QAAQ,SAAS,QAAQ,WAAW,CAAC,CAAC,KAC9E,KAAK,kBAAkB,aAAa,CACtC;CACF;CAEA,UAAoB,SAAkD;EACpE,OAAO,IAAI,cAAc,QAAQ,KAAK,CAAC,CAAC,KAAK,KAAK,kBAAkB,WAAW,CAAC;CAClF;CAEA,UAAoB,SAGJ;EACd,OAAO,IAAI,cAAc,QAAQ,OAAO,QAAQ,MAAM,CAAC,CAAC,KACtD,KAAK,kBAAkB,WAAW,CACpC;CACF;CAEA,WAAqB,SAA2E;EAC9F,OAAO,IAAI,eAAe,QAAQ,OAAO,QAAQ,MAAM,CAAC,CAAC,KACvD,KAAK,kBAAkB,YAAY,CACrC;CACF;CAEA,YAAsB,SAIN;EACd,OAAO,IAAI,gBAAgB,QAAQ,OAAO,QAAQ,OAAO,QAAQ,OAAO,CAAC,CAAC,KACxE,KAAK,kBAAkB,aAAa,CACtC;CACF;CAEA,UAAoB,SAA0E;EAC5F,OAAO,IAAI,cAAc,QAAQ,OAAO,QAAQ,KAAK,CAAC,CAAC,KACrD,KAAK,kBAAkB,WAAW,CACpC;CACF;CAEA,cAAwB,SAQR;EACd,OAAO,IAAI,kBAAkB,OAAO,CAAC,CAAC,KAAK,KAAK,kBAAkB,eAAe,CAAC;CACpF;AACF"}
package/package.json CHANGED
@@ -1,31 +1,31 @@
1
1
  {
2
2
  "name": "@prisma-next/target-sqlite",
3
- "version": "0.15.0",
3
+ "version": "0.16.0-dev.10",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "dependencies": {
8
- "@prisma-next/cli": "0.15.0",
9
- "@prisma-next/contract": "0.15.0",
10
- "@prisma-next/errors": "0.15.0",
11
- "@prisma-next/family-sql": "0.15.0",
12
- "@prisma-next/framework-components": "0.15.0",
13
- "@prisma-next/migration-tools": "0.15.0",
14
- "@prisma-next/sql-contract": "0.15.0",
15
- "@prisma-next/sql-errors": "0.15.0",
16
- "@prisma-next/sql-relational-core": "0.15.0",
17
- "@prisma-next/sql-runtime": "0.15.0",
18
- "@prisma-next/sql-schema-ir": "0.15.0",
19
- "@prisma-next/ts-render": "0.15.0",
20
- "@prisma-next/utils": "0.15.0",
8
+ "@prisma-next/cli": "0.16.0-dev.10",
9
+ "@prisma-next/contract": "0.16.0-dev.10",
10
+ "@prisma-next/errors": "0.16.0-dev.10",
11
+ "@prisma-next/family-sql": "0.16.0-dev.10",
12
+ "@prisma-next/framework-components": "0.16.0-dev.10",
13
+ "@prisma-next/migration-tools": "0.16.0-dev.10",
14
+ "@prisma-next/sql-contract": "0.16.0-dev.10",
15
+ "@prisma-next/sql-errors": "0.16.0-dev.10",
16
+ "@prisma-next/sql-relational-core": "0.16.0-dev.10",
17
+ "@prisma-next/sql-runtime": "0.16.0-dev.10",
18
+ "@prisma-next/sql-schema-ir": "0.16.0-dev.10",
19
+ "@prisma-next/ts-render": "0.16.0-dev.10",
20
+ "@prisma-next/utils": "0.16.0-dev.10",
21
21
  "@standard-schema/spec": "1.1.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@prisma-next/driver-sqlite": "0.15.0",
25
- "@prisma-next/test-utils": "0.15.0",
26
- "@prisma-next/tsconfig": "0.15.0",
27
- "@prisma-next/tsdown": "0.15.0",
28
- "tsdown": "0.22.3",
24
+ "@prisma-next/driver-sqlite": "0.16.0-dev.10",
25
+ "@prisma-next/test-utils": "0.16.0-dev.10",
26
+ "@prisma-next/tsconfig": "0.16.0-dev.10",
27
+ "@prisma-next/tsdown": "0.16.0-dev.10",
28
+ "tsdown": "0.22.8",
29
29
  "typescript": "5.9.3",
30
30
  "vitest": "4.1.10"
31
31
  },
@@ -1,9 +1,15 @@
1
- import { temporalAuthoringPresets } from '@prisma-next/family-sql/control';
1
+ import { temporalAuthoringPresets, temporalCodecPreset } from '@prisma-next/family-sql/control';
2
2
  import type { AuthoringFieldNamespace } from '@prisma-next/framework-components/authoring';
3
3
 
4
4
  export const sqliteAuthoringFieldPresets = {
5
- temporal: /* @__PURE__ */ temporalAuthoringPresets({
6
- codecId: 'sqlite/datetime@1',
7
- nativeType: 'text',
8
- }),
5
+ temporal: {
6
+ .../* @__PURE__ */ temporalAuthoringPresets({
7
+ codecId: 'sqlite/datetime@1',
8
+ nativeType: 'text',
9
+ }),
10
+ datetime: /* @__PURE__ */ temporalCodecPreset({
11
+ codecId: 'sqlite/datetime@1',
12
+ nativeType: 'text',
13
+ }),
14
+ },
9
15
  } as const satisfies AuthoringFieldNamespace;
@@ -12,8 +12,9 @@ import { CliStructuredError } from '@prisma-next/errors/control';
12
12
  * The `operation` argument is required so every throw site names the operation
13
13
  * that actually failed; a new operation cannot inherit a misattributed message.
14
14
  *
15
- * Distinct from `PN-MIG-2001` (placeholder not filled) because the missing
16
- * input is the stack itself, not the per-operation contract.
15
+ * Distinct from `MIGRATION.UNFILLED_PLACEHOLDER` (placeholder not filled)
16
+ * because the missing input is the stack itself, not the per-operation
17
+ * contract.
17
18
  *
18
19
  * Lives in `@prisma-next/target-sqlite/errors` rather than the shared
19
20
  * framework migration errors module because the failure is target-specific:
@@ -21,10 +22,13 @@ import { CliStructuredError } from '@prisma-next/errors/control';
21
22
  * adapter, the SQLite-target stack) only exists in this package.
22
23
  */
23
24
  export function errorSqliteMigrationStackMissing(operation: string): CliStructuredError {
24
- return new CliStructuredError('2008', `SqliteMigration.${operation} requires a control adapter`, {
25
- domain: 'MIG',
26
- why: `SqliteMigration.${operation} was invoked on an instance constructed without a ControlStack, so the stored controlAdapter is undefined and the operation cannot lower its DDL node.`,
27
- fix: 'Construct the migration via the migration CLI entrypoint (which assembles a ControlStack from the loaded prisma-next.config.ts), or pass a ControlStack containing a SQLite adapter to the migration constructor in test fixtures.',
28
- meta: { operation },
29
- });
25
+ return new CliStructuredError(
26
+ 'MIGRATION.SQLITE_CONTROL_STACK_MISSING',
27
+ `SqliteMigration.${operation} requires a control adapter`,
28
+ {
29
+ why: `SqliteMigration.${operation} was invoked on an instance constructed without a ControlStack, so the stored controlAdapter is undefined and the operation cannot lower its DDL node.`,
30
+ fix: 'Construct the migration via the migration CLI entrypoint (which assembles a ControlStack from the loaded prisma-next.config.ts), or pass a ControlStack containing a SQLite adapter to the migration constructor in test fixtures.',
31
+ meta: { operation },
32
+ },
33
+ );
30
34
  }
@@ -21,6 +21,7 @@ import type {
21
21
  } from '@prisma-next/family-sql/control';
22
22
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
23
23
  import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
24
+ import { issueOutcome, orderIssuesByDependencies } from '@prisma-next/framework-components/control';
24
25
  import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
25
26
  import {
26
27
  RelationalSchemaNodeKind,
@@ -63,51 +64,6 @@ import {
63
64
 
64
65
  export type { CallMigrationStrategy, StrategyContext };
65
66
 
66
- // ============================================================================
67
- // Node-keyed issue ordering (dependency order)
68
- // ============================================================================
69
-
70
- /**
71
- * Re-keys the legacy `ISSUE_KIND_ORDER` (kind string → priority number) on
72
- * `(nodeKind, reason)`. Numbers are preserved from the legacy table so the
73
- * dependency intent stays legible; the final emission order is actually
74
- * fixed downstream by category bucketing (create-table → add-column →
75
- * create-index → recreate → drop-column → drop-index → drop-table), so this
76
- * only breaks ties within a single bucket.
77
- */
78
- export function nodeIssueOrder(issue: SchemaDiffIssue): number {
79
- const node = issueNode(issue);
80
- if (node === undefined) return 99;
81
- switch (node.nodeKind) {
82
- case RelationalSchemaNodeKind.foreignKey:
83
- return issue.reason === 'not-expected' ? 10 : 60;
84
- case RelationalSchemaNodeKind.unique:
85
- return issue.reason === 'not-expected' ? 11 : 51;
86
- case RelationalSchemaNodeKind.primaryKey:
87
- return issue.reason === 'not-expected' ? 12 : 50;
88
- case RelationalSchemaNodeKind.index:
89
- return issue.reason === 'not-expected' ? 13 : 52;
90
- case RelationalSchemaNodeKind.columnDefault:
91
- if (issue.reason === 'not-expected') return 14;
92
- return issue.reason === 'not-found' ? 42 : 43;
93
- case RelationalSchemaNodeKind.column:
94
- if (issue.reason === 'not-expected') return 15;
95
- return issue.reason === 'not-found' ? 30 : 40;
96
- case RelationalSchemaNodeKind.table:
97
- return issue.reason === 'not-expected' ? 16 : 20;
98
- case RelationalSchemaNodeKind.check:
99
- if (issue.reason === 'not-found') return 53;
100
- return issue.reason === 'not-expected' ? 54 : 55;
101
- default:
102
- return 99;
103
- }
104
- }
105
-
106
- /** Deterministic tiebreak within an order bucket: the diff path itself already encodes table → child → grandchild. */
107
- export function nodeIssueKey(issue: SchemaDiffIssue): string {
108
- return issue.path.join(' ');
109
- }
110
-
111
67
  // ============================================================================
112
68
  // Subtree coalescing — the planner's responsibility per the differ's contract
113
69
  // ============================================================================
@@ -126,7 +82,9 @@ export function coalesceSubtreeIssues(
126
82
  issues: readonly SchemaDiffIssue[],
127
83
  ): readonly SchemaDiffIssue[] {
128
84
  const collapsingPaths = issues
129
- .filter((issue) => issue.reason === 'not-found' || issue.reason === 'not-expected')
85
+ .filter(
86
+ (issue) => issueOutcome(issue) === 'not-found' || issueOutcome(issue) === 'not-expected',
87
+ )
130
88
  .map((issue) => issue.path);
131
89
  if (collapsingPaths.length === 0) return issues;
132
90
  return issues.filter(
@@ -180,12 +138,11 @@ export function tableSpecFromNode(table: SqlTableIR): SqliteTableSpec {
180
138
  ...(u.name !== undefined ? { name: u.name } : {}),
181
139
  }));
182
140
  // Every FK node on the expected tree is constraint-bearing by construction
183
- // (contractToSchemaIR filters `constraint: false` FKs out before they ever
184
- // become nodes — those only ever contribute an index, never an FK node).
141
+ // (a persisted `foreignKeys[]` entry is a constraint; a non-constraint FK
142
+ // contributes only a backing index, never an FK node).
185
143
  const foreignKeys: SqliteForeignKeySpec[] = table.foreignKeys.map((fk) => ({
186
144
  columns: fk.columns,
187
145
  references: { table: fk.referencedTable, columns: fk.referencedColumns },
188
- constraint: true,
189
146
  ...(fk.name !== undefined ? { name: fk.name } : {}),
190
147
  ...(fk.onDelete !== undefined ? { onDelete: fk.onDelete } : {}),
191
148
  ...(fk.onUpdate !== undefined ? { onUpdate: fk.onUpdate } : {}),
@@ -274,14 +231,14 @@ function buildCreateTableCalls(table: SqlTableIR): SqliteOpFactoryCall[] {
274
231
  function mapTableIssue(
275
232
  issue: SchemaDiffIssue,
276
233
  ): Result<readonly SqliteOpFactoryCall[], SqlPlannerConflict> {
277
- if (issue.reason === 'not-found') {
234
+ if (issueOutcome(issue) === 'not-found') {
278
235
  const table = blindCast<
279
236
  SqlTableIR,
280
237
  'a not-found table issue always carries the expected table node'
281
238
  >(issue.expected);
282
239
  return ok(buildCreateTableCalls(table));
283
240
  }
284
- if (issue.reason === 'not-expected') {
241
+ if (issueOutcome(issue) === 'not-expected') {
285
242
  const table = blindCast<
286
243
  SqlTableIR,
287
244
  'a not-expected table issue always carries the actual table node'
@@ -310,7 +267,7 @@ function mapColumnIssue(
310
267
  ),
311
268
  );
312
269
  }
313
- if (issue.reason === 'not-found') {
270
+ if (issueOutcome(issue) === 'not-found') {
314
271
  const column = blindCast<
315
272
  SqlColumnIR,
316
273
  'a not-found column issue always carries the expected column node'
@@ -323,7 +280,7 @@ function mapColumnIssue(
323
280
  const inline = table !== undefined && isInlineAutoincrementPrimaryKeyNode(table, column);
324
281
  return ok([new AddColumnCall(tableName, columnSpecFromNode(column, inline))]);
325
282
  }
326
- if (issue.reason === 'not-expected') {
283
+ if (issueOutcome(issue) === 'not-expected') {
327
284
  const column = blindCast<
328
285
  SqlColumnIR,
329
286
  'a not-expected column issue always carries the actual column node'
@@ -357,7 +314,7 @@ function mapIndexIssue(
357
314
  ),
358
315
  );
359
316
  }
360
- if (issue.reason === 'not-found') {
317
+ if (issueOutcome(issue) === 'not-found') {
361
318
  const index = blindCast<
362
319
  SqlIndexIR,
363
320
  'a not-found index issue always carries the expected index node'
@@ -365,7 +322,7 @@ function mapIndexIssue(
365
322
  const indexName = index.name ?? defaultIndexName(tableName, index.columns);
366
323
  return ok([new CreateIndexCall(tableName, indexName, index.columns)]);
367
324
  }
368
- if (issue.reason === 'not-expected') {
325
+ if (issueOutcome(issue) === 'not-expected') {
369
326
  const index = blindCast<
370
327
  SqlIndexIR,
371
328
  'a not-expected index issue always carries the actual index node'
@@ -505,13 +462,12 @@ export function planIssues(
505
462
  }
506
463
  }
507
464
 
508
- const sorted = [...remaining].sort((a, b) => {
509
- const kindDelta = nodeIssueOrder(a) - nodeIssueOrder(b);
510
- if (kindDelta !== 0) return kindDelta;
511
- const keyA = nodeIssueKey(a);
512
- const keyB = nodeIssueKey(b);
513
- return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;
514
- });
465
+ // Dependency-graph order: a topological sort over the issues' `dependsOn`
466
+ // cross-links and containment edges (edge direction from the ordering law's
467
+ // presence rule), path-tiebroken for determinism. `classifyCall` bucketing
468
+ // downstream fixes the coarse create/recreate/drop sequence; this settles
469
+ // the order within each bucket.
470
+ const sorted = orderIssuesByDependencies(remaining);
515
471
 
516
472
  const defaultCalls: SqliteOpFactoryCall[] = [];
517
473
  const conflicts: SqlPlannerConflict[] = [];
@@ -563,7 +563,7 @@ export class DropIndexCall extends SqliteOpFactoryCallNode {
563
563
  * any future strategy that needs a placeholder data step can construct one
564
564
  * with its own id/label.
565
565
  *
566
- * `toOp()` always throws `PN-MIG-2001`: the planner cannot lower a stubbed
566
+ * `toOp()` always throws `MIGRATION.UNFILLED_PLACEHOLDER`: the planner cannot lower a stubbed
567
567
  * transform to a runtime op — the user must edit the rendered
568
568
  * `migration.ts` and re-emit.
569
569
  */
@@ -60,7 +60,6 @@ export interface SqliteForeignKeySpec {
60
60
  readonly name?: string;
61
61
  readonly onDelete?: ReferentialAction;
62
62
  readonly onUpdate?: ReferentialAction;
63
- readonly constraint: boolean;
64
63
  }
65
64
 
66
65
  /**
@@ -103,12 +102,11 @@ export function renderColumnDefinition(column: SqliteColumnSpec): string {
103
102
 
104
103
  /**
105
104
  * Renders an inline FOREIGN KEY constraint clause for a `CREATE TABLE`
106
- * body. Returns the empty string when `constraint` is false (the FK is
107
- * tracked at the contract level for index-creation purposes only and must
108
- * not produce DDL).
105
+ * body. Every `SqliteForeignKeySpec` is constraint-bearing by construction
106
+ * (`tableSpecFromNode` only ever builds specs from schema-IR FK nodes, which
107
+ * are themselves constraint-only — a non-constraint FK contributes no node).
109
108
  */
110
109
  export function renderForeignKeyClause(fk: SqliteForeignKeySpec): string {
111
- if (!fk.constraint) return '';
112
110
  const name = fk.name ? `CONSTRAINT ${quoteIdentifier(fk.name)} ` : '';
113
111
  let sql = `${name}FOREIGN KEY (${fk.columns.map(quoteIdentifier).join(', ')}) REFERENCES ${quoteIdentifier(fk.references.table)} (${fk.references.columns.map(quoteIdentifier).join(', ')})`;
114
112
  if (fk.onDelete !== undefined) {
@@ -1,6 +1,7 @@
1
1
  import type { MigrationOperationClass } from '@prisma-next/family-sql/control';
2
2
  import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
3
3
  import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
4
+ import { issueOutcome } from '@prisma-next/framework-components/control';
4
5
  import { RelationalSchemaNodeKind, type SqlColumnIR } from '@prisma-next/sql-schema-ir/types';
5
6
  import { blindCast } from '@prisma-next/utils/casts';
6
7
  import { tableExistsAst } from '../../../contract-free/checks';
@@ -294,7 +295,7 @@ export function buildRecreatePostchecks(
294
295
 
295
296
  for (const issue of issues) {
296
297
  const nodeKind = nodeKindOf(issue);
297
- if (nodeKind === RelationalSchemaNodeKind.column && issue.reason === 'not-equal') {
298
+ if (nodeKind === RelationalSchemaNodeKind.column && issueOutcome(issue) === 'not-equal') {
298
299
  const columnName = columnNameFromNode(issue);
299
300
  if (columnName === undefined) continue;
300
301
  const c = escapeLiteral(columnName);
@@ -325,7 +326,7 @@ export function buildRecreatePostchecks(
325
326
  const columnName = columnNameFromDefaultIssuePath(issue);
326
327
  if (columnName === undefined) continue;
327
328
  const c = escapeLiteral(columnName);
328
- if (issue.reason === 'not-expected') {
329
+ if (issueOutcome(issue) === 'not-expected') {
329
330
  checks.push({
330
331
  description: `verify "${columnName}" has no default on "${tableName}"`,
331
332
  sql: `SELECT COUNT(*) > 0 FROM pragma_table_info('${t}') WHERE name = '${c}' AND dflt_value IS NULL`,
@@ -25,6 +25,7 @@ export class TypeScriptRenderableSqliteMigration
25
25
  readonly #meta: MigrationMeta;
26
26
  readonly #destination: SqliteMigrationDestinationInfo;
27
27
  readonly #spaceId: string;
28
+ readonly #snapshotsImportPath: string;
28
29
  readonly #lowerer: ExecuteRequestLowerer | undefined;
29
30
  #operationsCache: readonly (Op | Promise<Op>)[] | undefined;
30
31
 
@@ -32,6 +33,7 @@ export class TypeScriptRenderableSqliteMigration
32
33
  calls: readonly OpFactoryCall[],
33
34
  meta: MigrationMeta,
34
35
  spaceId: string,
36
+ snapshotsImportPath: string,
35
37
  destination?: SqliteMigrationDestinationInfo,
36
38
  lowerer?: ExecuteRequestLowerer,
37
39
  ) {
@@ -39,6 +41,7 @@ export class TypeScriptRenderableSqliteMigration
39
41
  this.#calls = calls;
40
42
  this.#meta = meta;
41
43
  this.#spaceId = spaceId;
44
+ this.#snapshotsImportPath = snapshotsImportPath;
42
45
  this.#destination = destination ?? { storageHash: meta.to };
43
46
  this.#lowerer = lowerer;
44
47
  }
@@ -69,6 +72,7 @@ export class TypeScriptRenderableSqliteMigration
69
72
  return renderCallsToTypeScript(this.#calls, {
70
73
  from: this.#meta.from,
71
74
  to: this.#meta.to,
75
+ snapshotsImportPath: this.#snapshotsImportPath,
72
76
  });
73
77
  }
74
78
  }
@@ -19,6 +19,7 @@ import type {
19
19
  } from '@prisma-next/family-sql/control';
20
20
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
21
21
  import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
22
+ import { issueOutcome } from '@prisma-next/framework-components/control';
22
23
  import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
23
24
  import {
24
25
  RelationalSchemaNodeKind,
@@ -78,7 +79,7 @@ function classifyNodeIssue(issue: SchemaDiffIssue): 'widening' | 'destructive' |
78
79
  >(node).nodeKind;
79
80
  switch (nodeKind) {
80
81
  case RelationalSchemaNodeKind.column: {
81
- if (issue.reason !== 'not-equal') return null;
82
+ if (issueOutcome(issue) !== 'not-equal') return null;
82
83
  const expected = blindCast<SqlColumnIR, 'a not-equal column issue carries the expected node'>(
83
84
  issue.expected,
84
85
  );
@@ -91,7 +92,7 @@ function classifyNodeIssue(issue: SchemaDiffIssue): 'widening' | 'destructive' |
91
92
  return expected.nullable ? 'widening' : 'destructive';
92
93
  }
93
94
  case RelationalSchemaNodeKind.columnDefault:
94
- return issue.reason === 'not-expected' ? 'destructive' : 'widening';
95
+ return issueOutcome(issue) === 'not-expected' ? 'destructive' : 'widening';
95
96
  case RelationalSchemaNodeKind.primaryKey:
96
97
  case RelationalSchemaNodeKind.foreignKey:
97
98
  case RelationalSchemaNodeKind.unique:
@@ -206,7 +207,7 @@ export const nullabilityTighteningBackfillStrategy: CallMigrationStrategy = (iss
206
207
  const calls: SqliteOpFactoryCall[] = [];
207
208
  for (const issue of issues) {
208
209
  if (
209
- issue.reason !== 'not-equal' ||
210
+ issueOutcome(issue) !== 'not-equal' ||
210
211
  issue.expected === undefined ||
211
212
  issue.actual === undefined
212
213
  ) {
@@ -18,6 +18,7 @@ import type {
18
18
  SchemaDiffIssue,
19
19
  SchemaOwnership,
20
20
  } from '@prisma-next/framework-components/control';
21
+ import { issueOutcome } from '@prisma-next/framework-components/control';
21
22
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
22
23
  import { RelationalSchemaNodeKind, type SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
23
24
  import { buildSqlitePlanDiff } from './diff-database-schema';
@@ -91,6 +92,12 @@ export class SqliteMigrationPlanner
91
92
  * {@link SqlMigrationPlannerPlanOptions.ownership}.
92
93
  */
93
94
  readonly ownership?: SchemaOwnership;
95
+ /**
96
+ * POSIX-relative path from the migration package dir to
97
+ * `migrations/snapshots` — see
98
+ * {@link SqlMigrationPlannerPlanOptions.snapshotsImportPath}.
99
+ */
100
+ readonly snapshotsImportPath: string;
94
101
  }): SqlitePlanResult {
95
102
  return this.planSql(options as SqlMigrationPlannerPlanOptions);
96
103
  }
@@ -106,6 +113,7 @@ export class SqliteMigrationPlanner
106
113
  to: context.toHash,
107
114
  },
108
115
  spaceId,
116
+ context.snapshotsImportPath,
109
117
  undefined,
110
118
  this.#lowerer,
111
119
  );
@@ -163,6 +171,7 @@ export class SqliteMigrationPlanner
163
171
  to: options.contract.storage.storageHash,
164
172
  },
165
173
  options.spaceId,
174
+ options.snapshotsImportPath,
166
175
  destination,
167
176
  this.#lowerer,
168
177
  ),
@@ -229,7 +238,7 @@ export class SqliteMigrationPlanner
229
238
  });
230
239
  const coalesced = coalesceSubtreeIssues(rawIssues);
231
240
  const owned = retainUnownedExtras(coalesced, options.ownership);
232
- const issues = strict ? owned : owned.filter((issue) => issue.reason !== 'not-expected');
241
+ const issues = strict ? owned : owned.filter((issue) => issueOutcome(issue) !== 'not-expected');
233
242
  return { expected, actual, issues };
234
243
  }
235
244
  }
@@ -266,7 +275,7 @@ function retainUnownedExtras(
266
275
  ): readonly SchemaDiffIssue[] {
267
276
  if (ownership === undefined) return issues;
268
277
  return issues.filter((issue) => {
269
- if (issue.reason !== 'not-expected') return true;
278
+ if (issueOutcome(issue) !== 'not-expected') return true;
270
279
  const node = issueNode(issue);
271
280
  if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.table) return true;
272
281
  const tableName = issue.path[1];
@@ -4,13 +4,19 @@
4
4
  * specifier, same overall shape.
5
5
  */
6
6
 
7
- import type { OpFactoryCall } from '@prisma-next/framework-components/control';
7
+ import {
8
+ contractSnapshotJsonSpecifier,
9
+ contractSnapshotTypesSpecifier,
10
+ type OpFactoryCall,
11
+ } from '@prisma-next/framework-components/control';
8
12
  import { detectScaffoldRuntime, shebangLineFor } from '@prisma-next/migration-tools/migration-ts';
9
13
  import { type ImportRequirement, renderImports } from '@prisma-next/ts-render';
10
14
 
11
15
  export interface RenderMigrationMeta {
12
16
  readonly from: string | null;
13
17
  readonly to: string;
18
+ /** POSIX-relative path from the migration package dir to `migrations/snapshots`, e.g. '../../snapshots'. */
19
+ readonly snapshotsImportPath: string;
14
20
  }
15
21
 
16
22
  /**
@@ -72,31 +78,37 @@ function buildImports(calls: ReadonlyArray<OpFactoryCall>, meta: RenderMigration
72
78
 
73
79
  /**
74
80
  * The committed contract-JSON imports the scaffold reads its from/to identity
75
- * from. `end-contract.json` is always present; `start-contract.json` is added
76
- * only for a non-baseline migration (`meta.from !== null`). The matching
77
- * `Contract` type imports (aliased `Start`/`End`) feed the
81
+ * from, resolved to the deduplicated snapshot store under
82
+ * `meta.snapshotsImportPath`. The end snapshot is always present; the start
83
+ * snapshot is added only for a non-baseline migration (`meta.from !== null`).
84
+ * The matching `Contract` type imports (aliased `Start`/`End`) feed the
78
85
  * `Migration<Start, End>` generics. Baseline emits `Migration<never, End>` with
79
86
  * no start imports — `never` is the honest "no prior contract" Start.
80
87
  */
81
88
  function contractImports(meta: RenderMigrationMeta): readonly ImportRequirement[] {
82
89
  const reqs: ImportRequirement[] = [
83
90
  {
84
- moduleSpecifier: './end-contract.json',
91
+ moduleSpecifier: contractSnapshotJsonSpecifier(meta.snapshotsImportPath, meta.to),
85
92
  symbol: 'endContract',
86
93
  kind: 'default',
87
94
  attributes: { type: 'json' },
88
95
  },
89
- { moduleSpecifier: './end-contract', symbol: 'Contract', alias: 'End', typeOnly: true },
96
+ {
97
+ moduleSpecifier: contractSnapshotTypesSpecifier(meta.snapshotsImportPath, meta.to),
98
+ symbol: 'Contract',
99
+ alias: 'End',
100
+ typeOnly: true,
101
+ },
90
102
  ];
91
103
  if (meta.from !== null) {
92
104
  reqs.push({
93
- moduleSpecifier: './start-contract.json',
105
+ moduleSpecifier: contractSnapshotJsonSpecifier(meta.snapshotsImportPath, meta.from),
94
106
  symbol: 'startContract',
95
107
  kind: 'default',
96
108
  attributes: { type: 'json' },
97
109
  });
98
110
  reqs.push({
99
- moduleSpecifier: './start-contract',
111
+ moduleSpecifier: contractSnapshotTypesSpecifier(meta.snapshotsImportPath, meta.from),
100
112
  symbol: 'Contract',
101
113
  alias: 'Start',
102
114
  typeOnly: true,