@prisma-next/target-sqlite 0.13.0-dev.4 → 0.13.0-dev.40

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/contract-free.d.mts +35 -2
  2. package/dist/contract-free.d.mts.map +1 -1
  3. package/dist/contract-free.mjs +3 -22
  4. package/dist/contract-free.mjs.map +1 -1
  5. package/dist/control.d.mts +5 -8
  6. package/dist/control.d.mts.map +1 -1
  7. package/dist/control.mjs +18 -20
  8. package/dist/control.mjs.map +1 -1
  9. package/dist/ddl-DrtjQMFK.mjs +68 -0
  10. package/dist/ddl-DrtjQMFK.mjs.map +1 -0
  11. package/dist/{descriptor-meta-Dxx2A6PT.mjs → descriptor-meta-DxmEeTJ-.mjs} +10 -3
  12. package/dist/descriptor-meta-DxmEeTJ-.mjs.map +1 -0
  13. package/dist/migration.d.mts +4 -46
  14. package/dist/migration.d.mts.map +1 -1
  15. package/dist/migration.mjs +4 -3
  16. package/dist/migration.mjs.map +1 -1
  17. package/dist/op-factory-call-DmdfD1yd.mjs +794 -0
  18. package/dist/op-factory-call-DmdfD1yd.mjs.map +1 -0
  19. package/dist/op-factory-call.d.mts +22 -12
  20. package/dist/op-factory-call.d.mts.map +1 -1
  21. package/dist/op-factory-call.mjs +1 -1
  22. package/dist/pack.mjs +1 -1
  23. package/dist/{planner-DSNDwQy9.mjs → planner-Ciq8p_dL.mjs} +80 -12
  24. package/dist/planner-Ciq8p_dL.mjs.map +1 -0
  25. package/dist/{planner-produced-sqlite-migration-DowV_vHw.mjs → planner-produced-sqlite-migration-0xPEm3R1.mjs} +9 -5
  26. package/dist/planner-produced-sqlite-migration-0xPEm3R1.mjs.map +1 -0
  27. package/dist/{planner-produced-sqlite-migration-C1yqJAiM.d.mts → planner-produced-sqlite-migration-CpgsY-M9.d.mts} +5 -4
  28. package/dist/planner-produced-sqlite-migration-CpgsY-M9.d.mts.map +1 -0
  29. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  30. package/dist/planner-produced-sqlite-migration.mjs +1 -1
  31. package/dist/planner.d.mts +5 -2
  32. package/dist/planner.d.mts.map +1 -1
  33. package/dist/planner.mjs +1 -1
  34. package/dist/render-ops-BDW2tUeR.mjs +22 -0
  35. package/dist/render-ops-BDW2tUeR.mjs.map +1 -0
  36. package/dist/render-ops.d.mts +2 -1
  37. package/dist/render-ops.d.mts.map +1 -1
  38. package/dist/render-ops.mjs +1 -1
  39. package/dist/runtime.d.mts +1 -0
  40. package/dist/runtime.d.mts.map +1 -1
  41. package/dist/runtime.mjs +2 -2
  42. package/dist/shared-Dhc8mLK1.d.mts.map +1 -1
  43. package/dist/{sqlite-contract-serializer-jcRu8aHh.mjs → sqlite-contract-serializer--iaDgC8e.mjs} +17 -6
  44. package/dist/sqlite-contract-serializer--iaDgC8e.mjs.map +1 -0
  45. package/dist/sqlite-migration-A0rwqPOG.mjs +92 -0
  46. package/dist/sqlite-migration-A0rwqPOG.mjs.map +1 -0
  47. package/dist/sqlite-migration-DVfhQwN_.d.mts +75 -0
  48. package/dist/sqlite-migration-DVfhQwN_.d.mts.map +1 -0
  49. package/package.json +18 -18
  50. package/src/contract-free/checks.ts +75 -0
  51. package/src/core/control-target.ts +4 -4
  52. package/src/core/errors.ts +28 -0
  53. package/src/core/migrations/issue-planner.ts +151 -8
  54. package/src/core/migrations/op-factory-call.ts +332 -45
  55. package/src/core/migrations/operations/columns.ts +32 -26
  56. package/src/core/migrations/operations/indexes.ts +31 -27
  57. package/src/core/migrations/operations/shared.ts +11 -3
  58. package/src/core/migrations/operations/tables.ts +39 -37
  59. package/src/core/migrations/planner-ddl-builders.ts +7 -16
  60. package/src/core/migrations/planner-produced-sqlite-migration.ts +8 -2
  61. package/src/core/migrations/planner-strategies.ts +3 -3
  62. package/src/core/migrations/planner.ts +14 -2
  63. package/src/core/migrations/render-ops.ts +37 -9
  64. package/src/core/migrations/runner.ts +16 -12
  65. package/src/core/migrations/sqlite-migration.ts +119 -1
  66. package/src/core/sqlite-contract-serializer.ts +5 -0
  67. package/src/core/sqlite-unbound-database.ts +30 -54
  68. package/src/exports/contract-free.ts +8 -0
  69. package/src/exports/migration.ts +8 -3
  70. package/dist/descriptor-meta-Dxx2A6PT.mjs.map +0 -1
  71. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs +0 -12
  72. package/dist/descriptor-meta-runtime-BkXK3OjD.mjs.map +0 -1
  73. package/dist/op-factory-call-DymqdXQW.mjs +0 -279
  74. package/dist/op-factory-call-DymqdXQW.mjs.map +0 -1
  75. package/dist/planner-DSNDwQy9.mjs.map +0 -1
  76. package/dist/planner-produced-sqlite-migration-C1yqJAiM.d.mts.map +0 -1
  77. package/dist/planner-produced-sqlite-migration-DowV_vHw.mjs.map +0 -1
  78. package/dist/render-ops-CFRbJ3Yb.mjs +0 -8
  79. package/dist/render-ops-CFRbJ3Yb.mjs.map +0 -1
  80. package/dist/sqlite-contract-serializer-jcRu8aHh.mjs.map +0 -1
  81. package/dist/sqlite-migration-CUqgmzQH.mjs +0 -16
  82. package/dist/sqlite-migration-CUqgmzQH.mjs.map +0 -1
  83. package/dist/sqlite-migration-D4XGYzgQ.d.mts +0 -17
  84. package/dist/sqlite-migration-D4XGYzgQ.d.mts.map +0 -1
  85. package/dist/tables-CjB7vXCr.mjs +0 -412
  86. package/dist/tables-CjB7vXCr.mjs.map +0 -1
@@ -0,0 +1,75 @@
1
+ import { t as SqlitePlanTargetDetails } from "./planner-target-details-xR6UfIcz.mjs";
2
+ import { i as SqliteTableSpec, n as SqliteColumnSpec, r as SqliteIndexSpec } from "./shared-Dhc8mLK1.mjs";
3
+ import { DdlColumn, DdlTableConstraint } from "@prisma-next/sql-relational-core/ast";
4
+ import { MigrationOperationClass, SqlMigrationPlanOperation } from "@prisma-next/family-sql/control";
5
+ import { Migration } from "@prisma-next/family-sql/migration";
6
+ import { ControlStack } from "@prisma-next/framework-components/control";
7
+ import { SqlControlAdapter } from "@prisma-next/family-sql/control-adapter";
8
+
9
+ //#region src/core/migrations/sqlite-migration.d.ts
10
+ type Op = SqlMigrationPlanOperation<SqlitePlanTargetDetails>;
11
+ /**
12
+ * Target-owned base class for SQLite migrations. Fixes the `SqlMigration`
13
+ * generic to `SqlitePlanTargetDetails` and the abstract `targetId` to the
14
+ * SQLite literal, so both user-authored migrations and renderer-generated
15
+ * scaffolds can extend `SqliteMigration` directly without redeclaring
16
+ * target-local identity.
17
+ *
18
+ * The constructor materializes a single SQLite `SqlControlAdapter` from
19
+ * `stack.adapter.create(stack)` and stores it; the protected instance methods
20
+ * forward to the corresponding `*Call` with that stored adapter, so user
21
+ * migrations can write `this.createTable({...})` without threading the adapter
22
+ * through every call.
23
+ */
24
+ declare abstract class SqliteMigration extends Migration<SqlitePlanTargetDetails, 'sqlite'> {
25
+ readonly targetId: "sqlite";
26
+ /**
27
+ * Materialized SQLite control adapter, created once per migration
28
+ * instance from the injected stack. `undefined` only when the migration
29
+ * was instantiated without a stack (test fixtures); the operation methods
30
+ * throw in that case to surface the misuse.
31
+ */
32
+ protected readonly controlAdapter: SqlControlAdapter<'sqlite'> | undefined;
33
+ constructor(stack?: ControlStack<'sql', 'sqlite'>);
34
+ protected createTable(options: {
35
+ readonly table: string;
36
+ readonly ifNotExists?: boolean;
37
+ readonly columns: readonly DdlColumn[];
38
+ readonly constraints?: readonly DdlTableConstraint[];
39
+ }): Promise<Op>;
40
+ protected dropTable(options: {
41
+ readonly table: string;
42
+ }): Promise<Op>;
43
+ protected addColumn(options: {
44
+ readonly table: string;
45
+ readonly column: SqliteColumnSpec;
46
+ }): Promise<Op>;
47
+ protected dropColumn(options: {
48
+ readonly table: string;
49
+ readonly column: string;
50
+ }): Promise<Op>;
51
+ protected createIndex(options: {
52
+ readonly table: string;
53
+ readonly index: string;
54
+ readonly columns: readonly string[];
55
+ }): Promise<Op>;
56
+ protected dropIndex(options: {
57
+ readonly table: string;
58
+ readonly index: string;
59
+ }): Promise<Op>;
60
+ protected recreateTable(options: {
61
+ readonly tableName: string;
62
+ readonly contractTable: SqliteTableSpec;
63
+ readonly schemaColumnNames: readonly string[];
64
+ readonly indexes: readonly SqliteIndexSpec[];
65
+ readonly summary: string;
66
+ readonly postchecks: readonly {
67
+ readonly description: string;
68
+ readonly sql: string;
69
+ }[];
70
+ readonly operationClass: MigrationOperationClass;
71
+ }): Promise<Op>;
72
+ }
73
+ //#endregion
74
+ export { SqliteMigration as t };
75
+ //# sourceMappingURL=sqlite-migration-DVfhQwN_.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-migration-DVfhQwN_.d.mts","names":[],"sources":["../src/core/migrations/sqlite-migration.ts"],"mappings":";;;;;;;;;KAsBK,EAAA,GAAK,yBAAyB,CAAC,uBAAA;;AAFoC;;;;AAEb;AAe3D;;;;;;;uBAAsB,eAAA,SAAwB,SAAA,CAAa,uBAAA;EAAA,SAChD,QAAA;EAyBL;;;;;;EAAA,mBAjBe,cAAA,EAAgB,iBAAA;cAEvB,KAAA,GAAQ,YAAA;EAAA,UAUV,WAAA,CAAY,OAAA;IAAA,SACX,KAAA;IAAA,SACA,WAAA;IAAA,SACA,OAAA,WAAkB,SAAA;IAAA,SAClB,WAAA,YAAuB,kBAAA;EAAA,IAC9B,OAAA,CAAQ,EAAA;EAAA,UASF,SAAA,CAAU,OAAA;IAAA,SAAoB,KAAA;EAAA,IAAkB,OAAA,CAAQ,EAAA;EAAA,UAOxD,SAAA,CAAU,OAAA;IAAA,SACT,KAAA;IAAA,SACA,MAAA,EAAQ,gBAAA;EAAA,IACf,OAAA,CAAQ,EAAA;EAAA,UAOF,UAAA,CAAW,OAAA;IAAA,SAAoB,KAAA;IAAA,SAAwB,MAAA;EAAA,IAAmB,OAAA,CAAQ,EAAA;EAAA,UAOlF,WAAA,CAAY,OAAA;IAAA,SACX,KAAA;IAAA,SACA,KAAA;IAAA,SACA,OAAA;EAAA,IACP,OAAA,CAAQ,EAAA;EAAA,UASF,SAAA,CAAU,OAAA;IAAA,SAAoB,KAAA;IAAA,SAAwB,KAAA;EAAA,IAAkB,OAAA,CAAQ,EAAA;EAAA,UAOhF,aAAA,CAAc,OAAA;IAAA,SACb,SAAA;IAAA,SACA,aAAA,EAAe,eAAA;IAAA,SACf,iBAAA;IAAA,SACA,OAAA,WAAkB,eAAA;IAAA,SAClB,OAAA;IAAA,SACA,UAAA;MAAA,SAAgC,WAAA;MAAA,SAA8B,GAAA;IAAA;IAAA,SAC9D,cAAA,EAAgB,uBAAA;EAAA,IACvB,OAAA,CAAQ,EAAA;AAAA"}
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
1
  {
2
2
  "name": "@prisma-next/target-sqlite",
3
- "version": "0.13.0-dev.4",
3
+ "version": "0.13.0-dev.40",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "dependencies": {
8
- "@prisma-next/cli": "0.13.0-dev.4",
9
- "@prisma-next/contract": "0.13.0-dev.4",
10
- "@prisma-next/errors": "0.13.0-dev.4",
11
- "@prisma-next/family-sql": "0.13.0-dev.4",
12
- "@prisma-next/framework-components": "0.13.0-dev.4",
13
- "@prisma-next/migration-tools": "0.13.0-dev.4",
14
- "@prisma-next/sql-contract": "0.13.0-dev.4",
15
- "@prisma-next/sql-errors": "0.13.0-dev.4",
16
- "@prisma-next/sql-relational-core": "0.13.0-dev.4",
17
- "@prisma-next/sql-runtime": "0.13.0-dev.4",
18
- "@prisma-next/sql-schema-ir": "0.13.0-dev.4",
19
- "@prisma-next/ts-render": "0.13.0-dev.4",
20
- "@prisma-next/utils": "0.13.0-dev.4",
8
+ "@prisma-next/cli": "0.13.0-dev.40",
9
+ "@prisma-next/contract": "0.13.0-dev.40",
10
+ "@prisma-next/errors": "0.13.0-dev.40",
11
+ "@prisma-next/family-sql": "0.13.0-dev.40",
12
+ "@prisma-next/framework-components": "0.13.0-dev.40",
13
+ "@prisma-next/migration-tools": "0.13.0-dev.40",
14
+ "@prisma-next/sql-contract": "0.13.0-dev.40",
15
+ "@prisma-next/sql-errors": "0.13.0-dev.40",
16
+ "@prisma-next/sql-relational-core": "0.13.0-dev.40",
17
+ "@prisma-next/sql-runtime": "0.13.0-dev.40",
18
+ "@prisma-next/sql-schema-ir": "0.13.0-dev.40",
19
+ "@prisma-next/ts-render": "0.13.0-dev.40",
20
+ "@prisma-next/utils": "0.13.0-dev.40",
21
21
  "@standard-schema/spec": "1.1.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@prisma-next/driver-sqlite": "0.13.0-dev.4",
25
- "@prisma-next/test-utils": "0.13.0-dev.4",
26
- "@prisma-next/tsconfig": "0.13.0-dev.4",
27
- "@prisma-next/tsdown": "0.13.0-dev.4",
24
+ "@prisma-next/driver-sqlite": "0.13.0-dev.40",
25
+ "@prisma-next/test-utils": "0.13.0-dev.40",
26
+ "@prisma-next/tsconfig": "0.13.0-dev.40",
27
+ "@prisma-next/tsdown": "0.13.0-dev.40",
28
28
  "tsdown": "0.22.1",
29
29
  "typescript": "5.9.3",
30
30
  "vitest": "4.1.8"
@@ -0,0 +1,75 @@
1
+ import { FunctionSource, type SelectAst } from '@prisma-next/sql-relational-core/ast';
2
+ import { cfExpr, cfTable, exprSelect } from '@prisma-next/sql-relational-core/contract-free';
3
+ import { SQLITE_TEXT_CODEC_ID } from '../core/codec-ids';
4
+
5
+ export interface ColumnExistsCheckBuilder {
6
+ columnAbsent(): SelectAst;
7
+ columnPresent(): SelectAst;
8
+ }
9
+
10
+ /**
11
+ * Typed builder for the migration planner's column-existence checks. Produces
12
+ * `SELECT COUNT(*) {=|>} 0 AS "result" FROM pragma_table_info(?) WHERE "name" = ?`
13
+ * ASTs with the table and column names bound as text parameters — never
14
+ * inlined into the SQL.
15
+ */
16
+ export function columnExistsAst(table: string, column: string): ColumnExistsCheckBuilder {
17
+ const source = FunctionSource.of('pragma_table_info', [
18
+ cfExpr.param(table, SQLITE_TEXT_CODEC_ID).ast,
19
+ ]);
20
+ const where = cfExpr.identifierRef('name').eqParam(column, SQLITE_TEXT_CODEC_ID);
21
+ return {
22
+ columnAbsent: () =>
23
+ exprSelect().from(source).project('result', cfExpr.countStar().eqLit(0)).where(where).build(),
24
+ columnPresent: () =>
25
+ exprSelect().from(source).project('result', cfExpr.countStar().gtLit(0)).where(where).build(),
26
+ };
27
+ }
28
+
29
+ export interface TableExistsCheckBuilder {
30
+ tableAbsent(): SelectAst;
31
+ tablePresent(): SelectAst;
32
+ }
33
+
34
+ /**
35
+ * Typed builder for table-existence checks over `sqlite_master`.
36
+ * Produces `SELECT COUNT(*) {=|>} 0 AS "result" FROM "sqlite_master" WHERE "type" = ? AND "name" = ?`
37
+ * with the table name and the literal `'table'` bound as text parameters.
38
+ */
39
+ export function tableExistsAst(tableName: string): TableExistsCheckBuilder {
40
+ const source = cfTable('sqlite_master');
41
+ const where = cfExpr.allOf([
42
+ cfExpr.identifierRef('type').eqParam('table', SQLITE_TEXT_CODEC_ID),
43
+ cfExpr.identifierRef('name').eqParam(tableName, SQLITE_TEXT_CODEC_ID),
44
+ ]);
45
+ return {
46
+ tableAbsent: () =>
47
+ exprSelect().from(source).project('result', cfExpr.countStar().eqLit(0)).where(where).build(),
48
+ tablePresent: () =>
49
+ exprSelect().from(source).project('result', cfExpr.countStar().gtLit(0)).where(where).build(),
50
+ };
51
+ }
52
+
53
+ export interface IndexExistsCheckBuilder {
54
+ indexAbsent(): SelectAst;
55
+ indexPresent(): SelectAst;
56
+ }
57
+
58
+ /**
59
+ * Typed builder for index-existence checks over `sqlite_master`.
60
+ * Produces `SELECT COUNT(*) {=|>} 0 AS "result" FROM "sqlite_master" WHERE "type" = ? AND "name" = ?`
61
+ * with the index name and the literal `'index'` bound as text parameters.
62
+ */
63
+ export function indexExistsAst(indexName: string): IndexExistsCheckBuilder {
64
+ const source = cfTable('sqlite_master');
65
+ const where = cfExpr.allOf([
66
+ cfExpr.identifierRef('type').eqParam('index', SQLITE_TEXT_CODEC_ID),
67
+ cfExpr.identifierRef('name').eqParam(indexName, SQLITE_TEXT_CODEC_ID),
68
+ ]);
69
+ return {
70
+ indexAbsent: () =>
71
+ exprSelect().from(source).project('result', cfExpr.countStar().eqLit(0)).where(where).build(),
72
+ indexPresent: () =>
73
+ exprSelect().from(source).project('result', cfExpr.countStar().gtLit(0)).where(where).build(),
74
+ };
75
+ }
@@ -36,8 +36,8 @@ const sqliteControlTargetDescriptor: SqlControlTargetDescriptor<'sqlite', Sqlite
36
36
  contractSerializer: new SqliteContractSerializer(),
37
37
  schemaVerifier: new SqliteSchemaVerifier(),
38
38
  migrations: {
39
- createPlanner(_adapter: SqlControlAdapter<'sqlite'>): MigrationPlanner<'sql', 'sqlite'> {
40
- return createSqliteMigrationPlanner();
39
+ createPlanner(adapter: SqlControlAdapter<'sqlite'>): MigrationPlanner<'sql', 'sqlite'> {
40
+ return createSqliteMigrationPlanner(adapter);
41
41
  },
42
42
  createRunner(family) {
43
43
  return createSqliteMigrationRunner(family) as MigrationRunner<'sql', 'sqlite'>;
@@ -67,8 +67,8 @@ const sqliteControlTargetDescriptor: SqlControlTargetDescriptor<'sqlite', Sqlite
67
67
  targetId: 'sqlite',
68
68
  };
69
69
  },
70
- createPlanner(_adapter: SqlControlAdapter<'sqlite'>) {
71
- return createSqliteMigrationPlanner();
70
+ createPlanner(adapter: SqlControlAdapter<'sqlite'>) {
71
+ return createSqliteMigrationPlanner(adapter);
72
72
  },
73
73
  createRunner(family) {
74
74
  return createSqliteMigrationRunner(family);
@@ -0,0 +1,28 @@
1
+ import { CliStructuredError } from '@prisma-next/errors/control';
2
+
3
+ /**
4
+ * A `SqliteMigration` instance method that needs the materialized control
5
+ * adapter (currently only `this.createTable(...)`) was invoked, but the
6
+ * migration was constructed without a `ControlStack`. Concrete authoring
7
+ * usage always goes through the migration CLI entrypoint, which assembles
8
+ * a stack from the loaded `prisma-next.config.ts`; reaching this error
9
+ * means a test fixture or ad-hoc consumer instantiated `SqliteMigration`
10
+ * with the no-arg form (legal for `operations` / `describe` introspection
11
+ * only).
12
+ *
13
+ * Distinct from `PN-MIG-2001` (placeholder not filled) because the missing
14
+ * input is the stack itself, not the per-operation contract.
15
+ *
16
+ * Lives in `@prisma-next/target-sqlite/errors` rather than the shared
17
+ * framework migration errors module because the failure is target-specific:
18
+ * the contract it talks about (`SqliteMigration`, the SQLite control
19
+ * adapter, the SQLite-target stack) only exists in this package.
20
+ */
21
+ export function errorSqliteMigrationStackMissing(): CliStructuredError {
22
+ return new CliStructuredError('2008', 'SqliteMigration.createTable requires a control adapter', {
23
+ domain: 'MIG',
24
+ why: 'SqliteMigration.createTable was invoked on an instance constructed without a ControlStack. The stored controlAdapter is undefined, so createTable cannot lower its DDL node.',
25
+ 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.',
26
+ meta: {},
27
+ });
28
+ }
@@ -8,7 +8,7 @@
8
8
  * remaining issues flow through `mapIssueToCall` for the default case.
9
9
  */
10
10
 
11
- import type { Contract } from '@prisma-next/contract/types';
11
+ import type { Contract, JsonValue } from '@prisma-next/contract/types';
12
12
  import type {
13
13
  CodecControlHooks,
14
14
  MigrationOperationPolicy,
@@ -18,14 +18,24 @@ import type {
18
18
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
19
19
  import type { SchemaIssue } from '@prisma-next/framework-components/control';
20
20
  import type {
21
- PostgresEnumStorageEntry,
22
21
  SqlStorage,
23
22
  StorageColumn,
24
23
  StorageTable,
25
24
  StorageTypeInstance,
26
25
  } from '@prisma-next/sql-contract/types';
26
+ import type { CodecRef, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
27
+ import {
28
+ DdlColumn,
29
+ ForeignKeyConstraint,
30
+ FunctionColumnDefault,
31
+ LiteralColumnDefault,
32
+ PrimaryKeyConstraint,
33
+ UniqueConstraint,
34
+ } from '@prisma-next/sql-relational-core/ast';
27
35
  import { defaultIndexName } from '@prisma-next/sql-schema-ir/naming';
28
36
  import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
37
+ import { blindCast } from '@prisma-next/utils/casts';
38
+ import { ifDefined } from '@prisma-next/utils/defined';
29
39
  import type { Result } from '@prisma-next/utils/result';
30
40
  import { notOk, ok } from '@prisma-next/utils/result';
31
41
  import { CONTROL_TABLE_NAMES } from '../control-tables';
@@ -48,6 +58,7 @@ import {
48
58
  buildColumnDefaultSql,
49
59
  buildColumnTypeSql,
50
60
  isInlineAutoincrementPrimaryKey,
61
+ resolveColumnTypeMetadata,
51
62
  } from './planner-ddl-builders';
52
63
  import {
53
64
  type CallMigrationStrategy,
@@ -206,12 +217,15 @@ function isMissing(issue: SchemaIssue): boolean {
206
217
  export function toColumnSpec(
207
218
  name: string,
208
219
  column: StorageColumn,
209
- storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>,
220
+ storageTypes: Readonly<Record<string, StorageTypeInstance>>,
210
221
  inlineAutoincrementPrimaryKey = false,
211
222
  ): SqliteColumnSpec {
212
223
  const typeSql = buildColumnTypeSql(
213
224
  column,
214
- storageTypes as Record<string, StorageTypeInstance | PostgresEnumStorageEntry>,
225
+ blindCast<
226
+ Record<string, StorageTypeInstance>,
227
+ 'buildColumnTypeSql declares its storageTypes parameter as mutable Record while the planner stores it readonly; the helper does not mutate, so the readonly→mutable narrowing is sound'
228
+ >(storageTypes),
215
229
  );
216
230
  const defaultSql = buildColumnDefaultSql(column.default);
217
231
  return {
@@ -231,7 +245,7 @@ export function toColumnSpec(
231
245
  */
232
246
  export function toTableSpec(
233
247
  table: StorageTable,
234
- storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>,
248
+ storageTypes: Readonly<Record<string, StorageTypeInstance>>,
235
249
  ): SqliteTableSpec {
236
250
  const columns: SqliteColumnSpec[] = Object.entries(table.columns).map(([name, column]) =>
237
251
  toColumnSpec(name, column, storageTypes, isInlineAutoincrementPrimaryKey(table, name)),
@@ -256,6 +270,126 @@ export function toTableSpec(
256
270
  };
257
271
  }
258
272
 
273
+ // ============================================================================
274
+ // StorageTable / StorageColumn → DdlColumn[] + DdlTableConstraint[] (for CreateTableCall)
275
+ // ============================================================================
276
+
277
+ function sqliteDefaultToDdlColumnDefault(
278
+ columnDefault: StorageColumn['default'],
279
+ ): DdlColumn['default'] {
280
+ if (!columnDefault) return undefined;
281
+ switch (columnDefault.kind) {
282
+ case 'literal':
283
+ return new LiteralColumnDefault(columnDefault.value);
284
+ case 'function':
285
+ // `autoincrement()` is not a DEFAULT clause — SQLite encodes it as
286
+ // `INTEGER PRIMARY KEY AUTOINCREMENT` inline on the column. Skip it
287
+ // here; the renderer also has a defensive guard for the same case.
288
+ if (columnDefault.expression === 'autoincrement()') return undefined;
289
+ return new FunctionColumnDefault(columnDefault.expression);
290
+ default: {
291
+ const exhaustive: never = columnDefault;
292
+ throw new Error(
293
+ `sqliteDefaultToDdlColumnDefault: unhandled kind "${blindCast<{ kind: string }, 'exhaustiveness: surface the unhandled default kind'>(exhaustive).kind}"`,
294
+ );
295
+ }
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Converts a `StorageTable` to the `DdlColumn[]` + `DdlTableConstraint[]`
301
+ * pair used by `CreateTableCall`. This is the structured form consumed by
302
+ * the DDL lowering path; `toTableSpec` / `toColumnSpec` remain in use for
303
+ * `RecreateTableCall` and `AddColumnCall` (Phase 2).
304
+ */
305
+ export function tableToDdlParts(
306
+ table: StorageTable,
307
+ storageTypes: Record<string, StorageTypeInstance>,
308
+ ): { columns: DdlColumn[]; constraints: DdlTableConstraint[] } {
309
+ const columns: DdlColumn[] = Object.entries(table.columns).map(([name, column]) => {
310
+ const inlineAutoincrement = isInlineAutoincrementPrimaryKey(table, name);
311
+ const typeSql = buildColumnTypeSql(
312
+ column,
313
+ blindCast<
314
+ Record<string, StorageTypeInstance>,
315
+ 'buildColumnTypeSql declares its storageTypes parameter as mutable Record while the planner stores it readonly; the helper does not mutate, so the readonly→mutable narrowing is sound'
316
+ >(storageTypes),
317
+ );
318
+
319
+ if (inlineAutoincrement) {
320
+ // `DdlColumn` has no SQLite-specific autoincrement flag, so the full
321
+ // `PRIMARY KEY AUTOINCREMENT` clause is embedded in the `type` string.
322
+ // The DDL renderer (`ddl-renderer.ts`) substring-detects `AUTOINCREMENT`
323
+ // to suppress the normal NOT NULL / PRIMARY KEY / DEFAULT clause rendering
324
+ // and emit the entire type string verbatim. Both sites must stay in sync.
325
+ // The structural fix (a SQLite-specific column option) is tracked in TML-2866.
326
+ return new DdlColumn({ name, type: `${typeSql} PRIMARY KEY AUTOINCREMENT` });
327
+ }
328
+ const colDefault = sqliteDefaultToDdlColumnDefault(column.default);
329
+ const resolved = resolveColumnTypeMetadata(
330
+ column,
331
+ blindCast<
332
+ Record<string, StorageTypeInstance>,
333
+ 'resolveColumnTypeMetadata declares its storageTypes parameter as mutable Record while the planner stores it readonly; the helper does not mutate, so the readonly→mutable narrowing is sound'
334
+ >(storageTypes),
335
+ );
336
+ const codecRef: CodecRef | undefined = resolved.codecId
337
+ ? {
338
+ codecId: resolved.codecId,
339
+ ...(resolved.typeParams !== undefined
340
+ ? {
341
+ typeParams: blindCast<
342
+ JsonValue,
343
+ 'resolved.typeParams is JsonValue-shaped storage metadata; the narrowed (non-undefined) value lands in CodecRef.typeParams which is JsonValue'
344
+ >(resolved.typeParams),
345
+ }
346
+ : {}),
347
+ }
348
+ : undefined;
349
+ return new DdlColumn({
350
+ name,
351
+ type: typeSql,
352
+ ...(!column.nullable ? { notNull: true } : {}),
353
+ ...(colDefault !== undefined ? { default: colDefault } : {}),
354
+ ...(codecRef !== undefined ? { codecRef } : {}),
355
+ });
356
+ });
357
+
358
+ const constraints: DdlTableConstraint[] = [];
359
+
360
+ const hasInlinePk = Object.entries(table.columns).some(([name]) =>
361
+ isInlineAutoincrementPrimaryKey(table, name),
362
+ );
363
+ if (table.primaryKey && !hasInlinePk) {
364
+ constraints.push(new PrimaryKeyConstraint({ columns: table.primaryKey.columns }));
365
+ }
366
+
367
+ for (const u of table.uniques) {
368
+ constraints.push(
369
+ new UniqueConstraint({
370
+ columns: u.columns,
371
+ ...(u.name !== undefined ? { name: u.name } : {}),
372
+ }),
373
+ );
374
+ }
375
+
376
+ for (const fk of table.foreignKeys) {
377
+ if (fk.constraint === false) continue;
378
+ constraints.push(
379
+ new ForeignKeyConstraint({
380
+ columns: fk.source.columns,
381
+ refTable: fk.target.tableName,
382
+ refColumns: fk.target.columns,
383
+ ...ifDefined('name', fk.name),
384
+ ...ifDefined('onDelete', fk.onDelete),
385
+ ...ifDefined('onUpdate', fk.onUpdate),
386
+ }),
387
+ );
388
+ }
389
+
390
+ return { columns, constraints };
391
+ }
392
+
259
393
  // ============================================================================
260
394
  // Issue planner
261
395
  // ============================================================================
@@ -265,7 +399,7 @@ export interface IssuePlannerOptions {
265
399
  readonly toContract: Contract<SqlStorage>;
266
400
  readonly fromContract: Contract<SqlStorage> | null;
267
401
  readonly codecHooks: ReadonlyMap<string, CodecControlHooks>;
268
- readonly storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
402
+ readonly storageTypes: Readonly<Record<string, StorageTypeInstance>>;
269
403
  readonly schema?: SqlSchemaIR;
270
404
  readonly policy?: MigrationOperationPolicy;
271
405
  readonly frameworkComponents?: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
@@ -309,8 +443,17 @@ function mapIssueToCall(
309
443
  ),
310
444
  );
311
445
  }
312
- const tableSpec = toTableSpec(contractTable, ctx.storageTypes);
313
- const calls: SqliteOpFactoryCall[] = [new CreateTableCall(issue.table, tableSpec)];
446
+ const { columns: ddlColumns, constraints: ddlConstraints } = tableToDdlParts(
447
+ contractTable,
448
+ ctx.storageTypes,
449
+ );
450
+ const calls: SqliteOpFactoryCall[] = [
451
+ new CreateTableCall(
452
+ issue.table,
453
+ ddlColumns,
454
+ ddlConstraints.length > 0 ? ddlConstraints : undefined,
455
+ ),
456
+ ];
314
457
  const declaredIndexColumnKeys = new Set<string>();
315
458
  for (const index of contractTable.indexes) {
316
459
  const indexName = index.name ?? defaultIndexName(issue.table, index.columns);