@prisma-next/target-postgres 0.13.0 → 0.14.0-dev.2

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 (190) hide show
  1. package/dist/{codec-ids-CTikp1if.mjs → codec-ids-BvytN2P8.mjs} +3 -3
  2. package/dist/codec-ids-BvytN2P8.mjs.map +1 -0
  3. package/dist/{codec-ids-B1vOchLE.d.mts → codec-ids-CnXu9Qy3.d.mts} +3 -3
  4. package/dist/codec-ids-CnXu9Qy3.d.mts.map +1 -0
  5. package/dist/codec-ids.d.mts +2 -2
  6. package/dist/codec-ids.mjs +2 -2
  7. package/dist/{codec-types-CnFiNML4.d.mts → codec-types-DHCkwPKE.d.mts} +3 -3
  8. package/dist/{codec-types-CnFiNML4.d.mts.map → codec-types-DHCkwPKE.d.mts.map} +1 -1
  9. package/dist/codec-types.d.mts +1 -1
  10. package/dist/{codecs-CBpEv4s5.d.mts → codecs--0A5_4Bq.d.mts} +26 -23
  11. package/dist/codecs--0A5_4Bq.d.mts.map +1 -0
  12. package/dist/codecs.d.mts +2 -2
  13. package/dist/codecs.mjs +28 -35
  14. package/dist/codecs.mjs.map +1 -1
  15. package/dist/contract-free.d.mts +163 -15
  16. package/dist/contract-free.d.mts.map +1 -1
  17. package/dist/contract-free.mjs +4 -17
  18. package/dist/contract-free.mjs.map +1 -1
  19. package/dist/control.d.mts.map +1 -1
  20. package/dist/control.mjs +21 -27
  21. package/dist/control.mjs.map +1 -1
  22. package/dist/{data-transform-D25tLeYU.mjs → data-transform-BOWpliq8.mjs} +9 -17
  23. package/dist/data-transform-BOWpliq8.mjs.map +1 -0
  24. package/dist/{data-transform-DGOqcLrf.d.mts → data-transform-DDgWdB5o.d.mts} +2 -2
  25. package/dist/data-transform-DDgWdB5o.d.mts.map +1 -0
  26. package/dist/data-transform.d.mts +1 -1
  27. package/dist/data-transform.mjs +1 -1
  28. package/dist/ddl-QDyOSeLc.mjs +251 -0
  29. package/dist/ddl-QDyOSeLc.mjs.map +1 -0
  30. package/dist/ddl.d.mts +2 -2
  31. package/dist/ddl.mjs +2 -2
  32. package/dist/descriptor-meta-CpGygXpI.mjs +140 -0
  33. package/dist/descriptor-meta-CpGygXpI.mjs.map +1 -0
  34. package/dist/{issue-planner-Br0pt1Ea.mjs → issue-planner-DL6g3CmE.mjs} +52 -366
  35. package/dist/issue-planner-DL6g3CmE.mjs.map +1 -0
  36. package/dist/issue-planner.d.mts +8 -11
  37. package/dist/issue-planner.d.mts.map +1 -1
  38. package/dist/issue-planner.mjs +1 -1
  39. package/dist/migration.d.mts +5 -92
  40. package/dist/migration.d.mts.map +1 -1
  41. package/dist/migration.mjs +4 -4
  42. package/dist/{nodes-DZk2JZG3.mjs → nodes-Bbhs2rwj.mjs} +31 -2
  43. package/dist/nodes-Bbhs2rwj.mjs.map +1 -0
  44. package/dist/{nodes-779hmCfL.d.mts → nodes-pLeLgdis.d.mts} +30 -3
  45. package/dist/nodes-pLeLgdis.d.mts.map +1 -0
  46. package/dist/{op-factory-call-D2aAUhmS.mjs → op-factory-call-D_p5vxwt.mjs} +601 -418
  47. package/dist/op-factory-call-D_p5vxwt.mjs.map +1 -0
  48. package/dist/{op-factory-call-DMA86_2D.d.mts → op-factory-call-DmQEc3XV.d.mts} +119 -72
  49. package/dist/op-factory-call-DmQEc3XV.d.mts.map +1 -0
  50. package/dist/op-factory-call.d.mts +2 -2
  51. package/dist/op-factory-call.mjs +2 -2
  52. package/dist/pack.d.mts +36 -15
  53. package/dist/pack.d.mts.map +1 -1
  54. package/dist/pack.mjs +1 -1
  55. package/dist/{planner-CAYPJObw.mjs → planner-Bs_baQax.mjs} +25 -45
  56. package/dist/planner-Bs_baQax.mjs.map +1 -0
  57. package/dist/{planner-ddl-builders-Cw2n2llW.mjs → planner-ddl-builders-B2wOwLqI.mjs} +2 -2
  58. package/dist/planner-ddl-builders-B2wOwLqI.mjs.map +1 -0
  59. package/dist/planner-ddl-builders.d.mts +4 -4
  60. package/dist/planner-ddl-builders.d.mts.map +1 -1
  61. package/dist/planner-ddl-builders.mjs +1 -1
  62. package/dist/{planner-identity-values-BIpa5p2I.mjs → planner-identity-values-CJPha2Sz.mjs} +3 -9
  63. package/dist/planner-identity-values-CJPha2Sz.mjs.map +1 -0
  64. package/dist/planner-identity-values.d.mts +1 -1
  65. package/dist/planner-identity-values.d.mts.map +1 -1
  66. package/dist/planner-identity-values.mjs +1 -1
  67. package/dist/{planner-produced-postgres-migration-NSEhWL0L.mjs → planner-produced-postgres-migration-Cji5vxUf.mjs} +6 -4
  68. package/dist/planner-produced-postgres-migration-Cji5vxUf.mjs.map +1 -0
  69. package/dist/{planner-produced-postgres-migration-B4EDvLdz.d.mts → planner-produced-postgres-migration-QqHa2C2l.d.mts} +5 -6
  70. package/dist/planner-produced-postgres-migration-QqHa2C2l.d.mts.map +1 -0
  71. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  72. package/dist/planner-produced-postgres-migration.mjs +1 -1
  73. package/dist/planner-sql-checks-jqUUGyQR.mjs +152 -0
  74. package/dist/planner-sql-checks-jqUUGyQR.mjs.map +1 -0
  75. package/dist/planner-sql-checks.d.mts +3 -49
  76. package/dist/planner-sql-checks.d.mts.map +1 -1
  77. package/dist/planner-sql-checks.mjs +2 -2
  78. package/dist/{planner-type-resolution-836DExFN.mjs → planner-type-resolution-Bt2f_q-F.mjs} +1 -6
  79. package/dist/planner-type-resolution-Bt2f_q-F.mjs.map +1 -0
  80. package/dist/planner.d.mts +4 -4
  81. package/dist/planner.d.mts.map +1 -1
  82. package/dist/planner.mjs +1 -1
  83. package/dist/{postgres-contract-serializer-DYTyXjPf.mjs → postgres-contract-serializer-k3TAcPMY.mjs} +30 -37
  84. package/dist/postgres-contract-serializer-k3TAcPMY.mjs.map +1 -0
  85. package/dist/postgres-migration-B5jKrXv3.mjs +145 -0
  86. package/dist/postgres-migration-B5jKrXv3.mjs.map +1 -0
  87. package/dist/postgres-migration-Y4YBJqkS.d.mts +181 -0
  88. package/dist/postgres-migration-Y4YBJqkS.d.mts.map +1 -0
  89. package/dist/{postgres-schema-BuxCxbvB.mjs → postgres-schema-COGZ1ark.mjs} +82 -23
  90. package/dist/postgres-schema-COGZ1ark.mjs.map +1 -0
  91. package/dist/{render-ops-BpjstrKQ.mjs → render-ops-BREh1kHe.mjs} +10 -5
  92. package/dist/render-ops-BREh1kHe.mjs.map +1 -0
  93. package/dist/render-ops.d.mts +2 -2
  94. package/dist/render-ops.d.mts.map +1 -1
  95. package/dist/render-ops.mjs +1 -1
  96. package/dist/runtime.d.mts +1 -0
  97. package/dist/runtime.d.mts.map +1 -1
  98. package/dist/runtime.mjs +2 -2
  99. package/dist/table-source-BvFo7gVs.d.mts +15 -0
  100. package/dist/table-source-BvFo7gVs.d.mts.map +1 -0
  101. package/dist/types.d.mts +34 -19
  102. package/dist/types.d.mts.map +1 -1
  103. package/dist/types.mjs +2 -3
  104. package/package.json +17 -18
  105. package/src/contract-free/checks.ts +363 -0
  106. package/src/contract-free/ddl.ts +28 -1
  107. package/src/core/authoring.ts +43 -44
  108. package/src/core/codec-helpers.ts +0 -17
  109. package/src/core/codec-ids.ts +1 -1
  110. package/src/core/codec-type-map.ts +2 -2
  111. package/src/core/codecs.ts +43 -48
  112. package/src/core/ddl/nodes.ts +59 -1
  113. package/src/core/migrations/control-policy.ts +17 -47
  114. package/src/core/migrations/issue-planner.ts +34 -70
  115. package/src/core/migrations/op-factory-call.ts +486 -215
  116. package/src/core/migrations/operations/columns.ts +175 -140
  117. package/src/core/migrations/operations/constraints.ts +79 -108
  118. package/src/core/migrations/operations/data-transform.ts +15 -18
  119. package/src/core/migrations/operations/dependencies.ts +16 -14
  120. package/src/core/migrations/operations/indexes.ts +31 -28
  121. package/src/core/migrations/operations/shared.ts +2 -2
  122. package/src/core/migrations/operations/tables.ts +13 -14
  123. package/src/core/migrations/planner-ddl-builders.ts +3 -4
  124. package/src/core/migrations/planner-identity-values.ts +4 -28
  125. package/src/core/migrations/planner-produced-postgres-migration.ts +15 -7
  126. package/src/core/migrations/planner-recipes.ts +44 -39
  127. package/src/core/migrations/planner-sql-checks.ts +3 -178
  128. package/src/core/migrations/planner-strategies.ts +76 -449
  129. package/src/core/migrations/planner-type-resolution.ts +2 -20
  130. package/src/core/migrations/planner.ts +6 -6
  131. package/src/core/migrations/postgres-migration.ts +287 -7
  132. package/src/core/migrations/render-ops.ts +26 -13
  133. package/src/core/migrations/runner.ts +26 -20
  134. package/src/core/postgres-contract-serializer.ts +37 -54
  135. package/src/core/postgres-enum-type-schema.ts +17 -0
  136. package/src/core/postgres-schema.ts +86 -46
  137. package/src/exports/codecs.ts +2 -2
  138. package/src/exports/contract-free.ts +22 -1
  139. package/src/exports/control.ts +0 -22
  140. package/src/exports/ddl.ts +4 -0
  141. package/src/exports/migration.ts +1 -29
  142. package/src/exports/op-factory-call.ts +0 -4
  143. package/src/exports/planner-sql-checks.ts +0 -7
  144. package/src/exports/types.ts +0 -1
  145. package/dist/codec-ids-B1vOchLE.d.mts.map +0 -1
  146. package/dist/codec-ids-CTikp1if.mjs.map +0 -1
  147. package/dist/codecs-CBpEv4s5.d.mts.map +0 -1
  148. package/dist/data-transform-D25tLeYU.mjs.map +0 -1
  149. package/dist/data-transform-DGOqcLrf.d.mts.map +0 -1
  150. package/dist/ddl-77SyXgFt.mjs +0 -30
  151. package/dist/ddl-77SyXgFt.mjs.map +0 -1
  152. package/dist/descriptor-meta-DKmj-IMN.mjs +0 -14
  153. package/dist/descriptor-meta-DKmj-IMN.mjs.map +0 -1
  154. package/dist/descriptor-meta-runtime-My8_s4cs.mjs +0 -130
  155. package/dist/descriptor-meta-runtime-My8_s4cs.mjs.map +0 -1
  156. package/dist/enum-planning-BCyvlFHk.mjs +0 -0
  157. package/dist/enum-planning-BCyvlFHk.mjs.map +0 -1
  158. package/dist/enum-planning.d.mts +0 -86
  159. package/dist/enum-planning.d.mts.map +0 -1
  160. package/dist/enum-planning.mjs +0 -2
  161. package/dist/issue-planner-Br0pt1Ea.mjs.map +0 -1
  162. package/dist/nodes-779hmCfL.d.mts.map +0 -1
  163. package/dist/nodes-DZk2JZG3.mjs.map +0 -1
  164. package/dist/op-factory-call-D2aAUhmS.mjs.map +0 -1
  165. package/dist/op-factory-call-DMA86_2D.d.mts.map +0 -1
  166. package/dist/planner-CAYPJObw.mjs.map +0 -1
  167. package/dist/planner-ddl-builders-Cw2n2llW.mjs.map +0 -1
  168. package/dist/planner-identity-values-BIpa5p2I.mjs.map +0 -1
  169. package/dist/planner-produced-postgres-migration-B4EDvLdz.d.mts.map +0 -1
  170. package/dist/planner-produced-postgres-migration-NSEhWL0L.mjs.map +0 -1
  171. package/dist/planner-sql-checks-DAdhnI2c.mjs +0 -272
  172. package/dist/planner-sql-checks-DAdhnI2c.mjs.map +0 -1
  173. package/dist/planner-type-resolution-836DExFN.mjs.map +0 -1
  174. package/dist/postgres-contract-serializer-DYTyXjPf.mjs.map +0 -1
  175. package/dist/postgres-enum-type-BVn63a89.d.mts +0 -72
  176. package/dist/postgres-enum-type-BVn63a89.d.mts.map +0 -1
  177. package/dist/postgres-enum-type-DPKqCBem.mjs +0 -62
  178. package/dist/postgres-enum-type-DPKqCBem.mjs.map +0 -1
  179. package/dist/postgres-migration-COore9Mz.mjs +0 -71
  180. package/dist/postgres-migration-COore9Mz.mjs.map +0 -1
  181. package/dist/postgres-migration-DZ_gLUOW.d.mts +0 -72
  182. package/dist/postgres-migration-DZ_gLUOW.d.mts.map +0 -1
  183. package/dist/postgres-schema-BuxCxbvB.mjs.map +0 -1
  184. package/dist/render-ops-BpjstrKQ.mjs.map +0 -1
  185. package/dist/shared-DarONYBZ.d.mts +0 -43
  186. package/dist/shared-DarONYBZ.d.mts.map +0 -1
  187. package/src/core/migrations/enum-planning.ts +0 -213
  188. package/src/core/migrations/operations/enums.ts +0 -114
  189. package/src/core/postgres-enum-type.ts +0 -89
  190. package/src/exports/enum-planning.ts +0 -11
@@ -11,7 +11,7 @@
11
11
  * two journeys differ only in `policy.allowedOperationClasses`:
12
12
  *
13
13
  * - When `'data'` is in the policy, data-safe strategies (NOT NULL backfill,
14
- * nullability tightening, unsafe type changes, enum shrink/rebuild) emit
14
+ * nullability tightening, unsafe type changes) emit
15
15
  * `DataTransformCall` placeholders that the user fills in.
16
16
  * - When `'data'` is excluded, those strategies short-circuit so the
17
17
  * downstream walk-schema strategies (codec-hook type ops and temp-default
@@ -29,56 +29,36 @@ import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-comp
29
29
  import type { SchemaIssue } from '@prisma-next/framework-components/control';
30
30
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
31
31
  import {
32
- isPostgresEnumStorageEntry,
33
- type PostgresEnumStorageEntry,
34
32
  type SqlStorage,
35
33
  StorageTable,
36
34
  type StorageTypeInstance,
37
35
  } from '@prisma-next/sql-contract/types';
36
+ import type { CodecRef, DdlColumn } from '@prisma-next/sql-relational-core/ast';
37
+ import { col } from '@prisma-next/sql-relational-core/contract-free';
38
38
  import type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';
39
39
  import { blindCast } from '@prisma-next/utils/casts';
40
- import { PostgresEnumType } from '../postgres-enum-type';
40
+ import { ifDefined } from '@prisma-next/utils/defined';
41
+ import type { JsonValue } from '@prisma-next/utils/json';
41
42
  import { isPostgresSchema } from '../postgres-schema';
42
- import {
43
- determineEnumDiff,
44
- readExistingEnumValues,
45
- resolveDdlSchemaForNamespaceStorage,
46
- } from './enum-planning';
47
43
  import {
48
44
  AddCheckConstraintCall,
49
45
  AddColumnCall,
50
- AddEnumValuesCall,
46
+ AddNotNullColumnDirectCall,
47
+ AddNotNullColumnWithTempDefaultCall,
51
48
  AlterColumnTypeCall,
52
- CreateEnumTypeCall,
53
49
  DataTransformCall,
54
50
  DropCheckConstraintCall,
55
- DropEnumTypeCall,
56
51
  type PostgresOpFactoryCall,
52
+ postgresDefaultToDdlColumnDefault,
57
53
  RawSqlCall,
58
- RenameTypeCall,
59
54
  SetNotNullCall,
60
55
  } from './op-factory-call';
61
- import {
62
- buildAddColumnSql,
63
- buildColumnDefaultSql,
64
- buildColumnTypeSql,
65
- } from './planner-ddl-builders';
56
+ import { buildAddColumnSql, buildColumnTypeSql } from './planner-ddl-builders';
66
57
  import { resolveIdentityValue } from './planner-identity-values';
67
- import {
68
- buildAddColumnOperationIdentity,
69
- buildAddNotNullColumnWithTemporaryDefaultOperation,
70
- } from './planner-recipes';
71
58
  import { buildSchemaLookupMap, hasForeignKey, hasUniqueConstraint } from './planner-schema-lookup';
72
- import {
73
- buildExpectedFormatType,
74
- columnExistsCheck,
75
- columnNullabilityCheck,
76
- qualifyTableName,
77
- tableIsEmptyCheck,
78
- } from './planner-sql-checks';
59
+ import { buildExpectedFormatType, qualifyTableName } from './planner-sql-checks';
79
60
  import { buildTargetDetails, type PostgresPlanTargetDetails } from './planner-target-details';
80
-
81
- const REBUILD_SUFFIX = '__prisma_next_new';
61
+ import { resolveColumnTypeMetadata } from './planner-type-resolution';
82
62
 
83
63
  /**
84
64
  * Look up a storage table by its explicit namespace coordinate. Returns
@@ -95,9 +75,9 @@ export function tableAt(
95
75
  namespaceId: string,
96
76
  tableName: string,
97
77
  ): StorageTable | undefined {
98
- // Namespace.tables is typed as Record<string, IRNode> at the interface level;
99
- // SQL family namespaces always hold StorageTable instances.
100
- return storage.namespaces[namespaceId]?.entries.table[tableName] as StorageTable | undefined;
78
+ const ns = storage.namespaces[namespaceId];
79
+ if (ns === undefined) return undefined;
80
+ return ns.entries.table?.[tableName];
101
81
  }
102
82
 
103
83
  /**
@@ -131,63 +111,6 @@ export function resolveDdlSchemaForNamespace(ctx: StrategyContext, namespaceId:
131
111
  return namespaceId;
132
112
  }
133
113
 
134
- /** Default Postgres enum landing namespace — where contract-level (`types:`)
135
- * enums are placed by the authoring builder when no explicit namespace is
136
- * given. Mirrors `POSTGRES_ENUM_NAMESPACE_ID` in the contract-ts builder. */
137
- const DEFAULT_ENUM_NAMESPACE_ID = 'public';
138
-
139
- function namespaceHasEnum(storage: SqlStorage, namespaceId: string, typeName: string): boolean {
140
- const ns = storage.namespaces[namespaceId];
141
- if (!isPostgresSchema(ns)) return false;
142
- return ns.entries.type[typeName] !== undefined;
143
- }
144
-
145
- /**
146
- * Resolves which namespace's enum a column's bare `typeRef` binds to.
147
- *
148
- * Columns carry a bare (non-namespace-qualified) `typeRef`; the enum it names
149
- * may live in a different namespace than the column's own (the authoring
150
- * builder places contract-level `types:` enums in the default `public`
151
- * namespace while a model's table may sit in the unbound namespace). The
152
- * binding rule: an enum declared in the column's *own* namespace shadows
153
- * everything; otherwise the column references the ambient enum — the sole
154
- * namespace that defines `typeName`, preferring the default `public`
155
- * namespace when several do. Returns `undefined` when no namespace defines it.
156
- */
157
- function resolveColumnEnumNamespace(
158
- storage: SqlStorage,
159
- columnNamespaceId: string,
160
- typeName: string,
161
- ): string | undefined {
162
- if (namespaceHasEnum(storage, columnNamespaceId, typeName)) return columnNamespaceId;
163
- const owners = Object.keys(storage.namespaces).filter((nsId) =>
164
- namespaceHasEnum(storage, nsId, typeName),
165
- );
166
- if (owners.length === 1) return owners[0];
167
- if (owners.includes(DEFAULT_ENUM_NAMESPACE_ID)) return DEFAULT_ENUM_NAMESPACE_ID;
168
- return owners[0];
169
- }
170
-
171
- /**
172
- * Finds a type entry by explicit namespace coordinate. Namespace types (e.g.
173
- * Postgres enums) live under `storage.namespaces[nsId].entries.type`. Returns the
174
- * entry from the named namespace only — never scans other namespaces, so two
175
- * namespaces that hold an enum with the same name resolve independently.
176
- */
177
- function locateNamespaceType(
178
- storage: SqlStorage,
179
- namespaceId: string,
180
- typeName: string,
181
- ): PostgresEnumStorageEntry | undefined {
182
- const ns = storage.namespaces[namespaceId];
183
- const raw = ns?.entries['type']?.[typeName];
184
- if (raw === undefined) return undefined;
185
- return blindCast<
186
- PostgresEnumStorageEntry,
187
- 'postgres type slot carries PostgresEnumStorageEntry at the postgres target layer'
188
- >(raw);
189
- }
190
-
191
114
  // ============================================================================
192
115
  // Strategy types
193
116
  // ============================================================================
@@ -206,7 +129,7 @@ export interface StrategyContext {
206
129
  readonly fromContract: Contract<SqlStorage> | null;
207
130
  readonly schemaName: string;
208
131
  readonly codecHooks: ReadonlyMap<string, CodecControlHooks>;
209
- readonly storageTypes: Readonly<Record<string, StorageTypeInstance | PostgresEnumStorageEntry>>;
132
+ readonly storageTypes: Readonly<Record<string, StorageTypeInstance>>;
210
133
  readonly schema: SqlSchemaIR;
211
134
  readonly policy: MigrationOperationPolicy;
212
135
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
@@ -227,12 +150,9 @@ export type CallMigrationStrategy = (
227
150
  /**
228
151
  * `true` for strategies that emit cohesive sequential recipes whose
229
152
  * calls must stay contiguous and in the returned order — e.g.
230
- * `nativeEnumPlanCallStrategy` (createEnumTypealterColumnType
231
- * dropEnumType renameType, optionally prefixed by a
232
- * `DataTransformCall` placeholder), `notNullBackfillCallStrategy`
233
- * (addColumn → dataTransform → setNotNull). Defaults to `false`,
234
- * which lets `planIssues` hoist individual calls into their DDL
235
- * sequencing bucket.
153
+ * `notNullBackfillCallStrategy` (addColumndataTransform setNotNull).
154
+ * Defaults to `false`, which lets `planIssues` hoist individual calls
155
+ * into their DDL sequencing bucket.
236
156
  */
237
157
  recipe?: boolean;
238
158
  }
@@ -244,21 +164,34 @@ function buildColumnSpec(
244
164
  column: string,
245
165
  ctx: StrategyContext,
246
166
  overrides?: { nullable?: boolean },
247
- ) {
248
- const col = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
249
- if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
167
+ ): DdlColumn {
168
+ const storageCol = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
169
+ if (!storageCol)
170
+ throw new Error(`Column "${table}"."${column}" not found in destination contract`);
250
171
  const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
251
- const mutableTypes = ctx.storageTypes as Record<
252
- string,
253
- StorageTypeInstance | PostgresEnumStorageEntry
254
- >;
255
- return {
256
- name: column,
257
- typeSql: buildColumnTypeSql(col, mutableHooks, mutableTypes),
258
- defaultSql: buildColumnDefaultSql(col.default, col),
259
- columnDefault: col.default,
260
- nullable: overrides?.nullable ?? col.nullable,
261
- };
172
+ const mutableTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
173
+ const typeSql = buildColumnTypeSql(storageCol, mutableHooks, mutableTypes);
174
+ const ddlDefault = postgresDefaultToDdlColumnDefault(storageCol.default);
175
+ const resolved = resolveColumnTypeMetadata(storageCol, mutableTypes);
176
+ const typeParams =
177
+ resolved.typeParams === undefined
178
+ ? undefined
179
+ : blindCast<
180
+ JsonValue,
181
+ 'resolved.typeParams is JsonValue-shaped storage metadata; the narrowed value lands in CodecRef.typeParams which is JsonValue'
182
+ >(resolved.typeParams);
183
+ const codecRef: CodecRef | undefined = resolved.codecId
184
+ ? {
185
+ codecId: resolved.codecId,
186
+ ...ifDefined('typeParams', typeParams),
187
+ }
188
+ : undefined;
189
+ const nullable = overrides?.nullable ?? storageCol.nullable;
190
+ return col(column, typeSql, {
191
+ ...(!nullable ? { notNull: true } : {}),
192
+ ...ifDefined('default', ddlDefault),
193
+ ...ifDefined('codecRef', codecRef),
194
+ });
262
195
  }
263
196
 
264
197
  function buildAlterTypeOptions(
@@ -271,10 +204,7 @@ function buildAlterTypeOptions(
271
204
  const col = tableAt(ctx.toContract.storage, namespaceId, table)?.columns[column];
272
205
  if (!col) throw new Error(`Column "${table}"."${column}" not found in destination contract`);
273
206
  const mutableHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
274
- const mutableTypes = ctx.storageTypes as Record<
275
- string,
276
- StorageTypeInstance | PostgresEnumStorageEntry
277
- >;
207
+ const mutableTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
278
208
  const qualifiedTargetType = buildColumnTypeSql(col, mutableHooks, mutableTypes, false);
279
209
  const formatTypeExpected = buildExpectedFormatType(col, mutableHooks, mutableTypes);
280
210
  return {
@@ -417,249 +347,6 @@ export const nullableTighteningCallStrategy: CallMigrationStrategy = (issues, ct
417
347
  };
418
348
  };
419
349
 
420
- function enumRebuildCallRecipe(
421
- namespaceId: string,
422
- typeName: string,
423
- ctx: StrategyContext,
424
- ): readonly PostgresOpFactoryCall[] {
425
- const toType = locateNamespaceType(ctx.toContract.storage, namespaceId, typeName);
426
- if (!toType) return [];
427
- const isEnum = isPostgresEnumStorageEntry(toType);
428
- const nativeType = toType.nativeType;
429
- const desiredValues: readonly string[] = isEnum
430
- ? toType.values
431
- : (((toType as StorageTypeInstance).typeParams['values'] ?? []) as readonly string[]);
432
- const tempName = `${nativeType}${REBUILD_SUFFIX}`;
433
- // Type DDL targets the enum's real schema — the unbound coordinate resolves
434
- // to the introspected `current_schema()`, never the `__unbound__` sentinel.
435
- const ddlSchema = resolveDdlSchemaForNamespaceStorage(
436
- ctx.toContract.storage,
437
- namespaceId,
438
- ctx.schema,
439
- );
440
-
441
- // Migrate every column whose `typeRef` binds to *this* enum. The column's
442
- // bare `typeRef` resolves to an enum namespace (own-namespace shadows;
443
- // otherwise the ambient/default `public` enum), so a column in the unbound
444
- // namespace correctly binds to a `public`-namespace enum, while two
445
- // same-named enums in distinct namespaces keep their columns disjoint.
446
- const columnRefs: { namespaceId: string; table: string; column: string }[] = [];
447
- for (const [nsId, ns] of Object.entries(ctx.toContract.storage.namespaces)) {
448
- for (const [tableName, tableNode] of Object.entries(ns.entries.table)) {
449
- const table = tableNode as StorageTable;
450
- for (const [columnName, column] of Object.entries(table.columns)) {
451
- if (
452
- column.typeRef === typeName &&
453
- resolveColumnEnumNamespace(ctx.toContract.storage, nsId, typeName) === namespaceId
454
- ) {
455
- columnRefs.push({ namespaceId: nsId, table: tableName, column: columnName });
456
- }
457
- }
458
- }
459
- }
460
-
461
- return [
462
- new CreateEnumTypeCall(ddlSchema, tempName, desiredValues),
463
- ...columnRefs.map((ref) => {
464
- const using = `${ref.column}::text::${tempName}`;
465
- return new AlterColumnTypeCall(
466
- resolveDdlSchemaForNamespace(ctx, ref.namespaceId),
467
- ref.table,
468
- ref.column,
469
- {
470
- qualifiedTargetType: tempName,
471
- formatTypeExpected: tempName,
472
- rawTargetTypeForLabel: tempName,
473
- using,
474
- },
475
- );
476
- }),
477
- new DropEnumTypeCall(ddlSchema, nativeType),
478
- new RenameTypeCall(ddlSchema, tempName, nativeType),
479
- ];
480
- }
481
-
482
- // ============================================================================
483
- // Native enum planner strategy
484
- // ============================================================================
485
-
486
- /**
487
- * Single planner strategy for `PostgresEnumType` instances. Walks
488
- * `toContract.storage.types` directly (no codec-hook dispatch) and
489
- * resolves existing values via `readExistingEnumValues`, the same
490
- * Postgres bridging adapter the verifier uses.
491
- *
492
- * Per-enum dispatch:
493
- *
494
- * - No existing type → `CreateEnumTypeCall` with the contract's desired
495
- * values.
496
- * - Diff is `unchanged` → no calls emitted (consumes the matching
497
- * `enum_values_changed` issue if present).
498
- * - Diff is `add_values` → `AddEnumValuesCall` with the new labels.
499
- * - Diff is `rebuild` → the create-temp / migrate-columns /
500
- * drop-original / rename rebuild recipe. When
501
- * `policy.allowedOperationClasses` includes `'data'` and the rebuild
502
- * removes labels (`removedValues.length > 0`), prepend a
503
- * `DataTransformCall` placeholder so the user can author the value
504
- * remap before the destructive recipe runs. Without `'data'` in the
505
- * policy (`db update` / `db init`), the rebuild's PG `USING ::text`
506
- * cast surfaces any value-removal data loss as a runtime error rather
507
- * than silent loss.
508
- *
509
- * Returns `recipe: true` only when a rebuild recipe was emitted (its
510
- * `createEnumType(temp) → alterColumnType → dropEnumType(orig) →
511
- * renameType` sequence mixes `dep`-class and `alter`-class calls that
512
- * would mis-order if the planner hoisted them into its DDL sequencing
513
- * buckets). For the create-only and add-values paths the strategy
514
- * returns `recipe: false` so the planner hoists `CreateEnumTypeCall`
515
- * into the `dep` bucket — i.e. `CREATE TYPE` runs before any
516
- * `CreateTableCall` that references the new enum.
517
- */
518
- /**
519
- * Separator character for compound enum map keys (`namespaceId\u0000typeName`).
520
- * NUL (`\u0000`) is invalid in both Postgres identifiers and TypeScript symbol
521
- * names so it cannot appear in either component — unambiguous separator.
522
- */
523
- const COMPOUND_KEY_SEP = '\u0000';
524
-
525
- /** Builds the compound map key for a namespace-qualified enum entry. */
526
- function enumCompoundKey(namespaceId: string, typeName: string): string {
527
- return `${namespaceId}${COMPOUND_KEY_SEP}${typeName}`;
528
- }
529
-
530
- export const nativeEnumPlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
531
- const enumTypes = collectPostgresEnumTypes(ctx.toContract.storage);
532
- if (enumTypes.size === 0) return { kind: 'no_match' };
533
-
534
- const dataAllowed = ctx.policy.allowedOperationClasses.includes('data');
535
-
536
- const calls: PostgresOpFactoryCall[] = [];
537
- const handledKeys = new Set<string>();
538
- const introducedKeys = new Set<string>();
539
- const rebuiltKeys = new Set<string>();
540
- let emittedRebuildRecipe = false;
541
-
542
- for (const [key, enumType] of enumTypes) {
543
- const sepIdx = key.indexOf(COMPOUND_KEY_SEP);
544
- const enumNamespaceId = key.slice(0, sepIdx);
545
- const typeName = key.slice(sepIdx + 1);
546
-
547
- const desired = enumType.values;
548
- // The enum's live schema: for the unbound coordinate this resolves to the
549
- // introspected `current_schema()` (e.g. `public`), never the `__unbound__`
550
- // DDL-emit sentinel — so both the existing-values lookup key and the
551
- // emitted `CREATE TYPE` / `ALTER TYPE` target the real schema the type
552
- // lives in. Named namespaces resolve to their own DDL schema.
553
- const ddlSchema = resolveDdlSchemaForNamespaceStorage(
554
- ctx.toContract.storage,
555
- enumNamespaceId,
556
- ctx.schema,
557
- );
558
- const existing = readExistingEnumValues(ctx.schema, ddlSchema, enumType.nativeType);
559
- if (!existing) {
560
- calls.push(new CreateEnumTypeCall(ddlSchema, typeName, desired, enumType.nativeType));
561
- handledKeys.add(key);
562
- introducedKeys.add(key);
563
- continue;
564
- }
565
- const diff = determineEnumDiff(existing, desired);
566
- if (diff.kind === 'unchanged') {
567
- handledKeys.add(key);
568
- continue;
569
- }
570
- if (diff.kind === 'add_values') {
571
- calls.push(new AddEnumValuesCall(ddlSchema, typeName, enumType.nativeType, diff.values));
572
- handledKeys.add(key);
573
- continue;
574
- }
575
- if (dataAllowed && diff.removedValues.length > 0) {
576
- calls.push(
577
- new DataTransformCall(
578
- `migrate-${typeName}-values`,
579
- `migrate-${typeName}-values:check`,
580
- `migrate-${typeName}-values:run`,
581
- ),
582
- );
583
- }
584
- calls.push(...enumRebuildCallRecipe(enumNamespaceId, typeName, ctx));
585
- emittedRebuildRecipe = true;
586
- handledKeys.add(key);
587
- rebuiltKeys.add(key);
588
- }
589
-
590
- // The strategy emits a single `recipe` flag for the entire pass,
591
- // which routes every emitted call to either the contiguous recipe
592
- // slot (rebuild path) or the `dep` bucket (introduce / add-values
593
- // path). A plan that needs both shapes simultaneously cannot be
594
- // expressed today — the introduced `CreateEnumTypeCall` would land
595
- // in the recipe slot and any `CreateTableCall` referencing the new
596
- // enum would fail at runtime with a confusing `type "X" does not
597
- // exist` error. Surface the unrepresentable case here as a
598
- // planner-time error so the failure mode is loud, not silent.
599
- if (introducedKeys.size > 0 && rebuiltKeys.size > 0) {
600
- const introducedDisplay = [...introducedKeys]
601
- .sort()
602
- .map((k) => k.replace(COMPOUND_KEY_SEP, '.'))
603
- .join(', ');
604
- const rebuiltDisplay = [...rebuiltKeys]
605
- .sort()
606
- .map((k) => k.replace(COMPOUND_KEY_SEP, '.'))
607
- .join(', ');
608
- throw new Error(
609
- `nativeEnumPlanCallStrategy: cannot emit both a brand-new enum and a rebuild on a different enum in the same plan; the single recipe flag cannot route them to different buckets. Introduced: [${introducedDisplay}]; rebuilt: [${rebuiltDisplay}]. Split the strategy or grow the \`match\` return type before this case lands.`,
610
- );
611
- }
612
-
613
- const remaining = issues.filter(
614
- (issue) =>
615
- !(
616
- (issue.kind === 'type_missing' || issue.kind === 'enum_values_changed') &&
617
- issue.typeName &&
618
- handledKeys.has(enumCompoundKey(resolveNamespaceIdForIssue(issue), issue.typeName))
619
- ),
620
- );
621
-
622
- if (calls.length === 0 && remaining.length === issues.length) {
623
- return { kind: 'no_match' };
624
- }
625
- // `recipe: true` is required for the rebuild path — its
626
- // `createEnumType(temp) → alterColumnType → dropEnumType(orig) →
627
- // renameType` mixes `dep`-class and `alter`-class calls that would
628
- // mis-order if the planner hoisted them into its DDL sequencing
629
- // buckets. For the type_missing / add_values paths we want the
630
- // opposite: hoisted into the `dep` bucket so a brand-new
631
- // `CreateEnumTypeCall` runs *before* the `CreateTableCall` that
632
- // references it. The two cases never co-occur in the same plan
633
- // (introducing a new enum type and rebuilding an existing one in
634
- // one shot would require both buckets — a shape today's interface
635
- // does not surface; if that combination ever needs to land we'd
636
- // split this strategy or grow the `match` return type).
637
- return { kind: 'match', issues: remaining, calls, recipe: emittedRebuildRecipe };
638
- };
639
-
640
- /**
641
- * Collects every `PostgresEnumType` instance across all declared namespaces,
642
- * returning a compound-keyed map (`${namespaceId}\u0000${typeName}`). Two
643
- * namespaces that declare an enum with the same name produce two distinct
644
- * entries — no name collision, no last-write-wins.
645
- *
646
- * Entries within each namespace are sorted by name for deterministic ordering.
647
- */
648
- function collectPostgresEnumTypes(storage: SqlStorage): ReadonlyMap<string, PostgresEnumType> {
649
- const result = new Map<string, PostgresEnumType>();
650
- for (const [nsId, ns] of Object.entries(storage.namespaces)) {
651
- if (!isPostgresSchema(ns)) continue;
652
- for (const [name, instance] of Object.entries(ns.entries.type).sort(([a], [b]) =>
653
- a.localeCompare(b),
654
- )) {
655
- if (instance instanceof PostgresEnumType) {
656
- result.set(enumCompoundKey(nsId, name), instance);
657
- }
658
- }
659
- }
660
- return result;
661
- }
662
-
663
350
  /**
664
351
  * Collects every check constraint from a table in the contract storage.
665
352
  * Returns an empty array when the table has no checks or the table is absent.
@@ -670,7 +357,7 @@ function collectContractChecks(
670
357
  tableName: string,
671
358
  ): ReadonlyArray<{ name: string; column: string; permittedValues: readonly string[] }> {
672
359
  const ns = storage.namespaces[namespaceId];
673
- const tableRaw = ns?.entries.table[tableName];
360
+ const tableRaw = ns !== undefined ? ns.entries.table?.[tableName] : undefined;
674
361
  if (!(tableRaw instanceof StorageTable)) return [];
675
362
  const checks = tableRaw.checks;
676
363
  if (!checks || checks.length === 0) return [];
@@ -708,15 +395,13 @@ function checkValueSetsEqual(a: readonly string[], b: readonly string[]): boolea
708
395
  * be altered in place).
709
396
  *
710
397
  * Consumes `check_missing`, `check_removed`, and `check_mismatch` issues.
711
- * Does not touch the native enum path (`nativeEnumPlanCallStrategy` is
712
- * unchanged).
713
398
  */
714
399
  export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
715
400
  const calls: PostgresOpFactoryCall[] = [];
716
401
  const handledIssueKeys = new Set<string>();
717
402
 
718
403
  for (const [namespaceId, ns] of Object.entries(ctx.toContract.storage.namespaces)) {
719
- for (const tableName of Object.keys(ns.entries.table)) {
404
+ for (const tableName of Object.keys(ns.entries.table ?? {})) {
720
405
  const contractChecks = collectContractChecks(ctx.toContract.storage, namespaceId, tableName);
721
406
  if (contractChecks.length === 0) continue;
722
407
 
@@ -788,10 +473,9 @@ export const checkConstraintPlanCallStrategy: CallMigrationStrategy = (issues, c
788
473
  };
789
474
 
790
475
  /**
791
- * Dispatches non-enum codec-typed storage types through their codec's
476
+ * Dispatches codec-typed storage types through their codec's
792
477
  * `planTypeOperations` hook (the authoritative source for codec-driven DDL
793
- * such as custom type creation). Enum dispatch lives in
794
- * `nativeEnumPlanCallStrategy` and no longer relies on codec hooks.
478
+ * such as custom type creation).
795
479
  */
796
480
  export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx) => {
797
481
  const storageTypes = ctx.toContract.storage.types ?? {};
@@ -803,10 +487,6 @@ export const storageTypePlanCallStrategy: CallMigrationStrategy = (issues, ctx)
803
487
  for (const [typeName, typeInstance] of Object.entries(storageTypes).sort(([a], [b]) =>
804
488
  a.localeCompare(b),
805
489
  )) {
806
- // Enums walk natively in `nativeEnumPlanCallStrategy`; codec-hook
807
- // dispatch here is reserved for genuinely codec-typed entries
808
- // (decimal, varchar, pgvector, …).
809
- if (isPostgresEnumStorageEntry(typeInstance)) continue;
810
490
  const codecInstance = typeInstance as StorageTypeInstance;
811
491
  const hook = ctx.codecHooks.get(codecInstance.codecId);
812
492
  if (!hook?.planTypeOperations) continue;
@@ -874,10 +554,7 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
874
554
  const schemaLookups = buildSchemaLookupMap(ctx.schema);
875
555
 
876
556
  const mutableCodecHooks = ctx.codecHooks as Map<string, CodecControlHooks>;
877
- const mutableStorageTypes = ctx.storageTypes as Record<
878
- string,
879
- StorageTypeInstance | PostgresEnumType
880
- >;
557
+ const mutableStorageTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
881
558
 
882
559
  for (const issue of issues) {
883
560
  if (issue.kind !== 'missing_column' || !issue.table || !issue.column) continue;
@@ -910,74 +587,34 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
910
587
 
911
588
  if (canUseSharedTempDefault && temporaryDefault !== null) {
912
589
  calls.push(
913
- new RawSqlCall(
914
- buildAddNotNullColumnWithTemporaryDefaultOperation({
915
- schema: schemaForTable,
916
- tableName: issue.table,
917
- columnName: issue.column,
918
- column,
919
- codecHooks: mutableCodecHooks,
920
- storageTypes: mutableStorageTypes,
921
- temporaryDefault,
922
- }),
923
- ),
590
+ new AddNotNullColumnWithTempDefaultCall({
591
+ schemaName: schemaForTable,
592
+ tableName: issue.table,
593
+ columnName: issue.column,
594
+ column,
595
+ codecHooks: mutableCodecHooks,
596
+ storageTypes: mutableStorageTypes,
597
+ temporaryDefault,
598
+ }),
924
599
  );
925
600
  continue;
926
601
  }
927
602
 
928
603
  const qualified = qualifyTableName(schemaForTable, issue.table);
929
604
  calls.push(
930
- new RawSqlCall({
931
- ...buildAddColumnOperationIdentity(schemaForTable, issue.table, issue.column),
932
- operationClass: 'additive',
933
- precheck: [
934
- {
935
- description: `ensure column "${issue.column}" is missing`,
936
- sql: columnExistsCheck({
937
- schema: schemaForTable,
938
- table: issue.table,
939
- column: issue.column,
940
- exists: false,
941
- }),
942
- },
943
- {
944
- description: `ensure table "${issue.table}" is empty before adding NOT NULL column without default`,
945
- sql: tableIsEmptyCheck(qualified),
946
- },
947
- ],
948
- execute: [
949
- {
950
- description: `add column "${issue.column}"`,
951
- sql: buildAddColumnSql(
952
- qualified,
953
- issue.column,
954
- column,
955
- mutableCodecHooks,
956
- undefined,
957
- mutableStorageTypes,
958
- ),
959
- },
960
- ],
961
- postcheck: [
962
- {
963
- description: `verify column "${issue.column}" exists`,
964
- sql: columnExistsCheck({
965
- schema: schemaForTable,
966
- table: issue.table,
967
- column: issue.column,
968
- }),
969
- },
970
- {
971
- description: `verify column "${issue.column}" is NOT NULL`,
972
- sql: columnNullabilityCheck({
973
- schema: schemaForTable,
974
- table: issue.table,
975
- column: issue.column,
976
- nullable: false,
977
- }),
978
- },
979
- ],
980
- }),
605
+ new AddNotNullColumnDirectCall(
606
+ schemaForTable,
607
+ issue.table,
608
+ issue.column,
609
+ buildAddColumnSql(
610
+ qualified,
611
+ issue.column,
612
+ column,
613
+ mutableCodecHooks,
614
+ undefined,
615
+ mutableStorageTypes,
616
+ ),
617
+ ),
981
618
  );
982
619
  }
983
620
 
@@ -1033,8 +670,7 @@ function canUseSharedTemporaryDefaultStrategy(options: {
1033
670
  *
1034
671
  * - When `'data'` is allowed (`migration plan`), the data-safe strategies
1035
672
  * (`notNullBackfillCallStrategy`, `typeChangeCallStrategy`,
1036
- * `nullableTighteningCallStrategy`) and the enum walk
1037
- * (`nativeEnumPlanCallStrategy`) consume their matching issues and emit
673
+ * `nullableTighteningCallStrategy`) consume their matching issues and emit
1038
674
  * `DataTransformCall` placeholders or recipe ops.
1039
675
  *
1040
676
  * - When `'data'` is not allowed (`db update` / `db init`), the
@@ -1042,23 +678,14 @@ function canUseSharedTemporaryDefaultStrategy(options: {
1042
678
  * the issue for the downstream walk-schema strategies
1043
679
  * (`storageTypePlanCallStrategy`, `notNullAddColumnCallStrategy`) or the
1044
680
  * `mapIssueToCall` default to handle with direct DDL.
1045
- * `nativeEnumPlanCallStrategy` runs in both modes; under `db update` /
1046
- * `db init` it emits the rebuild recipe without the data-transform
1047
- * placeholder so value-removal data loss surfaces as a runtime cast
1048
- * error rather than silent loss.
1049
681
  *
1050
- * Enum dispatch is unified into a single strategy: the
1051
- * `nativeEnumPlanCallStrategy` decides per-emission whether to emit a
1052
- * rebuild recipe (`recipe: true`, contiguous slot) or hoist the call
1053
- * into the `dep` bucket (`recipe: false`, so a brand-new
1054
- * `CreateEnumTypeCall` runs before any `CreateTableCall` referencing
1055
- * it). Codec-typed entries continue through `storageTypePlanCallStrategy`.
682
+ * Codec-typed storage type entries are dispatched through
683
+ * `storageTypePlanCallStrategy`.
1056
684
  */
1057
685
  export const postgresPlannerStrategies: readonly CallMigrationStrategy[] = [
1058
686
  notNullBackfillCallStrategy,
1059
687
  typeChangeCallStrategy,
1060
688
  nullableTighteningCallStrategy,
1061
- nativeEnumPlanCallStrategy,
1062
689
  checkConstraintPlanCallStrategy,
1063
690
  storageTypePlanCallStrategy,
1064
691
  notNullAddColumnCallStrategy,