@prisma-next/target-postgres 0.12.0 → 0.13.0-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/dist/{codec-ids-D9fJ4HP5.d.mts → codec-ids-B1vOchLE.d.mts} +3 -2
  2. package/dist/codec-ids-B1vOchLE.d.mts.map +1 -0
  3. package/dist/{codec-ids-C5qzBqus.mjs → codec-ids-CTikp1if.mjs} +3 -2
  4. package/dist/codec-ids-CTikp1if.mjs.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-CRlHq7Cz.d.mts → codec-types-CnFiNML4.d.mts} +8 -9
  8. package/dist/codec-types-CnFiNML4.d.mts.map +1 -0
  9. package/dist/codec-types.d.mts +2 -2
  10. package/dist/{codecs-Dud5KDNk.d.mts → codecs-CBpEv4s5.d.mts} +33 -35
  11. package/dist/codecs-CBpEv4s5.d.mts.map +1 -0
  12. package/dist/codecs.d.mts +1 -1
  13. package/dist/codecs.mjs +37 -2
  14. package/dist/codecs.mjs.map +1 -1
  15. package/dist/contract-free.d.mts +80 -0
  16. package/dist/contract-free.d.mts.map +1 -0
  17. package/dist/contract-free.mjs +117 -0
  18. package/dist/contract-free.mjs.map +1 -0
  19. package/dist/control.d.mts +1 -1
  20. package/dist/control.d.mts.map +1 -1
  21. package/dist/control.mjs +67 -41
  22. package/dist/control.mjs.map +1 -1
  23. package/dist/{data-transform-CdtGUWp2.mjs → data-transform-D25tLeYU.mjs} +1 -1
  24. package/dist/{data-transform-CdtGUWp2.mjs.map → data-transform-D25tLeYU.mjs.map} +1 -1
  25. package/dist/{data-transform-bmOKkygi.d.mts → data-transform-DGOqcLrf.d.mts} +2 -2
  26. package/dist/{data-transform-bmOKkygi.d.mts.map → data-transform-DGOqcLrf.d.mts.map} +1 -1
  27. package/dist/data-transform.d.mts +1 -1
  28. package/dist/data-transform.mjs +1 -1
  29. package/dist/ddl-77SyXgFt.mjs +30 -0
  30. package/dist/ddl-77SyXgFt.mjs.map +1 -0
  31. package/dist/ddl.d.mts +2 -0
  32. package/dist/ddl.mjs +2 -0
  33. package/dist/{default-normalizer-CRscvhS5.mjs → default-normalizer-DyyCHQWs.mjs} +1 -1
  34. package/dist/{default-normalizer-CRscvhS5.mjs.map → default-normalizer-DyyCHQWs.mjs.map} +1 -1
  35. package/dist/default-normalizer.mjs +1 -1
  36. package/dist/descriptor-meta-DKmj-IMN.mjs +14 -0
  37. package/dist/descriptor-meta-DKmj-IMN.mjs.map +1 -0
  38. package/dist/{descriptor-meta-DLA2xV6B.mjs → descriptor-meta-runtime-My8_s4cs.mjs} +82 -78
  39. package/dist/descriptor-meta-runtime-My8_s4cs.mjs.map +1 -0
  40. package/dist/{enum-planning-Dz0Ye3Lb.mjs → enum-planning-DTMrPLkN.mjs} +0 -0
  41. package/dist/enum-planning-DTMrPLkN.mjs.map +1 -0
  42. package/dist/enum-planning.d.mts +14 -8
  43. package/dist/enum-planning.d.mts.map +1 -1
  44. package/dist/enum-planning.mjs +2 -2
  45. package/dist/{errors--zafB5_n.mjs → errors-CUk87ByX.mjs} +1 -1
  46. package/dist/{errors--zafB5_n.mjs.map → errors-CUk87ByX.mjs.map} +1 -1
  47. package/dist/errors.d.mts.map +1 -1
  48. package/dist/errors.mjs +1 -1
  49. package/dist/{issue-planner-ByQhUzS4.mjs → issue-planner-B0A7RFN2.mjs} +130 -28
  50. package/dist/issue-planner-B0A7RFN2.mjs.map +1 -0
  51. package/dist/issue-planner.d.mts +1 -1
  52. package/dist/issue-planner.d.mts.map +1 -1
  53. package/dist/issue-planner.mjs +1 -1
  54. package/dist/migration.d.mts +7 -8
  55. package/dist/migration.d.mts.map +1 -1
  56. package/dist/migration.mjs +5 -4
  57. package/dist/migration.mjs.map +1 -1
  58. package/dist/{native-type-normalizer-ClNPq__-.mjs → native-type-normalizer-Bc9XJzWC.mjs} +1 -1
  59. package/dist/{native-type-normalizer-ClNPq__-.mjs.map → native-type-normalizer-Bc9XJzWC.mjs.map} +1 -1
  60. package/dist/native-type-normalizer.mjs +1 -1
  61. package/dist/nodes-779hmCfL.d.mts +40 -0
  62. package/dist/nodes-779hmCfL.d.mts.map +1 -0
  63. package/dist/nodes-DZk2JZG3.mjs +47 -0
  64. package/dist/nodes-DZk2JZG3.mjs.map +1 -0
  65. package/dist/op-factory-call-Clp7Zr1z.mjs +1307 -0
  66. package/dist/op-factory-call-Clp7Zr1z.mjs.map +1 -0
  67. package/dist/{op-factory-call-Drccm_JD.d.mts → op-factory-call-DMA86_2D.d.mts} +39 -14
  68. package/dist/op-factory-call-DMA86_2D.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 +13 -12
  72. package/dist/pack.d.mts.map +1 -1
  73. package/dist/pack.mjs +1 -1
  74. package/dist/planner-DID7RZCQ.mjs +344 -0
  75. package/dist/planner-DID7RZCQ.mjs.map +1 -0
  76. package/dist/{planner-ddl-builders-BxRCSn_b.mjs → planner-ddl-builders-Cw2n2llW.mjs} +7 -30
  77. package/dist/planner-ddl-builders-Cw2n2llW.mjs.map +1 -0
  78. package/dist/planner-ddl-builders.d.mts +6 -7
  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-ojX-6cPV.mjs → planner-identity-values-BIpa5p2I.mjs} +1 -1
  82. package/dist/{planner-identity-values-ojX-6cPV.mjs.map → planner-identity-values-BIpa5p2I.mjs.map} +1 -1
  83. package/dist/planner-identity-values.mjs +1 -1
  84. package/dist/{planner-produced-postgres-migration-p-VKkCia.d.mts → planner-produced-postgres-migration-B4EDvLdz.d.mts} +5 -4
  85. package/dist/planner-produced-postgres-migration-B4EDvLdz.d.mts.map +1 -0
  86. package/dist/{planner-produced-postgres-migration-N1yqYg20.mjs → planner-produced-postgres-migration-B8gZBPOR.mjs} +8 -6
  87. package/dist/planner-produced-postgres-migration-B8gZBPOR.mjs.map +1 -0
  88. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  89. package/dist/planner-produced-postgres-migration.mjs +1 -1
  90. package/dist/{planner-schema-lookup-BGyukuzG.mjs → planner-schema-lookup-CiVaAQP-.mjs} +1 -1
  91. package/dist/{planner-schema-lookup-BGyukuzG.mjs.map → planner-schema-lookup-CiVaAQP-.mjs.map} +1 -1
  92. package/dist/planner-schema-lookup.mjs +1 -1
  93. package/dist/{planner-sql-checks-D3H-xOO1.mjs → planner-sql-checks-DRD5E8A1.mjs} +41 -30
  94. package/dist/planner-sql-checks-DRD5E8A1.mjs.map +1 -0
  95. package/dist/planner-sql-checks.d.mts.map +1 -1
  96. package/dist/planner-sql-checks.mjs +1 -1
  97. package/dist/{planner-target-details-CIj61DUj.d.mts → planner-target-details-CIY6tLeo.d.mts} +2 -2
  98. package/dist/planner-target-details-CIY6tLeo.d.mts.map +1 -0
  99. package/dist/planner-target-details.d.mts +2 -2
  100. package/dist/planner-type-resolution-836DExFN.mjs +20 -0
  101. package/dist/planner-type-resolution-836DExFN.mjs.map +1 -0
  102. package/dist/planner.d.mts +7 -3
  103. package/dist/planner.d.mts.map +1 -1
  104. package/dist/planner.mjs +1 -1
  105. package/dist/{postgres-contract-serializer-YJvjKrmo.mjs → postgres-contract-serializer-DCg7YaP3.mjs} +40 -24
  106. package/dist/postgres-contract-serializer-DCg7YaP3.mjs.map +1 -0
  107. package/dist/{postgres-enum-type-CNhPTDhy.d.mts → postgres-enum-type-BVn63a89.d.mts} +4 -1
  108. package/dist/postgres-enum-type-BVn63a89.d.mts.map +1 -0
  109. package/dist/{postgres-enum-type-DS-KLVRH.mjs → postgres-enum-type-DPKqCBem.mjs} +2 -1
  110. package/dist/postgres-enum-type-DPKqCBem.mjs.map +1 -0
  111. package/dist/{postgres-migration-uADmx0dW.mjs → postgres-migration-BCQEjFHK.mjs} +23 -3
  112. package/dist/postgres-migration-BCQEjFHK.mjs.map +1 -0
  113. package/dist/{postgres-migration-Fd4fQkBw.d.mts → postgres-migration-DZ_gLUOW.d.mts} +25 -3
  114. package/dist/postgres-migration-DZ_gLUOW.d.mts.map +1 -0
  115. package/dist/{postgres-schema-Bm7vjlOv.mjs → postgres-schema-BVTA2QH7.mjs} +41 -15
  116. package/dist/postgres-schema-BVTA2QH7.mjs.map +1 -0
  117. package/dist/{render-ops-BC2PtCkj.mjs → render-ops-BpjstrKQ.mjs} +4 -3
  118. package/dist/{render-ops-BC2PtCkj.mjs.map → render-ops-BpjstrKQ.mjs.map} +1 -1
  119. package/dist/render-ops.d.mts +3 -2
  120. package/dist/render-ops.d.mts.map +1 -1
  121. package/dist/render-ops.mjs +1 -1
  122. package/dist/{render-typescript-CPk7hhWH.mjs → render-typescript-KMgosran.mjs} +5 -2
  123. package/dist/render-typescript-KMgosran.mjs.map +1 -0
  124. package/dist/render-typescript.mjs +1 -1
  125. package/dist/runtime.d.mts.map +1 -1
  126. package/dist/runtime.mjs +3 -3
  127. package/dist/runtime.mjs.map +1 -1
  128. package/dist/{shared-ByhSooBS.d.mts → shared-DarONYBZ.d.mts} +5 -5
  129. package/dist/{shared-ByhSooBS.d.mts.map → shared-DarONYBZ.d.mts.map} +1 -1
  130. package/dist/{sql-utils-B_ruBD-M.mjs → sql-utils-DcfMz4MQ.mjs} +1 -1
  131. package/dist/{sql-utils-B_ruBD-M.mjs.map → sql-utils-DcfMz4MQ.mjs.map} +1 -1
  132. package/dist/sql-utils.mjs +1 -1
  133. package/dist/{types-D-XIpzHA.d.mts → types-BDKkx8MA.d.mts} +1 -1
  134. package/dist/types-BDKkx8MA.d.mts.map +1 -0
  135. package/dist/types.d.mts +18 -11
  136. package/dist/types.d.mts.map +1 -1
  137. package/dist/types.mjs +2 -2
  138. package/package.json +21 -20
  139. package/src/contract-free/columns.ts +49 -0
  140. package/src/contract-free/control-bootstrap.ts +55 -0
  141. package/src/contract-free/ddl.ts +37 -0
  142. package/src/core/ast/table-source.ts +23 -0
  143. package/src/core/authoring.ts +1 -1
  144. package/src/core/codec-ids.ts +1 -0
  145. package/src/core/codecs.ts +44 -0
  146. package/src/core/ddl/nodes.ts +72 -0
  147. package/src/core/descriptor-meta-runtime.ts +28 -0
  148. package/src/core/descriptor-meta.ts +3 -6
  149. package/src/core/migrations/control-policy.ts +234 -0
  150. package/src/core/migrations/enum-planning.ts +33 -29
  151. package/src/core/migrations/issue-planner.ts +81 -13
  152. package/src/core/migrations/op-factory-call.ts +289 -46
  153. package/src/core/migrations/operations/constraints.ts +79 -10
  154. package/src/core/migrations/operations/dependencies.ts +0 -17
  155. package/src/core/migrations/operations/shared.ts +3 -3
  156. package/src/core/migrations/operations/tables.ts +1 -39
  157. package/src/core/migrations/planner-ddl-builders.ts +7 -48
  158. package/src/core/migrations/planner-produced-postgres-migration.ts +11 -6
  159. package/src/core/migrations/planner-sql-checks.ts +9 -9
  160. package/src/core/migrations/planner-strategies.ts +149 -11
  161. package/src/core/migrations/planner-target-details.ts +2 -1
  162. package/src/core/migrations/planner.ts +66 -8
  163. package/src/core/migrations/postgres-migration.ts +41 -0
  164. package/src/core/migrations/render-ops.ts +7 -2
  165. package/src/core/migrations/render-typescript.ts +5 -1
  166. package/src/core/migrations/runner.ts +78 -50
  167. package/src/core/postgres-contract-serializer.ts +66 -46
  168. package/src/core/postgres-enum-type.ts +4 -0
  169. package/src/core/postgres-schema.ts +56 -19
  170. package/src/exports/contract-free.ts +7 -0
  171. package/src/exports/control.ts +15 -24
  172. package/src/exports/ddl.ts +7 -0
  173. package/src/exports/enum-planning.ts +0 -1
  174. package/src/exports/migration.ts +11 -2
  175. package/src/exports/op-factory-call.ts +2 -0
  176. package/src/exports/planner-ddl-builders.ts +0 -1
  177. package/src/exports/runtime.ts +2 -2
  178. package/dist/codec-ids-C5qzBqus.mjs.map +0 -1
  179. package/dist/codec-ids-D9fJ4HP5.d.mts.map +0 -1
  180. package/dist/codec-types-CRlHq7Cz.d.mts.map +0 -1
  181. package/dist/codecs-Dud5KDNk.d.mts.map +0 -1
  182. package/dist/descriptor-meta-DLA2xV6B.mjs.map +0 -1
  183. package/dist/enum-planning-Dz0Ye3Lb.mjs.map +0 -1
  184. package/dist/issue-planner-ByQhUzS4.mjs.map +0 -1
  185. package/dist/op-factory-call-B0WNg30h.mjs +0 -625
  186. package/dist/op-factory-call-B0WNg30h.mjs.map +0 -1
  187. package/dist/op-factory-call-Drccm_JD.d.mts.map +0 -1
  188. package/dist/planner-ClF0y0YR.mjs +0 -177
  189. package/dist/planner-ClF0y0YR.mjs.map +0 -1
  190. package/dist/planner-ddl-builders-BxRCSn_b.mjs.map +0 -1
  191. package/dist/planner-produced-postgres-migration-N1yqYg20.mjs.map +0 -1
  192. package/dist/planner-produced-postgres-migration-p-VKkCia.d.mts.map +0 -1
  193. package/dist/planner-sql-checks-D3H-xOO1.mjs.map +0 -1
  194. package/dist/planner-target-details-CIj61DUj.d.mts.map +0 -1
  195. package/dist/postgres-contract-serializer-YJvjKrmo.mjs.map +0 -1
  196. package/dist/postgres-enum-type-CNhPTDhy.d.mts.map +0 -1
  197. package/dist/postgres-enum-type-DS-KLVRH.mjs.map +0 -1
  198. package/dist/postgres-migration-Fd4fQkBw.d.mts.map +0 -1
  199. package/dist/postgres-migration-uADmx0dW.mjs.map +0 -1
  200. package/dist/postgres-schema-Bm7vjlOv.mjs.map +0 -1
  201. package/dist/render-typescript-CPk7hhWH.mjs.map +0 -1
  202. package/dist/statement-builders-vImtdfmM.mjs +0 -131
  203. package/dist/statement-builders-vImtdfmM.mjs.map +0 -1
  204. package/dist/statement-builders.d.mts +0 -51
  205. package/dist/statement-builders.d.mts.map +0 -1
  206. package/dist/statement-builders.mjs +0 -2
  207. package/dist/tables-Dcb2q9zV.mjs +0 -516
  208. package/dist/tables-Dcb2q9zV.mjs.map +0 -1
  209. package/dist/types-D-XIpzHA.d.mts.map +0 -1
  210. package/src/core/migrations/statement-builders.ts +0 -183
  211. package/src/exports/statement-builders.ts +0 -8
@@ -2,13 +2,17 @@ import type { Contract } from '@prisma-next/contract/types';
2
2
  import type {
3
3
  MigrationOperationPolicy,
4
4
  SqlMigrationPlannerPlanOptions,
5
+ SqlPlannerConflict,
5
6
  SqlPlannerFailureResult,
6
7
  } from '@prisma-next/family-sql/control';
7
8
  import {
8
9
  extractCodecControlHooks,
10
+ partitionCallsByControlPolicy,
11
+ partitionIssuesByControlPolicy,
9
12
  planFieldEventOperations,
10
13
  plannerFailure,
11
14
  } from '@prisma-next/family-sql/control';
15
+ import type { Lowerer } from '@prisma-next/family-sql/control-adapter';
12
16
  import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
13
17
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
14
18
  import type {
@@ -18,10 +22,18 @@ import type {
18
22
  SchemaIssue,
19
23
  } from '@prisma-next/framework-components/control';
20
24
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
25
+ import { blindCast } from '@prisma-next/utils/casts';
21
26
  import { parsePostgresDefault } from '../default-normalizer';
22
27
  import { normalizeSchemaNativeType } from '../native-type-normalizer';
28
+ import {
29
+ formatPostgresControlPolicySubjectLabel,
30
+ resolvePostgresCallControlPolicySubject,
31
+ resolvePostgresIssueControlPolicySubject,
32
+ resolvePostgresIssueCreationFactoryName,
33
+ } from './control-policy';
23
34
  import { createResolveExistingEnumValues } from './enum-planning';
24
35
  import { planIssues } from './issue-planner';
36
+ import type { PostgresOpFactoryCall } from './op-factory-call';
25
37
  import { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';
26
38
  import { postgresPlannerStrategies } from './planner-strategies';
27
39
  import { verifyPostgresNamespacePresence } from './verify-postgres-namespaces';
@@ -40,8 +52,8 @@ type VerifySqlSchemaOptionsWithComponents = Parameters<typeof verifySqlSchema>[0
40
52
  readonly frameworkComponents: PlannerFrameworkComponents;
41
53
  };
42
54
 
43
- export function createPostgresMigrationPlanner(): PostgresMigrationPlanner {
44
- return new PostgresMigrationPlanner();
55
+ export function createPostgresMigrationPlanner(lowerer: Lowerer): PostgresMigrationPlanner {
56
+ return new PostgresMigrationPlanner(lowerer);
45
57
  }
46
58
 
47
59
  /**
@@ -52,7 +64,11 @@ export function createPostgresMigrationPlanner(): PostgresMigrationPlanner {
52
64
  * uniformly.
53
65
  */
54
66
  export type PostgresPlanResult =
55
- | { readonly kind: 'success'; readonly plan: TypeScriptRenderablePostgresMigration }
67
+ | {
68
+ readonly kind: 'success';
69
+ readonly plan: TypeScriptRenderablePostgresMigration;
70
+ readonly warnings?: readonly SqlPlannerConflict[];
71
+ }
56
72
  | SqlPlannerFailureResult;
57
73
 
58
74
  /**
@@ -72,6 +88,12 @@ export type PostgresPlanResult =
72
88
  * authoring surface.
73
89
  */
74
90
  export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgres'> {
91
+ readonly #lowerer: Lowerer | undefined;
92
+
93
+ constructor(lowerer?: Lowerer) {
94
+ this.#lowerer = lowerer;
95
+ }
96
+
75
97
  plan(options: {
76
98
  readonly contract: unknown;
77
99
  readonly schema: unknown;
@@ -114,6 +136,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
114
136
  to: context.toHash,
115
137
  },
116
138
  spaceId,
139
+ this.#lowerer,
117
140
  );
118
141
  }
119
142
 
@@ -131,8 +154,25 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
131
154
  const codecHooks = extractCodecControlHooks(options.frameworkComponents);
132
155
  const storageTypes = options.contract.storage.types ?? {};
133
156
 
134
- const result = planIssues({
157
+ // Input-side control-policy partition. `external` / `observed` subjects
158
+ // — and non-creation issues for `tolerated` subjects — are dropped from
159
+ // the planner's input entirely; the planner never observes them, never
160
+ // diffs them, never generates DDL for them. Suppression warnings are
161
+ // built directly from the suppressed partition (one per subject), so the
162
+ // user-visible message survives even when the planner would have failed
163
+ // to model the subject's live shape.
164
+ const issuePartition = partitionIssuesByControlPolicy({
135
165
  issues: schemaIssues,
166
+ contract: options.contract,
167
+ resolveControlPolicySubject: (issue) =>
168
+ resolvePostgresIssueControlPolicySubject(issue, options.contract),
169
+ resolveCreationFactoryName: resolvePostgresIssueCreationFactoryName,
170
+ formatSubjectLabel: (factoryName, subject) =>
171
+ formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
172
+ });
173
+
174
+ const result = planIssues({
175
+ issues: issuePartition.plannable,
136
176
  toContract: options.contract,
137
177
  // `fromContract` is only supplied by `migration plan`. It is `null` for
138
178
  // `db update` / `db init`, which means data-safety strategies needing
@@ -163,10 +203,26 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
163
203
  newContract: options.contract,
164
204
  codecHooks,
165
205
  });
166
- // Codec-emitted calls already conform to `OpFactoryCall` render +
167
- // toOp + importRequirements ride directly through the same emit path
168
- // as structural ops, no `RawSqlCall` wrap.
169
- const calls = [...result.value.calls, ...fieldEventOps];
206
+ // Codec hook ops are target-agnostic `OpFactoryCall`; Postgres planning
207
+ // lifts them at this integration boundary (see field-event-planner JSDoc).
208
+ const fieldEventPostgresCalls = blindCast<
209
+ readonly PostgresOpFactoryCall[],
210
+ 'Codec hook ops conform to PostgresOpFactoryCall at the app emitter boundary'
211
+ >(fieldEventOps);
212
+ const fieldEventPartition = partitionCallsByControlPolicy({
213
+ calls: fieldEventPostgresCalls,
214
+ contract: options.contract,
215
+ resolveControlPolicySubject: (call) =>
216
+ resolvePostgresCallControlPolicySubject(call, options.contract),
217
+ resolveFactoryName: (call) => call.factoryName,
218
+ formatSubjectLabel: (factoryName, subject) =>
219
+ formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),
220
+ });
221
+ const calls = [...result.value.calls, ...fieldEventPartition.kept];
222
+ const warnings: SqlPlannerConflict[] = [
223
+ ...issuePartition.warnings,
224
+ ...fieldEventPartition.warnings,
225
+ ];
170
226
 
171
227
  return Object.freeze({
172
228
  kind: 'success' as const,
@@ -177,7 +233,9 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
177
233
  to: options.contract.storage.storageHash,
178
234
  },
179
235
  options.spaceId,
236
+ this.#lowerer,
180
237
  ),
238
+ ...(warnings.length > 0 ? { warnings: Object.freeze(warnings) } : {}),
181
239
  });
182
240
  }
183
241
 
@@ -3,8 +3,11 @@ import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control'
3
3
  import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
4
4
  import { Migration as SqlMigration } from '@prisma-next/family-sql/migration';
5
5
  import type { ControlStack } from '@prisma-next/framework-components/control';
6
+ import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
6
7
  import type { SqlStorage } from '@prisma-next/sql-contract/types';
8
+ import type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
7
9
  import { errorPostgresMigrationStackMissing } from '../errors';
10
+ import { CreateSchemaCall, CreateTableCall } from './op-factory-call';
8
11
  import { type DataTransformOptions, dataTransform } from './operations/data-transform';
9
12
  import type { PostgresPlanTargetDetails } from './planner-target-details';
10
13
 
@@ -67,4 +70,42 @@ export abstract class PostgresMigration extends SqlMigration<
67
70
  }
68
71
  return dataTransform(contract, name, options, this.controlAdapter);
69
72
  }
73
+
74
+ /**
75
+ * Emit a `CREATE TABLE` migration operation. Builds a typed DDL node from
76
+ * the supplied options and lowers it through the stored control adapter.
77
+ * Throws if no adapter is present (i.e. migration instantiated without a stack).
78
+ */
79
+ protected createTable(options: {
80
+ readonly schema?: string;
81
+ readonly table: string;
82
+ readonly ifNotExists?: boolean;
83
+ readonly columns: readonly DdlColumn[];
84
+ readonly constraints?: readonly DdlTableConstraint[];
85
+ }): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
86
+ if (!this.controlAdapter) {
87
+ throw errorPostgresMigrationStackMissing();
88
+ }
89
+ return new CreateTableCall(
90
+ options.schema ?? UNBOUND_NAMESPACE_ID,
91
+ options.table,
92
+ options.columns,
93
+ options.constraints,
94
+ ).toOp(this.controlAdapter);
95
+ }
96
+
97
+ /**
98
+ * Emit a `CREATE SCHEMA` migration operation. Builds a typed DDL node from
99
+ * the supplied options and lowers it through the stored control adapter.
100
+ * Throws if no adapter is present (i.e. migration instantiated without a stack).
101
+ */
102
+ protected createSchema(options: {
103
+ readonly schema: string;
104
+ readonly ifNotExists?: boolean;
105
+ }): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
106
+ if (!this.controlAdapter) {
107
+ throw errorPostgresMigrationStackMissing();
108
+ }
109
+ return new CreateSchemaCall(options.schema).toOp(this.controlAdapter);
110
+ }
70
111
  }
@@ -1,5 +1,7 @@
1
1
  import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
+ import type { Lowerer } from '@prisma-next/family-sql/control-adapter';
2
3
  import type { OpFactoryCall } from '@prisma-next/framework-components/control';
4
+ import { blindCast } from '@prisma-next/utils/casts';
3
5
  import type { PostgresPlanTargetDetails } from './planner-target-details';
4
6
 
5
7
  type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
@@ -23,9 +25,12 @@ function assertPostgresOp(
23
25
  }
24
26
  }
25
27
 
26
- export function renderOps(calls: readonly OpFactoryCall[]): Op[] {
28
+ export function renderOps(calls: readonly OpFactoryCall[], lowerer?: Lowerer): Op[] {
27
29
  return calls.map((c) => {
28
- const op = c.toOp();
30
+ const op = blindCast<
31
+ { toOp(lowerer?: Lowerer): ReturnType<OpFactoryCall['toOp']> },
32
+ 'PG OpFactoryCall.toOp accepts an optional Lowerer; the framework interface omits it because not all targets need a lowerer — the PG target overrides with this extended signature'
33
+ >(c).toOp(lowerer);
29
34
  assertPostgresOp(op, c.factoryName);
30
35
  return op;
31
36
  });
@@ -46,7 +46,7 @@ export function renderCallsToTypeScript(
46
46
  meta: RenderMigrationMeta,
47
47
  ): string {
48
48
  const imports = buildImports(calls);
49
- const operationsBody = calls.map((c) => c.renderTypeScript()).join(',\n');
49
+ const operationsBody = calls.map((c) => renderCall(c)).join(',\n');
50
50
 
51
51
  return [
52
52
  shebangLineFor(detectScaffoldRuntime()),
@@ -66,6 +66,10 @@ export function renderCallsToTypeScript(
66
66
  ].join('\n');
67
67
  }
68
68
 
69
+ function renderCall(call: OpFactoryCall): string {
70
+ return call.renderTypeScript();
71
+ }
72
+
69
73
  function buildImports(calls: ReadonlyArray<OpFactoryCall>): string {
70
74
  const requirements: ImportRequirement[] = [...BASE_IMPORTS];
71
75
  for (const call of calls) {
@@ -1,4 +1,4 @@
1
- import type { ContractMarkerRecord } from '@prisma-next/contract/types';
1
+ import type { Contract, ContractMarkerRecord } from '@prisma-next/contract/types';
2
2
  import type {
3
3
  MigrationOperationPolicy,
4
4
  SqlControlFamilyInstance,
@@ -13,13 +13,12 @@ import type {
13
13
  } from '@prisma-next/family-sql/control';
14
14
  import { runnerFailure, runnerSuccess } from '@prisma-next/family-sql/control';
15
15
  import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
16
- import type {
17
- ControlDriverInstance,
18
- MigrationRunnerResult,
19
- } from '@prisma-next/framework-components/control';
16
+ import type { MigrationRunnerResult } from '@prisma-next/framework-components/control';
20
17
  import { APP_SPACE_ID } from '@prisma-next/framework-components/control';
21
18
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
19
+ import type { SqlControlDriverInstance, SqlStorage } from '@prisma-next/sql-contract/types';
22
20
  import { SqlQueryError } from '@prisma-next/sql-errors';
21
+ import type { LoweredStatement } from '@prisma-next/sql-relational-core/ast';
23
22
  import { ifDefined } from '@prisma-next/utils/defined';
24
23
  import type { Result } from '@prisma-next/utils/result';
25
24
  import { notOk, ok, okVoid } from '@prisma-next/utils/result';
@@ -27,14 +26,6 @@ import { parsePostgresDefault } from '../default-normalizer';
27
26
  import { normalizeSchemaNativeType } from '../native-type-normalizer';
28
27
  import { createResolveExistingEnumValues } from './enum-planning';
29
28
  import type { PostgresPlanTargetDetails } from './planner-target-details';
30
- import {
31
- buildLedgerInsertStatement,
32
- buildMergeMarkerStatements,
33
- ensureLedgerTableStatement,
34
- ensureMarkerTableStatement,
35
- ensurePrismaContractSchemaStatement,
36
- type SqlStatement,
37
- } from './statement-builders';
38
29
 
39
30
  interface ApplyPlanSuccessValue {
40
31
  readonly operationsExecuted: number;
@@ -107,7 +98,7 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
107
98
  if (!policyCheck.ok) return policyCheck;
108
99
 
109
100
  await this.acquireLock(driver, lockKey);
110
- const ensureResult = await this.ensureControlTables(driver);
101
+ const ensureResult = await this.ensureControlTables(driver, options.destinationContract);
111
102
  if (!ensureResult.ok) return ensureResult;
112
103
  const existingMarker = await this.family.readMarker({ driver, space });
113
104
 
@@ -165,8 +156,9 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
165
156
  isSelfEdge && applyValue.operationsExecuted === 0 && incomingIsSubsetOfExisting;
166
157
 
167
158
  if (!isSelfEdgeNoOp) {
168
- await this.upsertMarker(driver, options, existingMarker, space);
169
- await this.recordLedgerEntry(driver, options, existingMarker, applyValue.executedOperations);
159
+ const markerResult = await this.upsertMarker(driver, options, existingMarker, space);
160
+ if (!markerResult.ok) return markerResult;
161
+ await this.recordLedgerEntries(driver, options, applyValue.executedOperations);
170
162
  }
171
163
 
172
164
  return runnerSuccess({
@@ -176,7 +168,7 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
176
168
  }
177
169
 
178
170
  async execute(options: {
179
- readonly driver: ControlDriverInstance<'sql', string>;
171
+ readonly driver: SqlControlDriverInstance<string>;
180
172
  readonly perSpaceOptions: ReadonlyArray<
181
173
  SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>
182
174
  >;
@@ -282,18 +274,22 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
282
274
 
283
275
  private async ensureControlTables(
284
276
  driver: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>['driver'],
277
+ contract: Contract<SqlStorage>,
285
278
  ): Promise<Result<void, SqlMigrationRunnerFailure>> {
286
- await this.executeStatement(driver, ensurePrismaContractSchemaStatement);
287
- // Pre-1.0 zero-range guardrail: detect a pre-cleanup single-row
288
- // marker table (no `space` column) and surface a structured failure
289
- // rather than silently auto-migrating it to the per-space shape.
290
- // See `specs/framework-mechanism.spec.md § 2`.
279
+ const lowererContext = { contract };
280
+ const bootstrapQueries = this.family.bootstrapControlTableQueries();
281
+ const [schemaQuery, ...tableQueries] = bootstrapQueries;
282
+ if (schemaQuery === undefined) {
283
+ throw new Error('Postgres control-table bootstrap must include CREATE SCHEMA');
284
+ }
285
+ await this.executeStatement(driver, this.family.lowerAst(schemaQuery, lowererContext));
291
286
  const legacyDetection = await this.detectLegacyMarkerShape(driver);
292
287
  if (!legacyDetection.ok) {
293
288
  return legacyDetection;
294
289
  }
295
- await this.executeStatement(driver, ensureMarkerTableStatement);
296
- await this.executeStatement(driver, ensureLedgerTableStatement);
290
+ for (const query of tableQueries) {
291
+ await this.executeStatement(driver, this.family.lowerAst(query, lowererContext));
292
+ }
297
293
  return okVoid();
298
294
  }
299
295
 
@@ -594,43 +590,75 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
594
590
  options: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>,
595
591
  existingMarker: ContractMarkerRecord | null,
596
592
  space: string,
597
- ): Promise<void> {
598
- const incomingInvariants = options.plan.providedInvariants ?? [];
599
- const writeStatements = buildMergeMarkerStatements({
600
- space,
593
+ ): Promise<Result<void, SqlMigrationRunnerFailure>> {
594
+ const destination = {
601
595
  storageHash: options.plan.destination.storageHash,
602
596
  profileHash:
603
597
  options.plan.destination.profileHash ??
604
598
  options.destinationContract.profileHash ??
605
599
  options.plan.destination.storageHash,
606
- contractJson: options.destinationContract,
607
- canonicalVersion: null,
608
- meta: {},
609
- invariants: incomingInvariants,
600
+ invariants: options.plan.providedInvariants ?? [],
601
+ };
602
+ if (!existingMarker) {
603
+ await this.family.initMarker({ driver, space, destination });
604
+ return okVoid();
605
+ }
606
+ const updated = await this.family.updateMarker({
607
+ driver,
608
+ space,
609
+ expectedFrom: existingMarker.storageHash,
610
+ destination,
610
611
  });
611
- const statement = existingMarker ? writeStatements.update : writeStatements.insert;
612
- await this.executeStatement(driver, statement);
612
+ if (!updated) {
613
+ return runnerFailure(
614
+ 'MARKER_CAS_FAILURE',
615
+ 'Marker was modified by another process during migration execution.',
616
+ {
617
+ meta: {
618
+ space,
619
+ expectedStorageHash: existingMarker.storageHash,
620
+ destinationStorageHash: options.plan.destination.storageHash,
621
+ },
622
+ },
623
+ );
624
+ }
625
+ return okVoid();
613
626
  }
614
627
 
615
- private async recordLedgerEntry(
628
+ private async recordLedgerEntries(
616
629
  driver: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>['driver'],
617
630
  options: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>,
618
- existingMarker: ContractMarkerRecord | null,
619
631
  executedOperations: readonly SqlMigrationPlanOperation<PostgresPlanTargetDetails>[],
620
632
  ): Promise<void> {
621
- const ledgerStatement = buildLedgerInsertStatement({
622
- originStorageHash: existingMarker?.storageHash ?? null,
623
- originProfileHash: existingMarker?.profileHash ?? null,
624
- destinationStorageHash: options.plan.destination.storageHash,
625
- destinationProfileHash:
626
- options.plan.destination.profileHash ??
627
- options.destinationContract.profileHash ??
628
- options.plan.destination.storageHash,
629
- contractJsonBefore: existingMarker?.contractJson ?? null,
630
- contractJsonAfter: options.destinationContract,
631
- operations: executedOperations,
632
- });
633
- await this.executeStatement(driver, ledgerStatement);
633
+ const plan = options.plan;
634
+ const space = plan.spaceId;
635
+ const edges = options.migrationEdges;
636
+ const totalEdgeOps = edges.reduce((sum, edge) => sum + edge.operationCount, 0);
637
+ if (totalEdgeOps !== plan.operations.length) {
638
+ throw new Error(
639
+ `Ledger write: plan.operations length (${plan.operations.length}) does not match sum of migrationEdges operationCount (${totalEdgeOps})`,
640
+ );
641
+ }
642
+ // The ledger records the operations as executed — idempotency-skipped ops
643
+ // are substituted with skip records (empty `execute`) by `applyPlan`, so the
644
+ // journal reflects what actually ran rather than the raw plan.
645
+ let offset = 0;
646
+ for (const edge of edges) {
647
+ const edgeOps = executedOperations.slice(offset, offset + edge.operationCount);
648
+ offset += edge.operationCount;
649
+ await this.family.writeLedgerEntry({
650
+ driver,
651
+ space,
652
+ entry: {
653
+ edgeId: `${edge.from}->${edge.to}`,
654
+ from: edge.from,
655
+ to: edge.to,
656
+ migrationName: edge.dirName,
657
+ migrationHash: edge.migrationHash,
658
+ operations: edgeOps,
659
+ },
660
+ });
661
+ }
634
662
  }
635
663
 
636
664
  private async acquireLock(
@@ -660,7 +688,7 @@ class PostgresMigrationRunner implements SqlMigrationRunner<PostgresPlanTargetDe
660
688
 
661
689
  private async executeStatement(
662
690
  driver: SqlMigrationRunnerExecuteOptions<PostgresPlanTargetDetails>['driver'],
663
- statement: SqlStatement,
691
+ statement: LoweredStatement,
664
692
  ): Promise<void> {
665
693
  if (statement.params.length > 0) {
666
694
  await driver.query(statement.sql, statement.params);
@@ -14,12 +14,8 @@ import {
14
14
  NamespaceBase,
15
15
  UNBOUND_NAMESPACE_ID,
16
16
  } from '@prisma-next/framework-components/ir';
17
- import type {
18
- SqlNamespaceTablesInput,
19
- SqlStorage,
20
- SqlStorageTypeEntry,
21
- StorageTable,
22
- } from '@prisma-next/sql-contract/types';
17
+ import type { SqlNamespaceTablesInput, SqlStorage } from '@prisma-next/sql-contract/types';
18
+ import { blindCast } from '@prisma-next/utils/casts';
23
19
  import type { JsonObject } from '@prisma-next/utils/json';
24
20
  import type { Type } from 'arktype';
25
21
  import { postgresAuthoringEntityTypes } from './authoring';
@@ -57,9 +53,8 @@ function collectEntityRegistryContributions(namespace: AuthoringEntityTypeNamesp
57
53
  if (isAuthoringEntityTypeDescriptor(value)) {
58
54
  if (isAuthoringEntityTypeFactoryOutput(value.output)) {
59
55
  const { factory } = value.output;
60
- entityTypeRegistry.set(
61
- value.discriminator,
62
- (raw) => factory(raw, POSTGRES_AUTHORING_CTX) as SqlStorageTypeEntry,
56
+ entityTypeRegistry.set(value.discriminator, (raw) =>
57
+ factory(raw, POSTGRES_AUTHORING_CTX),
63
58
  );
64
59
  }
65
60
  if (value.validatorSchema !== undefined) {
@@ -91,22 +86,46 @@ export class PostgresContractSerializer extends SqlContractSerializerBase<Contra
91
86
  if (raw instanceof NamespaceBase) {
92
87
  return raw;
93
88
  }
94
- const hydrated = super.hydrateSqlNamespaceEntry(nsId, raw) as {
95
- id: string;
96
- tables: Readonly<Record<string, StorageTable>>;
97
- enum?: Readonly<Record<string, PostgresEnumType>>;
98
- };
99
- const { id, tables, enum: hydratedNsEnums } = hydrated;
89
+ const hydrated = blindCast<
90
+ SqlNamespaceTablesInput,
91
+ 'super.hydrateSqlNamespaceEntry returns the tables form when raw is not a NamespaceBase'
92
+ >(super.hydrateSqlNamespaceEntry(nsId, raw));
93
+ const { id, entries } = hydrated;
94
+
95
+ // Extract the postgres-specific `type` slot directly from raw input.
96
+ // The family base handles the `table` slot; the postgres target owns `type`.
97
+ const rawRecord = raw as Record<string, unknown>;
98
+ const rawEntries = rawRecord['entries'];
99
+ let typeSlot: Record<string, PostgresEnumType> | undefined;
100
+ if (rawEntries !== null && typeof rawEntries === 'object' && !Array.isArray(rawEntries)) {
101
+ const rawTypeSlot = (rawEntries as Record<string, unknown>)['type'];
102
+ if (rawTypeSlot !== null && typeof rawTypeSlot === 'object' && !Array.isArray(rawTypeSlot)) {
103
+ const enumFactory = this.entityTypeRegistry.get('postgres-enum');
104
+ typeSlot = Object.fromEntries(
105
+ Object.entries(rawTypeSlot as Record<string, unknown>).map(([name, entry]) => [
106
+ name,
107
+ blindCast<PostgresEnumType, 'postgres-enum factory returns PostgresEnumType'>(
108
+ enumFactory !== undefined ? enumFactory(entry) : entry,
109
+ ),
110
+ ]),
111
+ );
112
+ }
113
+ }
100
114
 
101
- const emptyTables = Object.keys(tables).length === 0;
102
- const emptyEnums = !hydratedNsEnums || Object.keys(hydratedNsEnums).length === 0;
103
- if (id === UNBOUND_NAMESPACE_ID && emptyTables && emptyEnums) {
115
+ const valueSetSlot = entries.valueSet;
116
+ const hasValueSets = valueSetSlot !== undefined && Object.keys(valueSetSlot).length > 0;
117
+ const emptyTables = Object.keys(entries.table).length === 0;
118
+ const emptyTypes = !typeSlot || Object.keys(typeSlot).length === 0;
119
+ if (id === UNBOUND_NAMESPACE_ID && emptyTables && emptyTypes && !hasValueSets) {
104
120
  return PostgresSchema.unbound;
105
121
  }
106
122
  return new PostgresSchema({
107
123
  id,
108
- tables,
109
- ...(hydratedNsEnums !== undefined ? { enum: hydratedNsEnums } : {}),
124
+ entries: {
125
+ table: entries.table,
126
+ type: typeSlot ?? {},
127
+ ...(hasValueSets ? { valueSet: valueSetSlot } : {}),
128
+ },
110
129
  });
111
130
  }
112
131
 
@@ -117,30 +136,18 @@ export class PostgresContractSerializer extends SqlContractSerializerBase<Contra
117
136
  if (isPostgresSchema(ns)) {
118
137
  namespacesJson[nsId] = this.serializePostgresNamespace(ns, ns.id === UNBOUND_NAMESPACE_ID);
119
138
  } else {
120
- // Family-level SqlUnboundNamespace or other family-built SQL
121
- // namespaces haven't been promoted to a PostgresSchema instance
122
- // yet (e.g. they came straight from the TS builder before a target
123
- // `createNamespace` factory ran). Serialise them as postgres-schema /
124
- // postgres-unbound-schema so the round-trip through
125
- // deserializeContract hydrates them back into PostgresSchema
126
- // instances.
127
139
  const isUnboundSlot = nsId === UNBOUND_NAMESPACE_ID;
128
- const nsEnums = (ns as { readonly enum?: Readonly<Record<string, unknown>> }).enum ?? {};
129
140
  namespacesJson[nsId] = {
130
141
  id: nsId,
131
142
  kind: isUnboundSlot ? 'postgres-unbound-schema' : 'postgres-schema',
132
- tables: Object.fromEntries(
133
- Object.entries(ns.tables).map(([tableName, table]) => [
134
- tableName,
135
- this.serializeJsonValue(table) as JsonObject,
136
- ]),
137
- ),
138
- enum: Object.fromEntries(
139
- Object.entries(nsEnums).map(([typeName, entry]) => [
140
- typeName,
141
- this.serializeJsonValue(entry) as JsonObject,
142
- ]),
143
- ),
143
+ entries: {
144
+ table: Object.fromEntries(
145
+ Object.entries(ns.entries.table).map(([tableName, table]) => [
146
+ tableName,
147
+ this.serializeJsonValue(table) as JsonObject,
148
+ ]),
149
+ ),
150
+ },
144
151
  };
145
152
  }
146
153
  }
@@ -163,18 +170,31 @@ export class PostgresContractSerializer extends SqlContractSerializerBase<Contra
163
170
 
164
171
  private serializePostgresNamespace(ns: PostgresSchema, isUnboundSlot: boolean): JsonObject {
165
172
  const tablesOut: Record<string, JsonObject> = {};
166
- for (const [tableName, table] of Object.entries(ns.tables)) {
173
+ for (const [tableName, table] of Object.entries(ns.entries.table)) {
167
174
  tablesOut[tableName] = this.serializeJsonValue(table) as JsonObject;
168
175
  }
169
- const enumOut: Record<string, JsonObject> = {};
170
- for (const [typeName, ty] of Object.entries(ns.enum)) {
171
- enumOut[typeName] = this.serializeJsonValue(ty) as JsonObject;
176
+ const typeOut: Record<string, JsonObject> = {};
177
+ for (const [typeName, ty] of Object.entries(ns.entries.type)) {
178
+ typeOut[typeName] = this.serializeJsonValue(ty) as JsonObject;
179
+ }
180
+ const valueSetEntries = ns.entries.valueSet;
181
+ const valueSetOut: Record<string, JsonObject> = {};
182
+ if (valueSetEntries !== undefined) {
183
+ for (const [valueSetName, valueSet] of Object.entries(valueSetEntries)) {
184
+ valueSetOut[valueSetName] = blindCast<
185
+ JsonObject,
186
+ 'serializeJsonValue round-trips the value-set node through JSON, yielding a JsonObject'
187
+ >(this.serializeJsonValue(valueSet));
188
+ }
172
189
  }
173
190
  return {
174
191
  id: ns.id,
175
192
  kind: isUnboundSlot ? 'postgres-unbound-schema' : 'postgres-schema',
176
- tables: tablesOut,
177
- enum: enumOut,
193
+ entries: {
194
+ table: tablesOut,
195
+ type: typeOut,
196
+ ...(Object.keys(valueSetOut).length > 0 ? { valueSet: valueSetOut } : {}),
197
+ },
178
198
  };
179
199
  }
180
200
 
@@ -1,3 +1,4 @@
1
+ import type { ControlPolicy } from '@prisma-next/contract/types';
1
2
  import { freezeNode } from '@prisma-next/framework-components/ir';
2
3
  import { SqlNode } from '@prisma-next/sql-contract/types';
3
4
 
@@ -18,6 +19,7 @@ export interface PostgresEnumTypeInput<
18
19
  */
19
20
  readonly nativeType?: string;
20
21
  readonly values: TValues;
22
+ readonly control?: ControlPolicy;
21
23
  }
22
24
 
23
25
  /** Codec id used by Postgres enum-typed columns (text wire format). */
@@ -64,6 +66,7 @@ export class PostgresEnumType<
64
66
  * dispatching through the prototype-only `codecBinding` accessor.
65
67
  */
66
68
  readonly codecId: typeof PG_ENUM_CODEC_ID = PG_ENUM_CODEC_ID;
69
+ declare readonly control?: ControlPolicy;
67
70
 
68
71
  constructor(input: PostgresEnumTypeInput<TName, TValues>) {
69
72
  super();
@@ -73,6 +76,7 @@ export class PostgresEnumType<
73
76
  // `TValues` literal tuple. Cast preserves the caller-supplied
74
77
  // tuple shape so inferred contract types retain literal narrowing.
75
78
  this.values = Object.freeze([...input.values] as unknown as TValues);
79
+ if (input.control !== undefined) this.control = input.control;
76
80
  freezeNode(this);
77
81
  }
78
82