@prisma-next/target-postgres 0.5.0-dev.6 → 0.5.0-dev.60

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 (81) hide show
  1. package/dist/{codec-ids-CojIXVf9.mjs → codec-ids-ckQX9Kcg.mjs} +3 -2
  2. package/dist/{codec-ids-CojIXVf9.mjs.map → codec-ids-ckQX9Kcg.mjs.map} +1 -1
  3. package/dist/codec-ids.d.mts +2 -1
  4. package/dist/codec-ids.d.mts.map +1 -1
  5. package/dist/codec-ids.mjs +2 -2
  6. package/dist/codec-types.d.mts +1 -1
  7. package/dist/codec-types.mjs +2 -1
  8. package/dist/{codecs-D-F2KJqt.d.mts → codecs-B03dFv94.d.mts} +64 -30
  9. package/dist/codecs-B03dFv94.d.mts.map +1 -0
  10. package/dist/{codecs-BoahtY_Q.mjs → codecs-D0oXyJIH.mjs} +24 -104
  11. package/dist/codecs-D0oXyJIH.mjs.map +1 -0
  12. package/dist/codecs.d.mts +1 -1
  13. package/dist/codecs.mjs +2 -1
  14. package/dist/control.mjs +21 -63
  15. package/dist/control.mjs.map +1 -1
  16. package/dist/{data-transform-VfEGzXWt.mjs → data-transform-Be_i_DBc.mjs} +24 -5
  17. package/dist/data-transform-Be_i_DBc.mjs.map +1 -0
  18. package/dist/data-transform-CrpmG4uJ.d.mts +39 -0
  19. package/dist/data-transform-CrpmG4uJ.d.mts.map +1 -0
  20. package/dist/data-transform.d.mts +1 -1
  21. package/dist/data-transform.mjs +1 -1
  22. package/dist/{descriptor-meta-BVoVtyp-.mjs → descriptor-meta-Ieg1XLOs.mjs} +7 -12
  23. package/dist/descriptor-meta-Ieg1XLOs.mjs.map +1 -0
  24. package/dist/issue-planner.d.mts +1 -1
  25. package/dist/migration.d.mts +2 -2
  26. package/dist/migration.mjs +2 -2
  27. package/dist/op-factory-call-C3bWXKSP.d.mts.map +1 -1
  28. package/dist/pack.d.mts +28 -9
  29. package/dist/pack.d.mts.map +1 -1
  30. package/dist/pack.mjs +1 -1
  31. package/dist/{planner-CLUvVhUN.mjs → planner-Cm-ZLutk.mjs} +6 -6
  32. package/dist/planner-Cm-ZLutk.mjs.map +1 -0
  33. package/dist/{planner-produced-postgres-migration-DSSPq8QS.mjs → planner-produced-postgres-migration-Bi-RWO4-.mjs} +3 -4
  34. package/dist/{planner-produced-postgres-migration-DSSPq8QS.mjs.map → planner-produced-postgres-migration-Bi-RWO4-.mjs.map} +1 -1
  35. package/dist/{planner-produced-postgres-migration-CRRTno6Z.d.mts → planner-produced-postgres-migration-M3EfhWSS.d.mts} +2 -2
  36. package/dist/planner-produced-postgres-migration-M3EfhWSS.d.mts.map +1 -0
  37. package/dist/planner-produced-postgres-migration.d.mts +3 -2
  38. package/dist/planner-produced-postgres-migration.mjs +1 -1
  39. package/dist/planner.d.mts +17 -10
  40. package/dist/planner.d.mts.map +1 -1
  41. package/dist/planner.mjs +1 -1
  42. package/dist/{postgres-migration-BjA3Zmts.d.mts → postgres-migration-BFjbb25b.d.mts} +4 -3
  43. package/dist/postgres-migration-BFjbb25b.d.mts.map +1 -0
  44. package/dist/{postgres-migration-qtmtbONe.mjs → postgres-migration-BS9vQW97.mjs} +2 -2
  45. package/dist/postgres-migration-BS9vQW97.mjs.map +1 -0
  46. package/dist/{render-typescript-1rF_SB4g.mjs → render-typescript-Co3Emwgz.mjs} +1 -2
  47. package/dist/render-typescript-Co3Emwgz.mjs.map +1 -0
  48. package/dist/render-typescript.d.mts +1 -2
  49. package/dist/render-typescript.d.mts.map +1 -1
  50. package/dist/render-typescript.mjs +1 -1
  51. package/dist/runtime.mjs +1 -1
  52. package/dist/{statement-builders-BPnmt6wx.mjs → statement-builders-CHqCtSfe.mjs} +13 -8
  53. package/dist/statement-builders-CHqCtSfe.mjs.map +1 -0
  54. package/dist/statement-builders.d.mts +10 -3
  55. package/dist/statement-builders.d.mts.map +1 -1
  56. package/dist/statement-builders.mjs +2 -2
  57. package/package.json +15 -14
  58. package/src/core/authoring.ts +5 -11
  59. package/src/core/codec-ids.ts +1 -0
  60. package/src/core/codecs.ts +53 -40
  61. package/src/core/migrations/operations/data-transform.ts +86 -21
  62. package/src/core/migrations/planner-produced-postgres-migration.ts +0 -1
  63. package/src/core/migrations/planner.ts +17 -11
  64. package/src/core/migrations/postgres-migration.ts +3 -6
  65. package/src/core/migrations/render-typescript.ts +1 -5
  66. package/src/core/migrations/runner.ts +43 -112
  67. package/src/core/migrations/statement-builders.ts +22 -6
  68. package/src/exports/statement-builders.ts +1 -1
  69. package/dist/codecs-BoahtY_Q.mjs.map +0 -1
  70. package/dist/codecs-D-F2KJqt.d.mts.map +0 -1
  71. package/dist/data-transform-CxFRBIUp.d.mts +0 -32
  72. package/dist/data-transform-CxFRBIUp.d.mts.map +0 -1
  73. package/dist/data-transform-VfEGzXWt.mjs.map +0 -1
  74. package/dist/descriptor-meta-BVoVtyp-.mjs.map +0 -1
  75. package/dist/planner-CLUvVhUN.mjs.map +0 -1
  76. package/dist/planner-produced-postgres-migration-CRRTno6Z.d.mts.map +0 -1
  77. package/dist/postgres-migration-BjA3Zmts.d.mts.map +0 -1
  78. package/dist/postgres-migration-qtmtbONe.mjs.map +0 -1
  79. package/dist/render-typescript-1rF_SB4g.mjs.map +0 -1
  80. package/dist/statement-builders-BPnmt6wx.mjs.map +0 -1
  81. package/src/core/json-schema-type-expression.ts +0 -131
@@ -19,6 +19,7 @@ import { type as arktype } from 'arktype';
19
19
  import {
20
20
  PG_BIT_CODEC_ID,
21
21
  PG_BOOL_CODEC_ID,
22
+ PG_BYTEA_CODEC_ID,
22
23
  PG_CHAR_CODEC_ID,
23
24
  PG_ENUM_CODEC_ID,
24
25
  PG_FLOAT_CODEC_ID,
@@ -40,7 +41,6 @@ import {
40
41
  PG_VARBIT_CODEC_ID,
41
42
  PG_VARCHAR_CODEC_ID,
42
43
  } from './codec-ids';
43
- import { renderTypeScriptTypeFromJsonSchema } from './json-schema-type-expression';
44
44
 
45
45
  const lengthParamsSchema = arktype({
46
46
  length: 'number.integer > 0',
@@ -85,19 +85,13 @@ function renderPrecision(typeName: string, typeParams: Record<string, unknown>):
85
85
  return `${typeName}<${precision}>`;
86
86
  }
87
87
 
88
- function renderJsonOutputType(typeParams: Record<string, unknown>): string {
89
- const typeName = typeParams['type'];
90
- if (typeof typeName === 'string' && typeName.trim().length > 0) {
91
- return typeName.trim();
92
- }
93
- const schema = typeParams['schemaJson'];
94
- if (schema && typeof schema === 'object') {
95
- return renderTypeScriptTypeFromJsonSchema(schema);
96
- }
97
- throw new Error(
98
- `renderOutputType: JSON codec typeParams must contain "type" (string) or "schemaJson" (object), got keys: ${Object.keys(typeParams).join(', ')}`,
99
- );
100
- }
88
+ // Phase C: postgres' raw json/jsonb codecs no longer carry a
89
+ // `renderOutputType` slot — the schema-typed JSON surface that drove
90
+ // `typeParams: { schemaJson, type? }` retired in favor of the per-library
91
+ // extension package (`@prisma-next/extension-arktype-json`). Untyped
92
+ // json/jsonb columns have no typeParams; the framework emit path falls
93
+ // through to the generic `CodecTypes['pg/jsonb@1']['output']` accessor
94
+ // (which resolves to `JsonValue` via the codec-types map).
101
95
 
102
96
  function aliasCodec<
103
97
  Id extends string,
@@ -339,22 +333,15 @@ const pgFloat8Codec = codec({
339
333
  const pgTimestampCodec = codec<
340
334
  typeof PG_TIMESTAMP_CODEC_ID,
341
335
  readonly ['equality', 'order'],
342
- string | Date,
343
- string | Date
336
+ Date,
337
+ Date
344
338
  >({
345
339
  typeId: PG_TIMESTAMP_CODEC_ID,
346
340
  targetTypes: ['timestamp'],
347
341
  traits: ['equality', 'order'],
348
- encode: (value: string | Date): string => {
349
- if (value instanceof Date) return value.toISOString();
350
- if (typeof value === 'string') return value;
351
- return String(value);
352
- },
353
- decode: (wire: string | Date): string => {
354
- if (wire instanceof Date) return wire.toISOString();
355
- return wire;
356
- },
357
- encodeJson: (value: string | Date) => (value instanceof Date ? value.toISOString() : value),
342
+ encode: (value: Date): Date => value,
343
+ decode: (wire: Date): Date => wire,
344
+ encodeJson: (value: Date) => value.toISOString(),
358
345
  decodeJson: (json) => {
359
346
  if (typeof json !== 'string') {
360
347
  throw new Error(`Expected ISO date string for pg/timestamp@1, got ${typeof json}`);
@@ -381,22 +368,15 @@ const pgTimestampCodec = codec<
381
368
  const pgTimestamptzCodec = codec<
382
369
  typeof PG_TIMESTAMPTZ_CODEC_ID,
383
370
  readonly ['equality', 'order'],
384
- string | Date,
385
- string | Date
371
+ Date,
372
+ Date
386
373
  >({
387
374
  typeId: PG_TIMESTAMPTZ_CODEC_ID,
388
375
  targetTypes: ['timestamptz'],
389
376
  traits: ['equality', 'order'],
390
- encode: (value: string | Date): string => {
391
- if (value instanceof Date) return value.toISOString();
392
- if (typeof value === 'string') return value;
393
- return String(value);
394
- },
395
- decode: (wire: string | Date): string => {
396
- if (wire instanceof Date) return wire.toISOString();
397
- return wire;
398
- },
399
- encodeJson: (value: string | Date) => (value instanceof Date ? value.toISOString() : value),
377
+ encode: (value: Date): Date => value,
378
+ decode: (wire: Date): Date => wire,
379
+ encodeJson: (value: Date) => value.toISOString(),
400
380
  decodeJson: (json) => {
401
381
  if (typeof json !== 'string') {
402
382
  throw new Error(`Expected ISO date string for pg/timestamptz@1, got ${typeof json}`);
@@ -523,6 +503,40 @@ const pgVarbitCodec = codec<
523
503
  },
524
504
  });
525
505
 
506
+ const pgByteaCodec = codec({
507
+ typeId: PG_BYTEA_CODEC_ID,
508
+ targetTypes: ['bytea'],
509
+ traits: ['equality'],
510
+ encode: (value: Uint8Array): Uint8Array => value,
511
+ decode: (wire: Uint8Array): Uint8Array =>
512
+ // Postgres node drivers commonly return Buffer instances (which extend
513
+ // Uint8Array) — normalize to a plain Uint8Array view so engine-agnostic
514
+ // consumers don't accidentally observe Buffer-specific APIs.
515
+ wire instanceof Uint8Array && wire.constructor === Uint8Array
516
+ ? wire
517
+ : new Uint8Array(wire.buffer, wire.byteOffset, wire.byteLength),
518
+ encodeJson: (value: Uint8Array): string => Buffer.from(value).toString('base64'),
519
+ decodeJson: (json): Uint8Array => {
520
+ if (typeof json !== 'string') {
521
+ throw new Error(`Expected base64 string for pg/bytea@1, got ${typeof json}`);
522
+ }
523
+ const decoded = Buffer.from(json, 'base64');
524
+ if (decoded.toString('base64') !== json) {
525
+ throw new Error(`Invalid base64 string for pg/bytea@1 (length: ${json.length})`);
526
+ }
527
+ return new Uint8Array(decoded);
528
+ },
529
+ meta: {
530
+ db: {
531
+ sql: {
532
+ postgres: {
533
+ nativeType: 'bytea',
534
+ },
535
+ },
536
+ },
537
+ },
538
+ });
539
+
526
540
  const pgEnumCodec = codec({
527
541
  typeId: PG_ENUM_CODEC_ID,
528
542
  targetTypes: ['enum'],
@@ -576,7 +590,6 @@ const pgJsonCodec = codec({
576
590
  encode: (value: string | JsonValue): string => JSON.stringify(value),
577
591
  decode: (wire: string | JsonValue): JsonValue =>
578
592
  typeof wire === 'string' ? JSON.parse(wire) : wire,
579
- renderOutputType: renderJsonOutputType,
580
593
  meta: {
581
594
  db: {
582
595
  sql: {
@@ -595,7 +608,6 @@ const pgJsonbCodec = codec({
595
608
  encode: (value: string | JsonValue): string => JSON.stringify(value),
596
609
  decode: (wire: string | JsonValue): JsonValue =>
597
610
  typeof wire === 'string' ? JSON.parse(wire) : wire,
598
- renderOutputType: renderJsonOutputType,
599
611
  meta: {
600
612
  db: {
601
613
  sql: {
@@ -633,6 +645,7 @@ const codecs = defineCodecs()
633
645
  .add('bool', pgBoolCodec)
634
646
  .add('bit', pgBitCodec)
635
647
  .add('bit varying', pgVarbitCodec)
648
+ .add('bytea', pgByteaCodec)
636
649
  .add('interval', pgIntervalCodec)
637
650
  .add('enum', pgEnumCodec)
638
651
  .add('json', pgJsonCodec)
@@ -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
 
@@ -60,7 +60,6 @@ export class TypeScriptRenderablePostgresMigration
60
60
  return renderCallsToTypeScript(this.#calls, {
61
61
  from: this.#meta.from,
62
62
  to: this.#meta.to,
63
- ...ifDefined('kind', this.#meta.kind),
64
63
  ...ifDefined('labels', this.#meta.labels),
65
64
  });
66
65
  }
@@ -1,3 +1,4 @@
1
+ import type { Contract } from '@prisma-next/contract/types';
1
2
  import type {
2
3
  MigrationOperationPolicy,
3
4
  SqlMigrationPlannerPlanOptions,
@@ -83,20 +84,25 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
83
84
  readonly contract: unknown;
84
85
  readonly schema: unknown;
85
86
  readonly policy: MigrationOperationPolicy;
86
- readonly fromHash?: string;
87
87
  /**
88
88
  * 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.
89
+ * at), or `null` for reconciliation flows. Only `migration plan` ever
90
+ * supplies a non-null value; `db update` / `db init` reconcile against
91
+ * the live schema and pass `null`. When present alongside the
92
+ * `'data'` operation class, strategies that need from/to column-shape
93
+ * comparisons (unsafe type change, nullability tightening) activate.
94
+ *
95
+ * Typed as the framework `Contract | null` to satisfy the
96
+ * `MigrationPlanner` interface contract; `planSql` narrows to the SQL
97
+ * shape via `SqlMigrationPlannerPlanOptions`. Used to populate
98
+ * `describe().from` on the produced plan as
99
+ * `fromContract?.storage.storageHash ?? null`.
94
100
  */
95
- readonly fromContract?: unknown;
101
+ readonly fromContract: Contract | null;
96
102
  readonly schemaName?: string;
97
103
  readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;
98
104
  }): PostgresPlanResult {
99
- return this.planSql(options as SqlMigrationPlannerPlanOptions, options.fromHash ?? '');
105
+ return this.planSql(options as SqlMigrationPlannerPlanOptions);
100
106
  }
101
107
 
102
108
  emptyMigration(context: MigrationScaffoldContext): MigrationPlanWithAuthoringSurface {
@@ -106,7 +112,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
106
112
  });
107
113
  }
108
114
 
109
- private planSql(options: SqlMigrationPlannerPlanOptions, fromHash: string): PostgresPlanResult {
115
+ private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {
110
116
  const schemaName = options.schemaName ?? this.config.defaultSchema;
111
117
  const policyResult = this.ensureAdditivePolicy(options.policy);
112
118
  if (policyResult) {
@@ -125,7 +131,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
125
131
  // from/to comparisons (unsafe type change, nullable tightening) are
126
132
  // inapplicable there — reconciliation falls through to
127
133
  // `mapIssueToCall`'s direct destructive handlers.
128
- fromContract: options.fromContract ?? null,
134
+ fromContract: options.fromContract,
129
135
  schemaName,
130
136
  codecHooks,
131
137
  storageTypes,
@@ -142,7 +148,7 @@ export class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgr
142
148
  return Object.freeze({
143
149
  kind: 'success' as const,
144
150
  plan: new TypeScriptRenderablePostgresMigration(result.value.calls, {
145
- from: fromHash,
151
+ from: options.fromContract?.storage.storageHash ?? null,
146
152
  to: options.contract.storage.storageHash,
147
153
  }),
148
154
  });
@@ -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
  }
@@ -14,9 +14,8 @@ import { type ImportRequirement, jsonToTsSource, renderImports } from '@prisma-n
14
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
 
@@ -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
  }