@prisma-next/target-postgres 0.15.0-dev.8 → 0.15.0

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 (38) hide show
  1. package/dist/{control-policy-UPteqwtU.mjs → control-policy-DtbkLLGv.mjs} +72 -41
  2. package/dist/control-policy-DtbkLLGv.mjs.map +1 -0
  3. package/dist/control.mjs +3 -3
  4. package/dist/{diff-database-schema-47526Pv2.mjs → diff-database-schema-GncqMuSI.mjs} +22 -85
  5. package/dist/diff-database-schema-GncqMuSI.mjs.map +1 -0
  6. package/dist/diff-database-schema.d.mts +12 -3
  7. package/dist/diff-database-schema.d.mts.map +1 -1
  8. package/dist/diff-database-schema.mjs +1 -1
  9. package/dist/issue-planner.d.mts.map +1 -1
  10. package/dist/issue-planner.mjs +1 -1
  11. package/dist/{planner-uHcFFWkx.mjs → planner-kql24i_F.mjs} +9 -9
  12. package/dist/planner-kql24i_F.mjs.map +1 -0
  13. package/dist/planner.d.mts +1 -1
  14. package/dist/planner.d.mts.map +1 -1
  15. package/dist/planner.mjs +2 -2
  16. package/dist/{postgres-database-schema-node-DJOTywwQ.d.mts → postgres-database-schema-node-CJv5bfbs.d.mts} +2 -11
  17. package/dist/postgres-database-schema-node-CJv5bfbs.d.mts.map +1 -0
  18. package/dist/{postgres-role-schema-node-bg32e7I-.mjs → postgres-role-schema-node-Cm-j0ZGM.mjs} +2 -3
  19. package/dist/postgres-role-schema-node-Cm-j0ZGM.mjs.map +1 -0
  20. package/dist/types.d.mts +2 -3
  21. package/dist/types.mjs +3 -3
  22. package/package.json +20 -20
  23. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +19 -99
  24. package/src/core/migrations/control-policy.ts +3 -4
  25. package/src/core/migrations/diff-database-schema.ts +3 -3
  26. package/src/core/migrations/issue-planner.ts +80 -41
  27. package/src/core/migrations/planner-strategies.ts +6 -6
  28. package/src/core/migrations/planner.ts +4 -5
  29. package/src/core/migrations/verify-postgres-namespaces.ts +1 -0
  30. package/src/core/schema-ir/postgres-policy-schema-node.ts +2 -12
  31. package/src/exports/types.ts +0 -1
  32. package/dist/control-policy-UPteqwtU.mjs.map +0 -1
  33. package/dist/diff-database-schema-47526Pv2.mjs.map +0 -1
  34. package/dist/planner-uHcFFWkx.mjs.map +0 -1
  35. package/dist/postgres-database-schema-node-DJOTywwQ.d.mts.map +0 -1
  36. package/dist/postgres-role-schema-node-bg32e7I-.mjs.map +0 -1
  37. package/dist/schema-node-kinds-CrYzCR1i.d.mts +0 -30
  38. package/dist/schema-node-kinds-CrYzCR1i.d.mts.map +0 -1
package/package.json CHANGED
@@ -1,35 +1,35 @@
1
1
  {
2
2
  "name": "@prisma-next/target-postgres",
3
- "version": "0.15.0-dev.8",
3
+ "version": "0.15.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "Postgres target pack for Prisma Next",
8
8
  "dependencies": {
9
- "@prisma-next/cli": "0.15.0-dev.8",
10
- "@prisma-next/contract": "0.15.0-dev.8",
11
- "@prisma-next/errors": "0.15.0-dev.8",
12
- "@prisma-next/family-sql": "0.15.0-dev.8",
13
- "@prisma-next/framework-components": "0.15.0-dev.8",
14
- "@prisma-next/migration-tools": "0.15.0-dev.8",
15
- "@prisma-next/psl-parser": "0.15.0-dev.8",
16
- "@prisma-next/sql-contract": "0.15.0-dev.8",
17
- "@prisma-next/sql-errors": "0.15.0-dev.8",
18
- "@prisma-next/sql-operations": "0.15.0-dev.8",
19
- "@prisma-next/sql-relational-core": "0.15.0-dev.8",
20
- "@prisma-next/sql-schema-ir": "0.15.0-dev.8",
21
- "@prisma-next/ts-render": "0.15.0-dev.8",
22
- "@prisma-next/utils": "0.15.0-dev.8",
9
+ "@prisma-next/cli": "0.15.0",
10
+ "@prisma-next/contract": "0.15.0",
11
+ "@prisma-next/errors": "0.15.0",
12
+ "@prisma-next/family-sql": "0.15.0",
13
+ "@prisma-next/framework-components": "0.15.0",
14
+ "@prisma-next/migration-tools": "0.15.0",
15
+ "@prisma-next/psl-parser": "0.15.0",
16
+ "@prisma-next/sql-contract": "0.15.0",
17
+ "@prisma-next/sql-errors": "0.15.0",
18
+ "@prisma-next/sql-operations": "0.15.0",
19
+ "@prisma-next/sql-relational-core": "0.15.0",
20
+ "@prisma-next/sql-schema-ir": "0.15.0",
21
+ "@prisma-next/ts-render": "0.15.0",
22
+ "@prisma-next/utils": "0.15.0",
23
23
  "@standard-schema/spec": "^1.1.0",
24
24
  "arktype": "^2.2.2",
25
25
  "pathe": "^2.0.3"
26
26
  },
27
27
  "devDependencies": {
28
- "@prisma-next/psl-printer": "0.15.0-dev.8",
29
- "@prisma-next/sql-contract-psl": "0.15.0-dev.8",
30
- "@prisma-next/test-utils": "0.15.0-dev.8",
31
- "@prisma-next/tsconfig": "0.15.0-dev.8",
32
- "@prisma-next/tsdown": "0.15.0-dev.8",
28
+ "@prisma-next/psl-printer": "0.15.0",
29
+ "@prisma-next/sql-contract-psl": "0.15.0",
30
+ "@prisma-next/test-utils": "0.15.0",
31
+ "@prisma-next/tsconfig": "0.15.0",
32
+ "@prisma-next/tsdown": "0.15.0",
33
33
  "tsdown": "0.22.3",
34
34
  "typescript": "5.9.3",
35
35
  "vitest": "4.1.10"
@@ -1,14 +1,7 @@
1
1
  import type { ContractToSchemaIROptions } from '@prisma-next/family-sql/control';
2
2
  import { contractNamespaceToSchemaIR } from '@prisma-next/family-sql/control';
3
- import type { SchemaNodeRef } from '@prisma-next/framework-components/control';
4
3
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
5
- import {
6
- PrimaryKey,
7
- RelationalSchemaNodeKind,
8
- SqlForeignKeyIR,
9
- SqlIndexIR,
10
- SqlUniqueIR,
11
- } from '@prisma-next/sql-schema-ir/types';
4
+ import { SqlForeignKeyIR } from '@prisma-next/sql-schema-ir/types';
12
5
  import { ifDefined } from '@prisma-next/utils/defined';
13
6
  import type { PostgresRlsPolicy } from '../postgres-rls-policy';
14
7
  import type { PostgresContract } from '../postgres-schema';
@@ -19,43 +12,8 @@ import { PostgresNativeEnumSchemaNode } from '../schema-ir/postgres-native-enum-
19
12
  import { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';
20
13
  import { PostgresRoleSchemaNode } from '../schema-ir/postgres-role-schema-node';
21
14
  import { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';
22
- import { PostgresSchemaNodeKind } from '../schema-ir/schema-node-kinds';
23
15
  import { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';
24
16
 
25
- /** The database root's fixed sentinel id (`PostgresDatabaseSchemaNode#id`). */
26
- function databaseStep(): { readonly nodeKind: string; readonly id: string } {
27
- return { nodeKind: PostgresSchemaNodeKind.database, id: 'database' };
28
- }
29
-
30
- function tableDependsOn(namespaceId: string, tableName: string): SchemaNodeRef {
31
- return [
32
- databaseStep(),
33
- { nodeKind: PostgresSchemaNodeKind.namespace, id: namespaceId },
34
- { nodeKind: PostgresSchemaNodeKind.table, id: tableName },
35
- ];
36
- }
37
-
38
- function roleDependsOn(role: string): SchemaNodeRef {
39
- return [databaseStep(), { nodeKind: PostgresSchemaNodeKind.role, id: role }];
40
- }
41
-
42
- /**
43
- * The chains from a table-child object (foreign key, index, unique, primary
44
- * key) to each of the own columns it is built on, in the Postgres tree.
45
- * Dropping a covered column auto-drops the object, so the object's drop must
46
- * precede the column's; the graph derives that direction from these edges.
47
- */
48
- function columnDependsOn(
49
- namespaceId: string,
50
- tableName: string,
51
- columns: readonly string[],
52
- ): readonly SchemaNodeRef[] {
53
- return columns.map((column) => [
54
- ...tableDependsOn(namespaceId, tableName),
55
- { nodeKind: RelationalSchemaNodeKind.column, id: `column:${column}` },
56
- ]);
57
- }
58
-
59
17
  function toPolicyNode(policy: PostgresRlsPolicy, namespaceId: string): PostgresPolicySchemaNode {
60
18
  return new PostgresPolicySchemaNode({
61
19
  name: policy.name,
@@ -67,7 +25,6 @@ function toPolicyNode(policy: PostgresRlsPolicy, namespaceId: string): PostgresP
67
25
  ...ifDefined('using', policy.using),
68
26
  ...ifDefined('withCheck', policy.withCheck),
69
27
  permissive: policy.permissive,
70
- dependsOn: [tableDependsOn(namespaceId, policy.tableName), ...policy.roles.map(roleDependsOn)],
71
28
  });
72
29
  }
73
30
 
@@ -158,67 +115,30 @@ export function contractToPostgresDatabaseSchemaNode(
158
115
  // clauses, and resolves the real live DDL schema — introspected FKs
159
116
  // already carry the live schema, so this is what lets an expected FK
160
117
  // pair (by diff-node id) with its introspected counterpart.
161
- const foreignKeys = sqlTable.foreignKeys.map((fk) => {
162
- const resolvedReferencedNamespace = resolveDdlSchemaForNamespaceStorage(
163
- contract.storage,
164
- fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,
165
- );
166
- return new SqlForeignKeyIR({
167
- columns: fk.columns,
168
- referencedTable: fk.referencedTable,
169
- referencedColumns: fk.referencedColumns,
170
- referencedSchema: fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,
171
- ...ifDefined('name', fk.name),
172
- ...ifDefined('onDelete', fk.onDelete),
173
- ...ifDefined('onUpdate', fk.onUpdate),
174
- ...ifDefined('annotations', fk.annotations),
175
- resolvedReferencedNamespace,
176
- dependsOn: [
177
- tableDependsOn(resolvedReferencedNamespace, fk.referencedTable),
178
- ...columnDependsOn(ddlSchema, tableName, fk.columns),
179
- ],
180
- });
181
- });
182
- // The family stamped these children's own-column `dependsOn` with the
183
- // flat (single-schema) chain; the Postgres tree nests them under a
184
- // namespace, so re-stamp with the multi-schema chain that matches this
185
- // tree's paths. Every other field is carried through unchanged.
186
- const uniques = sqlTable.uniques.map(
187
- (u) =>
188
- new SqlUniqueIR({
189
- columns: u.columns,
190
- ...ifDefined('name', u.name),
191
- ...ifDefined('annotations', u.annotations),
192
- dependsOn: columnDependsOn(ddlSchema, tableName, u.columns),
193
- }),
194
- );
195
- const indexes = sqlTable.indexes.map(
196
- (i) =>
197
- new SqlIndexIR({
198
- columns: i.columns,
199
- unique: i.unique,
200
- ...ifDefined('name', i.name),
201
- ...ifDefined('type', i.type),
202
- ...ifDefined('options', i.options),
203
- ...ifDefined('annotations', i.annotations),
204
- dependsOn: columnDependsOn(ddlSchema, tableName, i.columns),
118
+ const foreignKeys = sqlTable.foreignKeys.map(
119
+ (fk) =>
120
+ new SqlForeignKeyIR({
121
+ columns: fk.columns,
122
+ referencedTable: fk.referencedTable,
123
+ referencedColumns: fk.referencedColumns,
124
+ referencedSchema: fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,
125
+ ...ifDefined('name', fk.name),
126
+ ...ifDefined('onDelete', fk.onDelete),
127
+ ...ifDefined('onUpdate', fk.onUpdate),
128
+ ...ifDefined('annotations', fk.annotations),
129
+ resolvedReferencedNamespace: resolveDdlSchemaForNamespaceStorage(
130
+ contract.storage,
131
+ fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,
132
+ ),
205
133
  }),
206
134
  );
207
- const primaryKey =
208
- sqlTable.primaryKey !== undefined
209
- ? new PrimaryKey({
210
- columns: sqlTable.primaryKey.columns,
211
- ...ifDefined('name', sqlTable.primaryKey.name),
212
- dependsOn: columnDependsOn(ddlSchema, tableName, sqlTable.primaryKey.columns),
213
- })
214
- : undefined;
215
135
  tables[tableName] = new PostgresTableSchemaNode({
216
136
  name: sqlTable.name,
217
137
  columns: sqlTable.columns,
218
138
  foreignKeys,
219
- uniques,
220
- indexes,
221
- ...ifDefined('primaryKey', primaryKey),
139
+ uniques: sqlTable.uniques,
140
+ indexes: sqlTable.indexes,
141
+ ...ifDefined('primaryKey', sqlTable.primaryKey),
222
142
  ...ifDefined('annotations', sqlTable.annotations),
223
143
  ...ifDefined('checks', sqlTable.checks),
224
144
  policies: policiesByTable.get(tableName) ?? [],
@@ -5,7 +5,6 @@ import type {
5
5
  SuppressionRecord,
6
6
  } from '@prisma-next/family-sql/control';
7
7
  import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
8
- import { issueOutcome } from '@prisma-next/framework-components/control';
9
8
  import { entityAt, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
10
9
  import type { SqlStorage, StorageTable } from '@prisma-next/sql-contract/types';
11
10
  import { blindCast } from '@prisma-next/utils/casts';
@@ -253,7 +252,7 @@ const POSTGRES_NODE_CREATION_FACTORY: Readonly<Record<string, string>> = Object.
253
252
  * it is not admitted as creation-class here.
254
253
  */
255
254
  function isEnablementCreationIssue(issue: SchemaDiffIssue<SqlSchemaDiffNode>): boolean {
256
- if (issueOutcome(issue) !== 'not-equal') return false;
255
+ if (issue.reason !== 'not-equal') return false;
257
256
  const { expected, actual } = issue;
258
257
  return (
259
258
  expected !== undefined &&
@@ -271,7 +270,7 @@ export function resolvePostgresNodeIssueCreationFactoryName(
271
270
  if (isEnablementCreationIssue(issue)) {
272
271
  return 'enableRowLevelSecurity';
273
272
  }
274
- if (issueOutcome(issue) !== 'not-found') return undefined;
273
+ if (issue.reason !== 'not-found') return undefined;
275
274
  const node = issue.expected ?? issue.actual;
276
275
  if (node === undefined) return undefined;
277
276
  return POSTGRES_NODE_CREATION_FACTORY[node.nodeKind];
@@ -299,7 +298,7 @@ export function resolvePostgresNodeIssueControlPolicySubject(
299
298
  >(node);
300
299
  return {
301
300
  namespaceId: resolveNamespaceIdForDdlSchema(contract, namespaceNode.schemaName),
302
- createsNewObject: issueOutcome(issue) === 'not-found',
301
+ createsNewObject: issue.reason === 'not-found',
303
302
  };
304
303
  }
305
304
 
@@ -4,7 +4,7 @@ import { buildNativeTypeExpander } from '@prisma-next/family-sql/control';
4
4
  import { classifyDiffSubjectGranularity } from '@prisma-next/family-sql/diff';
5
5
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
6
6
  import type { DiffableNode, SchemaDiffIssue } from '@prisma-next/framework-components/control';
7
- import { diffSchemas, issueOutcome } from '@prisma-next/framework-components/control';
7
+ import { diffSchemas } from '@prisma-next/framework-components/control';
8
8
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
9
9
  import type { SqlStorage } from '@prisma-next/sql-contract/types';
10
10
  import type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';
@@ -140,7 +140,7 @@ export function diffPostgresSchema(input: {
140
140
  const relationalOwned = ownedSchemaNames(expected);
141
141
  const structuralOwned = ownedSchemaNames(fullExpected);
142
142
  const issues = diffSchemas(expected, actual).filter((issue) => {
143
- if (issueOutcome(issue) !== 'not-expected') return true;
143
+ if (issue.reason !== 'not-expected') return true;
144
144
  if (isClusterScopedIssue(issue)) return true;
145
145
  const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
146
146
  const namespaceSegment = issue.path[1];
@@ -240,7 +240,7 @@ export function buildPostgresPlanDiff(input: {
240
240
  readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
241
241
  'both trees are PostgresDatabaseSchemaNodes, so every diff-issue node is a SqlSchemaDiffNode'
242
242
  >(diffSchemas(expected, paddedActual)).filter((issue) => {
243
- if (issueOutcome(issue) !== 'not-expected') return true;
243
+ if (issue.reason !== 'not-expected') return true;
244
244
  if (isClusterScopedIssue(issue)) return true;
245
245
  const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);
246
246
  const namespaceSegment = issue.path[1];
@@ -25,7 +25,6 @@ import type {
25
25
  } from '@prisma-next/family-sql/control';
26
26
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
27
27
  import type { DiffableNode, SchemaDiffIssue } from '@prisma-next/framework-components/control';
28
- import { issueOutcome, orderIssuesByDependencies } from '@prisma-next/framework-components/control';
29
28
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
30
29
  import type { SqlStorage, StorageTypeInstance } from '@prisma-next/sql-contract/types';
31
30
  import type { DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';
@@ -137,7 +136,6 @@ export interface IssuePlannerValue {
137
136
  type CallCategory =
138
137
  | 'dep'
139
138
  | 'drop'
140
- | 'dropType'
141
139
  | 'table'
142
140
  | 'rlsEnable'
143
141
  | 'rlsPolicy'
@@ -153,13 +151,6 @@ type CallCategory =
153
151
  * legacy walk-schema planner's emission order so `db init` and `db update`
154
152
  * produce byte-identical plans for the shared shape (deps → drops → tables
155
153
  * → columns → alters → PKs → uniques → indexes → FKs).
156
- *
157
- * `dropType` (DROP TYPE for a managed native enum) is its own bucket, ordered
158
- * after the general drop bucket: a type can only be dropped once every table
159
- * whose column uses it is gone. A column→enum edge would make this precise,
160
- * but `coalesceSubtreeIssues` removes the column issue under a whole-table
161
- * drop, so the edge never reaches the graph; the coarse bucket stands in until
162
- * that edge lands (the coordinate work is a later slice).
163
154
  */
164
155
  function classifyCall(call: PostgresOpFactoryCall): CallCategory {
165
156
  switch (call.factoryName) {
@@ -168,9 +159,8 @@ function classifyCall(call: PostgresOpFactoryCall): CallCategory {
168
159
  case 'createNativeEnumType':
169
160
  case 'addNativeEnumValue':
170
161
  return 'dep';
171
- case 'dropNativeEnumType':
172
- return 'dropType';
173
162
  case 'dropTable':
163
+ case 'dropNativeEnumType':
174
164
  case 'dropColumn':
175
165
  case 'dropConstraint':
176
166
  case 'dropCheckConstraint':
@@ -379,6 +369,57 @@ export function columnTypeChanged(expected: SqlColumnIR, actual: SqlColumnIR): b
379
369
  );
380
370
  }
381
371
 
372
+ // ----------------------------------------------------------------------------
373
+ // Node-keyed issue ordering (re-keys ISSUE_KIND_ORDER on nodeKind + reason)
374
+ // ----------------------------------------------------------------------------
375
+
376
+ /**
377
+ * Re-keys the legacy `ISSUE_KIND_ORDER` on `(nodeKind, reason)`, numbers
378
+ * preserved so the dependency intent stays legible. Final emission order is
379
+ * fixed downstream by `classifyCall` bucketing (dep → drop → table → column →
380
+ * recipe → alter → primaryKey → unique → index → foreignKey), so this only
381
+ * breaks ties within a bucket.
382
+ */
383
+ export function nodeIssueOrder(issue: SchemaDiffIssue): number {
384
+ const node = issueNode(issue);
385
+ if (node === undefined) return 99;
386
+ switch (node.nodeKind) {
387
+ case PostgresSchemaNodeKind.namespace:
388
+ return 1;
389
+ case PostgresSchemaNodeKind.nativeEnum:
390
+ // Creates order right after namespace creates within the 'dep' bucket
391
+ // (CREATE SCHEMA before CREATE TYPE); drops order after table drops
392
+ // within the 'drop' bucket (DROP TYPE only after its dependents left).
393
+ return issue.reason === 'not-expected' ? 17 : 2;
394
+ case RelationalSchemaNodeKind.foreignKey:
395
+ return issue.reason === 'not-expected' ? 10 : 60;
396
+ case RelationalSchemaNodeKind.unique:
397
+ return issue.reason === 'not-expected' ? 11 : 51;
398
+ case RelationalSchemaNodeKind.primaryKey:
399
+ return issue.reason === 'not-expected' ? 12 : 50;
400
+ case RelationalSchemaNodeKind.index:
401
+ return issue.reason === 'not-expected' ? 13 : 52;
402
+ case RelationalSchemaNodeKind.columnDefault:
403
+ if (issue.reason === 'not-expected') return 14;
404
+ return issue.reason === 'not-found' ? 42 : 43;
405
+ case RelationalSchemaNodeKind.column:
406
+ if (issue.reason === 'not-expected') return 15;
407
+ return issue.reason === 'not-found' ? 30 : 40;
408
+ case PostgresSchemaNodeKind.table:
409
+ return issue.reason === 'not-expected' ? 16 : 20;
410
+ case RelationalSchemaNodeKind.check:
411
+ if (issue.reason === 'not-found') return 53;
412
+ return issue.reason === 'not-expected' ? 55 : 54;
413
+ default:
414
+ return 99;
415
+ }
416
+ }
417
+
418
+ /** Deterministic tiebreak within an order bucket: the diff path already encodes schema → table → child. */
419
+ export function nodeIssueKey(issue: SchemaDiffIssue): string {
420
+ return issue.path.join(' ');
421
+ }
422
+
382
423
  // ----------------------------------------------------------------------------
383
424
  // Subtree coalescing (the planner's responsibility per the differ's contract)
384
425
  // ----------------------------------------------------------------------------
@@ -396,7 +437,7 @@ export function coalesceSubtreeIssues<TNode extends DiffableNode = DiffableNode>
396
437
  issues: readonly SchemaDiffIssue<TNode>[],
397
438
  ): readonly SchemaDiffIssue<TNode>[] {
398
439
  const collapsingPaths = issues
399
- .filter((issue) => issueOutcome(issue) !== 'not-equal')
440
+ .filter((issue) => issue.reason === 'not-found' || issue.reason === 'not-expected')
400
441
  .map((issue) => issue.path);
401
442
  if (collapsingPaths.length === 0) return issues;
402
443
  return issues.filter(
@@ -560,14 +601,14 @@ function mapNativeEnumNodeIssue(
560
601
  );
561
602
  }
562
603
  const schemaName = emissionSchemaName(ctx, ddlSchemaName);
563
- if (issueOutcome(issue) === 'not-found') {
604
+ if (issue.reason === 'not-found') {
564
605
  const expected = blindCast<
565
606
  PostgresNativeEnumSchemaNode,
566
607
  'a not-found native-enum issue always carries the expected PostgresNativeEnumSchemaNode'
567
608
  >(issue.expected);
568
609
  return ok([new CreateNativeEnumTypeCall(schemaName, expected.typeName, expected.members)]);
569
610
  }
570
- if (issueOutcome(issue) === 'not-expected') {
611
+ if (issue.reason === 'not-expected') {
571
612
  const actual = blindCast<
572
613
  PostgresNativeEnumSchemaNode,
573
614
  'a not-expected native-enum issue always carries the actual PostgresNativeEnumSchemaNode'
@@ -613,14 +654,14 @@ function mapTableNodeIssue(
613
654
  ddlSchemaName: string,
614
655
  codecHooks: ReadonlyMap<string, CodecControlHooks>,
615
656
  ): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
616
- if (issueOutcome(issue) === 'not-found') {
657
+ if (issue.reason === 'not-found') {
617
658
  const table = blindCast<
618
659
  PostgresTableSchemaNode,
619
660
  'a not-found table issue always carries the expected PostgresTableSchemaNode'
620
661
  >(issue.expected);
621
662
  return ok(buildCreateTableCallsFromNode(schemaName, ddlSchemaName, table, codecHooks));
622
663
  }
623
- if (issueOutcome(issue) === 'not-expected') {
664
+ if (issue.reason === 'not-expected') {
624
665
  const table = blindCast<
625
666
  PostgresTableSchemaNode,
626
667
  'a not-expected table issue always carries the actual PostgresTableSchemaNode'
@@ -662,7 +703,7 @@ function mapColumnNodeIssue(
662
703
  tableName: string,
663
704
  codecHooks: ReadonlyMap<string, CodecControlHooks>,
664
705
  ): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
665
- if (issueOutcome(issue) === 'not-found') {
706
+ if (issue.reason === 'not-found') {
666
707
  const column = blindCast<
667
708
  SqlColumnIR,
668
709
  'a not-found column issue always carries the expected column node'
@@ -671,7 +712,7 @@ function mapColumnNodeIssue(
671
712
  new AddColumnCall(schemaName, tableName, renderColumnDdl(column.name, column, codecHooks)),
672
713
  ]);
673
714
  }
674
- if (issueOutcome(issue) === 'not-expected') {
715
+ if (issue.reason === 'not-expected') {
675
716
  const column = blindCast<
676
717
  SqlColumnIR,
677
718
  'a not-expected column issue always carries the actual column node'
@@ -714,7 +755,7 @@ function mapColumnDefaultNodeIssue(
714
755
  tableName: string,
715
756
  columnName: string,
716
757
  ): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
717
- if (issueOutcome(issue) === 'not-expected') {
758
+ if (issue.reason === 'not-expected') {
718
759
  return ok([new DropDefaultCall(schemaName, tableName, columnName)]);
719
760
  }
720
761
  // not-found (SET DEFAULT, additive) or not-equal (SET DEFAULT, widening).
@@ -731,7 +772,7 @@ function mapColumnDefaultNodeIssue(
731
772
  tableName,
732
773
  columnName,
733
774
  defaultSql,
734
- issueOutcome(issue) === 'not-equal' ? 'widening' : 'additive',
775
+ issue.reason === 'not-equal' ? 'widening' : 'additive',
735
776
  ),
736
777
  ]);
737
778
  }
@@ -741,7 +782,7 @@ function mapPrimaryKeyNodeIssue(
741
782
  schemaName: string,
742
783
  tableName: string,
743
784
  ): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
744
- if (issueOutcome(issue) === 'not-found') {
785
+ if (issue.reason === 'not-found') {
745
786
  const pk = blindCast<
746
787
  { readonly columns: readonly string[]; readonly name?: string },
747
788
  'a not-found primary-key issue always carries the expected PrimaryKey node'
@@ -749,7 +790,7 @@ function mapPrimaryKeyNodeIssue(
749
790
  const constraintName = pk.name ?? `${tableName}_pkey`;
750
791
  return ok([new AddPrimaryKeyCall(schemaName, tableName, constraintName, [...pk.columns])]);
751
792
  }
752
- if (issueOutcome(issue) === 'not-expected') {
793
+ if (issue.reason === 'not-expected') {
753
794
  const pk = blindCast<
754
795
  { readonly name?: string },
755
796
  'a not-expected primary-key issue always carries the actual PrimaryKey node'
@@ -766,14 +807,14 @@ function mapForeignKeyNodeIssue(
766
807
  schemaName: string,
767
808
  tableName: string,
768
809
  ): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
769
- if (issueOutcome(issue) === 'not-found') {
810
+ if (issue.reason === 'not-found') {
770
811
  const fk = blindCast<
771
812
  SqlForeignKeyIR,
772
813
  'a not-found foreign-key issue always carries the expected foreign-key node'
773
814
  >(issue.expected);
774
815
  return ok([new AddForeignKeyCall(schemaName, tableName, fkSpecFromNode(fk, tableName))]);
775
816
  }
776
- if (issueOutcome(issue) === 'not-expected') {
817
+ if (issue.reason === 'not-expected') {
777
818
  const fk = blindCast<
778
819
  SqlForeignKeyIR,
779
820
  'a not-expected foreign-key issue always carries the actual foreign-key node'
@@ -789,7 +830,7 @@ function mapUniqueNodeIssue(
789
830
  schemaName: string,
790
831
  tableName: string,
791
832
  ): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
792
- if (issueOutcome(issue) === 'not-found') {
833
+ if (issue.reason === 'not-found') {
793
834
  const unique = blindCast<
794
835
  SqlUniqueIR,
795
836
  'a not-found unique issue always carries the expected unique node'
@@ -797,7 +838,7 @@ function mapUniqueNodeIssue(
797
838
  const name = unique.name ?? `${tableName}_${unique.columns.join('_')}_key`;
798
839
  return ok([new AddUniqueCall(schemaName, tableName, name, [...unique.columns])]);
799
840
  }
800
- if (issueOutcome(issue) === 'not-expected') {
841
+ if (issue.reason === 'not-expected') {
801
842
  const unique = blindCast<
802
843
  SqlUniqueIR,
803
844
  'a not-expected unique issue always carries the actual unique node'
@@ -813,7 +854,7 @@ function mapIndexNodeIssue(
813
854
  schemaName: string,
814
855
  tableName: string,
815
856
  ): Result<readonly PostgresOpFactoryCall[], SqlPlannerConflict> {
816
- if (issueOutcome(issue) === 'not-found') {
857
+ if (issue.reason === 'not-found') {
817
858
  const index = blindCast<
818
859
  SqlIndexIR,
819
860
  'a not-found index issue always carries the expected index node'
@@ -824,7 +865,7 @@ function mapIndexNodeIssue(
824
865
  if (index.options !== undefined) extras.options = index.options;
825
866
  return ok([new CreateIndexCall(schemaName, tableName, indexName, [...index.columns], extras)]);
826
867
  }
827
- if (issueOutcome(issue) === 'not-expected') {
868
+ if (issue.reason === 'not-expected') {
828
869
  const index = blindCast<
829
870
  SqlIndexIR,
830
871
  'a not-expected index issue always carries the actual index node'
@@ -844,7 +885,7 @@ function mapCheckNodeIssue(
844
885
  // the default mapper handles directly; check_missing / check_mismatch are
845
886
  // consumed by `checkConstraintPlanCallStrategy` (drop+recreate), so reaching
846
887
  // here for them means the strategy did not run — a conflict.
847
- if (issueOutcome(issue) === 'not-expected') {
888
+ if (issue.reason === 'not-expected') {
848
889
  const check = blindCast<
849
890
  { readonly name: string },
850
891
  'a not-expected check issue always carries the actual check node'
@@ -861,8 +902,8 @@ function mapCheckNodeIssue(
861
902
 
862
903
  /**
863
904
  * Maps one node-typed diff issue to its migration call(s), dispatching on the
864
- * node's `nodeKind` and the issue's presence-derived `issueOutcome`, reading
865
- * nodes and resolving column DDL from `codecRef` via `column-ddl-rendering.ts`.
905
+ * node's `nodeKind` + `issue.reason`, reading nodes and resolving column DDL
906
+ * from `codecRef` via `column-ddl-rendering.ts`.
866
907
  */
867
908
  export function mapNodeIssueToCall(
868
909
  issue: SchemaDiffIssue,
@@ -878,7 +919,7 @@ export function mapNodeIssueToCall(
878
919
  );
879
920
  }
880
921
  if (node.nodeKind === PostgresSchemaNodeKind.namespace) {
881
- if (issueOutcome(issue) !== 'not-found') {
922
+ if (issue.reason !== 'not-found') {
882
923
  return notOk(
883
924
  nodeConflict('unsupportedOperation', `Unexpected namespace drift: ${issue.path.join('/')}`),
884
925
  );
@@ -1011,12 +1052,13 @@ export function planIssues(
1011
1052
  }
1012
1053
  }
1013
1054
 
1014
- // Dependency-graph order: a topological sort over the issues' `dependsOn`
1015
- // cross-links and containment edges (the ordering law drives edge direction
1016
- // by presence), path-tiebroken for determinism. `classifyCall` bucketing
1017
- // downstream fixes the coarse cross-entity DDL sequence; this settles the
1018
- // order within each bucket (notably the teardown order among drops).
1019
- const sorted = orderIssuesByDependencies(remaining);
1055
+ const sorted = [...remaining].sort((a, b) => {
1056
+ const kindDelta = nodeIssueOrder(a) - nodeIssueOrder(b);
1057
+ if (kindDelta !== 0) return kindDelta;
1058
+ const keyA = nodeIssueKey(a);
1059
+ const keyB = nodeIssueKey(b);
1060
+ return keyA < keyB ? -1 : keyA > keyB ? 1 : 0;
1061
+ });
1020
1062
 
1021
1063
  const defaultCalls: PostgresOpFactoryCall[] = [];
1022
1064
  const conflicts: SqlPlannerConflict[] = [];
@@ -1064,9 +1106,6 @@ export function planIssues(
1064
1106
  const calls: PostgresOpFactoryCall[] = [
1065
1107
  ...byCategory('dep'),
1066
1108
  ...byCategory('drop'),
1067
- // DROP TYPE after every table drop — a managed enum type can only go once
1068
- // no live column still uses it.
1069
- ...byCategory('dropType'),
1070
1109
  ...byCategory('table'),
1071
1110
  ...byCategory('column'),
1072
1111
  ...gatedRecipe,
@@ -35,7 +35,6 @@ import type {
35
35
  import { resolveValueSetValues } from '@prisma-next/family-sql/control';
36
36
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
37
37
  import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
38
- import { issueOutcome } from '@prisma-next/framework-components/control';
39
38
  import { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
40
39
  import {
41
40
  type SqlStorage,
@@ -199,7 +198,7 @@ export const notNullBackfillCallStrategy: CallMigrationStrategy = (issues, ctx)
199
198
  const calls: PostgresOpFactoryCall[] = [];
200
199
 
201
200
  for (const issue of issues) {
202
- if (issueOutcome(issue) !== 'not-found') continue;
201
+ if (issue.reason !== 'not-found') continue;
203
202
  const node = issueNode(issue);
204
203
  if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) continue;
205
204
  const expected = blindCast<
@@ -264,7 +263,7 @@ export const typeChangeCallStrategy: CallMigrationStrategy = (issues, ctx) => {
264
263
  const calls: PostgresOpFactoryCall[] = [];
265
264
 
266
265
  for (const issue of issues) {
267
- if (issueOutcome(issue) !== 'not-equal') continue;
266
+ if (issue.reason !== 'not-equal') continue;
268
267
  const pair = columnNodePair(issue);
269
268
  if (pair === undefined) continue;
270
269
  const { expected, actual } = pair;
@@ -358,7 +357,7 @@ export const nullableTighteningCallStrategy: CallMigrationStrategy = (issues, ct
358
357
  const calls: PostgresOpFactoryCall[] = [];
359
358
 
360
359
  for (const issue of issues) {
361
- if (issueOutcome(issue) !== 'not-equal') continue;
360
+ if (issue.reason !== 'not-equal') continue;
362
361
  const pair = columnNodePair(issue);
363
362
  if (pair === undefined) continue;
364
363
  const { expected, actual } = pair;
@@ -591,7 +590,7 @@ export const notNullAddColumnCallStrategy: CallMigrationStrategy = (issues, ctx)
591
590
  const mutableStorageTypes = ctx.storageTypes as Record<string, StorageTypeInstance>;
592
591
 
593
592
  for (const issue of issues) {
594
- if (issueOutcome(issue) !== 'not-found') continue;
593
+ if (issue.reason !== 'not-found') continue;
595
594
  const node = issueNode(issue);
596
595
  if (node === undefined || node.nodeKind !== RelationalSchemaNodeKind.column) continue;
597
596
  const expected = blindCast<
@@ -686,7 +685,8 @@ function canUseSharedTemporaryDefaultStrategy(options: {
686
685
  }
687
686
 
688
687
  for (const foreignKey of table.foreignKeys) {
689
- if (!foreignKey.source.columns.includes(columnName)) continue;
688
+ if (foreignKey.constraint === false || !foreignKey.source.columns.includes(columnName))
689
+ continue;
690
690
  if (!schemaLookup || !hasForeignKey(schemaLookup, foreignKey)) return false;
691
691
  }
692
692