@prisma-next/target-sqlite 0.13.0-dev.34 → 0.13.0-dev.36

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 (51) 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 -3
  4. package/dist/control.d.mts.map +1 -1
  5. package/dist/control.mjs +3 -3
  6. package/dist/ddl-DrtjQMFK.mjs +68 -0
  7. package/dist/ddl-DrtjQMFK.mjs.map +1 -0
  8. package/dist/migration.d.mts +3 -45
  9. package/dist/migration.d.mts.map +1 -1
  10. package/dist/migration.mjs +3 -3
  11. package/dist/{op-factory-call-z4TT72k3.mjs → op-factory-call-DmdfD1yd.mjs} +143 -104
  12. package/dist/op-factory-call-DmdfD1yd.mjs.map +1 -0
  13. package/dist/op-factory-call.d.mts +12 -6
  14. package/dist/op-factory-call.d.mts.map +1 -1
  15. package/dist/op-factory-call.mjs +1 -1
  16. package/dist/{planner-jMHqfl1A.mjs → planner-Ciq8p_dL.mjs} +3 -3
  17. package/dist/{planner-jMHqfl1A.mjs.map → planner-Ciq8p_dL.mjs.map} +1 -1
  18. package/dist/{planner-produced-sqlite-migration-CyyvoPmm.mjs → planner-produced-sqlite-migration-0xPEm3R1.mjs} +2 -2
  19. package/dist/{planner-produced-sqlite-migration-CyyvoPmm.mjs.map → planner-produced-sqlite-migration-0xPEm3R1.mjs.map} +1 -1
  20. package/dist/{planner-produced-sqlite-migration-BWpnDmhM.d.mts → planner-produced-sqlite-migration-CpgsY-M9.d.mts} +2 -2
  21. package/dist/{planner-produced-sqlite-migration-BWpnDmhM.d.mts.map → planner-produced-sqlite-migration-CpgsY-M9.d.mts.map} +1 -1
  22. package/dist/planner-produced-sqlite-migration.d.mts +1 -1
  23. package/dist/planner-produced-sqlite-migration.mjs +1 -1
  24. package/dist/planner.d.mts +1 -1
  25. package/dist/planner.mjs +1 -1
  26. package/dist/runtime.mjs +1 -1
  27. package/dist/shared-Dhc8mLK1.d.mts.map +1 -1
  28. package/dist/{sqlite-contract-serializer-B_Cu0o3G.mjs → sqlite-contract-serializer-Cq9mXdXi.mjs} +5 -14
  29. package/dist/sqlite-contract-serializer-Cq9mXdXi.mjs.map +1 -0
  30. package/dist/{sqlite-migration-DhW4ycZV.mjs → sqlite-migration-A0rwqPOG.mjs} +32 -13
  31. package/dist/sqlite-migration-A0rwqPOG.mjs.map +1 -0
  32. package/dist/sqlite-migration-DVfhQwN_.d.mts +75 -0
  33. package/dist/sqlite-migration-DVfhQwN_.d.mts.map +1 -0
  34. package/package.json +18 -18
  35. package/src/contract-free/checks.ts +75 -0
  36. package/src/core/migrations/op-factory-call.ts +191 -43
  37. package/src/core/migrations/operations/columns.ts +32 -26
  38. package/src/core/migrations/operations/indexes.ts +31 -27
  39. package/src/core/migrations/operations/shared.ts +11 -3
  40. package/src/core/migrations/operations/tables.ts +39 -37
  41. package/src/core/migrations/sqlite-migration.ts +82 -14
  42. package/src/core/sqlite-unbound-database.ts +16 -23
  43. package/src/exports/contract-free.ts +8 -0
  44. package/src/exports/migration.ts +0 -3
  45. package/dist/ddl-CH8V_qcd.mjs +0 -23
  46. package/dist/ddl-CH8V_qcd.mjs.map +0 -1
  47. package/dist/op-factory-call-z4TT72k3.mjs.map +0 -1
  48. package/dist/sqlite-contract-serializer-B_Cu0o3G.mjs.map +0 -1
  49. package/dist/sqlite-migration-CJrASAxf.d.mts +0 -46
  50. package/dist/sqlite-migration-CJrASAxf.d.mts.map +0 -1
  51. package/dist/sqlite-migration-DhW4ycZV.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-migration-A0rwqPOG.mjs","names":["SqlMigration"],"sources":["../src/core/errors.ts","../src/core/migrations/sqlite-migration.ts"],"sourcesContent":["import { CliStructuredError } from '@prisma-next/errors/control';\n\n/**\n * A `SqliteMigration` instance method that needs the materialized control\n * adapter (currently only `this.createTable(...)`) was invoked, but the\n * migration was constructed without a `ControlStack`. Concrete authoring\n * usage always goes through the migration CLI entrypoint, which assembles\n * a stack from the loaded `prisma-next.config.ts`; reaching this error\n * means a test fixture or ad-hoc consumer instantiated `SqliteMigration`\n * with the no-arg form (legal for `operations` / `describe` introspection\n * only).\n *\n * Distinct from `PN-MIG-2001` (placeholder not filled) because the missing\n * input is the stack itself, not the per-operation 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(): CliStructuredError {\n return new CliStructuredError('2008', 'SqliteMigration.createTable requires a control adapter', {\n domain: 'MIG',\n why: 'SqliteMigration.createTable was invoked on an instance constructed without a ControlStack. The stored controlAdapter is undefined, so createTable 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: {},\n });\n}\n","import 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 type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { errorSqliteMigrationStackMissing } from '../errors';\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 */\nexport abstract class SqliteMigration extends SqlMigration<SqlitePlanTargetDetails, 'sqlite'> {\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); the operation methods\n * throw in that case to surface the misuse.\n */\n protected readonly controlAdapter: SqlControlAdapter<'sqlite'> | undefined;\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 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 if (!this.controlAdapter) {\n throw errorSqliteMigrationStackMissing();\n }\n return new CreateTableCall(options.table, options.columns, options.constraints).toOp(\n this.controlAdapter,\n );\n }\n\n protected dropTable(options: { readonly table: string }): Promise<Op> {\n if (!this.controlAdapter) {\n throw errorSqliteMigrationStackMissing();\n }\n return new DropTableCall(options.table).toOp(this.controlAdapter);\n }\n\n protected addColumn(options: {\n readonly table: string;\n readonly column: SqliteColumnSpec;\n }): Promise<Op> {\n if (!this.controlAdapter) {\n throw errorSqliteMigrationStackMissing();\n }\n return new AddColumnCall(options.table, options.column).toOp(this.controlAdapter);\n }\n\n protected dropColumn(options: { readonly table: string; readonly column: string }): Promise<Op> {\n if (!this.controlAdapter) {\n throw errorSqliteMigrationStackMissing();\n }\n return new DropColumnCall(options.table, options.column).toOp(this.controlAdapter);\n }\n\n protected createIndex(options: {\n readonly table: string;\n readonly index: string;\n readonly columns: readonly string[];\n }): Promise<Op> {\n if (!this.controlAdapter) {\n throw errorSqliteMigrationStackMissing();\n }\n return new CreateIndexCall(options.table, options.index, options.columns).toOp(\n this.controlAdapter,\n );\n }\n\n protected dropIndex(options: { readonly table: string; readonly index: string }): Promise<Op> {\n if (!this.controlAdapter) {\n throw errorSqliteMigrationStackMissing();\n }\n return new DropIndexCall(options.table, options.index).toOp(this.controlAdapter);\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 if (!this.controlAdapter) {\n throw errorSqliteMigrationStackMissing();\n }\n return new RecreateTableCall(options).toOp(this.controlAdapter);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,mCAAuD;CACrE,OAAO,IAAI,mBAAmB,QAAQ,0DAA0D;EAC9F,QAAQ;EACR,KAAK;EACL,KAAK;EACL,MAAM,CAAC;CACT,CAAC;AACH;;;;;;;;;;;;;;;;ACUA,IAAsB,kBAAtB,cAA8CA,UAAgD;CAC5F,WAAoB;;;;;;;CAQpB;CAEA,YAAY,OAAuC;EACjD,MAAM,KAAK;EACX,KAAK,iBAAiB,OAAO,UACzB,UAGE,MAAM,QAAQ,OAAO,KAAK,CAAC,IAC7B,KAAA;CACN;CAEA,YAAsB,SAKN;EACd,IAAI,CAAC,KAAK,gBACR,MAAM,iCAAiC;EAEzC,OAAO,IAAI,gBAAgB,QAAQ,OAAO,QAAQ,SAAS,QAAQ,WAAW,CAAC,CAAC,KAC9E,KAAK,cACP;CACF;CAEA,UAAoB,SAAkD;EACpE,IAAI,CAAC,KAAK,gBACR,MAAM,iCAAiC;EAEzC,OAAO,IAAI,cAAc,QAAQ,KAAK,CAAC,CAAC,KAAK,KAAK,cAAc;CAClE;CAEA,UAAoB,SAGJ;EACd,IAAI,CAAC,KAAK,gBACR,MAAM,iCAAiC;EAEzC,OAAO,IAAI,cAAc,QAAQ,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,KAAK,cAAc;CAClF;CAEA,WAAqB,SAA2E;EAC9F,IAAI,CAAC,KAAK,gBACR,MAAM,iCAAiC;EAEzC,OAAO,IAAI,eAAe,QAAQ,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,KAAK,cAAc;CACnF;CAEA,YAAsB,SAIN;EACd,IAAI,CAAC,KAAK,gBACR,MAAM,iCAAiC;EAEzC,OAAO,IAAI,gBAAgB,QAAQ,OAAO,QAAQ,OAAO,QAAQ,OAAO,CAAC,CAAC,KACxE,KAAK,cACP;CACF;CAEA,UAAoB,SAA0E;EAC5F,IAAI,CAAC,KAAK,gBACR,MAAM,iCAAiC;EAEzC,OAAO,IAAI,cAAc,QAAQ,OAAO,QAAQ,KAAK,CAAC,CAAC,KAAK,KAAK,cAAc;CACjF;CAEA,cAAwB,SAQR;EACd,IAAI,CAAC,KAAK,gBACR,MAAM,iCAAiC;EAEzC,OAAO,IAAI,kBAAkB,OAAO,CAAC,CAAC,KAAK,KAAK,cAAc;CAChE;AACF"}
@@ -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.34",
3
+ "version": "0.13.0-dev.36",
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.34",
9
- "@prisma-next/contract": "0.13.0-dev.34",
10
- "@prisma-next/errors": "0.13.0-dev.34",
11
- "@prisma-next/family-sql": "0.13.0-dev.34",
12
- "@prisma-next/framework-components": "0.13.0-dev.34",
13
- "@prisma-next/migration-tools": "0.13.0-dev.34",
14
- "@prisma-next/sql-contract": "0.13.0-dev.34",
15
- "@prisma-next/sql-errors": "0.13.0-dev.34",
16
- "@prisma-next/sql-relational-core": "0.13.0-dev.34",
17
- "@prisma-next/sql-runtime": "0.13.0-dev.34",
18
- "@prisma-next/sql-schema-ir": "0.13.0-dev.34",
19
- "@prisma-next/ts-render": "0.13.0-dev.34",
20
- "@prisma-next/utils": "0.13.0-dev.34",
8
+ "@prisma-next/cli": "0.13.0-dev.36",
9
+ "@prisma-next/contract": "0.13.0-dev.36",
10
+ "@prisma-next/errors": "0.13.0-dev.36",
11
+ "@prisma-next/family-sql": "0.13.0-dev.36",
12
+ "@prisma-next/framework-components": "0.13.0-dev.36",
13
+ "@prisma-next/migration-tools": "0.13.0-dev.36",
14
+ "@prisma-next/sql-contract": "0.13.0-dev.36",
15
+ "@prisma-next/sql-errors": "0.13.0-dev.36",
16
+ "@prisma-next/sql-relational-core": "0.13.0-dev.36",
17
+ "@prisma-next/sql-runtime": "0.13.0-dev.36",
18
+ "@prisma-next/sql-schema-ir": "0.13.0-dev.36",
19
+ "@prisma-next/ts-render": "0.13.0-dev.36",
20
+ "@prisma-next/utils": "0.13.0-dev.36",
21
21
  "@standard-schema/spec": "1.1.0"
22
22
  },
23
23
  "devDependencies": {
24
- "@prisma-next/driver-sqlite": "0.13.0-dev.34",
25
- "@prisma-next/test-utils": "0.13.0-dev.34",
26
- "@prisma-next/tsconfig": "0.13.0-dev.34",
27
- "@prisma-next/tsdown": "0.13.0-dev.34",
24
+ "@prisma-next/driver-sqlite": "0.13.0-dev.36",
25
+ "@prisma-next/test-utils": "0.13.0-dev.36",
26
+ "@prisma-next/tsconfig": "0.13.0-dev.36",
27
+ "@prisma-next/tsdown": "0.13.0-dev.36",
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
+ }
@@ -23,13 +23,14 @@ import type {
23
23
  } from '@prisma-next/sql-relational-core/ast';
24
24
  import { type ImportRequirement, jsonToTsSource, TsExpression } from '@prisma-next/ts-render';
25
25
  import { ifDefined } from '@prisma-next/utils/defined';
26
+ import { columnExistsAst, indexExistsAst, tableExistsAst } from '../../contract-free/checks';
26
27
  import * as contractFreeDdl from '../../contract-free/ddl';
27
- import { escapeLiteral } from '../sql-utils';
28
- import { addColumn, dropColumn } from './operations/columns';
29
- import { createIndex, dropIndex } from './operations/indexes';
28
+ import { quoteIdentifier } from '../sql-utils';
29
+ import { addColumnExecuteSql, dropColumnExecuteSql } from './operations/columns';
30
30
  import type { SqliteColumnSpec, SqliteIndexSpec, SqliteTableSpec } from './operations/shared';
31
31
  import { step } from './operations/shared';
32
- import { dropTable, recreateTable } from './operations/tables';
32
+ import { recreateTable } from './operations/tables';
33
+ import { buildCreateIndexSql, buildDropIndexSql } from './planner-ddl-builders';
33
34
  import type { SqlitePlanTargetDetails } from './planner-target-details';
34
35
  import { buildTargetDetails } from './planner-target-details';
35
36
 
@@ -152,19 +153,16 @@ export class CreateTableCall extends SqliteOpFactoryCallNode {
152
153
  });
153
154
  const statement = await lowerer.lowerToExecuteRequest(ddlNode);
154
155
  const tableName = this.tableName;
155
- const escapedName = escapeLiteral(tableName);
156
+ const tableChecks = tableExistsAst(tableName);
157
+ const absent = await lowerer.lowerToExecuteRequest(tableChecks.tableAbsent());
158
+ const present = await lowerer.lowerToExecuteRequest(tableChecks.tablePresent());
156
159
  return {
157
160
  id: `table.${tableName}`,
158
161
  label: `Create table ${tableName}`,
159
162
  summary: `Creates table ${tableName} with required columns`,
160
163
  operationClass: 'additive',
161
164
  target: { id: 'sqlite', details: buildTargetDetails('table', tableName) },
162
- precheck: [
163
- step(
164
- `ensure table "${tableName}" does not exist`,
165
- `SELECT COUNT(*) = 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapedName}'`,
166
- ),
167
- ],
165
+ precheck: [step(`ensure table "${tableName}" does not exist`, absent.sql, absent.params)],
168
166
  execute: [
169
167
  {
170
168
  description: `create table "${tableName}"`,
@@ -172,12 +170,7 @@ export class CreateTableCall extends SqliteOpFactoryCallNode {
172
170
  params: statement.params ?? [],
173
171
  },
174
172
  ],
175
- postcheck: [
176
- step(
177
- `verify table "${tableName}" exists`,
178
- `SELECT COUNT(*) > 0 FROM sqlite_master WHERE type = 'table' AND name = '${escapedName}'`,
179
- ),
180
- ],
173
+ postcheck: [step(`verify table "${tableName}" exists`, present.sql, present.params)],
181
174
  };
182
175
  }
183
176
 
@@ -223,12 +216,35 @@ export class DropTableCall extends SqliteOpFactoryCallNode {
223
216
  this.freeze();
224
217
  }
225
218
 
226
- toOp(_lowerer?: Lowerer): Op {
227
- return dropTable(this.tableName);
219
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
220
+ if (lowerer === undefined) {
221
+ throw new Error(
222
+ `DropTableCall.toOp: a lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
223
+ );
224
+ }
225
+ const checks = tableExistsAst(this.tableName);
226
+ const present = await lowerer.lowerToExecuteRequest(checks.tablePresent());
227
+ const absent = await lowerer.lowerToExecuteRequest(checks.tableAbsent());
228
+ return {
229
+ id: `dropTable.${this.tableName}`,
230
+ label: `Drop table ${this.tableName}`,
231
+ summary: `Drops table ${this.tableName} which is not in the contract`,
232
+ operationClass: 'destructive',
233
+ target: { id: 'sqlite', details: buildTargetDetails('table', this.tableName) },
234
+ precheck: [step(`ensure table "${this.tableName}" exists`, present.sql, present.params)],
235
+ execute: [
236
+ step(`drop table "${this.tableName}"`, `DROP TABLE ${quoteIdentifier(this.tableName)}`),
237
+ ],
238
+ postcheck: [step(`verify table "${this.tableName}" is gone`, absent.sql, absent.params)],
239
+ };
228
240
  }
229
241
 
230
242
  renderTypeScript(): string {
231
- return `dropTable(${jsonToTsSource(this.tableName)})`;
243
+ return `this.dropTable({ table: ${jsonToTsSource(this.tableName)} })`;
244
+ }
245
+
246
+ override importRequirements(): readonly ImportRequirement[] {
247
+ return [];
232
248
  }
233
249
  }
234
250
 
@@ -264,16 +280,24 @@ export class RecreateTableCall extends SqliteOpFactoryCallNode {
264
280
  this.freeze();
265
281
  }
266
282
 
267
- toOp(_lowerer?: Lowerer): Op {
268
- return recreateTable({
269
- tableName: this.tableName,
270
- contractTable: this.contractTable,
271
- schemaColumnNames: this.schemaColumnNames,
272
- indexes: this.indexes,
273
- summary: this.summary,
274
- postchecks: this.postchecks,
275
- operationClass: this.operationClass,
276
- });
283
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
284
+ if (lowerer === undefined) {
285
+ throw new Error(
286
+ `RecreateTableCall.toOp: a lowerer is required on the SQLite planner path (table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
287
+ );
288
+ }
289
+ return recreateTable(
290
+ {
291
+ tableName: this.tableName,
292
+ contractTable: this.contractTable,
293
+ schemaColumnNames: this.schemaColumnNames,
294
+ indexes: this.indexes,
295
+ summary: this.summary,
296
+ postchecks: this.postchecks,
297
+ operationClass: this.operationClass,
298
+ },
299
+ lowerer,
300
+ );
277
301
  }
278
302
 
279
303
  renderTypeScript(): string {
@@ -286,7 +310,11 @@ export class RecreateTableCall extends SqliteOpFactoryCallNode {
286
310
  postchecks: this.postchecks,
287
311
  operationClass: this.operationClass,
288
312
  };
289
- return `recreateTable(${jsonToTsSource(args)})`;
313
+ return `this.recreateTable(${jsonToTsSource(args)})`;
314
+ }
315
+
316
+ override importRequirements(): readonly ImportRequirement[] {
317
+ return [];
290
318
  }
291
319
  }
292
320
 
@@ -311,12 +339,38 @@ export class AddColumnCall extends SqliteOpFactoryCallNode {
311
339
  this.freeze();
312
340
  }
313
341
 
314
- toOp(_lowerer?: Lowerer): Op {
315
- return addColumn(this.tableName, this.column);
342
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
343
+ if (lowerer === undefined) {
344
+ throw new Error(
345
+ `AddColumnCall.toOp: a lowerer is required on the SQLite planner path (column "${this.column.name}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
346
+ );
347
+ }
348
+ const checks = columnExistsAst(this.tableName, this.column.name);
349
+ const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
350
+ const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
351
+ return {
352
+ id: `column.${this.tableName}.${this.column.name}`,
353
+ label: `Add column ${this.column.name} on ${this.tableName}`,
354
+ summary: `Adds column ${this.column.name} on ${this.tableName}`,
355
+ operationClass: 'additive',
356
+ target: {
357
+ id: 'sqlite',
358
+ details: buildTargetDetails('column', this.column.name, this.tableName),
359
+ },
360
+ precheck: [step(`ensure column "${this.column.name}" is missing`, absent.sql, absent.params)],
361
+ execute: [
362
+ step(`add column "${this.column.name}"`, addColumnExecuteSql(this.tableName, this.column)),
363
+ ],
364
+ postcheck: [step(`verify column "${this.column.name}" exists`, present.sql, present.params)],
365
+ };
316
366
  }
317
367
 
318
368
  renderTypeScript(): string {
319
- return `addColumn(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.column)})`;
369
+ return `this.addColumn({ table: ${jsonToTsSource(this.tableName)}, column: ${jsonToTsSource(this.column)} })`;
370
+ }
371
+
372
+ override importRequirements(): readonly ImportRequirement[] {
373
+ return [];
320
374
  }
321
375
  }
322
376
 
@@ -335,12 +389,53 @@ export class DropColumnCall extends SqliteOpFactoryCallNode {
335
389
  this.freeze();
336
390
  }
337
391
 
338
- toOp(_lowerer?: Lowerer): Op {
339
- return dropColumn(this.tableName, this.columnName);
392
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
393
+ if (lowerer === undefined) {
394
+ throw new Error(
395
+ `DropColumnCall.toOp: a lowerer is required on the SQLite planner path (column "${this.columnName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
396
+ );
397
+ }
398
+ const checks = columnExistsAst(this.tableName, this.columnName);
399
+ const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
400
+ const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
401
+ return {
402
+ id: `dropColumn.${this.tableName}.${this.columnName}`,
403
+ label: `Drop column ${this.columnName} on ${this.tableName}`,
404
+ summary: `Drops column ${this.columnName} on ${this.tableName} which is not in the contract`,
405
+ operationClass: 'destructive',
406
+ target: {
407
+ id: 'sqlite',
408
+ details: buildTargetDetails('column', this.columnName, this.tableName),
409
+ },
410
+ precheck: [
411
+ step(
412
+ `ensure column "${this.columnName}" exists on "${this.tableName}"`,
413
+ present.sql,
414
+ present.params,
415
+ ),
416
+ ],
417
+ execute: [
418
+ step(
419
+ `drop column "${this.columnName}" from "${this.tableName}"`,
420
+ dropColumnExecuteSql(this.tableName, this.columnName),
421
+ ),
422
+ ],
423
+ postcheck: [
424
+ step(
425
+ `verify column "${this.columnName}" is gone from "${this.tableName}"`,
426
+ absent.sql,
427
+ absent.params,
428
+ ),
429
+ ],
430
+ };
340
431
  }
341
432
 
342
433
  renderTypeScript(): string {
343
- return `dropColumn(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.columnName)})`;
434
+ return `this.dropColumn({ table: ${jsonToTsSource(this.tableName)}, column: ${jsonToTsSource(this.columnName)} })`;
435
+ }
436
+
437
+ override importRequirements(): readonly ImportRequirement[] {
438
+ return [];
344
439
  }
345
440
  }
346
441
 
@@ -365,12 +460,41 @@ export class CreateIndexCall extends SqliteOpFactoryCallNode {
365
460
  this.freeze();
366
461
  }
367
462
 
368
- toOp(_lowerer?: Lowerer): Op {
369
- return createIndex(this.tableName, this.indexName, this.columns);
463
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
464
+ if (lowerer === undefined) {
465
+ throw new Error(
466
+ `CreateIndexCall.toOp: a lowerer is required on the SQLite planner path (index "${this.indexName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
467
+ );
468
+ }
469
+ const checks = indexExistsAst(this.indexName);
470
+ const absent = await lowerer.lowerToExecuteRequest(checks.indexAbsent());
471
+ const present = await lowerer.lowerToExecuteRequest(checks.indexPresent());
472
+ return {
473
+ id: `index.${this.tableName}.${this.indexName}`,
474
+ label: `Create index ${this.indexName} on ${this.tableName}`,
475
+ summary: `Creates index ${this.indexName} on ${this.tableName}`,
476
+ operationClass: 'additive',
477
+ target: {
478
+ id: 'sqlite',
479
+ details: buildTargetDetails('index', this.indexName, this.tableName),
480
+ },
481
+ precheck: [step(`ensure index "${this.indexName}" is missing`, absent.sql, absent.params)],
482
+ execute: [
483
+ step(
484
+ `create index "${this.indexName}"`,
485
+ buildCreateIndexSql(this.tableName, this.indexName, this.columns),
486
+ ),
487
+ ],
488
+ postcheck: [step(`verify index "${this.indexName}" exists`, present.sql, present.params)],
489
+ };
370
490
  }
371
491
 
372
492
  renderTypeScript(): string {
373
- return `createIndex(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.indexName)}, ${jsonToTsSource(this.columns)})`;
493
+ return `this.createIndex({ table: ${jsonToTsSource(this.tableName)}, index: ${jsonToTsSource(this.indexName)}, columns: ${jsonToTsSource(this.columns)} })`;
494
+ }
495
+
496
+ override importRequirements(): readonly ImportRequirement[] {
497
+ return [];
374
498
  }
375
499
  }
376
500
 
@@ -389,12 +513,36 @@ export class DropIndexCall extends SqliteOpFactoryCallNode {
389
513
  this.freeze();
390
514
  }
391
515
 
392
- toOp(_lowerer?: Lowerer): Op {
393
- return dropIndex(this.tableName, this.indexName);
516
+ async toOp(lowerer?: ExecuteRequestLowerer): Promise<Op> {
517
+ if (lowerer === undefined) {
518
+ throw new Error(
519
+ `DropIndexCall.toOp: a lowerer is required on the SQLite planner path (index "${this.indexName}" on table "${this.tableName}"). Pass the control adapter to createSqliteMigrationPlanner.`,
520
+ );
521
+ }
522
+ const checks = indexExistsAst(this.indexName);
523
+ const present = await lowerer.lowerToExecuteRequest(checks.indexPresent());
524
+ const absent = await lowerer.lowerToExecuteRequest(checks.indexAbsent());
525
+ return {
526
+ id: `dropIndex.${this.tableName}.${this.indexName}`,
527
+ label: `Drop index ${this.indexName} on ${this.tableName}`,
528
+ summary: `Drops index ${this.indexName} on ${this.tableName} which is not in the contract`,
529
+ operationClass: 'destructive',
530
+ target: {
531
+ id: 'sqlite',
532
+ details: buildTargetDetails('index', this.indexName, this.tableName),
533
+ },
534
+ precheck: [step(`ensure index "${this.indexName}" exists`, present.sql, present.params)],
535
+ execute: [step(`drop index "${this.indexName}"`, buildDropIndexSql(this.indexName))],
536
+ postcheck: [step(`verify index "${this.indexName}" is gone`, absent.sql, absent.params)],
537
+ };
394
538
  }
395
539
 
396
540
  renderTypeScript(): string {
397
- return `dropIndex(${jsonToTsSource(this.tableName)}, ${jsonToTsSource(this.indexName)})`;
541
+ return `this.dropIndex({ table: ${jsonToTsSource(this.tableName)}, index: ${jsonToTsSource(this.indexName)} })`;
542
+ }
543
+
544
+ override importRequirements(): readonly ImportRequirement[] {
545
+ return [];
398
546
  }
399
547
  }
400
548
 
@@ -1,39 +1,51 @@
1
- import { escapeLiteral, quoteIdentifier } from '../../sql-utils';
1
+ import type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';
2
+ import { columnExistsAst } from '../../../contract-free/checks';
3
+ import { quoteIdentifier } from '../../sql-utils';
2
4
  import { buildTargetDetails } from '../planner-target-details';
3
5
  import { type Op, type SqliteColumnSpec, step } from './shared';
4
6
 
5
- export function addColumn(tableName: string, column: SqliteColumnSpec): Op {
7
+ export function addColumnExecuteSql(tableName: string, column: SqliteColumnSpec): string {
6
8
  const parts = [
7
9
  `ALTER TABLE ${quoteIdentifier(tableName)}`,
8
10
  `ADD COLUMN ${quoteIdentifier(column.name)} ${column.typeSql}`,
9
11
  column.defaultSql,
10
12
  column.nullable ? '' : 'NOT NULL',
11
13
  ].filter(Boolean);
12
- const addSql = parts.join(' ');
14
+ return parts.join(' ');
15
+ }
16
+
17
+ export function dropColumnExecuteSql(tableName: string, columnName: string): string {
18
+ return `ALTER TABLE ${quoteIdentifier(tableName)} DROP COLUMN ${quoteIdentifier(columnName)}`;
19
+ }
13
20
 
21
+ export async function addColumn(
22
+ tableName: string,
23
+ column: SqliteColumnSpec,
24
+ lowerer: ExecuteRequestLowerer,
25
+ ): Promise<Op> {
26
+ const checks = columnExistsAst(tableName, column.name);
27
+ const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
28
+ const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
14
29
  return {
15
30
  id: `column.${tableName}.${column.name}`,
16
31
  label: `Add column ${column.name} on ${tableName}`,
17
32
  summary: `Adds column ${column.name} on ${tableName}`,
18
33
  operationClass: 'additive',
19
34
  target: { id: 'sqlite', details: buildTargetDetails('column', column.name, tableName) },
20
- precheck: [
21
- step(
22
- `ensure column "${column.name}" is missing`,
23
- `SELECT COUNT(*) = 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(column.name)}'`,
24
- ),
25
- ],
26
- execute: [step(`add column "${column.name}"`, addSql)],
27
- postcheck: [
28
- step(
29
- `verify column "${column.name}" exists`,
30
- `SELECT COUNT(*) > 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(column.name)}'`,
31
- ),
32
- ],
35
+ precheck: [step(`ensure column "${column.name}" is missing`, absent.sql, absent.params)],
36
+ execute: [step(`add column "${column.name}"`, addColumnExecuteSql(tableName, column))],
37
+ postcheck: [step(`verify column "${column.name}" exists`, present.sql, present.params)],
33
38
  };
34
39
  }
35
40
 
36
- export function dropColumn(tableName: string, columnName: string): Op {
41
+ export async function dropColumn(
42
+ tableName: string,
43
+ columnName: string,
44
+ lowerer: ExecuteRequestLowerer,
45
+ ): Promise<Op> {
46
+ const checks = columnExistsAst(tableName, columnName);
47
+ const present = await lowerer.lowerToExecuteRequest(checks.columnPresent());
48
+ const absent = await lowerer.lowerToExecuteRequest(checks.columnAbsent());
37
49
  return {
38
50
  id: `dropColumn.${tableName}.${columnName}`,
39
51
  label: `Drop column ${columnName} on ${tableName}`,
@@ -41,22 +53,16 @@ export function dropColumn(tableName: string, columnName: string): Op {
41
53
  operationClass: 'destructive',
42
54
  target: { id: 'sqlite', details: buildTargetDetails('column', columnName, tableName) },
43
55
  precheck: [
44
- step(
45
- `ensure column "${columnName}" exists on "${tableName}"`,
46
- `SELECT COUNT(*) > 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(columnName)}'`,
47
- ),
56
+ step(`ensure column "${columnName}" exists on "${tableName}"`, present.sql, present.params),
48
57
  ],
49
58
  execute: [
50
59
  step(
51
60
  `drop column "${columnName}" from "${tableName}"`,
52
- `ALTER TABLE ${quoteIdentifier(tableName)} DROP COLUMN ${quoteIdentifier(columnName)}`,
61
+ dropColumnExecuteSql(tableName, columnName),
53
62
  ),
54
63
  ],
55
64
  postcheck: [
56
- step(
57
- `verify column "${columnName}" is gone from "${tableName}"`,
58
- `SELECT COUNT(*) = 0 FROM pragma_table_info('${escapeLiteral(tableName)}') WHERE name = '${escapeLiteral(columnName)}'`,
59
- ),
65
+ step(`verify column "${columnName}" is gone from "${tableName}"`, absent.sql, absent.params),
60
66
  ],
61
67
  };
62
68
  }