@prisma-next/target-postgres 0.14.0-dev.5 → 0.14.0-dev.51

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 (238) hide show
  1. package/dist/authoring-DzHOdWTl.mjs +364 -0
  2. package/dist/authoring-DzHOdWTl.mjs.map +1 -0
  3. package/dist/canonicalize-Bn3BM1Cn.mjs +46 -0
  4. package/dist/canonicalize-Bn3BM1Cn.mjs.map +1 -0
  5. package/dist/canonicalize-CAbXtVtB.d.mts +37 -0
  6. package/dist/canonicalize-CAbXtVtB.d.mts.map +1 -0
  7. package/dist/{codec-ids-CnXu9Qy3.d.mts → codec-ids-BzrFF-I4.d.mts} +3 -2
  8. package/dist/codec-ids-BzrFF-I4.d.mts.map +1 -0
  9. package/dist/{codec-ids-BvytN2P8.mjs → codec-ids-C_-Hj6bL.mjs} +3 -2
  10. package/dist/codec-ids-C_-Hj6bL.mjs.map +1 -0
  11. package/dist/codec-ids.d.mts +2 -2
  12. package/dist/codec-ids.mjs +2 -2
  13. package/dist/{codec-types-DHCkwPKE.d.mts → codec-types-BDVmvKAW.d.mts} +3 -2
  14. package/dist/codec-types-BDVmvKAW.d.mts.map +1 -0
  15. package/dist/codec-types.d.mts +1 -1
  16. package/dist/{codecs--0A5_4Bq.d.mts → codecs-Dk2sxGLv.d.mts} +77 -4
  17. package/dist/codecs-Dk2sxGLv.d.mts.map +1 -0
  18. package/dist/codecs.d.mts +1 -1
  19. package/dist/codecs.mjs +117 -2
  20. package/dist/codecs.mjs.map +1 -1
  21. package/dist/contract-free.d.mts +8 -3
  22. package/dist/contract-free.d.mts.map +1 -1
  23. package/dist/contract-free.mjs +4 -4
  24. package/dist/control-policy-DUFgy9jO.mjs +1103 -0
  25. package/dist/control-policy-DUFgy9jO.mjs.map +1 -0
  26. package/dist/control.d.mts +2 -2
  27. package/dist/control.d.mts.map +1 -1
  28. package/dist/control.mjs +757 -35
  29. package/dist/control.mjs.map +1 -1
  30. package/dist/data-transform-BOWpliq8.mjs.map +1 -1
  31. package/dist/{data-transform-DDgWdB5o.d.mts → data-transform-DPzwBMhW.d.mts} +3 -3
  32. package/dist/{data-transform-DDgWdB5o.d.mts.map → data-transform-DPzwBMhW.d.mts.map} +1 -1
  33. package/dist/data-transform.d.mts +1 -1
  34. package/dist/{ddl-QDyOSeLc.mjs → ddl-ujxH2A7r.mjs} +63 -6
  35. package/dist/ddl-ujxH2A7r.mjs.map +1 -0
  36. package/dist/ddl.d.mts +3 -2
  37. package/dist/ddl.mjs +2 -2
  38. package/dist/default-normalizer.d.mts +1 -1
  39. package/dist/default-normalizer.d.mts.map +1 -1
  40. package/dist/default-normalizer.mjs +247 -1
  41. package/dist/default-normalizer.mjs.map +1 -0
  42. package/dist/descriptor-meta-CuGDsrG4.mjs +19 -0
  43. package/dist/descriptor-meta-CuGDsrG4.mjs.map +1 -0
  44. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs +12 -0
  45. package/dist/descriptor-meta-runtime-UwYdV8fD.mjs.map +1 -0
  46. package/dist/diff-database-schema-BmSmWcaY.mjs +313 -0
  47. package/dist/diff-database-schema-BmSmWcaY.mjs.map +1 -0
  48. package/dist/diff-database-schema.d.mts +46 -0
  49. package/dist/diff-database-schema.d.mts.map +1 -0
  50. package/dist/diff-database-schema.mjs +2 -0
  51. package/dist/issue-planner.d.mts +35 -11
  52. package/dist/issue-planner.d.mts.map +1 -1
  53. package/dist/issue-planner.mjs +2 -2
  54. package/dist/migration.d.mts +5 -5
  55. package/dist/migration.d.mts.map +1 -1
  56. package/dist/migration.mjs +4 -4
  57. package/dist/namespace-ids-xtp_ZY86.mjs +7 -0
  58. package/dist/namespace-ids-xtp_ZY86.mjs.map +1 -0
  59. package/dist/native-type-normalizer.mjs +37 -1
  60. package/dist/native-type-normalizer.mjs.map +1 -0
  61. package/dist/nodes-CSkPX3rI.d.mts +120 -0
  62. package/dist/nodes-CSkPX3rI.d.mts.map +1 -0
  63. package/dist/{nodes-Bbhs2rwj.mjs → nodes-Dh__NS58.mjs} +56 -2
  64. package/dist/nodes-Dh__NS58.mjs.map +1 -0
  65. package/dist/{op-factory-call-D_p5vxwt.mjs → op-factory-call-BKRnMgrB.mjs} +205 -31
  66. package/dist/op-factory-call-BKRnMgrB.mjs.map +1 -0
  67. package/dist/{op-factory-call-DmQEc3XV.d.mts → op-factory-call-Dt-Jx5JS.d.mts} +46 -15
  68. package/dist/op-factory-call-Dt-Jx5JS.d.mts.map +1 -0
  69. package/dist/op-factory-call.d.mts +2 -2
  70. package/dist/op-factory-call.mjs +2 -2
  71. package/dist/pack.d.mts +115 -3
  72. package/dist/pack.d.mts.map +1 -1
  73. package/dist/pack.mjs +1 -1
  74. package/dist/planner-BMBRPzsv.mjs +367 -0
  75. package/dist/planner-BMBRPzsv.mjs.map +1 -0
  76. package/dist/{planner-ddl-builders-B2wOwLqI.mjs → planner-ddl-builders-B8Nn6nHN.mjs} +12 -27
  77. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -0
  78. package/dist/planner-ddl-builders.d.mts +4 -6
  79. package/dist/planner-ddl-builders.d.mts.map +1 -1
  80. package/dist/planner-ddl-builders.mjs +2 -2
  81. package/dist/planner-identity-values-CJPha2Sz.mjs.map +1 -1
  82. package/dist/planner-identity-values.d.mts +8 -2
  83. package/dist/planner-identity-values.d.mts.map +1 -1
  84. package/dist/planner-identity-values.mjs +2 -2
  85. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts → planner-produced-postgres-migration-C0a7I1-e.d.mts} +4 -4
  86. package/dist/{planner-produced-postgres-migration-QqHa2C2l.d.mts.map → planner-produced-postgres-migration-C0a7I1-e.d.mts.map} +1 -1
  87. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs → planner-produced-postgres-migration-Dp6IO_gj.mjs} +3 -3
  88. package/dist/{planner-produced-postgres-migration-Cji5vxUf.mjs.map → planner-produced-postgres-migration-Dp6IO_gj.mjs.map} +1 -1
  89. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  90. package/dist/planner-produced-postgres-migration.mjs +1 -1
  91. package/dist/{planner-sql-checks-jqUUGyQR.mjs → planner-sql-checks-Cze3vMfe.mjs} +2 -2
  92. package/dist/{planner-sql-checks-jqUUGyQR.mjs.map → planner-sql-checks-Cze3vMfe.mjs.map} +1 -1
  93. package/dist/planner-sql-checks.d.mts +2 -2
  94. package/dist/planner-sql-checks.d.mts.map +1 -1
  95. package/dist/planner-sql-checks.mjs +1 -1
  96. package/dist/{planner-target-details-CIY6tLeo.d.mts → planner-target-details-HkP4RrRO.d.mts} +2 -2
  97. package/dist/planner-target-details-HkP4RrRO.d.mts.map +1 -0
  98. package/dist/planner-target-details.d.mts +1 -1
  99. package/dist/planner.d.mts +55 -17
  100. package/dist/planner.d.mts.map +1 -1
  101. package/dist/planner.mjs +3 -2
  102. package/dist/{postgres-contract-serializer-k3TAcPMY.mjs → postgres-contract-view-3xfUgjnC.mjs} +44 -41
  103. package/dist/postgres-contract-view-3xfUgjnC.mjs.map +1 -0
  104. package/dist/postgres-contract-view-C9j5cqP0.d.mts +34 -0
  105. package/dist/postgres-contract-view-C9j5cqP0.d.mts.map +1 -0
  106. package/dist/postgres-database-schema-node-C9bTuMrd.d.mts +190 -0
  107. package/dist/postgres-database-schema-node-C9bTuMrd.d.mts.map +1 -0
  108. package/dist/{postgres-migration-B5jKrXv3.mjs → postgres-migration-D0jwzYDT.mjs} +21 -2
  109. package/dist/postgres-migration-D0jwzYDT.mjs.map +1 -0
  110. package/dist/{postgres-migration-Y4YBJqkS.d.mts → postgres-migration-DFaxuRyI.d.mts} +20 -7
  111. package/dist/postgres-migration-DFaxuRyI.d.mts.map +1 -0
  112. package/dist/postgres-native-enum-xdiqmBf_.mjs +41 -0
  113. package/dist/postgres-native-enum-xdiqmBf_.mjs.map +1 -0
  114. package/dist/postgres-rls-policy--CSddvMR.d.mts +52 -0
  115. package/dist/postgres-rls-policy--CSddvMR.d.mts.map +1 -0
  116. package/dist/{postgres-schema-COGZ1ark.mjs → postgres-schema-CajYAwny.mjs} +135 -7
  117. package/dist/postgres-schema-CajYAwny.mjs.map +1 -0
  118. package/dist/postgres-schema-DqnhQRvR.d.mts +226 -0
  119. package/dist/postgres-schema-DqnhQRvR.d.mts.map +1 -0
  120. package/dist/postgres-table-schema-node-BgaSrxYN.mjs +249 -0
  121. package/dist/postgres-table-schema-node-BgaSrxYN.mjs.map +1 -0
  122. package/dist/render-ops.d.mts +1 -1
  123. package/dist/{render-typescript-KMgosran.mjs → render-typescript-7yqILcwr.mjs} +45 -16
  124. package/dist/render-typescript-7yqILcwr.mjs.map +1 -0
  125. package/dist/render-typescript.mjs +1 -1
  126. package/dist/resolve-ddl-schema-Bv1ZvjX8.mjs +22 -0
  127. package/dist/resolve-ddl-schema-Bv1ZvjX8.mjs.map +1 -0
  128. package/dist/rls-canonicalize.d.mts +2 -0
  129. package/dist/rls-canonicalize.mjs +2 -0
  130. package/dist/runtime.d.mts +6 -8
  131. package/dist/runtime.d.mts.map +1 -1
  132. package/dist/runtime.mjs +3 -3
  133. package/dist/runtime.mjs.map +1 -1
  134. package/dist/schema-ir-annotations.d.mts +15 -0
  135. package/dist/schema-ir-annotations.d.mts.map +1 -0
  136. package/dist/schema-ir-annotations.mjs +2 -0
  137. package/dist/schema-node-kinds-ClScchhi.mjs +76 -0
  138. package/dist/schema-node-kinds-ClScchhi.mjs.map +1 -0
  139. package/dist/types.d.mts +4 -140
  140. package/dist/types.mjs +3 -2
  141. package/package.json +23 -17
  142. package/src/contract-free/checks.ts +73 -0
  143. package/src/contract-free/ddl.ts +45 -1
  144. package/src/core/authoring.ts +287 -2
  145. package/src/core/codec-ids.ts +1 -0
  146. package/src/core/codec-type-map.ts +2 -0
  147. package/src/core/codecs.ts +141 -1
  148. package/src/core/ddl/nodes.ts +94 -2
  149. package/src/core/default-normalizer.ts +122 -2
  150. package/src/core/descriptor-meta.ts +6 -1
  151. package/src/core/entity-kinds.ts +30 -0
  152. package/src/core/migrations/bound-schema.ts +10 -0
  153. package/src/core/migrations/column-ddl-rendering.ts +117 -0
  154. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +149 -0
  155. package/src/core/migrations/control-policy.ts +70 -47
  156. package/src/core/migrations/diff-database-schema.ts +280 -0
  157. package/src/core/migrations/issue-planner.ts +647 -652
  158. package/src/core/migrations/op-factory-call.ts +109 -21
  159. package/src/core/migrations/operations/columns.ts +24 -12
  160. package/src/core/migrations/operations/constraints.ts +1 -1
  161. package/src/core/migrations/operations/data-transform.ts +6 -0
  162. package/src/core/migrations/operations/dependencies.ts +2 -1
  163. package/src/core/migrations/operations/rls.ts +106 -0
  164. package/src/core/migrations/operations/shared.ts +0 -30
  165. package/src/core/migrations/planner-ddl-builders.ts +20 -58
  166. package/src/core/migrations/planner-identity-values.ts +1 -1
  167. package/src/core/migrations/planner-recipes.ts +31 -13
  168. package/src/core/migrations/planner-sql-checks.ts +1 -1
  169. package/src/core/migrations/planner-strategies.ts +248 -216
  170. package/src/core/migrations/planner-target-details.ts +3 -1
  171. package/src/core/migrations/planner.ts +297 -59
  172. package/src/core/migrations/postgres-migration.ts +34 -4
  173. package/src/core/migrations/render-typescript.ts +42 -15
  174. package/src/core/migrations/resolve-ddl-schema.ts +26 -0
  175. package/src/core/migrations/runner.ts +10 -16
  176. package/src/core/migrations/verify-postgres-namespaces.ts +47 -30
  177. package/src/core/namespace-ids.ts +3 -0
  178. package/src/core/postgres-contract-serializer.ts +41 -71
  179. package/src/core/postgres-contract-view.ts +48 -0
  180. package/src/core/postgres-native-enum.ts +51 -0
  181. package/src/core/postgres-rls-policy.ts +64 -0
  182. package/src/core/postgres-role.ts +37 -0
  183. package/src/core/postgres-schema-verifier.ts +10 -7
  184. package/src/core/postgres-schema.ts +62 -14
  185. package/src/core/postgres-validators.ts +27 -0
  186. package/src/core/psl-infer/infer-psl-contract.ts +1068 -0
  187. package/src/core/psl-infer/postgres-default-mapping.ts +16 -0
  188. package/src/core/psl-infer/postgres-type-map.ts +157 -0
  189. package/src/core/rls/canonicalize.ts +48 -0
  190. package/src/core/schema-ir/postgres-database-schema-node.ts +60 -0
  191. package/src/core/schema-ir/postgres-namespace-schema-node.ts +60 -0
  192. package/src/core/schema-ir/postgres-policy-schema-node.ts +90 -0
  193. package/src/core/schema-ir/postgres-role-schema-node.ts +63 -0
  194. package/src/core/schema-ir/postgres-table-schema-node.ts +118 -0
  195. package/src/core/schema-ir/schema-node-kinds.ts +106 -0
  196. package/src/exports/contract-free.ts +7 -1
  197. package/src/exports/control.ts +27 -33
  198. package/src/exports/ddl.ts +4 -0
  199. package/src/exports/diff-database-schema.ts +7 -0
  200. package/src/exports/issue-planner.ts +1 -1
  201. package/src/exports/migration.ts +1 -0
  202. package/src/exports/op-factory-call.ts +1 -0
  203. package/src/exports/planner-ddl-builders.ts +0 -2
  204. package/src/exports/planner-identity-values.ts +4 -1
  205. package/src/exports/planner.ts +1 -0
  206. package/src/exports/rls-canonicalize.ts +6 -0
  207. package/src/exports/runtime.ts +1 -0
  208. package/src/exports/schema-ir-annotations.ts +1 -0
  209. package/src/exports/types.ts +28 -0
  210. package/dist/codec-ids-BvytN2P8.mjs.map +0 -1
  211. package/dist/codec-ids-CnXu9Qy3.d.mts.map +0 -1
  212. package/dist/codec-types-DHCkwPKE.d.mts.map +0 -1
  213. package/dist/codecs--0A5_4Bq.d.mts.map +0 -1
  214. package/dist/ddl-QDyOSeLc.mjs.map +0 -1
  215. package/dist/default-normalizer-DyyCHQWs.mjs +0 -131
  216. package/dist/default-normalizer-DyyCHQWs.mjs.map +0 -1
  217. package/dist/descriptor-meta-CpGygXpI.mjs +0 -140
  218. package/dist/descriptor-meta-CpGygXpI.mjs.map +0 -1
  219. package/dist/issue-planner-DL6g3CmE.mjs +0 -824
  220. package/dist/issue-planner-DL6g3CmE.mjs.map +0 -1
  221. package/dist/native-type-normalizer-Bc9XJzWC.mjs +0 -38
  222. package/dist/native-type-normalizer-Bc9XJzWC.mjs.map +0 -1
  223. package/dist/nodes-Bbhs2rwj.mjs.map +0 -1
  224. package/dist/nodes-pLeLgdis.d.mts +0 -67
  225. package/dist/nodes-pLeLgdis.d.mts.map +0 -1
  226. package/dist/op-factory-call-D_p5vxwt.mjs.map +0 -1
  227. package/dist/op-factory-call-DmQEc3XV.d.mts.map +0 -1
  228. package/dist/planner-Bs_baQax.mjs +0 -324
  229. package/dist/planner-Bs_baQax.mjs.map +0 -1
  230. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +0 -1
  231. package/dist/planner-target-details-CIY6tLeo.d.mts.map +0 -1
  232. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +0 -1
  233. package/dist/postgres-migration-B5jKrXv3.mjs.map +0 -1
  234. package/dist/postgres-migration-Y4YBJqkS.d.mts.map +0 -1
  235. package/dist/postgres-schema-COGZ1ark.mjs.map +0 -1
  236. package/dist/render-typescript-KMgosran.mjs.map +0 -1
  237. package/dist/types.d.mts.map +0 -1
  238. package/src/core/postgres-enum-type-schema.ts +0 -17
@@ -1,824 +0,0 @@
1
- import { r as isPostgresSchema } from "./postgres-schema-COGZ1ark.mjs";
2
- import { t as resolveColumnTypeMetadata } from "./planner-type-resolution-Bt2f_q-F.mjs";
3
- import { n as qualifyTableName, t as buildExpectedFormatType } from "./planner-sql-checks-jqUUGyQR.mjs";
4
- import { C as SetNotNullCall, S as SetDefaultCall, T as buildTargetDetails, _ as DropDefaultCall, a as AddNotNullColumnWithTempDefaultCall, b as DropTableCall, c as AlterColumnTypeCall, d as CreateSchemaCall, f as CreateTableCall, g as DropConstraintCall, h as DropColumnCall, i as AddNotNullColumnDirectCall, m as DropCheckConstraintCall, n as AddColumnCall, o as AddPrimaryKeyCall, p as DataTransformCall, r as AddForeignKeyCall, s as AddUniqueCall, t as AddCheckConstraintCall, u as CreateIndexCall, v as DropIndexCall, w as postgresDefaultToDdlColumnDefault, x as RawSqlCall, y as DropNotNullCall } from "./op-factory-call-D_p5vxwt.mjs";
5
- import { n as buildColumnDefaultSql, r as buildColumnTypeSql, t as buildAddColumnSql } from "./planner-ddl-builders-B2wOwLqI.mjs";
6
- import { n as resolveIdentityValue } from "./planner-identity-values-CJPha2Sz.mjs";
7
- import { i as hasUniqueConstraint, n as hasForeignKey, t as buildSchemaLookupMap } from "./planner-schema-lookup-CiVaAQP-.mjs";
8
- import { blindCast } from "@prisma-next/utils/casts";
9
- import { resolveValueSetValues } from "@prisma-next/family-sql/control";
10
- import { ifDefined } from "@prisma-next/utils/defined";
11
- import { arraysEqual } from "@prisma-next/family-sql/schema-verify";
12
- import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
13
- import * as contractFree from "@prisma-next/sql-relational-core/contract-free";
14
- import { col } from "@prisma-next/sql-relational-core/contract-free";
15
- import { notOk, ok } from "@prisma-next/utils/result";
16
- import { StorageTable } from "@prisma-next/sql-contract/types";
17
- //#region src/core/migrations/planner-strategies.ts
18
- /**
19
- * Look up a storage table by its explicit namespace coordinate. Returns
20
- * `undefined` when the namespace has no table by that name (or no such
21
- * namespace exists). Callers that get `undefined` MUST treat it as an
22
- * explicit conflict — never silently fall back to a global default
23
- * schema or a name-only walk, because that footgun would resolve a
24
- * stale or duplicate table name to whichever namespace the iteration
25
- * order surfaced first (a real data-loss hazard in multi-namespace
26
- * contracts where two namespaces can carry the same table name).
27
- */
28
- function tableAt(storage, namespaceId, tableName) {
29
- const ns = storage.namespaces[namespaceId];
30
- if (ns === void 0) return void 0;
31
- return ns.entries.table?.[tableName];
32
- }
33
- /**
34
- * Default namespace coordinate for an issue that does not carry one
35
- * explicitly. Hand-crafted unit-test issues and `extra_table` issues
36
- * fall back to `__unbound__`, the only namespace any single-namespace
37
- * contract carries — verifier-emitted issues for legacy
38
- * single-namespace contracts already stamp this id explicitly. Typed
39
- * structurally so issue variants without a `namespaceId` slot
40
- * (e.g. `EnumValuesChangedIssue`) flow through to the same fallback.
41
- */
42
- function resolveNamespaceIdForIssue(issue) {
43
- return issue.namespaceId ?? UNBOUND_NAMESPACE_ID;
44
- }
45
- /**
46
- * Resolve the DDL schema name for a namespace coordinate. Postgres-aware
47
- * namespaces dispatch to their polymorphic `ddlSchemaName` override —
48
- * named schemas return their own id; the unbound singleton returns
49
- * `UNBOUND_NAMESPACE_ID`. Legacy single-namespace contracts whose
50
- * `__unbound__` slot is the framework-default `SqlUnboundNamespace`
51
- * (rather than the Postgres-aware `PostgresUnboundSchema`) flow the
52
- * coordinate through unchanged so downstream `qualifyTableName`
53
- * resolves polymorphically.
54
- */
55
- function resolveDdlSchemaForNamespace(ctx, namespaceId) {
56
- const namespace = ctx.toContract.storage.namespaces[namespaceId];
57
- if (isPostgresSchema(namespace)) return namespace.ddlSchemaName(ctx.toContract.storage);
58
- return namespaceId;
59
- }
60
- function buildColumnSpec(namespaceId, table, column, ctx, overrides) {
61
- const storageCol = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
62
- if (!storageCol) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
63
- const mutableHooks = ctx.codecHooks;
64
- const mutableTypes = ctx.storageTypes;
65
- const typeSql = buildColumnTypeSql(storageCol, mutableHooks, mutableTypes);
66
- const ddlDefault = postgresDefaultToDdlColumnDefault(storageCol.default);
67
- const resolved = resolveColumnTypeMetadata(storageCol, mutableTypes);
68
- const typeParams = resolved.typeParams === void 0 ? void 0 : blindCast(resolved.typeParams);
69
- const codecRef = resolved.codecId ? {
70
- codecId: resolved.codecId,
71
- ...ifDefined("typeParams", typeParams)
72
- } : void 0;
73
- return col(column, typeSql, {
74
- ...!(overrides?.nullable ?? storageCol.nullable) ? { notNull: true } : {},
75
- ...ifDefined("default", ddlDefault),
76
- ...ifDefined("codecRef", codecRef)
77
- });
78
- }
79
- function buildAlterTypeOptions(namespaceId, table, column, ctx, using) {
80
- const col = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
81
- if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
82
- const mutableHooks = ctx.codecHooks;
83
- const mutableTypes = ctx.storageTypes;
84
- const qualifiedTargetType = buildColumnTypeSql(col, mutableHooks, mutableTypes, false);
85
- return {
86
- qualifiedTargetType,
87
- formatTypeExpected: buildExpectedFormatType(col, mutableHooks, mutableTypes),
88
- rawTargetTypeForLabel: qualifiedTargetType,
89
- ...using !== void 0 ? { using } : {}
90
- };
91
- }
92
- const notNullBackfillCallStrategy = (issues, ctx) => {
93
- if (!ctx.policy.allowedOperationClasses.includes("data")) return { kind: "no_match" };
94
- const matched = [];
95
- const calls = [];
96
- for (const issue of issues) {
97
- if (issue.kind !== "missing_column" || !issue.table || !issue.column) continue;
98
- const namespaceId = resolveNamespaceIdForIssue(issue);
99
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
100
- if (!column) continue;
101
- if (column.nullable === true || column.default !== void 0) continue;
102
- matched.push(issue);
103
- const spec = buildColumnSpec(namespaceId, issue.table, issue.column, ctx, { nullable: true });
104
- const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
105
- calls.push(new AddColumnCall(schemaForTable, issue.table, spec), new DataTransformCall(`backfill-${issue.table}-${issue.column}`, `backfill-${issue.table}-${issue.column}:check`, `backfill-${issue.table}-${issue.column}:run`), new SetNotNullCall(schemaForTable, issue.table, issue.column));
106
- }
107
- if (matched.length === 0) return { kind: "no_match" };
108
- return {
109
- kind: "match",
110
- issues: issues.filter((i) => !matched.includes(i)),
111
- calls,
112
- recipe: true
113
- };
114
- };
115
- const SAFE_WIDENINGS = new Set([
116
- "int2→int4",
117
- "int2→int8",
118
- "int4→int8",
119
- "float4→float8"
120
- ]);
121
- const typeChangeCallStrategy = (issues, ctx) => {
122
- const dataAllowed = ctx.policy.allowedOperationClasses.includes("data");
123
- const matched = [];
124
- const calls = [];
125
- for (const issue of issues) {
126
- if (issue.kind !== "type_mismatch") continue;
127
- if (!issue.table || !issue.column) continue;
128
- const namespaceId = resolveNamespaceIdForIssue(issue);
129
- const fromColumn = ctx.fromContract ? tableAt(ctx.fromContract.storage, namespaceId, issue.table)?.columns[issue.column] : void 0;
130
- const toColumn = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
131
- if (!fromColumn || !toColumn) continue;
132
- const fromType = fromColumn.nativeType;
133
- const toType = toColumn.nativeType;
134
- if (fromType === toType) continue;
135
- const isSafeWidening = SAFE_WIDENINGS.has(`${fromType}→${toType}`);
136
- if (!isSafeWidening && !dataAllowed) continue;
137
- matched.push(issue);
138
- const alterOpts = buildAlterTypeOptions(namespaceId, issue.table, issue.column, ctx);
139
- const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
140
- if (isSafeWidening) calls.push(new AlterColumnTypeCall(schemaForTable, issue.table, issue.column, alterOpts));
141
- else calls.push(new DataTransformCall(`typechange-${issue.table}-${issue.column}`, `typechange-${issue.table}-${issue.column}:check`, `typechange-${issue.table}-${issue.column}:run`), new AlterColumnTypeCall(schemaForTable, issue.table, issue.column, alterOpts));
142
- }
143
- if (matched.length === 0) return { kind: "no_match" };
144
- return {
145
- kind: "match",
146
- issues: issues.filter((i) => !matched.includes(i)),
147
- calls,
148
- recipe: true
149
- };
150
- };
151
- const nullableTighteningCallStrategy = (issues, ctx) => {
152
- if (!ctx.policy.allowedOperationClasses.includes("data")) return { kind: "no_match" };
153
- const matched = [];
154
- const calls = [];
155
- for (const issue of issues) {
156
- if (issue.kind !== "nullability_mismatch" || !issue.table || !issue.column) continue;
157
- const namespaceId = resolveNamespaceIdForIssue(issue);
158
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
159
- if (!column) continue;
160
- if (column.nullable === true) continue;
161
- matched.push(issue);
162
- const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
163
- calls.push(new DataTransformCall(`handle-nulls-${issue.table}-${issue.column}`, `handle-nulls-${issue.table}-${issue.column}:check`, `handle-nulls-${issue.table}-${issue.column}:run`), new SetNotNullCall(schemaForTable, issue.table, issue.column));
164
- }
165
- if (matched.length === 0) return { kind: "no_match" };
166
- return {
167
- kind: "match",
168
- issues: issues.filter((i) => !matched.includes(i)),
169
- calls,
170
- recipe: true
171
- };
172
- };
173
- /**
174
- * Collects every check constraint from a table in the contract storage.
175
- * Returns an empty array when the table has no checks or the table is absent.
176
- */
177
- function collectContractChecks(storage, namespaceId, tableName) {
178
- const ns = storage.namespaces[namespaceId];
179
- const tableRaw = ns !== void 0 ? ns.entries.table?.[tableName] : void 0;
180
- if (!(tableRaw instanceof StorageTable)) return [];
181
- const checks = tableRaw.checks;
182
- if (!checks || checks.length === 0) return [];
183
- return checks.map((c) => ({
184
- name: c.name,
185
- column: c.column,
186
- permittedValues: resolveValueSetValues(c.valueSet, storage, `check "${c.name}" on "${tableName}"`)
187
- }));
188
- }
189
- /**
190
- * Compares two value arrays as unordered sets.
191
- */
192
- function checkValueSetsEqual(a, b) {
193
- if (a.length !== b.length) return false;
194
- const bSet = new Set(b);
195
- return a.every((v) => bSet.has(v));
196
- }
197
- /**
198
- * Plans check-constraint migrations for `enumType`-authored columns.
199
- *
200
- * Walks every namespace's tables in the target contract. For each table that
201
- * carries `checks`, diffs the contract-expected checks against the live
202
- * schema's checks:
203
- *
204
- * - Check in contract, absent from live DB → `AddCheckConstraintCall`.
205
- * - Check in live DB, absent from contract → `DropCheckConstraintCall`.
206
- * - Check on both sides but value sets differ → `DropCheckConstraintCall`
207
- * then `AddCheckConstraintCall` (drop + recreate; a check predicate cannot
208
- * be altered in place).
209
- *
210
- * Consumes `check_missing`, `check_removed`, and `check_mismatch` issues.
211
- */
212
- const checkConstraintPlanCallStrategy = (issues, ctx) => {
213
- const calls = [];
214
- const handledIssueKeys = /* @__PURE__ */ new Set();
215
- for (const [namespaceId, ns] of Object.entries(ctx.toContract.storage.namespaces)) for (const tableName of Object.keys(ns.entries.table ?? {})) {
216
- const contractChecks = collectContractChecks(ctx.toContract.storage, namespaceId, tableName);
217
- if (contractChecks.length === 0) continue;
218
- const liveChecks = ctx.schema.tables[tableName]?.checks ?? [];
219
- const ddlSchema = resolveDdlSchemaForNamespace(ctx, namespaceId);
220
- for (const contractCheck of contractChecks) {
221
- const liveCheck = liveChecks.find((c) => c.name === contractCheck.name);
222
- const issueKey = `${tableName}${contractCheck.name}`;
223
- if (!liveCheck) {
224
- calls.push(new AddCheckConstraintCall(ddlSchema, tableName, contractCheck.name, contractCheck.column, contractCheck.permittedValues));
225
- handledIssueKeys.add(issueKey);
226
- } else if (!checkValueSetsEqual(contractCheck.permittedValues, liveCheck.permittedValues)) {
227
- calls.push(new DropCheckConstraintCall(ddlSchema, tableName, contractCheck.name), new AddCheckConstraintCall(ddlSchema, tableName, contractCheck.name, contractCheck.column, contractCheck.permittedValues));
228
- handledIssueKeys.add(issueKey);
229
- } else handledIssueKeys.add(issueKey);
230
- }
231
- for (const liveCheck of liveChecks) if (!contractChecks.some((c) => c.name === liveCheck.name)) {
232
- const issueKey = `${tableName}${liveCheck.name}`;
233
- calls.push(new DropCheckConstraintCall(ddlSchema, tableName, liveCheck.name));
234
- handledIssueKeys.add(issueKey);
235
- }
236
- }
237
- if (calls.length === 0 && handledIssueKeys.size === 0) return { kind: "no_match" };
238
- return {
239
- kind: "match",
240
- issues: issues.filter((issue) => {
241
- if (issue.kind !== "check_missing" && issue.kind !== "check_removed" && issue.kind !== "check_mismatch") return true;
242
- if (!issue.table || !issue.indexOrConstraint) return true;
243
- const key = `${issue.table}${issue.indexOrConstraint}`;
244
- return !handledIssueKeys.has(key);
245
- }),
246
- calls
247
- };
248
- };
249
- /**
250
- * Dispatches codec-typed storage types through their codec's
251
- * `planTypeOperations` hook (the authoritative source for codec-driven DDL
252
- * such as custom type creation).
253
- */
254
- const storageTypePlanCallStrategy = (issues, ctx) => {
255
- const storageTypes = ctx.toContract.storage.types ?? {};
256
- if (Object.keys(storageTypes).length === 0) return { kind: "no_match" };
257
- const calls = [];
258
- const handledTypeNames = /* @__PURE__ */ new Set();
259
- for (const [typeName, typeInstance] of Object.entries(storageTypes).sort(([a], [b]) => a.localeCompare(b))) {
260
- const codecInstance = typeInstance;
261
- const hook = ctx.codecHooks.get(codecInstance.codecId);
262
- if (!hook?.planTypeOperations) continue;
263
- const planResult = hook.planTypeOperations({
264
- typeName,
265
- typeInstance: codecInstance,
266
- contract: ctx.toContract,
267
- schema: ctx.schema,
268
- schemaName: ctx.schemaName,
269
- policy: ctx.policy
270
- });
271
- if (!planResult) continue;
272
- if (planResult.operations.length === 0) {
273
- handledTypeNames.add(typeName);
274
- continue;
275
- }
276
- handledTypeNames.add(typeName);
277
- for (const op of planResult.operations) calls.push(new RawSqlCall({
278
- ...op,
279
- target: {
280
- id: op.target.id,
281
- details: buildTargetDetails("type", typeName, ctx.schemaName)
282
- }
283
- }));
284
- }
285
- const remaining = issues.filter((issue) => !((issue.kind === "type_missing" || issue.kind === "enum_values_changed") && issue.typeName && handledTypeNames.has(issue.typeName)));
286
- if (calls.length === 0 && remaining.length === issues.length) return { kind: "no_match" };
287
- return {
288
- kind: "match",
289
- issues: remaining,
290
- calls
291
- };
292
- };
293
- /**
294
- * Handles `missing_column` issues for NOT NULL columns without a contract
295
- * default. Replaces the walk-schema `buildAddColumnItem` non-default branches.
296
- *
297
- * Two shapes:
298
- * - Shared-temp-default safe: emit a single atomic composite op (add
299
- * nullable → backfill identity value → `SET NOT NULL` → `DROP DEFAULT`).
300
- * - Empty-table guarded: emit a hand-built op with a `tableIsEmptyCheck`
301
- * precheck so the failure message is "table is not empty" rather than the
302
- * raw PG NOT NULL violation.
303
- *
304
- * "Normal" missing_column cases (nullable or has a contract default) are left
305
- * for `mapIssueToCall`'s default `AddColumnCall` emission.
306
- */
307
- const notNullAddColumnCallStrategy = (issues, ctx) => {
308
- const matched = [];
309
- const calls = [];
310
- const schemaLookups = buildSchemaLookupMap(ctx.schema);
311
- const mutableCodecHooks = ctx.codecHooks;
312
- const mutableStorageTypes = ctx.storageTypes;
313
- for (const issue of issues) {
314
- if (issue.kind !== "missing_column" || !issue.table || !issue.column) continue;
315
- const namespaceId = resolveNamespaceIdForIssue(issue);
316
- const contractTable = tableAt(ctx.toContract.storage, namespaceId, issue.table);
317
- const column = contractTable?.columns[issue.column];
318
- if (!column) continue;
319
- const notNull = column.nullable !== true;
320
- const hasDefault = column.default !== void 0;
321
- if (!notNull || hasDefault) continue;
322
- const schemaTable = ctx.schema.tables[issue.table];
323
- if (!schemaTable) continue;
324
- const temporaryDefault = resolveIdentityValue(column, mutableCodecHooks, mutableStorageTypes);
325
- const schemaLookup = schemaLookups.get(issue.table);
326
- const canUseSharedTempDefault = temporaryDefault !== null && canUseSharedTemporaryDefaultStrategy({
327
- table: contractTable,
328
- schemaTable,
329
- schemaLookup,
330
- columnName: issue.column
331
- });
332
- matched.push(issue);
333
- const schemaForTable = resolveDdlSchemaForNamespace(ctx, namespaceId);
334
- if (canUseSharedTempDefault && temporaryDefault !== null) {
335
- calls.push(new AddNotNullColumnWithTempDefaultCall({
336
- schemaName: schemaForTable,
337
- tableName: issue.table,
338
- columnName: issue.column,
339
- column,
340
- codecHooks: mutableCodecHooks,
341
- storageTypes: mutableStorageTypes,
342
- temporaryDefault
343
- }));
344
- continue;
345
- }
346
- const qualified = qualifyTableName(schemaForTable, issue.table);
347
- calls.push(new AddNotNullColumnDirectCall(schemaForTable, issue.table, issue.column, buildAddColumnSql(qualified, issue.column, column, mutableCodecHooks, void 0, mutableStorageTypes)));
348
- }
349
- if (matched.length === 0) return { kind: "no_match" };
350
- return {
351
- kind: "match",
352
- issues: issues.filter((i) => !matched.includes(i)),
353
- calls
354
- };
355
- };
356
- function canUseSharedTemporaryDefaultStrategy(options) {
357
- const { table, schemaTable, schemaLookup, columnName } = options;
358
- if (table.primaryKey?.columns.includes(columnName) && !schemaTable.primaryKey) return false;
359
- for (const unique of table.uniques) {
360
- if (!unique.columns.includes(columnName)) continue;
361
- if (!schemaLookup || !hasUniqueConstraint(schemaLookup, unique.columns)) return false;
362
- }
363
- for (const foreignKey of table.foreignKeys) {
364
- if (foreignKey.constraint === false || !foreignKey.source.columns.includes(columnName)) continue;
365
- if (!schemaLookup || !hasForeignKey(schemaLookup, foreignKey)) return false;
366
- }
367
- return true;
368
- }
369
- /**
370
- * Ordered list of Postgres planner strategies, shared by `migration plan`
371
- * and `db update` / `db init`. The issue planner runs each strategy in
372
- * order, letting it consume any issues it handles, and routes whatever's
373
- * left through `mapIssueToCall`. Behavior diverges purely on
374
- * `policy.allowedOperationClasses`:
375
- *
376
- * - When `'data'` is allowed (`migration plan`), the data-safe strategies
377
- * (`notNullBackfillCallStrategy`, `typeChangeCallStrategy`,
378
- * `nullableTighteningCallStrategy`) consume their matching issues and emit
379
- * `DataTransformCall` placeholders or recipe ops.
380
- *
381
- * - When `'data'` is not allowed (`db update` / `db init`), the
382
- * placeholder-emitting strategies short-circuit to `no_match`, leaving
383
- * the issue for the downstream walk-schema strategies
384
- * (`storageTypePlanCallStrategy`, `notNullAddColumnCallStrategy`) or the
385
- * `mapIssueToCall` default to handle with direct DDL.
386
- *
387
- * Codec-typed storage type entries are dispatched through
388
- * `storageTypePlanCallStrategy`.
389
- */
390
- const postgresPlannerStrategies = [
391
- notNullBackfillCallStrategy,
392
- typeChangeCallStrategy,
393
- nullableTighteningCallStrategy,
394
- checkConstraintPlanCallStrategy,
395
- storageTypePlanCallStrategy,
396
- notNullAddColumnCallStrategy
397
- ];
398
- //#endregion
399
- //#region src/core/migrations/issue-planner.ts
400
- const ISSUE_KIND_ORDER = {
401
- missing_schema: 1,
402
- type_missing: 2,
403
- type_values_mismatch: 3,
404
- enum_values_changed: 3,
405
- extra_foreign_key: 10,
406
- extra_unique_constraint: 11,
407
- extra_primary_key: 12,
408
- extra_index: 13,
409
- extra_default: 14,
410
- extra_column: 15,
411
- extra_table: 16,
412
- missing_table: 20,
413
- missing_column: 30,
414
- type_mismatch: 40,
415
- nullability_mismatch: 41,
416
- default_missing: 42,
417
- default_mismatch: 43,
418
- primary_key_mismatch: 50,
419
- unique_constraint_mismatch: 51,
420
- index_mismatch: 52,
421
- foreign_key_mismatch: 60,
422
- check_missing: 53,
423
- check_mismatch: 54,
424
- check_removed: 55
425
- };
426
- function issueOrder(issue) {
427
- return ISSUE_KIND_ORDER[issue.kind] ?? 99;
428
- }
429
- function issueConflict(kind, summary, location) {
430
- return {
431
- kind,
432
- summary,
433
- why: "Use `migration new` to author a custom migration for this change.",
434
- ...location ? { location } : {}
435
- };
436
- }
437
- function isMissing(issue) {
438
- if (issue.kind === "enum_values_changed") return false;
439
- return issue.actual === void 0;
440
- }
441
- function toDdlColumn(name, column, codecHooks, storageTypes) {
442
- const typeSql = buildColumnTypeSql(column, codecHooks, storageTypes);
443
- const ddlDefault = postgresDefaultToDdlColumnDefault(column.default);
444
- const resolved = resolveColumnTypeMetadata(column, storageTypes);
445
- const codecRef = resolved.codecId ? {
446
- codecId: resolved.codecId,
447
- ...resolved.typeParams !== void 0 ? { typeParams: blindCast(resolved.typeParams) } : {}
448
- } : void 0;
449
- return contractFree.col(name, typeSql, {
450
- ...!column.nullable ? { notNull: true } : {},
451
- ...ifDefined("default", ddlDefault),
452
- ...ifDefined("codecRef", codecRef)
453
- });
454
- }
455
- function mapIssueToCall(issue, ctx) {
456
- const { schemaName, codecHooks, storageTypes } = ctx;
457
- const tableSchema = (issue) => {
458
- if (issue.kind === "extra_table") return schemaName;
459
- if (!("table" in issue) || !issue.table) return schemaName;
460
- return resolveDdlSchemaForNamespace(ctx, resolveNamespaceIdForIssue(issue));
461
- };
462
- switch (issue.kind) {
463
- case "missing_schema": {
464
- const namespaceId = issue.namespaceId;
465
- if (!namespaceId) return notOk(issueConflict("unsupportedOperation", "Missing schema issue has no namespaceId"));
466
- return ok([new CreateSchemaCall(resolveDdlSchemaForNamespace(ctx, namespaceId))]);
467
- }
468
- case "missing_table": {
469
- if (!issue.table) return notOk(issueConflict("unsupportedOperation", "Missing table issue has no table name"));
470
- const namespaceId = resolveNamespaceIdForIssue(issue);
471
- const contractTable = tableAt(ctx.toContract.storage, namespaceId, issue.table);
472
- if (!contractTable) return notOk(issueConflict("unsupportedOperation", `Table "${issue.table}" in namespace "${namespaceId}" reported missing but not found in destination contract`));
473
- const schemaForTable = tableSchema(issue);
474
- const ddlColumns = Object.entries(contractTable.columns).map(([name, column]) => toDdlColumn(name, column, codecHooks, storageTypes));
475
- const ddlConstraints = contractTable.primaryKey ? [contractFree.primaryKey(contractTable.primaryKey.columns, { ...contractTable.primaryKey.name ? { name: contractTable.primaryKey.name } : {} })] : void 0;
476
- const calls = [new CreateTableCall(schemaForTable, issue.table, ddlColumns, ddlConstraints)];
477
- for (const index of contractTable.indexes) {
478
- const indexName = index.name ?? `${issue.table}_${index.columns.join("_")}_idx`;
479
- const extras = {};
480
- if (index.type !== void 0) extras.type = index.type;
481
- if (index.options !== void 0) extras.options = index.options;
482
- calls.push(new CreateIndexCall(schemaForTable, issue.table, indexName, [...index.columns], extras));
483
- }
484
- const explicitIndexColumnSets = new Set(contractTable.indexes.map((idx) => idx.columns.join(",")));
485
- for (const fk of contractTable.foreignKeys) {
486
- if (fk.constraint) {
487
- const fkSpec = {
488
- name: fk.name ?? `${issue.table}_${fk.source.columns.join("_")}_fkey`,
489
- columns: fk.source.columns,
490
- references: {
491
- schema: fk.target.namespaceId,
492
- table: fk.target.tableName,
493
- columns: fk.target.columns
494
- },
495
- ...fk.onDelete !== void 0 && { onDelete: fk.onDelete },
496
- ...fk.onUpdate !== void 0 && { onUpdate: fk.onUpdate }
497
- };
498
- calls.push(new AddForeignKeyCall(schemaForTable, issue.table, fkSpec));
499
- }
500
- if (fk.index && !explicitIndexColumnSets.has(fk.source.columns.join(","))) {
501
- const indexName = `${issue.table}_${fk.source.columns.join("_")}_idx`;
502
- calls.push(new CreateIndexCall(schemaForTable, issue.table, indexName, [...fk.source.columns]));
503
- }
504
- }
505
- for (const unique of contractTable.uniques) {
506
- const constraintName = unique.name ?? `${issue.table}_${unique.columns.join("_")}_key`;
507
- calls.push(new AddUniqueCall(schemaForTable, issue.table, constraintName, [...unique.columns]));
508
- }
509
- return ok(calls);
510
- }
511
- case "missing_column":
512
- if (!issue.table || !issue.column) return notOk(issueConflict("unsupportedOperation", "Missing column issue has no table/column name"));
513
- {
514
- const namespaceId = resolveNamespaceIdForIssue(issue);
515
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
516
- if (!column) return notOk(issueConflict("unsupportedOperation", `Column "${issue.table}"."${issue.column}" not in destination contract`));
517
- return ok([new AddColumnCall(tableSchema(issue), issue.table, toDdlColumn(issue.column, column, codecHooks, storageTypes))]);
518
- }
519
- case "default_missing":
520
- if (!issue.table || !issue.column) return notOk(issueConflict("unsupportedOperation", "Default missing issue has no table/column name"));
521
- {
522
- const namespaceId = resolveNamespaceIdForIssue(issue);
523
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
524
- if (!column?.default) return notOk(issueConflict("unsupportedOperation", `Column "${issue.table}"."${issue.column}" has no default in contract`));
525
- const defaultSql = buildColumnDefaultSql(column.default, column);
526
- if (!defaultSql) return ok([]);
527
- return ok([new SetDefaultCall(tableSchema(issue), issue.table, issue.column, defaultSql)]);
528
- }
529
- case "extra_table":
530
- if (!issue.table) return notOk(issueConflict("unsupportedOperation", "Extra table issue has no table name"));
531
- return ok([new DropTableCall(tableSchema(issue), issue.table)]);
532
- case "extra_column":
533
- if (!issue.table || !issue.column) return notOk(issueConflict("unsupportedOperation", "Extra column issue has no table/column name"));
534
- return ok([new DropColumnCall(tableSchema(issue), issue.table, issue.column)]);
535
- case "extra_index":
536
- if (!issue.table || !issue.indexOrConstraint) return notOk(issueConflict("unsupportedOperation", "Extra index issue has no table/index name"));
537
- return ok([new DropIndexCall(tableSchema(issue), issue.table, issue.indexOrConstraint)]);
538
- case "extra_unique_constraint":
539
- case "extra_foreign_key":
540
- case "extra_primary_key": {
541
- if (!issue.table) return notOk(issueConflict("unsupportedOperation", "Extra constraint issue has no table/constraint name"));
542
- const constraintName = issue.indexOrConstraint ?? (issue.kind === "extra_primary_key" ? `${issue.table}_pkey` : void 0);
543
- if (!constraintName) return notOk(issueConflict("unsupportedOperation", "Extra constraint issue has no table/constraint name"));
544
- return ok([new DropConstraintCall(tableSchema(issue), issue.table, constraintName, {
545
- extra_unique_constraint: "unique",
546
- extra_foreign_key: "foreignKey",
547
- extra_primary_key: "primaryKey"
548
- }[issue.kind])]);
549
- }
550
- case "extra_default":
551
- if (!issue.table || !issue.column) return notOk(issueConflict("unsupportedOperation", "Extra default issue has no table/column name"));
552
- return ok([new DropDefaultCall(tableSchema(issue), issue.table, issue.column)]);
553
- case "nullability_mismatch": {
554
- if (!issue.table || !issue.column) return notOk(issueConflict("nullabilityConflict", "Nullability mismatch has no table/column name"));
555
- const namespaceId = resolveNamespaceIdForIssue(issue);
556
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
557
- if (!column) return notOk(issueConflict("nullabilityConflict", `Column "${issue.table}"."${issue.column}" not found in destination contract`));
558
- const schemaForTable = tableSchema(issue);
559
- return ok(column.nullable ? [new DropNotNullCall(schemaForTable, issue.table, issue.column)] : [new SetNotNullCall(schemaForTable, issue.table, issue.column)]);
560
- }
561
- case "type_mismatch":
562
- if (!issue.table || !issue.column) return notOk(issueConflict("typeMismatch", "Type mismatch has no table/column name"));
563
- {
564
- const namespaceId = resolveNamespaceIdForIssue(issue);
565
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
566
- if (!column) return notOk(issueConflict("typeMismatch", `Column "${issue.table}"."${issue.column}" not in destination contract`));
567
- const hooksMap = codecHooks;
568
- const typesMap = storageTypes;
569
- const qualifiedTargetType = buildColumnTypeSql(column, hooksMap, typesMap, false);
570
- const formatTypeExpected = buildExpectedFormatType(column, hooksMap, typesMap);
571
- return ok([new AlterColumnTypeCall(tableSchema(issue), issue.table, issue.column, {
572
- qualifiedTargetType,
573
- formatTypeExpected,
574
- rawTargetTypeForLabel: qualifiedTargetType
575
- })]);
576
- }
577
- case "default_mismatch":
578
- if (!issue.table || !issue.column) return notOk(issueConflict("unsupportedOperation", "Default mismatch has no table/column name"));
579
- {
580
- const namespaceId = resolveNamespaceIdForIssue(issue);
581
- const column = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.columns[issue.column];
582
- if (!column?.default) return ok([]);
583
- const defaultSql = buildColumnDefaultSql(column.default, column);
584
- if (!defaultSql) return ok([]);
585
- return ok([new SetDefaultCall(tableSchema(issue), issue.table, issue.column, defaultSql, "widening")]);
586
- }
587
- case "primary_key_mismatch":
588
- if (!issue.table) return notOk(issueConflict("indexIncompatible", "Primary key issue has no table name"));
589
- if (isMissing(issue)) {
590
- const namespaceId = resolveNamespaceIdForIssue(issue);
591
- const pk = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.primaryKey;
592
- if (!pk) return notOk(issueConflict("indexIncompatible", `No primary key in contract for "${issue.table}"`));
593
- const constraintName = pk.name ?? `${issue.table}_pkey`;
594
- return ok([new AddPrimaryKeyCall(tableSchema(issue), issue.table, constraintName, pk.columns)]);
595
- }
596
- return notOk(issueConflict("indexIncompatible", `Primary key on "${issue.table}" has different columns (expected: ${issue.expected}, actual: ${issue.actual})`, { table: issue.table }));
597
- case "unique_constraint_mismatch":
598
- if (!issue.table) return notOk(issueConflict("indexIncompatible", "Unique constraint issue has no table name"));
599
- if (isMissing(issue) && issue.expected) {
600
- const columns = issue.expected.split(", ");
601
- const constraintName = `${issue.table}_${columns.join("_")}_key`;
602
- return ok([new AddUniqueCall(tableSchema(issue), issue.table, constraintName, columns)]);
603
- }
604
- return notOk(issueConflict("indexIncompatible", `Unique constraint on "${issue.table}" differs (expected: ${issue.expected}, actual: ${issue.actual})`, { table: issue.table }));
605
- case "index_mismatch":
606
- if (!issue.table) return notOk(issueConflict("indexIncompatible", "Index issue has no table name"));
607
- if (isMissing(issue) && issue.expected) {
608
- const namespaceId = resolveNamespaceIdForIssue(issue);
609
- const columns = issue.expected.split(", ");
610
- const contractIndex = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.indexes.find((idx) => arraysEqual(idx.columns, columns));
611
- const indexName = contractIndex?.name ?? `${issue.table}_${columns.join("_")}_idx`;
612
- const extras = {};
613
- if (contractIndex?.type !== void 0) extras.type = contractIndex.type;
614
- if (contractIndex?.options !== void 0) extras.options = contractIndex.options;
615
- return ok([new CreateIndexCall(tableSchema(issue), issue.table, indexName, columns, extras)]);
616
- }
617
- return notOk(issueConflict("indexIncompatible", `Index on "${issue.table}" differs (expected: ${issue.expected}, actual: ${issue.actual})`, { table: issue.table }));
618
- case "check_missing":
619
- if (!issue.table || !issue.indexOrConstraint) return notOk(issueConflict("unsupportedOperation", "Check missing issue has no table/constraint name"));
620
- return notOk(issueConflict("unsupportedOperation", `Check constraint "${issue.indexOrConstraint}" missing on "${issue.table}" — handled by checkConstraintPlanCallStrategy`));
621
- case "check_mismatch":
622
- if (!issue.table || !issue.indexOrConstraint) return notOk(issueConflict("unsupportedOperation", "Check mismatch issue has no table/constraint name"));
623
- return notOk(issueConflict("unsupportedOperation", `Check constraint "${issue.indexOrConstraint}" values mismatch on "${issue.table}" — handled by checkConstraintPlanCallStrategy`));
624
- case "check_removed":
625
- if (!issue.table || !issue.indexOrConstraint) return notOk(issueConflict("unsupportedOperation", "Check removed issue has no table/constraint name"));
626
- return ok([new DropCheckConstraintCall(tableSchema(issue), issue.table, issue.indexOrConstraint)]);
627
- case "foreign_key_mismatch":
628
- if (!issue.table) return notOk(issueConflict("foreignKeyConflict", "Foreign key issue has no table name"));
629
- if (isMissing(issue) && issue.expected) {
630
- const arrowIdx = issue.expected.indexOf(" -> ");
631
- if (arrowIdx >= 0) {
632
- const namespaceId = resolveNamespaceIdForIssue(issue);
633
- const columns = issue.expected.slice(0, arrowIdx).split(", ");
634
- const fkName = `${issue.table}_${columns.join("_")}_fkey`;
635
- const fk = tableAt(ctx.toContract.storage, namespaceId, issue.table)?.foreignKeys.find((k) => k.source.columns.join(", ") === columns.join(", "));
636
- if (fk) {
637
- const fkSpec = {
638
- name: fkName,
639
- columns: fk.source.columns,
640
- references: {
641
- schema: fk.target.namespaceId,
642
- table: fk.target.tableName,
643
- columns: fk.target.columns
644
- },
645
- ...fk.onDelete !== void 0 && { onDelete: fk.onDelete },
646
- ...fk.onUpdate !== void 0 && { onUpdate: fk.onUpdate }
647
- };
648
- return ok([new AddForeignKeyCall(tableSchema(issue), issue.table, fkSpec)]);
649
- }
650
- return notOk(issueConflict("foreignKeyConflict", `Foreign key on "${issue.table}" (${columns.join(", ")}) not found in destination contract`, { table: issue.table }));
651
- }
652
- }
653
- return notOk(issueConflict("foreignKeyConflict", `Foreign key on "${issue.table}" differs (expected: ${issue.expected}, actual: ${issue.actual})`, { table: issue.table }));
654
- case "type_missing": {
655
- if (!issue.typeName) return notOk(issueConflict("unsupportedOperation", "Type missing issue has no typeName"));
656
- const typeInstance = ctx.toContract.storage.types?.[issue.typeName];
657
- if (!typeInstance) return notOk(issueConflict("unsupportedOperation", `Type "${issue.typeName}" reported missing but not found in destination contract`));
658
- return notOk(issueConflict("unsupportedOperation", `Type "${issue.typeName}" uses codec "${typeInstance.codecId}" — only value-set types are supported`));
659
- }
660
- case "type_values_mismatch": return notOk(issueConflict("unsupportedOperation", `Type "${issue.typeName ?? "unknown"}" values differ — type alteration not yet supported`));
661
- default: return notOk(issueConflict("unsupportedOperation", `Unhandled issue kind: ${issue.kind}`));
662
- }
663
- }
664
- /**
665
- * Classifies calls into DDL sequencing buckets. The order matches the
666
- * legacy walk-schema planner's emission order so `db init` and `db update`
667
- * produce byte-identical plans for the shared shape (deps → drops → tables
668
- * → columns → alters → PKs → uniques → indexes → FKs).
669
- */
670
- function classifyCall(call) {
671
- switch (call.factoryName) {
672
- case "createExtension":
673
- case "createSchema": return "dep";
674
- case "dropTable":
675
- case "dropColumn":
676
- case "dropConstraint":
677
- case "dropCheckConstraint":
678
- case "dropIndex":
679
- case "dropDefault": return "drop";
680
- case "addCheckConstraint": return "unique";
681
- case "createTable": return "table";
682
- case "addColumn": return "column";
683
- case "alterColumnType":
684
- case "setNotNull":
685
- case "dropNotNull":
686
- case "setDefault": return "alter";
687
- case "addPrimaryKey": return "primaryKey";
688
- case "addUnique": return "unique";
689
- case "createIndex": return "index";
690
- case "addForeignKey": return "foreignKey";
691
- case "rawSql":
692
- if (call.op?.target?.details?.objectType === "type") return "dep";
693
- return "alter";
694
- default: return "alter";
695
- }
696
- }
697
- /** Stable lexical key used to order issues within the same kind bucket. */
698
- function issueKey(issue) {
699
- return `${"table" in issue && typeof issue.table === "string" ? issue.table : ""}\u0000${"column" in issue && typeof issue.column === "string" ? issue.column : ""}\u0000${"indexOrConstraint" in issue && typeof issue.indexOrConstraint === "string" ? issue.indexOrConstraint : ""}`;
700
- }
701
- const DEFAULT_POLICY = { allowedOperationClasses: [
702
- "additive",
703
- "widening",
704
- "destructive",
705
- "data"
706
- ] };
707
- function emptySchemaIR() {
708
- return { tables: {} };
709
- }
710
- function conflictKindForCall(call) {
711
- switch (call.factoryName) {
712
- case "alterColumnType": return "typeMismatch";
713
- case "setNotNull":
714
- case "dropNotNull": return "nullabilityConflict";
715
- case "addForeignKey":
716
- case "dropConstraint": return "foreignKeyConflict";
717
- case "createIndex":
718
- case "dropIndex": return "indexIncompatible";
719
- default: return "missingButNonAdditive";
720
- }
721
- }
722
- function locationForCall(call) {
723
- const anyCall = call;
724
- const location = {};
725
- if (anyCall.tableName) location.table = anyCall.tableName;
726
- if (anyCall.columnName) location.column = anyCall.columnName;
727
- if (anyCall.indexName) location.index = anyCall.indexName;
728
- if (anyCall.constraintName) location.constraint = anyCall.constraintName;
729
- if (anyCall.typeName) location.type = anyCall.typeName;
730
- return Object.keys(location).length > 0 ? location : void 0;
731
- }
732
- function conflictForDisallowedCall(call, allowed) {
733
- const summary = `Operation "${call.label}" requires class "${call.operationClass}", but policy allows only: ${allowed.join(", ")}`;
734
- const location = locationForCall(call);
735
- return {
736
- kind: conflictKindForCall(call),
737
- summary,
738
- why: "Use `migration new` to author a custom migration for this change.",
739
- ...location ? { location } : {}
740
- };
741
- }
742
- function planIssues(options) {
743
- const policyProvided = options.policy !== void 0;
744
- const policy = options.policy ?? DEFAULT_POLICY;
745
- const schema = options.schema ?? emptySchemaIR();
746
- const frameworkComponents = options.frameworkComponents ?? [];
747
- const context = {
748
- toContract: options.toContract,
749
- fromContract: options.fromContract,
750
- schemaName: options.schemaName,
751
- codecHooks: options.codecHooks,
752
- storageTypes: options.storageTypes,
753
- schema,
754
- policy,
755
- frameworkComponents
756
- };
757
- const strategies = options.strategies ?? postgresPlannerStrategies;
758
- let remaining = options.issues;
759
- const recipeCalls = [];
760
- const bucketablePatternCalls = [];
761
- for (const strategy of strategies) {
762
- const result = strategy(remaining, context);
763
- if (result.kind === "match") {
764
- remaining = result.issues;
765
- if (result.recipe) recipeCalls.push(...result.calls);
766
- else bucketablePatternCalls.push(...result.calls);
767
- }
768
- }
769
- const sorted = [...remaining].sort((a, b) => {
770
- const kindDelta = issueOrder(a) - issueOrder(b);
771
- if (kindDelta !== 0) return kindDelta;
772
- const keyA = issueKey(a);
773
- const keyB = issueKey(b);
774
- return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;
775
- });
776
- const defaultCalls = [];
777
- const conflicts = [];
778
- for (const issue of sorted) {
779
- const result = mapIssueToCall(issue, context);
780
- if (result.ok) defaultCalls.push(...result.value);
781
- else conflicts.push(result.failure);
782
- }
783
- const allowed = policy.allowedOperationClasses;
784
- let gatedDefault = defaultCalls;
785
- let gatedRecipe = recipeCalls;
786
- let gatedBucketable = bucketablePatternCalls;
787
- if (policyProvided) {
788
- const keepIfAllowed = (bucket) => (call) => {
789
- if (allowed.includes(call.operationClass)) {
790
- bucket.push(call);
791
- return;
792
- }
793
- conflicts.push(conflictForDisallowedCall(call, allowed));
794
- };
795
- const gatedDefaultBucket = [];
796
- const gatedRecipeBucket = [];
797
- const gatedBucketableBucket = [];
798
- defaultCalls.forEach(keepIfAllowed(gatedDefaultBucket));
799
- recipeCalls.forEach(keepIfAllowed(gatedRecipeBucket));
800
- bucketablePatternCalls.forEach(keepIfAllowed(gatedBucketableBucket));
801
- gatedDefault = gatedDefaultBucket;
802
- gatedRecipe = gatedRecipeBucket;
803
- gatedBucketable = gatedBucketableBucket;
804
- }
805
- if (conflicts.length > 0) return notOk(conflicts);
806
- const combinedBucketable = [...gatedDefault, ...gatedBucketable];
807
- const byCategory = (cat) => combinedBucketable.filter((c) => classifyCall(c) === cat);
808
- return ok({ calls: [
809
- ...byCategory("dep"),
810
- ...byCategory("drop"),
811
- ...byCategory("table"),
812
- ...byCategory("column"),
813
- ...gatedRecipe,
814
- ...byCategory("alter"),
815
- ...byCategory("primaryKey"),
816
- ...byCategory("unique"),
817
- ...byCategory("index"),
818
- ...byCategory("foreignKey")
819
- ] });
820
- }
821
- //#endregion
822
- export { postgresPlannerStrategies as n, planIssues as t };
823
-
824
- //# sourceMappingURL=issue-planner-DL6g3CmE.mjs.map