@prisma-next/target-postgres 0.5.0-dev.8 → 0.5.0-dev.80

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 (180) hide show
  1. package/dist/{codec-ids-CojIXVf9.mjs → codec-ids-C5qzBqus.mjs} +4 -4
  2. package/dist/{codec-ids-CojIXVf9.mjs.map → codec-ids-C5qzBqus.mjs.map} +1 -1
  3. package/dist/codec-ids-CplrEfmx.d.mts +29 -0
  4. package/dist/codec-ids-CplrEfmx.d.mts.map +1 -0
  5. package/dist/codec-ids.d.mts +2 -28
  6. package/dist/codec-ids.mjs +2 -3
  7. package/dist/codec-types-lrsb3N07.d.mts +79 -0
  8. package/dist/codec-types-lrsb3N07.d.mts.map +1 -0
  9. package/dist/codec-types.d.mts +2 -42
  10. package/dist/codec-types.mjs +1 -4
  11. package/dist/codecs-Cue97Xqf.d.mts +558 -0
  12. package/dist/codecs-Cue97Xqf.d.mts.map +1 -0
  13. package/dist/codecs.d.mts +13 -2
  14. package/dist/codecs.d.mts.map +1 -0
  15. package/dist/codecs.mjs +738 -3
  16. package/dist/codecs.mjs.map +1 -0
  17. package/dist/control.d.mts +1 -1
  18. package/dist/control.d.mts.map +1 -1
  19. package/dist/control.mjs +131 -94
  20. package/dist/control.mjs.map +1 -1
  21. package/dist/{data-transform-VfEGzXWt.mjs → data-transform-DKWXdHuZ.mjs} +25 -7
  22. package/dist/data-transform-DKWXdHuZ.mjs.map +1 -0
  23. package/dist/data-transform-bIeAcZIJ.d.mts +38 -0
  24. package/dist/data-transform-bIeAcZIJ.d.mts.map +1 -0
  25. package/dist/data-transform.d.mts +1 -1
  26. package/dist/data-transform.mjs +2 -3
  27. package/dist/{default-normalizer-DNOpRoOF.mjs → default-normalizer-C8XyZj85.mjs} +2 -2
  28. package/dist/{default-normalizer-DNOpRoOF.mjs.map → default-normalizer-C8XyZj85.mjs.map} +1 -1
  29. package/dist/default-normalizer.d.mts +0 -1
  30. package/dist/default-normalizer.d.mts.map +1 -1
  31. package/dist/default-normalizer.mjs +2 -3
  32. package/dist/descriptor-meta-Dde_BS3K.mjs +99 -0
  33. package/dist/descriptor-meta-Dde_BS3K.mjs.map +1 -0
  34. package/dist/{errors-AFvEPZ1R.mjs → errors-Chm2bKcS.mjs} +2 -3
  35. package/dist/{errors-AFvEPZ1R.mjs.map → errors-Chm2bKcS.mjs.map} +1 -1
  36. package/dist/errors.d.mts +0 -1
  37. package/dist/errors.d.mts.map +1 -1
  38. package/dist/errors.mjs +2 -3
  39. package/dist/{issue-planner-CFjB0_oO.mjs → issue-planner-B10B70JF.mjs} +9 -13
  40. package/dist/issue-planner-B10B70JF.mjs.map +1 -0
  41. package/dist/issue-planner.d.mts +2 -4
  42. package/dist/issue-planner.d.mts.map +1 -1
  43. package/dist/issue-planner.mjs +2 -3
  44. package/dist/migration.d.mts +3 -3
  45. package/dist/migration.d.mts.map +1 -1
  46. package/dist/migration.mjs +4 -5
  47. package/dist/migration.mjs.map +1 -1
  48. package/dist/{native-type-normalizer-CInai_oY.mjs → native-type-normalizer-Cry4QoLf.mjs} +2 -2
  49. package/dist/native-type-normalizer-Cry4QoLf.mjs.map +1 -0
  50. package/dist/native-type-normalizer.d.mts.map +1 -1
  51. package/dist/native-type-normalizer.mjs +2 -3
  52. package/dist/{op-factory-call-C3bWXKSP.d.mts → op-factory-call-CW8pzxmB.d.mts} +3 -4
  53. package/dist/op-factory-call-CW8pzxmB.d.mts.map +1 -0
  54. package/dist/{op-factory-call-BKlruaiC.mjs → op-factory-call-Cq8s4Fz1.mjs} +3 -4
  55. package/dist/{op-factory-call-BKlruaiC.mjs.map → op-factory-call-Cq8s4Fz1.mjs.map} +1 -1
  56. package/dist/op-factory-call.d.mts +1 -2
  57. package/dist/op-factory-call.mjs +2 -3
  58. package/dist/pack.d.mts +28 -9
  59. package/dist/pack.d.mts.map +1 -1
  60. package/dist/pack.mjs +2 -3
  61. package/dist/{planner-CLUvVhUN.mjs → planner-BvKUuqG-.mjs} +22 -16
  62. package/dist/planner-BvKUuqG-.mjs.map +1 -0
  63. package/dist/{planner-ddl-builders-Dxvw1LHw.mjs → planner-ddl-builders-CLB7Umhh.mjs} +4 -5
  64. package/dist/planner-ddl-builders-CLB7Umhh.mjs.map +1 -0
  65. package/dist/planner-ddl-builders.d.mts +1 -1
  66. package/dist/planner-ddl-builders.d.mts.map +1 -1
  67. package/dist/planner-ddl-builders.mjs +2 -3
  68. package/dist/{planner-identity-values-Dju-o5GF.mjs → planner-identity-values-DTx0gePL.mjs} +2 -3
  69. package/dist/{planner-identity-values-Dju-o5GF.mjs.map → planner-identity-values-DTx0gePL.mjs.map} +1 -1
  70. package/dist/planner-identity-values.d.mts +0 -1
  71. package/dist/planner-identity-values.d.mts.map +1 -1
  72. package/dist/planner-identity-values.mjs +2 -3
  73. package/dist/{planner-produced-postgres-migration-CRRTno6Z.d.mts → planner-produced-postgres-migration-CjxWIVgh.d.mts} +11 -7
  74. package/dist/planner-produced-postgres-migration-CjxWIVgh.d.mts.map +1 -0
  75. package/dist/{planner-produced-postgres-migration-DSSPq8QS.mjs → planner-produced-postgres-migration-DphktB2N.mjs} +16 -8
  76. package/dist/planner-produced-postgres-migration-DphktB2N.mjs.map +1 -0
  77. package/dist/planner-produced-postgres-migration.d.mts +1 -4
  78. package/dist/planner-produced-postgres-migration.mjs +2 -3
  79. package/dist/{planner-schema-lookup-B7lkypwn.mjs → planner-schema-lookup-B1ags8ys.mjs} +2 -2
  80. package/dist/{planner-schema-lookup-B7lkypwn.mjs.map → planner-schema-lookup-B1ags8ys.mjs.map} +1 -1
  81. package/dist/planner-schema-lookup.d.mts +0 -1
  82. package/dist/planner-schema-lookup.d.mts.map +1 -1
  83. package/dist/planner-schema-lookup.mjs +2 -3
  84. package/dist/{planner-sql-checks-7jkgm9TX.mjs → planner-sql-checks-DwZvGlV4.mjs} +3 -5
  85. package/dist/planner-sql-checks-DwZvGlV4.mjs.map +1 -0
  86. package/dist/planner-sql-checks.d.mts.map +1 -1
  87. package/dist/planner-sql-checks.mjs +2 -3
  88. package/dist/{planner-target-details-DH-azLu-.d.mts → planner-target-details-bVVcanWh.d.mts} +1 -1
  89. package/dist/planner-target-details-bVVcanWh.d.mts.map +1 -0
  90. package/dist/planner-target-details.d.mts +1 -1
  91. package/dist/planner-target-details.mjs +1 -1
  92. package/dist/planner.d.mts +21 -12
  93. package/dist/planner.d.mts.map +1 -1
  94. package/dist/planner.mjs +2 -4
  95. package/dist/{postgres-migration-qtmtbONe.mjs → postgres-migration-Bkv140RW.mjs} +4 -5
  96. package/dist/postgres-migration-Bkv140RW.mjs.map +1 -0
  97. package/dist/{postgres-migration-BjA3Zmts.d.mts → postgres-migration-UkcHfZAA.d.mts} +6 -6
  98. package/dist/postgres-migration-UkcHfZAA.d.mts.map +1 -0
  99. package/dist/render-ops--1nnfNus.mjs +23 -0
  100. package/dist/render-ops--1nnfNus.mjs.map +1 -0
  101. package/dist/render-ops.d.mts +3 -4
  102. package/dist/render-ops.d.mts.map +1 -1
  103. package/dist/render-ops.mjs +2 -3
  104. package/dist/{render-typescript-1rF_SB4g.mjs → render-typescript-D3doH-vX.mjs} +2 -14
  105. package/dist/render-typescript-D3doH-vX.mjs.map +1 -0
  106. package/dist/render-typescript.d.mts +3 -6
  107. package/dist/render-typescript.d.mts.map +1 -1
  108. package/dist/render-typescript.mjs +2 -3
  109. package/dist/runtime.d.mts +5 -9
  110. package/dist/runtime.d.mts.map +1 -1
  111. package/dist/runtime.mjs +7 -14
  112. package/dist/runtime.mjs.map +1 -1
  113. package/dist/{shared-Bxkt8pNO.d.mts → shared-MpwjwAjM.d.mts} +2 -2
  114. package/dist/shared-MpwjwAjM.d.mts.map +1 -0
  115. package/dist/{sql-utils-r-Lw535w.mjs → sql-utils-CggjWNij.mjs} +4 -2
  116. package/dist/sql-utils-CggjWNij.mjs.map +1 -0
  117. package/dist/sql-utils.d.mts.map +1 -1
  118. package/dist/sql-utils.mjs +2 -3
  119. package/dist/{statement-builders-BPnmt6wx.mjs → statement-builders-BT889jV0.mjs} +28 -13
  120. package/dist/statement-builders-BT889jV0.mjs.map +1 -0
  121. package/dist/statement-builders.d.mts +31 -3
  122. package/dist/statement-builders.d.mts.map +1 -1
  123. package/dist/statement-builders.mjs +2 -3
  124. package/dist/{tables-BmdW_FWO.mjs → tables-Ej122-iI.mjs} +4 -11
  125. package/dist/tables-Ej122-iI.mjs.map +1 -0
  126. package/dist/{types-ClK03Ojd.d.mts → types-CTqpysRY.d.mts} +1 -1
  127. package/dist/types-CTqpysRY.d.mts.map +1 -0
  128. package/dist/types.d.mts +1 -1
  129. package/dist/types.mjs +1 -1
  130. package/package.json +21 -19
  131. package/src/core/authoring.ts +5 -11
  132. package/src/core/codec-helpers.ts +135 -0
  133. package/src/core/codec-ids.ts +1 -0
  134. package/src/core/codec-type-map.ts +81 -0
  135. package/src/core/codecs.ts +941 -547
  136. package/src/core/descriptor-meta.ts +1 -1
  137. package/src/core/migrations/operations/data-transform.ts +86 -21
  138. package/src/core/migrations/planner-produced-postgres-migration.ts +17 -5
  139. package/src/core/migrations/planner.ts +62 -20
  140. package/src/core/migrations/postgres-migration.ts +3 -6
  141. package/src/core/migrations/render-ops.ts +26 -3
  142. package/src/core/migrations/render-typescript.ts +5 -9
  143. package/src/core/migrations/runner.ts +172 -151
  144. package/src/core/migrations/statement-builders.ts +49 -10
  145. package/src/core/registry.ts +11 -0
  146. package/src/exports/codec-types.ts +4 -13
  147. package/src/exports/codecs.ts +49 -2
  148. package/src/exports/runtime.ts +6 -11
  149. package/src/exports/statement-builders.ts +2 -1
  150. package/dist/codec-ids.d.mts.map +0 -1
  151. package/dist/codec-types.d.mts.map +0 -1
  152. package/dist/codecs-BQEm9_oo.d.mts +0 -319
  153. package/dist/codecs-BQEm9_oo.d.mts.map +0 -1
  154. package/dist/codecs-BoahtY_Q.mjs +0 -385
  155. package/dist/codecs-BoahtY_Q.mjs.map +0 -1
  156. package/dist/data-transform-CxFRBIUp.d.mts +0 -32
  157. package/dist/data-transform-CxFRBIUp.d.mts.map +0 -1
  158. package/dist/data-transform-VfEGzXWt.mjs.map +0 -1
  159. package/dist/descriptor-meta-BVoVtyp-.mjs +0 -120
  160. package/dist/descriptor-meta-BVoVtyp-.mjs.map +0 -1
  161. package/dist/issue-planner-CFjB0_oO.mjs.map +0 -1
  162. package/dist/native-type-normalizer-CInai_oY.mjs.map +0 -1
  163. package/dist/op-factory-call-C3bWXKSP.d.mts.map +0 -1
  164. package/dist/planner-CLUvVhUN.mjs.map +0 -1
  165. package/dist/planner-ddl-builders-Dxvw1LHw.mjs.map +0 -1
  166. package/dist/planner-produced-postgres-migration-CRRTno6Z.d.mts.map +0 -1
  167. package/dist/planner-produced-postgres-migration-DSSPq8QS.mjs.map +0 -1
  168. package/dist/planner-sql-checks-7jkgm9TX.mjs.map +0 -1
  169. package/dist/planner-target-details-DH-azLu-.d.mts.map +0 -1
  170. package/dist/postgres-migration-BjA3Zmts.d.mts.map +0 -1
  171. package/dist/postgres-migration-qtmtbONe.mjs.map +0 -1
  172. package/dist/render-ops-D6_DHdOK.mjs +0 -8
  173. package/dist/render-ops-D6_DHdOK.mjs.map +0 -1
  174. package/dist/render-typescript-1rF_SB4g.mjs.map +0 -1
  175. package/dist/shared-Bxkt8pNO.d.mts.map +0 -1
  176. package/dist/sql-utils-r-Lw535w.mjs.map +0 -1
  177. package/dist/statement-builders-BPnmt6wx.mjs.map +0 -1
  178. package/dist/tables-BmdW_FWO.mjs.map +0 -1
  179. package/dist/types-ClK03Ojd.d.mts.map +0 -1
  180. package/src/core/json-schema-type-expression.ts +0 -131
@@ -1,5 +1,5 @@
1
+ import type { CodecTypes } from '../exports/codec-types';
1
2
  import { postgresAuthoringFieldPresets, postgresAuthoringTypes } from './authoring';
2
- import type { CodecTypes } from './codecs';
3
3
 
4
4
  const postgresTargetDescriptorMetaBase = {
5
5
  kind: 'target',
@@ -11,7 +11,7 @@
11
11
  * override get operations() {
12
12
  * return [
13
13
  * this.dataTransform(endContract, 'backfill emails', {
14
- * check: () => db.users.count().where(({ email }) => email.isNull()),
14
+ * check: () => db.users.select('id').where(({ email }) => email.isNull()).limit(1),
15
15
  * run: () => db.users.update({ email: '' }).where(({ email }) => email.isNull()),
16
16
  * }),
17
17
  * ];
@@ -23,20 +23,49 @@
23
23
  * invokes each one, asserts that its `meta.storageHash` matches the
24
24
  * `contract` it was handed (→ `PN-MIG-2005` on mismatch), and lowers the
25
25
  * plan via the supplied control adapter to a serialized `{sql, params}`
26
- * payload for `ops.json`. The free factory remains usable standalone
27
- * (tests, ad-hoc tooling, non-class contexts) by passing the adapter
28
- * explicitly as the fourth argument.
26
+ * payload.
27
+ *
28
+ * The factory then lowers the data transform to the unified migration-op
29
+ * shape `{ precheck, execute, postcheck }`. The user's `check` plan is
30
+ * wrapped twice with opposite truth values:
31
+ *
32
+ * - precheck `SELECT EXISTS (<check>) AS ok` asserts there is work to do
33
+ * (precheck is short-circuited by the runner's pre-satisfied-skip path
34
+ * when nothing remains to backfill).
35
+ * - postcheck `SELECT NOT EXISTS (<check>) AS ok` asserts the work is
36
+ * complete after the run steps execute.
37
+ *
38
+ * The `check` plan is therefore expected to be a **rowset query whose
39
+ * presence of any row signals "work remains"** — typically `select('id')
40
+ * .where(<violation predicate>).limit(1)`. Scalar/aggregate shapes
41
+ * (`count(*)`, `bool_and(...)`) do not work under this contract: they
42
+ * always return exactly one row, so `EXISTS` is always true and
43
+ * `NOT EXISTS` is always false. (This is the same row-presence contract
44
+ * the pre-unification runner relied on; the wrapping is just lifting it
45
+ * into SQL.)
46
+ *
47
+ * Each `run` plan becomes an execute step. Because the `Step.params`
48
+ * field threads through `driver.query(sql, params)`, the user's bound
49
+ * values flow through the driver's parameter binder rather than being
50
+ * inlined into the SQL text.
51
+ *
52
+ * The free factory remains usable standalone (tests, ad-hoc tooling,
53
+ * non-class contexts) by passing the adapter explicitly as the fourth
54
+ * argument.
29
55
  */
30
56
 
31
57
  import type { Contract } from '@prisma-next/contract/types';
32
58
  import { errorDataTransformContractMismatch } from '@prisma-next/errors/migration';
33
- import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
34
59
  import type {
35
- DataTransformOperation,
36
- SerializedQueryPlan,
37
- } from '@prisma-next/framework-components/control';
60
+ SqlMigrationPlanOperation,
61
+ SqlMigrationPlanOperationStep,
62
+ } from '@prisma-next/family-sql/control';
63
+ import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
64
+ import type { SerializedQueryPlan } from '@prisma-next/framework-components/control';
38
65
  import type { SqlStorage } from '@prisma-next/sql-contract/types';
39
66
  import type { SqlQueryPlan } from '@prisma-next/sql-relational-core/plan';
67
+ import { ifDefined } from '@prisma-next/utils/defined';
68
+ import type { PostgresPlanTargetDetails } from '../planner-target-details';
40
69
 
41
70
  interface Buildable<R = unknown> {
42
71
  build(): SqlQueryPlan<R>;
@@ -49,36 +78,72 @@ interface Buildable<R = unknown> {
49
78
  export type DataTransformClosure = () => SqlQueryPlan | Buildable;
50
79
 
51
80
  export interface DataTransformOptions {
52
- /** Optional pre-flight query. `undefined` means "no check". */
81
+ /**
82
+ * Optional opt-in routing identity. Presence opts the transform into
83
+ * invariant-aware routing; absence means it is path-dependent and
84
+ * not referenceable from refs.
85
+ */
86
+ readonly invariantId?: string;
87
+ /**
88
+ * Optional pre-flight query. `undefined` means "no check". When
89
+ * supplied, the closure must return a **rowset query** whose
90
+ * presence of any row signals "violations remain". Conventional
91
+ * shape: `db.<table>.select('id').where(<violation>).limit(1)`.
92
+ * Scalar/aggregate shapes do not satisfy this contract.
93
+ */
53
94
  readonly check?: DataTransformClosure;
54
95
  /** One or more mutation queries to execute. */
55
96
  readonly run: DataTransformClosure | readonly DataTransformClosure[];
56
97
  }
57
98
 
58
- /**
59
- * Concrete Postgres flavor of `DataTransformOperation`, re-exported so the
60
- * `PostgresMigration.dataTransform` instance method can name it without
61
- * leaking the framework-components symbol into call sites.
62
- */
63
- export type PostgresDataTransformOperation = DataTransformOperation;
64
-
65
99
  export function dataTransform<TContract extends Contract<SqlStorage>>(
66
100
  contract: TContract,
67
101
  name: string,
68
102
  options: DataTransformOptions,
69
103
  adapter: SqlControlAdapter<'postgres'>,
70
- ): DataTransformOperation {
104
+ ): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
71
105
  const runClosures: readonly DataTransformClosure[] = Array.isArray(options.run)
72
106
  ? options.run
73
107
  : [options.run as DataTransformClosure];
108
+
109
+ const checkPlan = options.check ? invokeAndLower(options.check, contract, adapter, name) : null;
110
+ const runPlans = runClosures.map((closure) => invokeAndLower(closure, contract, adapter, name));
111
+
112
+ const precheck: readonly SqlMigrationPlanOperationStep[] = checkPlan
113
+ ? [
114
+ {
115
+ description: `Check ${name} has work to do`,
116
+ sql: `SELECT EXISTS (${checkPlan.sql}) AS ok`,
117
+ params: checkPlan.params,
118
+ },
119
+ ]
120
+ : [];
121
+
122
+ const execute: readonly SqlMigrationPlanOperationStep[] = runPlans.map((plan) => ({
123
+ description: `Run ${name}`,
124
+ sql: plan.sql,
125
+ params: plan.params,
126
+ }));
127
+
128
+ const postcheck: readonly SqlMigrationPlanOperationStep[] = checkPlan
129
+ ? [
130
+ {
131
+ description: `Verify ${name} resolved all violations`,
132
+ sql: `SELECT NOT EXISTS (${checkPlan.sql}) AS ok`,
133
+ params: checkPlan.params,
134
+ },
135
+ ]
136
+ : [];
137
+
74
138
  return {
75
139
  id: `data_migration.${name}`,
76
140
  label: `Data transform: ${name}`,
77
141
  operationClass: 'data',
78
- name,
79
- source: 'migration.ts',
80
- check: options.check ? invokeAndLower(options.check, contract, adapter, name) : null,
81
- run: runClosures.map((closure) => invokeAndLower(closure, contract, adapter, name)),
142
+ ...ifDefined('invariantId', options.invariantId),
143
+ target: { id: 'postgres' },
144
+ precheck,
145
+ execute,
146
+ postcheck,
82
147
  };
83
148
  }
84
149
 
@@ -24,10 +24,12 @@
24
24
  */
25
25
 
26
26
  import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
27
- import type { MigrationPlanWithAuthoringSurface } from '@prisma-next/framework-components/control';
27
+ import type {
28
+ MigrationPlanWithAuthoringSurface,
29
+ OpFactoryCall,
30
+ } from '@prisma-next/framework-components/control';
28
31
  import type { MigrationMeta } from '@prisma-next/migration-tools/migration';
29
32
  import { ifDefined } from '@prisma-next/utils/defined';
30
- import type { PostgresOpFactoryCall } from './op-factory-call';
31
33
  import type { PostgresPlanTargetDetails } from './planner-target-details';
32
34
  import { PostgresMigration } from './postgres-migration';
33
35
  import { renderOps } from './render-ops';
@@ -39,13 +41,15 @@ export class TypeScriptRenderablePostgresMigration
39
41
  extends PostgresMigration
40
42
  implements MigrationPlanWithAuthoringSurface
41
43
  {
42
- readonly #calls: readonly PostgresOpFactoryCall[];
44
+ readonly #calls: readonly OpFactoryCall[];
43
45
  readonly #meta: MigrationMeta;
46
+ readonly #spaceId: string;
44
47
 
45
- constructor(calls: readonly PostgresOpFactoryCall[], meta: MigrationMeta) {
48
+ constructor(calls: readonly OpFactoryCall[], meta: MigrationMeta, spaceId: string) {
46
49
  super();
47
50
  this.#calls = calls;
48
51
  this.#meta = meta;
52
+ this.#spaceId = spaceId;
49
53
  }
50
54
 
51
55
  override get operations(): readonly Op[] {
@@ -56,11 +60,19 @@ export class TypeScriptRenderablePostgresMigration
56
60
  return this.#meta;
57
61
  }
58
62
 
63
+ /**
64
+ * Contract space this planner-produced plan applies to. Threaded
65
+ * from the planner options so the runner keys the marker row by
66
+ * the right space when executing the plan.
67
+ */
68
+ get spaceId(): string {
69
+ return this.#spaceId;
70
+ }
71
+
59
72
  renderTypeScript(): string {
60
73
  return renderCallsToTypeScript(this.#calls, {
61
74
  from: this.#meta.from,
62
75
  to: this.#meta.to,
63
- ...ifDefined('kind', this.#meta.kind),
64
76
  ...ifDefined('labels', this.#meta.labels),
65
77
  });
66
78
  }
@@ -1,9 +1,14 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
1
2
  import type {
2
3
  MigrationOperationPolicy,
3
4
  SqlMigrationPlannerPlanOptions,
4
5
  SqlPlannerFailureResult,
5
6
  } from '@prisma-next/family-sql/control';
6
- import { extractCodecControlHooks, plannerFailure } from '@prisma-next/family-sql/control';
7
+ import {
8
+ extractCodecControlHooks,
9
+ planFieldEventOperations,
10
+ plannerFailure,
11
+ } from '@prisma-next/family-sql/control';
7
12
  import { verifySqlSchema } from '@prisma-next/family-sql/schema-verify';
8
13
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
9
14
  import type {
@@ -83,30 +88,48 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
83
88
  readonly contract: unknown;
84
89
  readonly schema: unknown;
85
90
  readonly policy: MigrationOperationPolicy;
86
- readonly fromHash?: string;
87
91
  /**
88
92
  * The "from" contract (state the planner assumes the database starts
89
- * at). Only `migration plan` supplies this; `db update` / `db init`
90
- * reconcile against the live schema with no old contract. When present
91
- * alongside the `'data'` operation class, strategies that need from/to
92
- * column shape comparisons (unsafe type change, nullability tightening)
93
- * activate.
93
+ * at), or `null` for reconciliation flows. Only `migration plan` ever
94
+ * supplies a non-null value; `db update` / `db init` reconcile against
95
+ * the live schema and pass `null`. When present alongside the
96
+ * `'data'` operation class, strategies that need from/to column-shape
97
+ * comparisons (unsafe type change, nullability tightening) activate.
98
+ *
99
+ * Typed as the framework `Contract | null` to satisfy the
100
+ * `MigrationPlanner` interface contract; `planSql` narrows to the SQL
101
+ * shape via `SqlMigrationPlannerPlanOptions`. Used to populate
102
+ * `describe().from` on the produced plan as
103
+ * `fromContract?.storage.storageHash ?? null`.
94
104
  */
95
- readonly fromContract?: unknown;
105
+ readonly fromContract: Contract | null;
96
106
  readonly schemaName?: string;
97
107
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
108
+ /**
109
+ * Contract space this plan applies to. Stamped onto the produced
110
+ * {@link TypeScriptRenderablePostgresMigration.spaceId} so the runner keys
111
+ * the marker row by the right space.
112
+ */
113
+ readonly spaceId: string;
98
114
  }): PostgresPlanResult {
99
- return this.planSql(options as SqlMigrationPlannerPlanOptions, options.fromHash ?? '');
115
+ return this.planSql(options as SqlMigrationPlannerPlanOptions);
100
116
  }
101
117
 
102
- emptyMigration(context: MigrationScaffoldContext): MigrationPlanWithAuthoringSurface {
103
- return new TypeScriptRenderablePostgresMigration([], {
104
- from: context.fromHash,
105
- to: context.toHash,
106
- });
118
+ emptyMigration(
119
+ context: MigrationScaffoldContext,
120
+ spaceId: string,
121
+ ): MigrationPlanWithAuthoringSurface {
122
+ return new TypeScriptRenderablePostgresMigration(
123
+ [],
124
+ {
125
+ from: context.fromHash,
126
+ to: context.toHash,
127
+ },
128
+ spaceId,
129
+ );
107
130
  }
108
131
 
109
- private planSql(options: SqlMigrationPlannerPlanOptions, fromHash: string): PostgresPlanResult {
132
+ private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {
110
133
  const schemaName = options.schemaName ?? this.config.defaultSchema;
111
134
  const policyResult = this.ensureAdditivePolicy(options.policy);
112
135
  if (policyResult) {
@@ -125,7 +148,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
125
148
  // from/to comparisons (unsafe type change, nullable tightening) are
126
149
  // inapplicable there — reconciliation falls through to
127
150
  // `mapIssueToCall`'s direct destructive handlers.
128
- fromContract: options.fromContract ?? null,
151
+ fromContract: options.fromContract,
129
152
  schemaName,
130
153
  codecHooks,
131
154
  storageTypes,
@@ -139,12 +162,31 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
139
162
  return plannerFailure(result.failure);
140
163
  }
141
164
 
165
+ // Inline `onFieldEvent`-emitted ops after structural DDL. The fixed
166
+ // ordering is `structural → added → dropped → altered`, with
167
+ // within-group sorting by `(tableName, fieldName)` so re-emits are
168
+ // byte-stable. The hook fires only at the application emitter —
169
+ // extension-space planning never reaches this helper.
170
+ const fieldEventOps = planFieldEventOperations({
171
+ priorContract: options.fromContract,
172
+ newContract: options.contract,
173
+ codecHooks,
174
+ });
175
+ // Codec-emitted calls already conform to `OpFactoryCall` — render +
176
+ // toOp + importRequirements ride directly through the same emit path
177
+ // as structural ops, no `RawSqlCall` wrap.
178
+ const calls = [...result.value.calls, ...fieldEventOps];
179
+
142
180
  return Object.freeze({
143
181
  kind: 'success' as const,
144
- plan: new TypeScriptRenderablePostgresMigration(result.value.calls, {
145
- from: fromHash,
146
- to: options.contract.storage.storageHash,
147
- }),
182
+ plan: new TypeScriptRenderablePostgresMigration(
183
+ calls,
184
+ {
185
+ from: options.fromContract?.storage.storageHash ?? null,
186
+ to: options.contract.storage.storageHash,
187
+ },
188
+ options.spaceId,
189
+ ),
148
190
  });
149
191
  }
150
192
 
@@ -1,14 +1,11 @@
1
1
  import type { Contract } from '@prisma-next/contract/types';
2
+ import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
3
  import type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';
3
4
  import { Migration as SqlMigration } from '@prisma-next/family-sql/migration';
4
5
  import type { ControlStack } from '@prisma-next/framework-components/control';
5
6
  import type { SqlStorage } from '@prisma-next/sql-contract/types';
6
7
  import { errorPostgresMigrationStackMissing } from '../errors';
7
- import {
8
- type DataTransformOptions,
9
- dataTransform,
10
- type PostgresDataTransformOperation,
11
- } from './operations/data-transform';
8
+ import { type DataTransformOptions, dataTransform } from './operations/data-transform';
12
9
  import type { PostgresPlanTargetDetails } from './planner-target-details';
13
10
 
14
11
  /**
@@ -64,7 +61,7 @@ export abstract class PostgresMigration extends SqlMigration<
64
61
  contract: TContract,
65
62
  name: string,
66
63
  options: DataTransformOptions,
67
- ): PostgresDataTransformOperation {
64
+ ): SqlMigrationPlanOperation<PostgresPlanTargetDetails> {
68
65
  if (!this.controlAdapter) {
69
66
  throw errorPostgresMigrationStackMissing();
70
67
  }
@@ -1,9 +1,32 @@
1
1
  import type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';
2
- import type { PostgresOpFactoryCall } from './op-factory-call';
2
+ import type { OpFactoryCall } from '@prisma-next/framework-components/control';
3
3
  import type { PostgresPlanTargetDetails } from './planner-target-details';
4
4
 
5
5
  type Op = SqlMigrationPlanOperation<PostgresPlanTargetDetails>;
6
6
 
7
- export function renderOps(calls: readonly PostgresOpFactoryCall[]): Op[] {
8
- return calls.map((c) => c.toOp());
7
+ /**
8
+ * Asserts an op materialised by an `OpFactoryCall` targets postgres. The
9
+ * extension surface lets any contributor emit calls, so this is the
10
+ * integration boundary where a stray non-postgres op would otherwise
11
+ * silently flow through to postgres-shaped renderers — exactly the
12
+ * place to fail loudly with op metadata (`id` + `target.id`).
13
+ */
14
+ function assertPostgresOp(
15
+ op: ReturnType<OpFactoryCall['toOp']>,
16
+ callFactoryName: string,
17
+ ): asserts op is Op {
18
+ const targetId = (op as Partial<Op>).target?.id;
19
+ if (targetId !== 'postgres') {
20
+ throw new Error(
21
+ `renderOps: expected postgres op but got target.id="${String(targetId)}" for op.id="${op.id}" (factoryName="${callFactoryName}"). An OpFactoryCall produced an op for a different target on the postgres planner path; check the call's target binding.`,
22
+ );
23
+ }
24
+ }
25
+
26
+ export function renderOps(calls: readonly OpFactoryCall[]): Op[] {
27
+ return calls.map((c) => {
28
+ const op = c.toOp();
29
+ assertPostgresOp(op, c.factoryName);
30
+ return op;
31
+ });
9
32
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Polymorphic TypeScript emitter for the Postgres migration IR.
3
3
  *
4
- * Each `PostgresOpFactoryCall` renders itself via `renderTypeScript()` and
4
+ * Each `OpFactoryCall` renders itself via `renderTypeScript()` and
5
5
  * declares its own `importRequirements()`; this file just composes the module
6
6
  * source around those contributions. The design mirrors the Mongo target's
7
7
  * `render-typescript.ts` deliberately — byte-for-byte alignment isn't required
@@ -9,14 +9,13 @@
9
9
  * shape is, so future consolidation to a framework-level helper is mechanical.
10
10
  */
11
11
 
12
+ import type { OpFactoryCall } from '@prisma-next/framework-components/control';
12
13
  import { detectScaffoldRuntime, shebangLineFor } from '@prisma-next/migration-tools/migration-ts';
13
14
  import { type ImportRequirement, jsonToTsSource, renderImports } from '@prisma-next/ts-render';
14
- import type { PostgresOpFactoryCall } from './op-factory-call';
15
15
 
16
16
  export interface RenderMigrationMeta {
17
- readonly from: string;
17
+ readonly from: string | null;
18
18
  readonly to: string;
19
- readonly kind?: string;
20
19
  readonly labels?: readonly string[];
21
20
  }
22
21
 
@@ -44,7 +43,7 @@ const BASE_IMPORTS: readonly ImportRequirement[] = [
44
43
  ];
45
44
 
46
45
  export function renderCallsToTypeScript(
47
- calls: ReadonlyArray<PostgresOpFactoryCall>,
46
+ calls: ReadonlyArray<OpFactoryCall>,
48
47
  meta: RenderMigrationMeta,
49
48
  ): string {
50
49
  const imports = buildImports(calls);
@@ -68,7 +67,7 @@ export function renderCallsToTypeScript(
68
67
  ].join('\n');
69
68
  }
70
69
 
71
- function buildImports(calls: ReadonlyArray<PostgresOpFactoryCall>): string {
70
+ function buildImports(calls: ReadonlyArray<OpFactoryCall>): string {
72
71
  const requirements: ImportRequirement[] = [...BASE_IMPORTS];
73
72
  for (const call of calls) {
74
73
  for (const req of call.importRequirements()) {
@@ -84,9 +83,6 @@ function buildDescribeMethod(meta: RenderMigrationMeta): string {
84
83
  lines.push(' return {');
85
84
  lines.push(` from: ${JSON.stringify(meta.from)},`);
86
85
  lines.push(` to: ${JSON.stringify(meta.to)},`);
87
- if (meta.kind) {
88
- lines.push(` kind: ${JSON.stringify(meta.kind)},`);
89
- }
90
86
  if (meta.labels && meta.labels.length > 0) {
91
87
  lines.push(` labels: ${jsonToTsSource(meta.labels)},`);
92
88
  }