@prisma-next/target-postgres 0.14.0-dev.56 → 0.14.0-dev.58

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.
package/dist/control.mjs CHANGED
@@ -2,10 +2,10 @@ import { t as PostgresNativeEnum } from "./postgres-native-enum-xdiqmBf_.mjs";
2
2
  import { t as postgresTargetDescriptorMeta } from "./descriptor-meta-BOcKPaLE.mjs";
3
3
  import { i as postgresDiffSubjectGranularity, r as postgresDiffSubjectEntityKind } from "./postgres-table-schema-node-9FCtCUz2.mjs";
4
4
  import { i as PostgresDatabaseSchemaNode } from "./postgres-role-schema-node-BiSTCTNg.mjs";
5
- import { n as diffPostgresSchema, r as contractToPostgresDatabaseSchemaNode } from "./diff-database-schema-CWb-jeLy.mjs";
5
+ import { n as diffPostgresSchema, r as contractToPostgresDatabaseSchemaNode } from "./diff-database-schema-CtSK3us9.mjs";
6
6
  import { r as renderDefaultLiteral } from "./planner-ddl-builders-B8Nn6nHN.mjs";
7
7
  import { n as PostgresContractSerializer } from "./postgres-contract-view-Dg-N7KA7.mjs";
8
- import { t as createPostgresMigrationPlanner } from "./planner-BryLk0Ie.mjs";
8
+ import { t as createPostgresMigrationPlanner } from "./planner-jocGHxIR.mjs";
9
9
  import { UNBOUND_NAMESPACE_ID, coordinateKey, elementCoordinates, isPlainRecord } from "@prisma-next/framework-components/ir";
10
10
  import { blindCast } from "@prisma-next/utils/casts";
11
11
  import { buildNativeTypeExpander, runnerFailure, runnerSuccess } from "@prisma-next/family-sql/control";
@@ -8,7 +8,7 @@ import { blindCast } from "@prisma-next/utils/casts";
8
8
  import { buildNativeTypeExpander, contractNamespaceToSchemaIR } from "@prisma-next/family-sql/control";
9
9
  import { ifDefined } from "@prisma-next/utils/defined";
10
10
  import { SqlForeignKeyIR } from "@prisma-next/sql-schema-ir/types";
11
- import { classifyDiffSubjectGranularity, resolveSemanticSatisfaction } from "@prisma-next/family-sql/diff";
11
+ import { classifyDiffSubjectGranularity } from "@prisma-next/family-sql/diff";
12
12
  import { diffSchemas } from "@prisma-next/framework-components/control";
13
13
  //#region src/core/migrations/contract-to-postgres-database-schema-node.ts
14
14
  function toPolicyNode(policy, namespaceId) {
@@ -73,7 +73,7 @@ function contractToPostgresDatabaseSchemaNode(contract, options) {
73
73
  columns: fk.columns,
74
74
  referencedTable: fk.referencedTable,
75
75
  referencedColumns: fk.referencedColumns,
76
- ...ifDefined("referencedSchema", fk.referencedSchema),
76
+ referencedSchema: fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,
77
77
  ...ifDefined("name", fk.name),
78
78
  ...ifDefined("onDelete", fk.onDelete),
79
79
  ...ifDefined("onUpdate", fk.onUpdate),
@@ -127,60 +127,6 @@ function ownedSchemaNames(expected) {
127
127
  return new Set([...policyNamespaces, ...expected.existingSchemas]);
128
128
  }
129
129
  /**
130
- * Applies the family's semantic-satisfaction normalization across a Postgres
131
- * tree pair: every actual table with an expected counterpart (paired by
132
- * namespace id, then table id) gets its unique/index child lists adjusted;
133
- * everything else passes through untouched.
134
- */
135
- function normalizePostgresActualForDiff(expected, actual) {
136
- const namespaces = {};
137
- for (const [nsId, actualNs] of Object.entries(actual.namespaces)) {
138
- const expectedNs = expected.namespaces[nsId];
139
- if (expectedNs === void 0) {
140
- namespaces[nsId] = actualNs;
141
- continue;
142
- }
143
- const tables = {};
144
- for (const [tableName, actualTable] of Object.entries(actualNs.tables)) {
145
- const expectedTable = expectedNs.tables[tableName];
146
- if (expectedTable === void 0) {
147
- tables[tableName] = actualTable;
148
- continue;
149
- }
150
- const adjusted = resolveSemanticSatisfaction({
151
- expectedUniques: expectedTable.uniques,
152
- expectedIndexes: expectedTable.indexes,
153
- actualUniques: actualTable.uniques,
154
- actualIndexes: actualTable.indexes
155
- });
156
- tables[tableName] = new PostgresTableSchemaNode({
157
- name: actualTable.name,
158
- columns: actualTable.columns,
159
- foreignKeys: actualTable.foreignKeys,
160
- uniques: adjusted.actualUniques,
161
- indexes: adjusted.actualIndexes,
162
- ...ifDefined("primaryKey", actualTable.primaryKey),
163
- ...ifDefined("annotations", actualTable.annotations),
164
- ...ifDefined("checks", actualTable.checks),
165
- policies: [...actualTable.policies],
166
- rlsEnabled: actualTable.rlsEnabled
167
- });
168
- }
169
- namespaces[nsId] = new PostgresNamespaceSchemaNode({
170
- schemaName: actualNs.schemaName,
171
- tables,
172
- nativeEnumTypeNames: actualNs.nativeEnumTypeNames,
173
- nativeEnums: actualNs.nativeEnums
174
- });
175
- }
176
- return new PostgresDatabaseSchemaNode({
177
- namespaces,
178
- roles: [...actual.roles],
179
- existingSchemas: [...actual.existingSchemas],
180
- pgVersion: actual.pgVersion
181
- });
182
- }
183
- /**
184
130
  * Drops contract namespaces that declare no tables from the verdict-diff
185
131
  * expected tree and the relational owned-schema set. The legacy relational
186
132
  * walk skipped a table-less namespace (e.g. an enums-only schema) before
@@ -213,9 +159,8 @@ function resolveControlPolicy(issue, contract) {
213
159
  /**
214
160
  * The Postgres full-tree node diff for the family verify verdict: derive
215
161
  * the expected tree (resolved leaf values, expander threaded, FK schemas
216
- * resolved, table-less namespaces pruned), normalize the actual tree for
217
- * semantic satisfaction, run the generic
218
- * differ, and scope out `not-expected` findings under namespaces the
162
+ * resolved, table-less namespaces pruned), run the generic
163
+ * differ over the trees as derived, and scope out `not-expected` findings under namespaces the
219
164
  * contract does not own. Ownership is role-aware, mirroring the legacy
220
165
  * decomposition: relational extras check the PRUNED owned set (the legacy
221
166
  * per-namespace walk never visited a table-less namespace, so its live
@@ -236,11 +181,10 @@ function diffPostgresSchema(input) {
236
181
  ...ifDefined("expandNativeType", buildNativeTypeExpander(input.frameworkComponents))
237
182
  });
238
183
  const expected = pruneTableLessNamespaces(fullExpected);
239
- const normalizedActual = normalizePostgresActualForDiff(expected, actual);
240
184
  const relationalOwned = ownedSchemaNames(expected);
241
185
  const structuralOwned = ownedSchemaNames(fullExpected);
242
186
  return {
243
- issues: diffSchemas(expected, normalizedActual).filter((issue) => {
187
+ issues: diffSchemas(expected, actual).filter((issue) => {
244
188
  if (issue.reason !== "not-expected") return true;
245
189
  const namespaceSegment = issue.path[1];
246
190
  if (namespaceSegment === void 0) return true;
@@ -284,8 +228,8 @@ function padActualNamespaces(expected, actual) {
284
228
  /**
285
229
  * The Postgres planner's diff input: the SAME tree-building
286
230
  * `diffPostgresSchema` uses (expander threaded, FK schemas resolved,
287
- * table-less namespaces pruned, actual normalized for semantic satisfaction,
288
- * role-aware ownership filter) plus actual namespace padding (so a missing
231
+ * table-less namespaces pruned, role-aware ownership filter) plus actual
232
+ * namespace padding (so a missing
289
233
  * schema's tables surface as `not-found` instead of a swallowed namespace
290
234
  * `not-found`). One differ drives both verify and plan; this is the
291
235
  * plan-side derivation. The single issue list covers tables / columns /
@@ -302,13 +246,13 @@ function buildPostgresPlanDiff(input) {
302
246
  ...ifDefined("expandNativeType", buildNativeTypeExpander(input.frameworkComponents))
303
247
  });
304
248
  const expected = pruneTableLessNamespaces(fullExpected);
305
- const normalizedActual = normalizePostgresActualForDiff(expected, padActualNamespaces(expected, actual));
249
+ const paddedActual = padActualNamespaces(expected, actual);
306
250
  const relationalOwned = ownedSchemaNames(expected);
307
251
  const structuralOwned = ownedSchemaNames(fullExpected);
308
252
  return {
309
253
  expected,
310
- actual: normalizedActual,
311
- issues: blindCast(diffSchemas(expected, normalizedActual)).filter((issue) => {
254
+ actual: paddedActual,
255
+ issues: blindCast(diffSchemas(expected, paddedActual)).filter((issue) => {
312
256
  if (issue.reason !== "not-expected") return true;
313
257
  const namespaceSegment = issue.path[1];
314
258
  if (namespaceSegment === void 0) return true;
@@ -319,4 +263,4 @@ function buildPostgresPlanDiff(input) {
319
263
  //#endregion
320
264
  export { diffPostgresSchema as n, contractToPostgresDatabaseSchemaNode as r, buildPostgresPlanDiff as t };
321
265
 
322
- //# sourceMappingURL=diff-database-schema-CWb-jeLy.mjs.map
266
+ //# sourceMappingURL=diff-database-schema-CtSK3us9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-database-schema-CtSK3us9.mjs","names":[],"sources":["../src/core/migrations/contract-to-postgres-database-schema-node.ts","../src/core/migrations/diff-database-schema.ts"],"sourcesContent":["import type { ContractToSchemaIROptions } from '@prisma-next/family-sql/control';\nimport { contractNamespaceToSchemaIR } from '@prisma-next/family-sql/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport { SqlForeignKeyIR } from '@prisma-next/sql-schema-ir/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { PostgresRlsPolicy } from '../postgres-rls-policy';\nimport type { PostgresContract } from '../postgres-schema';\nimport { isPostgresSchema } from '../postgres-schema';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';\nimport { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';\nimport { PostgresRoleSchemaNode } from '../schema-ir/postgres-role-schema-node';\nimport { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';\nimport { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';\n\nfunction toPolicyNode(policy: PostgresRlsPolicy, namespaceId: string): PostgresPolicySchemaNode {\n return new PostgresPolicySchemaNode({\n name: policy.name,\n prefix: policy.prefix,\n tableName: policy.tableName,\n namespaceId,\n operation: policy.operation,\n roles: [...policy.roles],\n ...ifDefined('using', policy.using),\n ...ifDefined('withCheck', policy.withCheck),\n permissive: policy.permissive,\n });\n}\n\n/**\n * Projects a Postgres contract into the expected schema-diff tree: a\n * `PostgresDatabaseSchemaNode` root holding one `PostgresNamespaceSchemaNode`\n * per Postgres namespace, each holding its `PostgresTableSchemaNode`s with\n * their `PostgresPolicySchemaNode`s, plus the database roles on the root.\n *\n * Not a duplicate of the family's `contractToSchemaIR`: that builds a flat,\n * single `{ tables }` map (and throws on cross-namespace name collisions, with\n * no RLS/role concept) for SQLite's single-schema world. This is the\n * Postgres-specific *tree* shape — multi-schema, RLS-policy-aware, role-aware.\n * It reuses the family's per-namespace table conversion (`contractNamespaceToSchemaIR`)\n * for column/FK/index building and only adds the Postgres tree/policy/role shape.\n *\n * Tables are grouped by their owning namespace (resolved DDL schema name) so\n * the tree mirrors Postgres's object hierarchy. The DDL schema name is\n * resolved once per namespace.\n *\n * A policy that references a table absent from its namespace is a malformed\n * contract — the loop throws rather than fabricating a stub table.\n */\nexport function contractToPostgresDatabaseSchemaNode(\n contract: PostgresContract | null,\n options: ContractToSchemaIROptions,\n): PostgresDatabaseSchemaNode {\n if (contract === null) {\n return new PostgresDatabaseSchemaNode({\n namespaces: {},\n roles: [],\n existingSchemas: [],\n pgVersion: '',\n });\n }\n\n const namespaces: Record<string, PostgresNamespaceSchemaNode> = {};\n const roles: PostgresRoleSchemaNode[] = [];\n const ownedSchemas: string[] = [];\n\n for (const ns of Object.values(contract.storage.namespaces)) {\n if (!isPostgresSchema(ns)) continue;\n const ddlSchema = resolveDdlSchemaForNamespaceStorage(contract.storage, ns.id);\n ownedSchemas.push(ddlSchema);\n\n // Convert only THIS namespace's tables (passing the full storage for\n // type/value-set/enum resolution that spans namespaces), so the same table\n // name can exist in two schemas without colliding in a bare-keyed record.\n const sqlTables = contractNamespaceToSchemaIR(contract.storage, ns.id, options).tables;\n\n const policiesByTable = new Map<string, PostgresPolicySchemaNode[]>();\n for (const policy of Object.values(ns.policy)) {\n const list = policiesByTable.get(policy.tableName) ?? [];\n list.push(toPolicyNode(policy, ddlSchema));\n policiesByTable.set(policy.tableName, list);\n }\n\n const tables: Record<string, PostgresTableSchemaNode> = {};\n for (const tableName of Object.keys(ns.table)) {\n const sqlTable = sqlTables[tableName];\n if (sqlTable === undefined) continue;\n // The family conversion stamps `referencedSchema` only for bound FK\n // targets; an absent value means the FK targets the unbound namespace.\n // Postgres restores its own coordinate for that slot (the unbound\n // singleton's id) so the raw coordinate keeps qualifying REFERENCES\n // clauses, and resolves the real live DDL schema — introspected FKs\n // already carry the live schema, so this is what lets an expected FK\n // pair (by diff-node id) with its introspected counterpart.\n const foreignKeys = sqlTable.foreignKeys.map(\n (fk) =>\n new SqlForeignKeyIR({\n columns: fk.columns,\n referencedTable: fk.referencedTable,\n referencedColumns: fk.referencedColumns,\n referencedSchema: fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,\n ...ifDefined('name', fk.name),\n ...ifDefined('onDelete', fk.onDelete),\n ...ifDefined('onUpdate', fk.onUpdate),\n ...ifDefined('annotations', fk.annotations),\n resolvedReferencedNamespace: resolveDdlSchemaForNamespaceStorage(\n contract.storage,\n fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,\n ),\n }),\n );\n tables[tableName] = new PostgresTableSchemaNode({\n name: sqlTable.name,\n columns: sqlTable.columns,\n foreignKeys,\n uniques: sqlTable.uniques,\n indexes: sqlTable.indexes,\n ...ifDefined('primaryKey', sqlTable.primaryKey),\n ...ifDefined('annotations', sqlTable.annotations),\n ...ifDefined('checks', sqlTable.checks),\n policies: policiesByTable.get(tableName) ?? [],\n // Marker-driven, never derived from the policy set: the `rls` entry\n // is the single authored source of enablement.\n rlsEnabled: Object.hasOwn(ns.rls, tableName),\n });\n }\n\n for (const [tableName, tablePolicies] of policiesByTable) {\n if (!(tableName in tables)) {\n const policyName = tablePolicies[0]?.name ?? '(unknown)';\n throw new Error(\n `contract-to-postgres-database-schema-node: policy \"${policyName}\" references table \"${tableName}\" not present in namespace \"${ddlSchema}\"`,\n );\n }\n if (!Object.hasOwn(ns.rls, tableName)) {\n const policyPrefix = tablePolicies[0]?.prefix ?? '(unknown)';\n throw new Error(\n `contract-to-postgres-database-schema-node: policy \"${policyPrefix}\" targets table \"${tableName}\" in namespace \"${ddlSchema}\", which is not RLS-controlled. Mark the model with @@rls (entries.rls[\"${tableName}\"]) or remove the policy.`,\n );\n }\n }\n\n namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({\n schemaName: ddlSchema,\n tables,\n nativeEnumTypeNames: [],\n });\n\n for (const role of Object.values(ns.role)) {\n roles.push(new PostgresRoleSchemaNode({ name: role.name, namespaceId: role.namespaceId }));\n }\n }\n\n return new PostgresDatabaseSchemaNode({\n namespaces,\n roles,\n existingSchemas: ownedSchemas,\n pgVersion: '',\n });\n}\n","import type { Contract, ControlPolicy } from '@prisma-next/contract/types';\nimport type { SqlSchemaDiffResult } from '@prisma-next/family-sql/control';\nimport { buildNativeTypeExpander } from '@prisma-next/family-sql/control';\nimport { classifyDiffSubjectGranularity } from '@prisma-next/family-sql/diff';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type { SchemaDiffIssue } from '@prisma-next/framework-components/control';\nimport { diffSchemas } from '@prisma-next/framework-components/control';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { PostgresContract } from '../postgres-schema';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';\nimport {\n postgresDiffSubjectGranularity,\n type SqlSchemaDiffNode,\n} from '../schema-ir/schema-node-kinds';\nimport { contractToPostgresDatabaseSchemaNode } from './contract-to-postgres-database-schema-node';\nimport { resolvePostgresNodeIssueControlPolicySubject } from './control-policy';\n\nfunction ownedSchemaNames(expected: PostgresDatabaseSchemaNode): ReadonlySet<string> {\n const policyNamespaces = Object.values(expected.namespaces).flatMap((ns) =>\n Object.values(ns.tables).flatMap((t) => t.policies.map((p) => p.namespaceId)),\n );\n return new Set([...policyNamespaces, ...expected.existingSchemas]);\n}\n\n/**\n * Drops contract namespaces that declare no tables from the verdict-diff\n * expected tree and the relational owned-schema set. The legacy relational\n * walk skipped a table-less namespace (e.g. an enums-only schema) before\n * pairing, so neither its DDL schema's absence nor that schema's live\n * relational contents ever reached the verdict — the pruned tree\n * reproduces that. The prune loses no expected policies: policies attach\n * to tables, so a table-less namespace carries none (the projection\n * throws on a policy referencing an absent table). Live policies in a\n * pruned schema remain governed via the full owned set (see\n * {@link diffPostgresSchema}).\n */\nfunction pruneTableLessNamespaces(\n expected: PostgresDatabaseSchemaNode,\n): PostgresDatabaseSchemaNode {\n const namespaces = Object.fromEntries(\n Object.entries(expected.namespaces).filter(([, ns]) => Object.keys(ns.tables).length > 0),\n );\n return new PostgresDatabaseSchemaNode({\n namespaces,\n roles: [...expected.roles],\n existingSchemas: expected.existingSchemas.filter((s) => namespaces[s] !== undefined),\n pgVersion: expected.pgVersion,\n });\n}\n\n/**\n * Resolves a verdict-diff issue's subject table's declared control policy\n * directly from the contract, by delegating to the same node-typed resolver\n * ({@link resolvePostgresNodeIssueControlPolicySubject}) the planner uses to\n * gate DDL calls. `undefined` when the issue resolves to no contract table.\n */\nfunction resolveControlPolicy(\n issue: SchemaDiffIssue,\n contract: Contract<SqlStorage>,\n): ControlPolicy | undefined {\n const nodeIssue = blindCast<\n SchemaDiffIssue<SqlSchemaDiffNode>,\n 'every node in a Postgres schema diff tree is a SqlSchemaDiffNode'\n >(issue);\n return resolvePostgresNodeIssueControlPolicySubject(nodeIssue, contract)\n ?.explicitNodeControlPolicy;\n}\n\n/**\n * The Postgres full-tree node diff for the family verify verdict: derive\n * the expected tree (resolved leaf values, expander threaded, FK schemas\n * resolved, table-less namespaces pruned), run the generic\n * differ over the trees as derived, and scope out `not-expected` findings under namespaces the\n * contract does not own. Ownership is role-aware, mirroring the legacy\n * decomposition: relational extras check the PRUNED owned set (the legacy\n * per-namespace walk never visited a table-less namespace, so its live\n * relational contents are invisible), while `structural` extras (RLS\n * policies) check the FULL owned set (the legacy policy diff governed\n * every contract schema regardless of tables — RLS governance does not\n * shrink because a namespace declares no tables). The codec `verifyType`\n * hooks run once per contract namespace with tables against that\n * namespace's paired actual node (the hooks read namespace-scoped state\n * such as `nativeEnumTypeNames`).\n */\nexport function diffPostgresSchema(input: {\n readonly contract: Contract<SqlStorage>;\n readonly schema: SqlSchemaIRNode;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;\n}): SqlSchemaDiffResult {\n const postgresContract = blindCast<\n PostgresContract,\n 'diffPostgresSchema is only called with a postgres contract'\n >(input.contract);\n PostgresDatabaseSchemaNode.assert(input.schema);\n const actual = input.schema;\n const expandNativeType = buildNativeTypeExpander(input.frameworkComponents);\n const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, {\n annotationNamespace: 'pg',\n ...ifDefined('expandNativeType', expandNativeType),\n });\n const expected = pruneTableLessNamespaces(fullExpected);\n const relationalOwned = ownedSchemaNames(expected);\n const structuralOwned = ownedSchemaNames(fullExpected);\n const issues = diffSchemas(expected, actual).filter((issue) => {\n if (issue.reason !== 'not-expected') return true;\n const namespaceSegment = issue.path[1];\n if (namespaceSegment === undefined) return true;\n const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);\n const owned = granularity === 'structural' ? structuralOwned : relationalOwned;\n return owned.has(namespaceSegment);\n });\n const namespacePairs = Object.values(expected.namespaces).map((ns) => ({\n actual: actual.namespaces[ns.schemaName],\n }));\n return {\n issues,\n resolveControlPolicy: (issue) => resolveControlPolicy(issue, postgresContract),\n namespacePairs,\n };\n}\n\n/**\n * Adds an empty namespace node to the actual tree for every expected namespace\n * absent from it. The relational plan diff pairs on namespace: a contract\n * namespace whose live schema does not exist yet must surface each of its\n * tables as `not-found` (→ `CREATE TABLE`), NOT as a single namespace\n * `not-found` that subtree-coalescing would collapse (leaving `CREATE SCHEMA`\n * with no tables). Padding makes the namespaces pair, so only table/column/\n * policy drift surfaces; `CREATE SCHEMA` comes separately from the synthesized\n * namespace-presence stitch (`verifyPostgresNamespacePresence`), never from the\n * tree diff — matching the retired per-namespace-paired relational walk, which\n * paired a missing schema against an empty namespace node.\n */\nfunction padActualNamespaces(\n expected: PostgresDatabaseSchemaNode,\n actual: PostgresDatabaseSchemaNode,\n): PostgresDatabaseSchemaNode {\n const namespaces: Record<string, PostgresNamespaceSchemaNode> = { ...actual.namespaces };\n let padded = false;\n for (const schemaName of Object.keys(expected.namespaces)) {\n if (namespaces[schemaName] === undefined) {\n namespaces[schemaName] = new PostgresNamespaceSchemaNode({\n schemaName,\n tables: {},\n nativeEnumTypeNames: [],\n });\n padded = true;\n }\n }\n if (!padded) return actual;\n return new PostgresDatabaseSchemaNode({\n namespaces,\n roles: [...actual.roles],\n existingSchemas: [...actual.existingSchemas],\n pgVersion: actual.pgVersion,\n });\n}\n\nexport interface PostgresPlanDiff {\n /** The desired (\"end\") tree — resolved leaf values (incl. `codecRef`) on every column, table-less namespaces pruned. */\n readonly expected: PostgresDatabaseSchemaNode;\n /** The live (\"start\") tree, padded with empty namespaces so a missing schema's tables pair. */\n readonly actual: PostgresDatabaseSchemaNode;\n /** The one node diff over the two trees: relational + policy drift, role-aware ownership filtered. */\n readonly issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[];\n}\n\n/**\n * The Postgres planner's diff input: the SAME tree-building\n * `diffPostgresSchema` uses (expander threaded, FK schemas resolved,\n * table-less namespaces pruned, role-aware ownership filter) plus actual\n * namespace padding (so a missing\n * schema's tables surface as `not-found` instead of a swallowed namespace\n * `not-found`). One differ drives both verify and plan; this is the\n * plan-side derivation. The single issue list covers tables / columns /\n * constraints / indexes / defaults AND policies — the caller splits it\n * (relational → `mapNodeIssueToCall`; policy → RLS ops) and stitches in\n * `CREATE SCHEMA` separately.\n */\nexport function buildPostgresPlanDiff(input: {\n readonly contract: Contract<SqlStorage>;\n readonly actualSchema: SqlSchemaIRNode;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;\n}): PostgresPlanDiff {\n const postgresContract = blindCast<\n PostgresContract,\n 'buildPostgresPlanDiff is only called with a postgres contract'\n >(input.contract);\n PostgresDatabaseSchemaNode.assert(input.actualSchema);\n const actual = input.actualSchema;\n const expandNativeType = buildNativeTypeExpander(input.frameworkComponents);\n const projectionOptions = {\n annotationNamespace: 'pg',\n ...ifDefined('expandNativeType', expandNativeType),\n };\n const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, projectionOptions);\n const expected = pruneTableLessNamespaces(fullExpected);\n const paddedActual = padActualNamespaces(expected, actual);\n const relationalOwned = ownedSchemaNames(expected);\n const structuralOwned = ownedSchemaNames(fullExpected);\n const issues = blindCast<\n readonly SchemaDiffIssue<SqlSchemaDiffNode>[],\n 'both trees are PostgresDatabaseSchemaNodes, so every diff-issue node is a SqlSchemaDiffNode'\n >(diffSchemas(expected, paddedActual)).filter((issue) => {\n if (issue.reason !== 'not-expected') return true;\n const namespaceSegment = issue.path[1];\n if (namespaceSegment === undefined) return true;\n const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);\n const owned = granularity === 'structural' ? structuralOwned : relationalOwned;\n return owned.has(namespaceSegment);\n });\n return { expected, actual: paddedActual, issues };\n}\n"],"mappings":"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"}
@@ -19,7 +19,7 @@ type SqlSchemaDiffNode = SqlSchemaIRNode & DiffableNode;
19
19
  interface PostgresPlanDiff {
20
20
  /** The desired ("end") tree — resolved leaf values (incl. `codecRef`) on every column, table-less namespaces pruned. */
21
21
  readonly expected: PostgresDatabaseSchemaNode;
22
- /** The live ("start") tree, padded with empty namespaces and normalized for semantic satisfaction against `expected`. */
22
+ /** The live ("start") tree, padded with empty namespaces so a missing schema's tables pair. */
23
23
  readonly actual: PostgresDatabaseSchemaNode;
24
24
  /** The one node diff over the two trees: relational + policy drift, role-aware ownership filtered. */
25
25
  readonly issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[];
@@ -27,8 +27,8 @@ interface PostgresPlanDiff {
27
27
  /**
28
28
  * The Postgres planner's diff input: the SAME tree-building
29
29
  * `diffPostgresSchema` uses (expander threaded, FK schemas resolved,
30
- * table-less namespaces pruned, actual normalized for semantic satisfaction,
31
- * role-aware ownership filter) plus actual namespace padding (so a missing
30
+ * table-less namespaces pruned, role-aware ownership filter) plus actual
31
+ * namespace padding (so a missing
32
32
  * schema's tables surface as `not-found` instead of a swallowed namespace
33
33
  * `not-found`). One differ drives both verify and plan; this is the
34
34
  * plan-side derivation. The single issue list covers tables / columns /
@@ -1 +1 @@
1
- {"version":3,"file":"diff-database-schema.d.mts","names":[],"sources":["../src/core/schema-ir/schema-node-kinds.ts","../src/core/migrations/diff-database-schema.ts"],"mappings":";;;;;;;;;;;;;;;KAiBY,iBAAA,GAAoB,eAAA,GAAkB,YAAY;;;UCiN7C,gBAAA;EAsBa;EAAA,SApBnB,QAAA,EAAU,0BAAA;EAqBI;EAAA,SAnBd,MAAA,EAAQ,0BAAA;EAoBa;EAAA,SAlBrB,MAAA,WAAiB,eAAA,CAAgB,iBAAA;AAAA;;;;;;;;;;;;;iBAe5B,qBAAA,CAAsB,KAAA;EAAA,SAC3B,QAAA,EAAU,QAAA,CAAS,UAAA;EAAA,SACnB,YAAA,EAAc,eAAA;EAAA,SACd,mBAAA,EAAqB,aAAA,CAAc,8BAAA;AAAA,IAC1C,gBAAA"}
1
+ {"version":3,"file":"diff-database-schema.d.mts","names":[],"sources":["../src/core/schema-ir/schema-node-kinds.ts","../src/core/migrations/diff-database-schema.ts"],"mappings":";;;;;;;;;;;;;;;KAiBY,iBAAA,GAAoB,eAAA,GAAkB,YAAY;;;UCiJ7C,gBAAA;EAMN;EAAA,SAJA,QAAA,EAAU,0BAAA;EAIuB;EAAA,SAFjC,MAAA,EAAQ,0BAAA;EAE0C;EAAA,SAAlD,MAAA,WAAiB,eAAA,CAAgB,iBAAA;AAAA;;;;;;;;;;;;;iBAe5B,qBAAA,CAAsB,KAAA;EAAA,SAC3B,QAAA,EAAU,QAAA,CAAS,UAAA;EAAA,SACnB,YAAA,EAAc,eAAA;EAAA,SACd,mBAAA,EAAqB,aAAA,CAAc,8BAAA;AAAA,IAC1C,gBAAA"}
@@ -1,2 +1,2 @@
1
- import { t as buildPostgresPlanDiff } from "./diff-database-schema-CWb-jeLy.mjs";
1
+ import { t as buildPostgresPlanDiff } from "./diff-database-schema-CtSK3us9.mjs";
2
2
  export { buildPostgresPlanDiff };
@@ -4,7 +4,7 @@ import { n as parseRlsPolicyWireName } from "./wire-name-rHHtr1h0.mjs";
4
4
  import { n as PostgresSchemaNodeKind } from "./postgres-table-schema-node-9FCtCUz2.mjs";
5
5
  import { i as PostgresDatabaseSchemaNode, n as PostgresPolicySchemaNode, r as PostgresNamespaceSchemaNode } from "./postgres-role-schema-node-BiSTCTNg.mjs";
6
6
  import { t as resolveDdlSchemaForNamespaceStorage } from "./resolve-ddl-schema-Ca88Tg6W.mjs";
7
- import { t as buildPostgresPlanDiff } from "./diff-database-schema-CWb-jeLy.mjs";
7
+ import { t as buildPostgresPlanDiff } from "./diff-database-schema-CtSK3us9.mjs";
8
8
  import { E as RenamePostgresRlsPolicyCall, S as DropPostgresRlsPolicyCall, d as CreatePostgresRlsPolicyCall } from "./op-factory-call-DxNNKUeZ.mjs";
9
9
  import { a as resolvePostgresNodeIssueCreationFactoryName, c as issueSchemaName, d as postgresPlannerStrategies, i as resolvePostgresNodeIssueControlPolicySubject, l as issueTableName, n as resolveNamespaceIdForDdlSchema, o as coalesceSubtreeIssues, r as resolvePostgresCallControlPolicySubject, s as issueNode, t as formatPostgresControlPolicySubjectLabel, u as planIssues } from "./control-policy-BtOZBVxL.mjs";
10
10
  import { t as TypeScriptRenderablePostgresMigration } from "./planner-produced-postgres-migration-BLJH1zIc.mjs";
@@ -420,4 +420,4 @@ function policyNodeToContractPolicy(node) {
420
420
  //#endregion
421
421
  export { createPostgresMigrationPlanner as t };
422
422
 
423
- //# sourceMappingURL=planner-BryLk0Ie.mjs.map
423
+ //# sourceMappingURL=planner-jocGHxIR.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"planner-BryLk0Ie.mjs","names":["#lowerer"],"sources":["../src/core/migrations/verify-postgres-namespaces.ts","../src/core/migrations/planner.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport type { SchemaDiffIssue } from '@prisma-next/framework-components/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';\nimport { DEFAULT_NAMESPACE_ID } from '../namespace-ids';\nimport { isPostgresSchema } from '../postgres-schema';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';\nimport type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';\n\n/**\n * Resolves the live-database schema name for a given namespace\n * coordinate. Mirrors `resolveDdlSchemaForNamespace` in\n * `planner-strategies.ts` so the verifier's projection and the\n * planner's projection always agree — Postgres-aware namespaces (the\n * production path) dispatch to `ddlSchemaName(storage)`, and bare\n * object payloads (used by some tests) fall back to the coordinate\n * itself.\n */\nfunction resolveDdlSchemaName(storage: SqlStorage, namespaceId: string): string {\n const namespace = storage.namespaces[namespaceId];\n if (isPostgresSchema(namespace)) {\n return namespace.ddlSchemaName(storage);\n }\n return namespaceId;\n}\n\n/**\n * Reads the introspected list of schema names from the database-root schema\n * node. `existingSchemas` is database-level, so it lives on the\n * `PostgresDatabaseSchemaNode` root — not on the per-schema namespace nodes.\n *\n * Defaults to the always-present `public` schema when the node is not the\n * database root — a fresh Postgres database always carries `public` (unless an\n * operator dropped it manually), so any verifier path that runs without an\n * enriched introspection still suppresses the redundant `CREATE SCHEMA\n * \"public\"`.\n *\n * Production introspection (`PostgresControlAdapter.introspect`) is the\n * authoritative source: it queries `pg_namespace` and sets `existingSchemas`\n * on the returned root. Tests that want to assert against a richer initial\n * state construct a `PostgresDatabaseSchemaNode` explicitly.\n */\nfunction existingSchemasFromSchema(schema: SqlSchemaIRNode): readonly string[] {\n if (PostgresDatabaseSchemaNode.is(schema)) {\n return schema.existingSchemas;\n }\n return [DEFAULT_NAMESPACE_ID];\n}\n\n/**\n * Emits a `postgres-namespace` `not-found` diff issue for every\n * contract-declared Postgres namespace whose live container does not yet\n * exist. The planner prepends these (node-typed, synthesized) to the\n * relational diff issues so a multi-schema plan emits `CREATE SCHEMA`\n * before the tables that need it — a planner-only concern (verify already\n * rejects via the `not-found` table issues a missing schema already\n * produces), so this is not part of the shared diff.\n *\n * A namespace's live container is the schema returned by its\n * polymorphic `ddlSchemaName(storage)` method — named schemas resolve\n * to their own id; the unbound singleton returns `UNBOUND_NAMESPACE_ID`\n * and is skipped explicitly (late-bound namespaces have no fixed DDL\n * schema). Issues are emitted only when the resolved name is a real,\n * creatable schema (not the unbound sentinel) and is missing from the\n * introspected list. `public` is suppressed implicitly because the\n * introspection (or its sensible default) always carries it.\n *\n * Each emitted issue's path is `['database', ddlName]` — an ancestor of\n * every table path under that schema, so it must never be run through\n * `coalesceSubtreeIssues` alongside the table diff (it would swallow the\n * table-level `not-found` issues that drive `CREATE TABLE`); the planner\n * adds these AFTER coalescing the relational issues, and they are not\n * subject to sibling-space ownership scoping (mirrors the retired\n * coordinate walk, which prepended namespace issues after that filter).\n */\nexport function verifyPostgresNamespacePresence(input: {\n readonly contract: Contract<SqlStorage>;\n readonly schema: SqlSchemaIRNode;\n}): readonly SchemaDiffIssue<SqlSchemaDiffNode>[] {\n const { contract, schema } = input;\n const existing = new Set(existingSchemasFromSchema(schema));\n const issues: SchemaDiffIssue<SqlSchemaDiffNode>[] = [];\n const namespaceIds = Object.keys(contract.storage.namespaces).sort();\n for (const namespaceId of namespaceIds) {\n if (namespaceId === UNBOUND_NAMESPACE_ID) continue;\n const ddlName = resolveDdlSchemaName(contract.storage, namespaceId);\n if (ddlName === UNBOUND_NAMESPACE_ID) continue;\n if (existing.has(ddlName)) continue;\n const namespace = new PostgresNamespaceSchemaNode({\n schemaName: ddlName,\n tables: {},\n nativeEnumTypeNames: [],\n });\n issues.push({\n path: ['database', ddlName],\n reason: 'not-found',\n message: `Schema \"${ddlName}\" is missing from database`,\n expected: namespace,\n });\n }\n return issues;\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type {\n MigrationOperationPolicy,\n SqlMigrationPlannerPlanOptions,\n SqlPlannerConflict,\n SqlPlannerFailureResult,\n} from '@prisma-next/family-sql/control';\nimport {\n extractCodecControlHooks,\n partitionCallsByControlPolicy,\n partitionIssuesByControlPolicy,\n planFieldEventOperations,\n plannerFailure,\n} from '@prisma-next/family-sql/control';\nimport type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n MigrationPlanner,\n MigrationPlanWithAuthoringSurface,\n MigrationScaffoldContext,\n SchemaDiffIssue,\n SchemaOwnership,\n} from '@prisma-next/framework-components/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { PostgresRlsPolicy } from '../postgres-rls-policy';\nimport { parseRlsPolicyWireName } from '../rls/wire-name';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';\nimport { PostgresSchemaNodeKind, type SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';\nimport {\n formatPostgresControlPolicySubjectLabel,\n resolveNamespaceIdForDdlSchema,\n resolvePostgresCallControlPolicySubject,\n resolvePostgresNodeIssueControlPolicySubject,\n resolvePostgresNodeIssueCreationFactoryName,\n} from './control-policy';\nimport { buildPostgresPlanDiff } from './diff-database-schema';\nimport {\n coalesceSubtreeIssues,\n issueNode,\n issueSchemaName,\n issueTableName,\n planIssues,\n} from './issue-planner';\nimport type { PostgresOpFactoryCall } from './op-factory-call';\nimport {\n CreatePostgresRlsPolicyCall,\n DropPostgresRlsPolicyCall,\n RenamePostgresRlsPolicyCall,\n} from './op-factory-call';\nimport { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';\nimport { postgresPlannerStrategies } from './planner-strategies';\nimport { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';\nimport { verifyPostgresNamespacePresence } from './verify-postgres-namespaces';\n\ntype PlannerFrameworkComponents = SqlMigrationPlannerPlanOptions extends {\n readonly frameworkComponents: infer T;\n}\n ? T\n : ReadonlyArray<unknown>;\n\ntype PlannerOptionsWithComponents = SqlMigrationPlannerPlanOptions & {\n readonly frameworkComponents: PlannerFrameworkComponents;\n};\n\nexport function createPostgresMigrationPlanner(\n lowerer: ExecuteRequestLowerer,\n): PostgresMigrationPlanner {\n return new PostgresMigrationPlanner(lowerer);\n}\n\n/**\n * Result of `PostgresMigrationPlanner.plan()`. A discriminated union whose\n * success variant carries a `TypeScriptRenderablePostgresMigration` — a\n * migration object that both the CLI (via `renderTypeScript()`) and the\n * SQL-typed callers (via `operations`, `describe()`, etc.) consume\n * uniformly.\n */\nexport type PostgresPlanResult =\n | {\n readonly kind: 'success';\n readonly plan: TypeScriptRenderablePostgresMigration;\n readonly warnings?: readonly SqlPlannerConflict[];\n }\n | SqlPlannerFailureResult;\n\n/**\n * Postgres migration planner — a thin wrapper over `planIssues`.\n *\n * `plan()` diffs the target contract against the live schema via the one\n * differ (`buildPostgresPlanDiff`, producing node-typed `SchemaDiffIssue[]`)\n * and delegates to `planIssues` with the unified `postgresPlannerStrategies`\n * list: NOT-NULL backfill, type-change, nullable-tightening, codec-hook\n * storage types, component-declared dependency installs, and\n * shared-temp-default / empty-table-guarded NOT-NULL add-column. The same\n * strategy list runs for `migration plan`, `db update`, and `db init`;\n * behavior diverges purely on `policy.allowedOperationClasses` (the\n * data-safe strategies short-circuit when `'data'` is excluded). The issue\n * planner applies operation-class policy gates and emits a single\n * `PostgresOpFactoryCall[]` that drives both the runtime-ops view (via\n * `renderOps`) and the `renderTypeScript()` authoring surface. RLS policy\n * drift (the structural half of the same one-differ tree) is handled\n * separately via `planPostgresSchemaDiff`.\n */\nexport class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgres'> {\n readonly #lowerer: ExecuteRequestLowerer | undefined;\n\n constructor(lowerer?: ExecuteRequestLowerer) {\n this.#lowerer = lowerer;\n }\n\n plan(options: {\n readonly contract: unknown;\n readonly schema: unknown;\n readonly policy: MigrationOperationPolicy;\n /**\n * The \"from\" contract (state the planner assumes the database starts\n * at), or `null` for reconciliation flows. Only `migration plan` ever\n * supplies a non-null value; `db update` / `db init` reconcile against\n * the live schema and pass `null`. When present alongside the\n * `'data'` operation class, strategies that need from/to column-shape\n * comparisons (unsafe type change, nullability tightening) activate.\n *\n * Typed as the framework `Contract | null` to satisfy the\n * `MigrationPlanner` interface contract; `planSql` narrows to the SQL\n * shape via `SqlMigrationPlannerPlanOptions`. Used to populate\n * `describe().from` on the produced plan as\n * `fromContract?.storage.storageHash ?? null`.\n */\n readonly fromContract: Contract | null;\n readonly schemaName?: string;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;\n /**\n * Contract space this plan applies to. Stamped onto the produced\n * {@link TypeScriptRenderablePostgresMigration.spaceId} so the runner keys\n * the marker row by the right space.\n */\n readonly spaceId: string;\n /**\n * Ownership oracle over the contract-space composition — see\n * {@link SqlMigrationPlannerPlanOptions.ownership}.\n */\n readonly ownership?: SchemaOwnership;\n }): PostgresPlanResult {\n return this.planSql(options as SqlMigrationPlannerPlanOptions);\n }\n\n emptyMigration(\n context: MigrationScaffoldContext,\n spaceId: string,\n ): MigrationPlanWithAuthoringSurface {\n return new TypeScriptRenderablePostgresMigration(\n [],\n {\n from: context.fromHash,\n to: context.toHash,\n },\n spaceId,\n this.#lowerer,\n );\n }\n\n private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {\n const schemaName =\n options.schemaName ??\n Object.keys(options.contract.storage.namespaces).find((id) => id !== UNBOUND_NAMESPACE_ID) ??\n UNBOUND_NAMESPACE_ID;\n const policyResult = this.ensureAdditivePolicy(options.policy);\n if (policyResult) {\n return policyResult;\n }\n\n // The one combined tree diff drives the whole plan: relational findings\n // become structural DDL via `planIssues`, policy findings become RLS ops\n // via `planPostgresSchemaDiff`. Verify runs its own full-tree node diff\n // (`diffSchema`) over the same schema and rejects on a\n // surviving failure.\n PostgresDatabaseSchemaNode.assert(options.schema);\n const { issues: rawIssues } = buildPostgresPlanDiff({\n contract: options.contract,\n actualSchema: options.schema,\n frameworkComponents: options.frameworkComponents,\n });\n const policyDiffIssues = rawIssues.filter((issue) => isPolicyDiffIssue(issue));\n const relationalDiffIssues = rawIssues.filter((issue) => !isPolicyDiffIssue(issue));\n\n // The generic differ is total and un-gated: strict-mode extras filtering\n // (dropping `not-expected` findings outside strict mode, mirroring the\n // retired coordinate walk's `if (strict) { ...extra_* } }` guards),\n // subtree coalescing (a missing/extra table also emits an issue for\n // every child under it — redundant once the table-level Create/Drop call\n // already accounts for the whole subtree), and ownership are all post-diff\n // planner steps.\n //\n // Ownership: a live extra is only this plan's to drop when no contract\n // space owns it. The differ ran against THIS space's contract, so a table\n // a sibling space owns surfaces here as `not-expected`; the planner asks\n // the ownership oracle (the passive aggregate) whether any space declares\n // it and, if so, leaves it alone — it is a sibling's table, not an orphan.\n // A table no space owns stays a genuine extra to drop under a destructive\n // policy. Ownership lives in the aggregate; the planner only asks. Absent\n // oracle (single-space, none handed) keeps every extra. Coalescing MUST\n // run before this so a sibling-owned table's child issues have already\n // collapsed into the one table-level issue that carries the table name.\n const strict =\n options.policy.allowedOperationClasses.includes('widening') ||\n options.policy.allowedOperationClasses.includes('destructive');\n const coalesced = coalesceSubtreeIssues(relationalDiffIssues);\n const owned = retainUnownedExtras(coalesced, options.ownership, options.contract);\n const gated = strict ? owned : owned.filter((issue) => issue.reason !== 'not-expected');\n\n // Namespace presence (`CREATE SCHEMA`) is a planner-only op-generation\n // concern stitched in here rather than inside the shared diff — verify\n // never needs it (a missing schema already surfaces as a `not-found`\n // table in the relational findings). These synthesized issues are added\n // AFTER coalescing/scoping (never coalesced against the table diff —\n // their path is an ancestor of every table path under that schema, so\n // running them through the same coalesce would swallow the table-level\n // `not-found` issues that drive `CREATE TABLE`) and are NOT subject to\n // sibling-space scoping, matching the retired coordinate walk exactly.\n const namespaceIssues = verifyPostgresNamespacePresence({\n contract: options.contract,\n schema: options.schema,\n });\n const schemaIssues = [...namespaceIssues, ...gated];\n\n const codecHooks = extractCodecControlHooks(options.frameworkComponents);\n const storageTypes = options.contract.storage.types ?? {};\n // The strategy layer reads the live schema by bare table name for existence\n // checks (shared-temp-default safety, FK/unique probes), so it takes one\n // per-schema namespace node — never the whole tree root, and never a flat\n // merge of every namespace (which would collide same-named tables across\n // schemas). Probing more than one namespace at once is future work.\n const relationalSchema = relationalNamespaceNode(options.schema, schemaName);\n\n // Input-side control-policy partition. `external` / `observed` subjects\n // — and non-creation issues for `tolerated` subjects — are dropped from\n // the planner's input entirely; the planner never observes them, never\n // diffs them, never generates DDL for them. Suppression warnings are\n // built directly from the suppressed partition (one per subject), so the\n // user-visible message survives even when the planner would have failed\n // to model the subject's live shape.\n const issuePartition = partitionIssuesByControlPolicy({\n issues: schemaIssues,\n contract: options.contract,\n resolveControlPolicySubject: (issue) =>\n resolvePostgresNodeIssueControlPolicySubject(issue, options.contract),\n resolveCreationFactoryName: resolvePostgresNodeIssueCreationFactoryName,\n formatSubjectLabel: (factoryName, subject) =>\n formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),\n });\n\n const result = planIssues({\n issues: issuePartition.plannable,\n toContract: options.contract,\n // `fromContract` is only supplied by `migration plan`. It is `null` for\n // `db update` / `db init`, which means data-safety strategies needing\n // from/to comparisons (unsafe type change, nullable tightening) are\n // inapplicable there — reconciliation falls through to\n // `mapNodeIssueToCall`'s direct destructive handlers.\n fromContract: options.fromContract,\n schemaName,\n codecHooks,\n storageTypes,\n ...ifDefined('schema', relationalSchema),\n policy: options.policy,\n frameworkComponents: options.frameworkComponents,\n strategies: postgresPlannerStrategies,\n });\n\n if (!result.ok) {\n return plannerFailure(result.failure);\n }\n\n const schemaDiffCalls = this.planPostgresSchemaDiff(options, policyDiffIssues);\n const schemaDiffPartition = partitionCallsByControlPolicy({\n calls: schemaDiffCalls,\n contract: options.contract,\n resolveControlPolicySubject: (call) =>\n resolvePostgresCallControlPolicySubject(call, options.contract),\n resolveFactoryName: (call) => call.factoryName,\n formatSubjectLabel: (factoryName, subject) =>\n formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),\n });\n\n // Inline `onFieldEvent`-emitted ops after structural DDL. The fixed\n // ordering is `structural → added → dropped → altered`, with\n // within-group sorting by `(tableName, fieldName)` so re-emits are\n // byte-stable. The hook fires only at the application emitter —\n // extension-space planning never reaches this helper.\n const fieldEventOps = planFieldEventOperations({\n priorContract: options.fromContract,\n newContract: options.contract,\n codecHooks,\n });\n // Codec hook ops are target-agnostic `OpFactoryCall`; Postgres planning\n // lifts them at this integration boundary (see field-event-planner JSDoc).\n const fieldEventPostgresCalls = blindCast<\n readonly PostgresOpFactoryCall[],\n 'Codec hook ops conform to PostgresOpFactoryCall at the app emitter boundary'\n >(fieldEventOps);\n const fieldEventPartition = partitionCallsByControlPolicy({\n calls: fieldEventPostgresCalls,\n contract: options.contract,\n resolveControlPolicySubject: (call) =>\n resolvePostgresCallControlPolicySubject(call, options.contract),\n resolveFactoryName: (call) => call.factoryName,\n formatSubjectLabel: (factoryName, subject) =>\n formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),\n });\n const calls = [...result.value.calls, ...schemaDiffPartition.kept, ...fieldEventPartition.kept];\n const warnings: SqlPlannerConflict[] = [\n ...issuePartition.warnings,\n ...schemaDiffPartition.warnings,\n ...fieldEventPartition.warnings,\n ];\n\n return Object.freeze({\n kind: 'success' as const,\n plan: new TypeScriptRenderablePostgresMigration(\n calls,\n {\n from: options.fromContract?.storage.storageHash ?? null,\n to: options.contract.storage.storageHash,\n },\n options.spaceId,\n this.#lowerer,\n ),\n ...(warnings.length > 0 ? { warnings: Object.freeze(warnings) } : {}),\n });\n }\n\n /**\n * Maps the RLS policy presence findings of the one combined tree diff\n * (`buildPostgresPlanDiff`, already ownership-filtered) into\n * `CREATE POLICY` / `DROP POLICY` / `ALTER POLICY … RENAME TO` ops. It\n * does not re-diff — it consumes exactly the policy-node subset of the\n * shared diff's issues. Enablement is NOT decided here: `ENABLE`/`DISABLE\n * ROW LEVEL SECURITY` derive from the table's marker-driven `rlsEnabled`\n * attribute diff on the relational side.\n *\n * Rename post-pass: a `not-found` and a `not-expected` policy on the SAME\n * table whose wire-name content hashes match but prefixes differ are one\n * prefix-only rename, collapsed into a single non-destructive\n * `RenamePostgresRlsPolicyCall`. Multi-candidate hash groups pair\n * deterministically by sorted wire name; leftovers proceed as\n * create/drop. Unparseable wire names never pair.\n *\n * The pairing runs only when the policy allows `widening` (rename's\n * class). Without it (db-init's additive-only set), pairing degrades\n * deliberately to the additive half: the new name is CREATEd and the old\n * policy survives live until a widening/destructive-allowed plan runs —\n * emitting an ungated widening rename would only fail at the runner's\n * class re-enforcement.\n */\n private planPostgresSchemaDiff(\n options: PlannerOptionsWithComponents,\n filteredDiffIssues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],\n ): readonly PostgresOpFactoryCall[] {\n const allowsDestructive = options.policy.allowedOperationClasses.includes('destructive');\n const allowsWidening = options.policy.allowedOperationClasses.includes('widening');\n\n interface PolicyFinding {\n readonly node: PostgresPolicySchemaNode;\n readonly schemaForTable: string;\n }\n const missing: PolicyFinding[] = [];\n const extra: PolicyFinding[] = [];\n\n for (const issue of filteredDiffIssues) {\n // 'not-equal' is unreachable for content-addressed policies: the wire name\n // encodes the body hash, so two policies sharing a local key (same name)\n // are always equal and isEqualTo never returns false.\n if (issue.reason === 'not-found') {\n const expected = issue.expected;\n PostgresPolicySchemaNode.assert(expected);\n // expected.namespaceId is the DDL schema name (resolved during projection);\n // this re-resolution is a no-op as long as PostgresSchema.ddlSchemaName() returns this.id.\n missing.push({\n node: expected,\n schemaForTable: resolveDdlSchemaForNamespaceStorage(\n options.contract.storage,\n expected.namespaceId,\n ),\n });\n } else if (issue.reason === 'not-expected') {\n const actual = issue.actual;\n PostgresPolicySchemaNode.assert(actual);\n extra.push({\n node: actual,\n schemaForTable: resolveDdlSchemaForNamespaceStorage(\n options.contract.storage,\n actual.namespaceId,\n ),\n });\n }\n }\n\n const calls: PostgresOpFactoryCall[] = [];\n const renamedExtras = new Set<PolicyFinding>();\n const renamedMissing = new Set<PolicyFinding>();\n const pairingKey = (finding: PolicyFinding, hash: string): string =>\n JSON.stringify([finding.schemaForTable, finding.node.tableName, hash]);\n\n if (allowsWidening) {\n const extrasByGroup = new Map<string, PolicyFinding[]>();\n for (const finding of extra) {\n const parsed = parseRlsPolicyWireName(finding.node.name);\n if (parsed === undefined) continue;\n const key = pairingKey(finding, parsed.hash);\n const group = extrasByGroup.get(key) ?? [];\n group.push(finding);\n extrasByGroup.set(key, group);\n }\n for (const group of extrasByGroup.values()) {\n group.sort((a, b) => (a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0));\n }\n\n const sortedMissing = [...missing].sort((a, b) =>\n a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0,\n );\n for (const missingFinding of sortedMissing) {\n const parsed = parseRlsPolicyWireName(missingFinding.node.name);\n if (parsed === undefined) continue;\n const candidates = extrasByGroup.get(pairingKey(missingFinding, parsed.hash));\n const candidate = candidates?.shift();\n if (candidate === undefined) continue;\n // Same name would never surface as missing+extra (the differ pairs by\n // name), so a matched candidate always differs in prefix only.\n renamedExtras.add(candidate);\n renamedMissing.add(missingFinding);\n calls.push(\n new RenamePostgresRlsPolicyCall(\n missingFinding.schemaForTable,\n missingFinding.node.tableName,\n candidate.node.name,\n missingFinding.node.name,\n ),\n );\n }\n }\n\n for (const finding of missing) {\n if (renamedMissing.has(finding)) continue;\n calls.push(\n new CreatePostgresRlsPolicyCall(\n finding.schemaForTable,\n finding.node.tableName,\n policyNodeToContractPolicy(finding.node),\n ),\n );\n }\n if (allowsDestructive) {\n for (const finding of extra) {\n if (renamedExtras.has(finding)) continue;\n calls.push(\n new DropPostgresRlsPolicyCall(\n finding.schemaForTable,\n finding.node.tableName,\n finding.node.name,\n ),\n );\n }\n }\n\n return calls;\n }\n\n private ensureAdditivePolicy(policy: MigrationOperationPolicy) {\n if (!policy.allowedOperationClasses.includes('additive')) {\n return plannerFailure([\n {\n kind: 'unsupportedOperation',\n summary: 'Migration planner requires additive operations be allowed',\n why: 'The planner requires the \"additive\" operation class to be allowed in the policy.',\n },\n ]);\n }\n return null;\n }\n}\n\n/**\n * A diff issue whose node is an RLS policy — the structural half of the one\n * combined tree diff, routed to `planPostgresSchemaDiff` instead of\n * `planIssues`.\n */\nfunction isPolicyDiffIssue(issue: SchemaDiffIssue<SqlSchemaDiffNode>): boolean {\n const node = issue.expected ?? issue.actual;\n return node !== undefined && PostgresPolicySchemaNode.is(node);\n}\n\n/**\n * Drops a `not-expected` issue when it is a whole extra TABLE that some\n * contract space owns, asking the ownership oracle per node.\n *\n * The consultation applies ONLY to table-level extras — a live table this\n * space's contract lacks — identified by asking the issue's own node\n * (`nodeKind === table`), never by counting path segments. `declaresEntity`\n * answers over the whole composition (self included), keyed on the schema-IR\n * entity coordinate so a genuine orphan in one namespace is never conflated\n * with a same-named table a sibling space declares in another, or with a\n * same-named non-table entity (an enum, an RLS policy) a sibling declares in\n * the SAME namespace: a positive answer means another space owns THIS table\n * in THIS namespace (a table this space owned would be in its expected tree,\n * never an extra) — leave it. A negative answer means no space owns it — a\n * genuine orphan to drop. `entityKind` is the literal `'table'` here: this\n * function only ever asks about a node already confirmed to be\n * `PostgresSchemaNodeKind.table` (checked just above), and the diff tree's\n * `nodeKind` vocabulary (`'postgres-table'`) is distinct from the storage\n * `entries` vocabulary `elementCoordinates` walks (`'table'`) — the literal\n * is that storage-entries spelling, not the node kind.\n *\n * The issue path carries the *resolved DDL schema* (e.g. `public`), but a\n * contract space's own declared namespace id can be the unbound sentinel\n * (`__unbound__`) that resolves to that schema — the two spellings would\n * never string-match. `resolveNamespaceIdForDdlSchema` recovers the raw\n * namespace id THIS space's own contract would use for that DDL schema, so\n * a table this space's own unbound namespace declares (and any sibling\n * whose own unbound namespace resolves the same way) is compared on the\n * same coordinate the aggregate's `elementCoordinates` walk yields.\n *\n * A DEEPER extra (an extra column/constraint on a table this space DOES own —\n * only the child drifted, so the table is in the expected tree) is this\n * space's own drift and is always kept for dropping; asking the oracle there\n * would wrongly suppress it, because the owned table answers `true`. Shallower\n * issues (namespace/root) are never ownership-scoped here. No oracle ⇒ every\n * extra is kept (single-space plan).\n */\nfunction retainUnownedExtras(\n issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],\n ownership: SchemaOwnership | undefined,\n contract: Contract<SqlStorage>,\n): readonly SchemaDiffIssue<SqlSchemaDiffNode>[] {\n if (ownership === undefined) return issues;\n return issues.filter((issue) => {\n if (issue.reason !== 'not-expected') return true;\n const node = issueNode(issue);\n if (node === undefined || node.nodeKind !== PostgresSchemaNodeKind.table) return true;\n const ddlSchemaName = issueSchemaName(issue);\n const tableName = issueTableName(issue);\n if (ddlSchemaName === undefined || tableName === undefined) return true;\n const namespaceId = resolveNamespaceIdForDdlSchema(contract, ddlSchemaName);\n return !ownership.declaresEntity({ namespaceId, entityKind: 'table', entityName: tableName });\n });\n}\n\n/**\n * Returns the one namespace node the relational strategy layer probes for\n * live-table existence and reads codec-hook context off — the namespace\n * matching the planner's resolved schema name, or the first namespace when\n * none matches. `undefined` when the tree has no namespaces, so the strategy\n * context uses its empty-schema default.\n *\n * The relational strategies key tables by bare name, so they can only probe one\n * namespace at a time; probing across every namespace at once is future work.\n *\n * Returns the real `PostgresNamespaceSchemaNode` reference rather than a\n * projection: `storageTypePlanCallStrategy` hands this same value to codec\n * `planTypeOperations` hooks as `schema`, and hooks read the Postgres-specific\n * `nativeEnumTypeNames` field off it (via `PostgresNamespaceSchemaNode.is`) to\n * decide whether a native enum type already exists — a projection that only\n * copies `tables` would silently drop that signal. `StrategyContext.schema`'s\n * declared type (`SqlSchemaIR`, shared with SQLite's flat shape) doesn't\n * capture this, so the return is `blindCast` the same way `namespaceSchemaNodes`\n * in the family's relational walk already treats a namespace node as a\n * structurally-`SqlSchemaIR`-shaped value.\n */\nfunction relationalNamespaceNode(\n schema: PostgresDatabaseSchemaNode,\n schemaName: string,\n): SqlSchemaIR | undefined {\n const namespaceNodes = Object.values(schema.namespaces);\n const namespaceNode =\n namespaceNodes.find((node) => node.schemaName === schemaName) ?? namespaceNodes[0];\n if (namespaceNode === undefined) return undefined;\n return blindCast<\n SqlSchemaIR,\n 'PostgresNamespaceSchemaNode carries tables (+ nativeEnumTypeNames, read by codec hooks via PostgresNamespaceSchemaNode.is) structurally compatible with the SqlSchemaIR shape the strategy layer declares'\n >(namespaceNode);\n}\n\n/**\n * Rebuilds the `PostgresRlsPolicy` contract entity `CreatePostgresRlsPolicyCall`\n * carries (its `renderTypeScript`/`createRlsPolicy` paths serialize the whole\n * entity, `namespaceId` included). This reconstructs rather than looking the\n * original up in the contract on purpose: the diff node's `namespaceId` is the\n * *resolved DDL schema* (set when the expected tree was built), which is the\n * value the emitted op must carry; the contract-stored entity holds the raw,\n * pre-resolution coordinate, so a lookup would change the migration output.\n */\nfunction policyNodeToContractPolicy(node: PostgresPolicySchemaNode): PostgresRlsPolicy {\n return new PostgresRlsPolicy({\n name: node.name,\n prefix: node.prefix,\n tableName: node.tableName,\n namespaceId: node.namespaceId,\n operation: node.operation,\n roles: [...node.roles],\n ...ifDefined('using', node.using),\n ...ifDefined('withCheck', node.withCheck),\n permissive: node.permissive,\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"planner-jocGHxIR.mjs","names":["#lowerer"],"sources":["../src/core/migrations/verify-postgres-namespaces.ts","../src/core/migrations/planner.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport type { SchemaDiffIssue } from '@prisma-next/framework-components/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';\nimport { DEFAULT_NAMESPACE_ID } from '../namespace-ids';\nimport { isPostgresSchema } from '../postgres-schema';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';\nimport type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';\n\n/**\n * Resolves the live-database schema name for a given namespace\n * coordinate. Mirrors `resolveDdlSchemaForNamespace` in\n * `planner-strategies.ts` so the verifier's projection and the\n * planner's projection always agree — Postgres-aware namespaces (the\n * production path) dispatch to `ddlSchemaName(storage)`, and bare\n * object payloads (used by some tests) fall back to the coordinate\n * itself.\n */\nfunction resolveDdlSchemaName(storage: SqlStorage, namespaceId: string): string {\n const namespace = storage.namespaces[namespaceId];\n if (isPostgresSchema(namespace)) {\n return namespace.ddlSchemaName(storage);\n }\n return namespaceId;\n}\n\n/**\n * Reads the introspected list of schema names from the database-root schema\n * node. `existingSchemas` is database-level, so it lives on the\n * `PostgresDatabaseSchemaNode` root — not on the per-schema namespace nodes.\n *\n * Defaults to the always-present `public` schema when the node is not the\n * database root — a fresh Postgres database always carries `public` (unless an\n * operator dropped it manually), so any verifier path that runs without an\n * enriched introspection still suppresses the redundant `CREATE SCHEMA\n * \"public\"`.\n *\n * Production introspection (`PostgresControlAdapter.introspect`) is the\n * authoritative source: it queries `pg_namespace` and sets `existingSchemas`\n * on the returned root. Tests that want to assert against a richer initial\n * state construct a `PostgresDatabaseSchemaNode` explicitly.\n */\nfunction existingSchemasFromSchema(schema: SqlSchemaIRNode): readonly string[] {\n if (PostgresDatabaseSchemaNode.is(schema)) {\n return schema.existingSchemas;\n }\n return [DEFAULT_NAMESPACE_ID];\n}\n\n/**\n * Emits a `postgres-namespace` `not-found` diff issue for every\n * contract-declared Postgres namespace whose live container does not yet\n * exist. The planner prepends these (node-typed, synthesized) to the\n * relational diff issues so a multi-schema plan emits `CREATE SCHEMA`\n * before the tables that need it — a planner-only concern (verify already\n * rejects via the `not-found` table issues a missing schema already\n * produces), so this is not part of the shared diff.\n *\n * A namespace's live container is the schema returned by its\n * polymorphic `ddlSchemaName(storage)` method — named schemas resolve\n * to their own id; the unbound singleton returns `UNBOUND_NAMESPACE_ID`\n * and is skipped explicitly (late-bound namespaces have no fixed DDL\n * schema). Issues are emitted only when the resolved name is a real,\n * creatable schema (not the unbound sentinel) and is missing from the\n * introspected list. `public` is suppressed implicitly because the\n * introspection (or its sensible default) always carries it.\n *\n * Each emitted issue's path is `['database', ddlName]` — an ancestor of\n * every table path under that schema, so it must never be run through\n * `coalesceSubtreeIssues` alongside the table diff (it would swallow the\n * table-level `not-found` issues that drive `CREATE TABLE`); the planner\n * adds these AFTER coalescing the relational issues, and they are not\n * subject to sibling-space ownership scoping (mirrors the retired\n * coordinate walk, which prepended namespace issues after that filter).\n */\nexport function verifyPostgresNamespacePresence(input: {\n readonly contract: Contract<SqlStorage>;\n readonly schema: SqlSchemaIRNode;\n}): readonly SchemaDiffIssue<SqlSchemaDiffNode>[] {\n const { contract, schema } = input;\n const existing = new Set(existingSchemasFromSchema(schema));\n const issues: SchemaDiffIssue<SqlSchemaDiffNode>[] = [];\n const namespaceIds = Object.keys(contract.storage.namespaces).sort();\n for (const namespaceId of namespaceIds) {\n if (namespaceId === UNBOUND_NAMESPACE_ID) continue;\n const ddlName = resolveDdlSchemaName(contract.storage, namespaceId);\n if (ddlName === UNBOUND_NAMESPACE_ID) continue;\n if (existing.has(ddlName)) continue;\n const namespace = new PostgresNamespaceSchemaNode({\n schemaName: ddlName,\n tables: {},\n nativeEnumTypeNames: [],\n });\n issues.push({\n path: ['database', ddlName],\n reason: 'not-found',\n message: `Schema \"${ddlName}\" is missing from database`,\n expected: namespace,\n });\n }\n return issues;\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type {\n MigrationOperationPolicy,\n SqlMigrationPlannerPlanOptions,\n SqlPlannerConflict,\n SqlPlannerFailureResult,\n} from '@prisma-next/family-sql/control';\nimport {\n extractCodecControlHooks,\n partitionCallsByControlPolicy,\n partitionIssuesByControlPolicy,\n planFieldEventOperations,\n plannerFailure,\n} from '@prisma-next/family-sql/control';\nimport type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type {\n MigrationPlanner,\n MigrationPlanWithAuthoringSurface,\n MigrationScaffoldContext,\n SchemaDiffIssue,\n SchemaOwnership,\n} from '@prisma-next/framework-components/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { SqlSchemaIR } from '@prisma-next/sql-schema-ir/types';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { PostgresRlsPolicy } from '../postgres-rls-policy';\nimport { parseRlsPolicyWireName } from '../rls/wire-name';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';\nimport { PostgresSchemaNodeKind, type SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';\nimport {\n formatPostgresControlPolicySubjectLabel,\n resolveNamespaceIdForDdlSchema,\n resolvePostgresCallControlPolicySubject,\n resolvePostgresNodeIssueControlPolicySubject,\n resolvePostgresNodeIssueCreationFactoryName,\n} from './control-policy';\nimport { buildPostgresPlanDiff } from './diff-database-schema';\nimport {\n coalesceSubtreeIssues,\n issueNode,\n issueSchemaName,\n issueTableName,\n planIssues,\n} from './issue-planner';\nimport type { PostgresOpFactoryCall } from './op-factory-call';\nimport {\n CreatePostgresRlsPolicyCall,\n DropPostgresRlsPolicyCall,\n RenamePostgresRlsPolicyCall,\n} from './op-factory-call';\nimport { TypeScriptRenderablePostgresMigration } from './planner-produced-postgres-migration';\nimport { postgresPlannerStrategies } from './planner-strategies';\nimport { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';\nimport { verifyPostgresNamespacePresence } from './verify-postgres-namespaces';\n\ntype PlannerFrameworkComponents = SqlMigrationPlannerPlanOptions extends {\n readonly frameworkComponents: infer T;\n}\n ? T\n : ReadonlyArray<unknown>;\n\ntype PlannerOptionsWithComponents = SqlMigrationPlannerPlanOptions & {\n readonly frameworkComponents: PlannerFrameworkComponents;\n};\n\nexport function createPostgresMigrationPlanner(\n lowerer: ExecuteRequestLowerer,\n): PostgresMigrationPlanner {\n return new PostgresMigrationPlanner(lowerer);\n}\n\n/**\n * Result of `PostgresMigrationPlanner.plan()`. A discriminated union whose\n * success variant carries a `TypeScriptRenderablePostgresMigration` — a\n * migration object that both the CLI (via `renderTypeScript()`) and the\n * SQL-typed callers (via `operations`, `describe()`, etc.) consume\n * uniformly.\n */\nexport type PostgresPlanResult =\n | {\n readonly kind: 'success';\n readonly plan: TypeScriptRenderablePostgresMigration;\n readonly warnings?: readonly SqlPlannerConflict[];\n }\n | SqlPlannerFailureResult;\n\n/**\n * Postgres migration planner — a thin wrapper over `planIssues`.\n *\n * `plan()` diffs the target contract against the live schema via the one\n * differ (`buildPostgresPlanDiff`, producing node-typed `SchemaDiffIssue[]`)\n * and delegates to `planIssues` with the unified `postgresPlannerStrategies`\n * list: NOT-NULL backfill, type-change, nullable-tightening, codec-hook\n * storage types, component-declared dependency installs, and\n * shared-temp-default / empty-table-guarded NOT-NULL add-column. The same\n * strategy list runs for `migration plan`, `db update`, and `db init`;\n * behavior diverges purely on `policy.allowedOperationClasses` (the\n * data-safe strategies short-circuit when `'data'` is excluded). The issue\n * planner applies operation-class policy gates and emits a single\n * `PostgresOpFactoryCall[]` that drives both the runtime-ops view (via\n * `renderOps`) and the `renderTypeScript()` authoring surface. RLS policy\n * drift (the structural half of the same one-differ tree) is handled\n * separately via `planPostgresSchemaDiff`.\n */\nexport class PostgresMigrationPlanner implements MigrationPlanner<'sql', 'postgres'> {\n readonly #lowerer: ExecuteRequestLowerer | undefined;\n\n constructor(lowerer?: ExecuteRequestLowerer) {\n this.#lowerer = lowerer;\n }\n\n plan(options: {\n readonly contract: unknown;\n readonly schema: unknown;\n readonly policy: MigrationOperationPolicy;\n /**\n * The \"from\" contract (state the planner assumes the database starts\n * at), or `null` for reconciliation flows. Only `migration plan` ever\n * supplies a non-null value; `db update` / `db init` reconcile against\n * the live schema and pass `null`. When present alongside the\n * `'data'` operation class, strategies that need from/to column-shape\n * comparisons (unsafe type change, nullability tightening) activate.\n *\n * Typed as the framework `Contract | null` to satisfy the\n * `MigrationPlanner` interface contract; `planSql` narrows to the SQL\n * shape via `SqlMigrationPlannerPlanOptions`. Used to populate\n * `describe().from` on the produced plan as\n * `fromContract?.storage.storageHash ?? null`.\n */\n readonly fromContract: Contract | null;\n readonly schemaName?: string;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;\n /**\n * Contract space this plan applies to. Stamped onto the produced\n * {@link TypeScriptRenderablePostgresMigration.spaceId} so the runner keys\n * the marker row by the right space.\n */\n readonly spaceId: string;\n /**\n * Ownership oracle over the contract-space composition — see\n * {@link SqlMigrationPlannerPlanOptions.ownership}.\n */\n readonly ownership?: SchemaOwnership;\n }): PostgresPlanResult {\n return this.planSql(options as SqlMigrationPlannerPlanOptions);\n }\n\n emptyMigration(\n context: MigrationScaffoldContext,\n spaceId: string,\n ): MigrationPlanWithAuthoringSurface {\n return new TypeScriptRenderablePostgresMigration(\n [],\n {\n from: context.fromHash,\n to: context.toHash,\n },\n spaceId,\n this.#lowerer,\n );\n }\n\n private planSql(options: SqlMigrationPlannerPlanOptions): PostgresPlanResult {\n const schemaName =\n options.schemaName ??\n Object.keys(options.contract.storage.namespaces).find((id) => id !== UNBOUND_NAMESPACE_ID) ??\n UNBOUND_NAMESPACE_ID;\n const policyResult = this.ensureAdditivePolicy(options.policy);\n if (policyResult) {\n return policyResult;\n }\n\n // The one combined tree diff drives the whole plan: relational findings\n // become structural DDL via `planIssues`, policy findings become RLS ops\n // via `planPostgresSchemaDiff`. Verify runs its own full-tree node diff\n // (`diffSchema`) over the same schema and rejects on a\n // surviving failure.\n PostgresDatabaseSchemaNode.assert(options.schema);\n const { issues: rawIssues } = buildPostgresPlanDiff({\n contract: options.contract,\n actualSchema: options.schema,\n frameworkComponents: options.frameworkComponents,\n });\n const policyDiffIssues = rawIssues.filter((issue) => isPolicyDiffIssue(issue));\n const relationalDiffIssues = rawIssues.filter((issue) => !isPolicyDiffIssue(issue));\n\n // The generic differ is total and un-gated: strict-mode extras filtering\n // (dropping `not-expected` findings outside strict mode, mirroring the\n // retired coordinate walk's `if (strict) { ...extra_* } }` guards),\n // subtree coalescing (a missing/extra table also emits an issue for\n // every child under it — redundant once the table-level Create/Drop call\n // already accounts for the whole subtree), and ownership are all post-diff\n // planner steps.\n //\n // Ownership: a live extra is only this plan's to drop when no contract\n // space owns it. The differ ran against THIS space's contract, so a table\n // a sibling space owns surfaces here as `not-expected`; the planner asks\n // the ownership oracle (the passive aggregate) whether any space declares\n // it and, if so, leaves it alone — it is a sibling's table, not an orphan.\n // A table no space owns stays a genuine extra to drop under a destructive\n // policy. Ownership lives in the aggregate; the planner only asks. Absent\n // oracle (single-space, none handed) keeps every extra. Coalescing MUST\n // run before this so a sibling-owned table's child issues have already\n // collapsed into the one table-level issue that carries the table name.\n const strict =\n options.policy.allowedOperationClasses.includes('widening') ||\n options.policy.allowedOperationClasses.includes('destructive');\n const coalesced = coalesceSubtreeIssues(relationalDiffIssues);\n const owned = retainUnownedExtras(coalesced, options.ownership, options.contract);\n const gated = strict ? owned : owned.filter((issue) => issue.reason !== 'not-expected');\n\n // Namespace presence (`CREATE SCHEMA`) is a planner-only op-generation\n // concern stitched in here rather than inside the shared diff — verify\n // never needs it (a missing schema already surfaces as a `not-found`\n // table in the relational findings). These synthesized issues are added\n // AFTER coalescing/scoping (never coalesced against the table diff —\n // their path is an ancestor of every table path under that schema, so\n // running them through the same coalesce would swallow the table-level\n // `not-found` issues that drive `CREATE TABLE`) and are NOT subject to\n // sibling-space scoping, matching the retired coordinate walk exactly.\n const namespaceIssues = verifyPostgresNamespacePresence({\n contract: options.contract,\n schema: options.schema,\n });\n const schemaIssues = [...namespaceIssues, ...gated];\n\n const codecHooks = extractCodecControlHooks(options.frameworkComponents);\n const storageTypes = options.contract.storage.types ?? {};\n // The strategy layer reads the live schema by bare table name for existence\n // checks (shared-temp-default safety, FK/unique probes), so it takes one\n // per-schema namespace node — never the whole tree root, and never a flat\n // merge of every namespace (which would collide same-named tables across\n // schemas). Probing more than one namespace at once is future work.\n const relationalSchema = relationalNamespaceNode(options.schema, schemaName);\n\n // Input-side control-policy partition. `external` / `observed` subjects\n // — and non-creation issues for `tolerated` subjects — are dropped from\n // the planner's input entirely; the planner never observes them, never\n // diffs them, never generates DDL for them. Suppression warnings are\n // built directly from the suppressed partition (one per subject), so the\n // user-visible message survives even when the planner would have failed\n // to model the subject's live shape.\n const issuePartition = partitionIssuesByControlPolicy({\n issues: schemaIssues,\n contract: options.contract,\n resolveControlPolicySubject: (issue) =>\n resolvePostgresNodeIssueControlPolicySubject(issue, options.contract),\n resolveCreationFactoryName: resolvePostgresNodeIssueCreationFactoryName,\n formatSubjectLabel: (factoryName, subject) =>\n formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),\n });\n\n const result = planIssues({\n issues: issuePartition.plannable,\n toContract: options.contract,\n // `fromContract` is only supplied by `migration plan`. It is `null` for\n // `db update` / `db init`, which means data-safety strategies needing\n // from/to comparisons (unsafe type change, nullable tightening) are\n // inapplicable there — reconciliation falls through to\n // `mapNodeIssueToCall`'s direct destructive handlers.\n fromContract: options.fromContract,\n schemaName,\n codecHooks,\n storageTypes,\n ...ifDefined('schema', relationalSchema),\n policy: options.policy,\n frameworkComponents: options.frameworkComponents,\n strategies: postgresPlannerStrategies,\n });\n\n if (!result.ok) {\n return plannerFailure(result.failure);\n }\n\n const schemaDiffCalls = this.planPostgresSchemaDiff(options, policyDiffIssues);\n const schemaDiffPartition = partitionCallsByControlPolicy({\n calls: schemaDiffCalls,\n contract: options.contract,\n resolveControlPolicySubject: (call) =>\n resolvePostgresCallControlPolicySubject(call, options.contract),\n resolveFactoryName: (call) => call.factoryName,\n formatSubjectLabel: (factoryName, subject) =>\n formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),\n });\n\n // Inline `onFieldEvent`-emitted ops after structural DDL. The fixed\n // ordering is `structural → added → dropped → altered`, with\n // within-group sorting by `(tableName, fieldName)` so re-emits are\n // byte-stable. The hook fires only at the application emitter —\n // extension-space planning never reaches this helper.\n const fieldEventOps = planFieldEventOperations({\n priorContract: options.fromContract,\n newContract: options.contract,\n codecHooks,\n });\n // Codec hook ops are target-agnostic `OpFactoryCall`; Postgres planning\n // lifts them at this integration boundary (see field-event-planner JSDoc).\n const fieldEventPostgresCalls = blindCast<\n readonly PostgresOpFactoryCall[],\n 'Codec hook ops conform to PostgresOpFactoryCall at the app emitter boundary'\n >(fieldEventOps);\n const fieldEventPartition = partitionCallsByControlPolicy({\n calls: fieldEventPostgresCalls,\n contract: options.contract,\n resolveControlPolicySubject: (call) =>\n resolvePostgresCallControlPolicySubject(call, options.contract),\n resolveFactoryName: (call) => call.factoryName,\n formatSubjectLabel: (factoryName, subject) =>\n formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract),\n });\n const calls = [...result.value.calls, ...schemaDiffPartition.kept, ...fieldEventPartition.kept];\n const warnings: SqlPlannerConflict[] = [\n ...issuePartition.warnings,\n ...schemaDiffPartition.warnings,\n ...fieldEventPartition.warnings,\n ];\n\n return Object.freeze({\n kind: 'success' as const,\n plan: new TypeScriptRenderablePostgresMigration(\n calls,\n {\n from: options.fromContract?.storage.storageHash ?? null,\n to: options.contract.storage.storageHash,\n },\n options.spaceId,\n this.#lowerer,\n ),\n ...(warnings.length > 0 ? { warnings: Object.freeze(warnings) } : {}),\n });\n }\n\n /**\n * Maps the RLS policy presence findings of the one combined tree diff\n * (`buildPostgresPlanDiff`, already ownership-filtered) into\n * `CREATE POLICY` / `DROP POLICY` / `ALTER POLICY … RENAME TO` ops. It\n * does not re-diff — it consumes exactly the policy-node subset of the\n * shared diff's issues. Enablement is NOT decided here: `ENABLE`/`DISABLE\n * ROW LEVEL SECURITY` derive from the table's marker-driven `rlsEnabled`\n * attribute diff on the relational side.\n *\n * Rename post-pass: a `not-found` and a `not-expected` policy on the SAME\n * table whose wire-name content hashes match but prefixes differ are one\n * prefix-only rename, collapsed into a single non-destructive\n * `RenamePostgresRlsPolicyCall`. Multi-candidate hash groups pair\n * deterministically by sorted wire name; leftovers proceed as\n * create/drop. Unparseable wire names never pair.\n *\n * The pairing runs only when the policy allows `widening` (rename's\n * class). Without it (db-init's additive-only set), pairing degrades\n * deliberately to the additive half: the new name is CREATEd and the old\n * policy survives live until a widening/destructive-allowed plan runs —\n * emitting an ungated widening rename would only fail at the runner's\n * class re-enforcement.\n */\n private planPostgresSchemaDiff(\n options: PlannerOptionsWithComponents,\n filteredDiffIssues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],\n ): readonly PostgresOpFactoryCall[] {\n const allowsDestructive = options.policy.allowedOperationClasses.includes('destructive');\n const allowsWidening = options.policy.allowedOperationClasses.includes('widening');\n\n interface PolicyFinding {\n readonly node: PostgresPolicySchemaNode;\n readonly schemaForTable: string;\n }\n const missing: PolicyFinding[] = [];\n const extra: PolicyFinding[] = [];\n\n for (const issue of filteredDiffIssues) {\n // 'not-equal' is unreachable for content-addressed policies: the wire name\n // encodes the body hash, so two policies sharing a local key (same name)\n // are always equal and isEqualTo never returns false.\n if (issue.reason === 'not-found') {\n const expected = issue.expected;\n PostgresPolicySchemaNode.assert(expected);\n // expected.namespaceId is the DDL schema name (resolved during projection);\n // this re-resolution is a no-op as long as PostgresSchema.ddlSchemaName() returns this.id.\n missing.push({\n node: expected,\n schemaForTable: resolveDdlSchemaForNamespaceStorage(\n options.contract.storage,\n expected.namespaceId,\n ),\n });\n } else if (issue.reason === 'not-expected') {\n const actual = issue.actual;\n PostgresPolicySchemaNode.assert(actual);\n extra.push({\n node: actual,\n schemaForTable: resolveDdlSchemaForNamespaceStorage(\n options.contract.storage,\n actual.namespaceId,\n ),\n });\n }\n }\n\n const calls: PostgresOpFactoryCall[] = [];\n const renamedExtras = new Set<PolicyFinding>();\n const renamedMissing = new Set<PolicyFinding>();\n const pairingKey = (finding: PolicyFinding, hash: string): string =>\n JSON.stringify([finding.schemaForTable, finding.node.tableName, hash]);\n\n if (allowsWidening) {\n const extrasByGroup = new Map<string, PolicyFinding[]>();\n for (const finding of extra) {\n const parsed = parseRlsPolicyWireName(finding.node.name);\n if (parsed === undefined) continue;\n const key = pairingKey(finding, parsed.hash);\n const group = extrasByGroup.get(key) ?? [];\n group.push(finding);\n extrasByGroup.set(key, group);\n }\n for (const group of extrasByGroup.values()) {\n group.sort((a, b) => (a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0));\n }\n\n const sortedMissing = [...missing].sort((a, b) =>\n a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0,\n );\n for (const missingFinding of sortedMissing) {\n const parsed = parseRlsPolicyWireName(missingFinding.node.name);\n if (parsed === undefined) continue;\n const candidates = extrasByGroup.get(pairingKey(missingFinding, parsed.hash));\n const candidate = candidates?.shift();\n if (candidate === undefined) continue;\n // Same name would never surface as missing+extra (the differ pairs by\n // name), so a matched candidate always differs in prefix only.\n renamedExtras.add(candidate);\n renamedMissing.add(missingFinding);\n calls.push(\n new RenamePostgresRlsPolicyCall(\n missingFinding.schemaForTable,\n missingFinding.node.tableName,\n candidate.node.name,\n missingFinding.node.name,\n ),\n );\n }\n }\n\n for (const finding of missing) {\n if (renamedMissing.has(finding)) continue;\n calls.push(\n new CreatePostgresRlsPolicyCall(\n finding.schemaForTable,\n finding.node.tableName,\n policyNodeToContractPolicy(finding.node),\n ),\n );\n }\n if (allowsDestructive) {\n for (const finding of extra) {\n if (renamedExtras.has(finding)) continue;\n calls.push(\n new DropPostgresRlsPolicyCall(\n finding.schemaForTable,\n finding.node.tableName,\n finding.node.name,\n ),\n );\n }\n }\n\n return calls;\n }\n\n private ensureAdditivePolicy(policy: MigrationOperationPolicy) {\n if (!policy.allowedOperationClasses.includes('additive')) {\n return plannerFailure([\n {\n kind: 'unsupportedOperation',\n summary: 'Migration planner requires additive operations be allowed',\n why: 'The planner requires the \"additive\" operation class to be allowed in the policy.',\n },\n ]);\n }\n return null;\n }\n}\n\n/**\n * A diff issue whose node is an RLS policy — the structural half of the one\n * combined tree diff, routed to `planPostgresSchemaDiff` instead of\n * `planIssues`.\n */\nfunction isPolicyDiffIssue(issue: SchemaDiffIssue<SqlSchemaDiffNode>): boolean {\n const node = issue.expected ?? issue.actual;\n return node !== undefined && PostgresPolicySchemaNode.is(node);\n}\n\n/**\n * Drops a `not-expected` issue when it is a whole extra TABLE that some\n * contract space owns, asking the ownership oracle per node.\n *\n * The consultation applies ONLY to table-level extras — a live table this\n * space's contract lacks — identified by asking the issue's own node\n * (`nodeKind === table`), never by counting path segments. `declaresEntity`\n * answers over the whole composition (self included), keyed on the schema-IR\n * entity coordinate so a genuine orphan in one namespace is never conflated\n * with a same-named table a sibling space declares in another, or with a\n * same-named non-table entity (an enum, an RLS policy) a sibling declares in\n * the SAME namespace: a positive answer means another space owns THIS table\n * in THIS namespace (a table this space owned would be in its expected tree,\n * never an extra) — leave it. A negative answer means no space owns it — a\n * genuine orphan to drop. `entityKind` is the literal `'table'` here: this\n * function only ever asks about a node already confirmed to be\n * `PostgresSchemaNodeKind.table` (checked just above), and the diff tree's\n * `nodeKind` vocabulary (`'postgres-table'`) is distinct from the storage\n * `entries` vocabulary `elementCoordinates` walks (`'table'`) — the literal\n * is that storage-entries spelling, not the node kind.\n *\n * The issue path carries the *resolved DDL schema* (e.g. `public`), but a\n * contract space's own declared namespace id can be the unbound sentinel\n * (`__unbound__`) that resolves to that schema — the two spellings would\n * never string-match. `resolveNamespaceIdForDdlSchema` recovers the raw\n * namespace id THIS space's own contract would use for that DDL schema, so\n * a table this space's own unbound namespace declares (and any sibling\n * whose own unbound namespace resolves the same way) is compared on the\n * same coordinate the aggregate's `elementCoordinates` walk yields.\n *\n * A DEEPER extra (an extra column/constraint on a table this space DOES own —\n * only the child drifted, so the table is in the expected tree) is this\n * space's own drift and is always kept for dropping; asking the oracle there\n * would wrongly suppress it, because the owned table answers `true`. Shallower\n * issues (namespace/root) are never ownership-scoped here. No oracle ⇒ every\n * extra is kept (single-space plan).\n */\nfunction retainUnownedExtras(\n issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[],\n ownership: SchemaOwnership | undefined,\n contract: Contract<SqlStorage>,\n): readonly SchemaDiffIssue<SqlSchemaDiffNode>[] {\n if (ownership === undefined) return issues;\n return issues.filter((issue) => {\n if (issue.reason !== 'not-expected') return true;\n const node = issueNode(issue);\n if (node === undefined || node.nodeKind !== PostgresSchemaNodeKind.table) return true;\n const ddlSchemaName = issueSchemaName(issue);\n const tableName = issueTableName(issue);\n if (ddlSchemaName === undefined || tableName === undefined) return true;\n const namespaceId = resolveNamespaceIdForDdlSchema(contract, ddlSchemaName);\n return !ownership.declaresEntity({ namespaceId, entityKind: 'table', entityName: tableName });\n });\n}\n\n/**\n * Returns the one namespace node the relational strategy layer probes for\n * live-table existence and reads codec-hook context off — the namespace\n * matching the planner's resolved schema name, or the first namespace when\n * none matches. `undefined` when the tree has no namespaces, so the strategy\n * context uses its empty-schema default.\n *\n * The relational strategies key tables by bare name, so they can only probe one\n * namespace at a time; probing across every namespace at once is future work.\n *\n * Returns the real `PostgresNamespaceSchemaNode` reference rather than a\n * projection: `storageTypePlanCallStrategy` hands this same value to codec\n * `planTypeOperations` hooks as `schema`, and hooks read the Postgres-specific\n * `nativeEnumTypeNames` field off it (via `PostgresNamespaceSchemaNode.is`) to\n * decide whether a native enum type already exists — a projection that only\n * copies `tables` would silently drop that signal. `StrategyContext.schema`'s\n * declared type (`SqlSchemaIR`, shared with SQLite's flat shape) doesn't\n * capture this, so the return is `blindCast` the same way `namespaceSchemaNodes`\n * in the family's relational walk already treats a namespace node as a\n * structurally-`SqlSchemaIR`-shaped value.\n */\nfunction relationalNamespaceNode(\n schema: PostgresDatabaseSchemaNode,\n schemaName: string,\n): SqlSchemaIR | undefined {\n const namespaceNodes = Object.values(schema.namespaces);\n const namespaceNode =\n namespaceNodes.find((node) => node.schemaName === schemaName) ?? namespaceNodes[0];\n if (namespaceNode === undefined) return undefined;\n return blindCast<\n SqlSchemaIR,\n 'PostgresNamespaceSchemaNode carries tables (+ nativeEnumTypeNames, read by codec hooks via PostgresNamespaceSchemaNode.is) structurally compatible with the SqlSchemaIR shape the strategy layer declares'\n >(namespaceNode);\n}\n\n/**\n * Rebuilds the `PostgresRlsPolicy` contract entity `CreatePostgresRlsPolicyCall`\n * carries (its `renderTypeScript`/`createRlsPolicy` paths serialize the whole\n * entity, `namespaceId` included). This reconstructs rather than looking the\n * original up in the contract on purpose: the diff node's `namespaceId` is the\n * *resolved DDL schema* (set when the expected tree was built), which is the\n * value the emitted op must carry; the contract-stored entity holds the raw,\n * pre-resolution coordinate, so a lookup would change the migration output.\n */\nfunction policyNodeToContractPolicy(node: PostgresPolicySchemaNode): PostgresRlsPolicy {\n return new PostgresRlsPolicy({\n name: node.name,\n prefix: node.prefix,\n tableName: node.tableName,\n namespaceId: node.namespaceId,\n operation: node.operation,\n roles: [...node.roles],\n ...ifDefined('using', node.using),\n ...ifDefined('withCheck', node.withCheck),\n permissive: node.permissive,\n });\n}\n"],"mappings":"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"}
package/dist/planner.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { r as contractToPostgresDatabaseSchemaNode } from "./diff-database-schema-CWb-jeLy.mjs";
2
- import { t as createPostgresMigrationPlanner } from "./planner-BryLk0Ie.mjs";
1
+ import { r as contractToPostgresDatabaseSchemaNode } from "./diff-database-schema-CtSK3us9.mjs";
2
+ import { t as createPostgresMigrationPlanner } from "./planner-jocGHxIR.mjs";
3
3
  export { contractToPostgresDatabaseSchemaNode, createPostgresMigrationPlanner };
package/package.json CHANGED
@@ -1,35 +1,35 @@
1
1
  {
2
2
  "name": "@prisma-next/target-postgres",
3
- "version": "0.14.0-dev.56",
3
+ "version": "0.14.0-dev.58",
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.14.0-dev.56",
10
- "@prisma-next/contract": "0.14.0-dev.56",
11
- "@prisma-next/errors": "0.14.0-dev.56",
12
- "@prisma-next/family-sql": "0.14.0-dev.56",
13
- "@prisma-next/framework-components": "0.14.0-dev.56",
14
- "@prisma-next/migration-tools": "0.14.0-dev.56",
15
- "@prisma-next/psl-parser": "0.14.0-dev.56",
16
- "@prisma-next/sql-contract": "0.14.0-dev.56",
17
- "@prisma-next/sql-errors": "0.14.0-dev.56",
18
- "@prisma-next/sql-operations": "0.14.0-dev.56",
19
- "@prisma-next/sql-relational-core": "0.14.0-dev.56",
20
- "@prisma-next/sql-schema-ir": "0.14.0-dev.56",
21
- "@prisma-next/ts-render": "0.14.0-dev.56",
22
- "@prisma-next/utils": "0.14.0-dev.56",
9
+ "@prisma-next/cli": "0.14.0-dev.58",
10
+ "@prisma-next/contract": "0.14.0-dev.58",
11
+ "@prisma-next/errors": "0.14.0-dev.58",
12
+ "@prisma-next/family-sql": "0.14.0-dev.58",
13
+ "@prisma-next/framework-components": "0.14.0-dev.58",
14
+ "@prisma-next/migration-tools": "0.14.0-dev.58",
15
+ "@prisma-next/psl-parser": "0.14.0-dev.58",
16
+ "@prisma-next/sql-contract": "0.14.0-dev.58",
17
+ "@prisma-next/sql-errors": "0.14.0-dev.58",
18
+ "@prisma-next/sql-operations": "0.14.0-dev.58",
19
+ "@prisma-next/sql-relational-core": "0.14.0-dev.58",
20
+ "@prisma-next/sql-schema-ir": "0.14.0-dev.58",
21
+ "@prisma-next/ts-render": "0.14.0-dev.58",
22
+ "@prisma-next/utils": "0.14.0-dev.58",
23
23
  "@standard-schema/spec": "^1.1.0",
24
24
  "arktype": "^2.2.0",
25
25
  "pathe": "^2.0.3"
26
26
  },
27
27
  "devDependencies": {
28
- "@prisma-next/psl-printer": "0.14.0-dev.56",
29
- "@prisma-next/sql-contract-psl": "0.14.0-dev.56",
30
- "@prisma-next/test-utils": "0.14.0-dev.56",
31
- "@prisma-next/tsconfig": "0.14.0-dev.56",
32
- "@prisma-next/tsdown": "0.14.0-dev.56",
28
+ "@prisma-next/psl-printer": "0.14.0-dev.58",
29
+ "@prisma-next/sql-contract-psl": "0.14.0-dev.58",
30
+ "@prisma-next/test-utils": "0.14.0-dev.58",
31
+ "@prisma-next/tsconfig": "0.14.0-dev.58",
32
+ "@prisma-next/tsdown": "0.14.0-dev.58",
33
33
  "tsdown": "0.22.1",
34
34
  "typescript": "5.9.3",
35
35
  "vitest": "4.1.8"
@@ -85,18 +85,20 @@ export function contractToPostgresDatabaseSchemaNode(
85
85
  for (const tableName of Object.keys(ns.table)) {
86
86
  const sqlTable = sqlTables[tableName];
87
87
  if (sqlTable === undefined) continue;
88
- // The family conversion stamps `referencedSchema` with the FK target's
89
- // namespace id verbatim, which can be the unbound sentinel. Resolve it
90
- // to the real live DDL schema here introspected FKs already carry the
91
- // live schema, so this is what lets an expected FK pair (by diff-node
92
- // id) with its introspected counterpart.
88
+ // The family conversion stamps `referencedSchema` only for bound FK
89
+ // targets; an absent value means the FK targets the unbound namespace.
90
+ // Postgres restores its own coordinate for that slot (the unbound
91
+ // singleton's id) so the raw coordinate keeps qualifying REFERENCES
92
+ // clauses, and resolves the real live DDL schema — introspected FKs
93
+ // already carry the live schema, so this is what lets an expected FK
94
+ // pair (by diff-node id) with its introspected counterpart.
93
95
  const foreignKeys = sqlTable.foreignKeys.map(
94
96
  (fk) =>
95
97
  new SqlForeignKeyIR({
96
98
  columns: fk.columns,
97
99
  referencedTable: fk.referencedTable,
98
100
  referencedColumns: fk.referencedColumns,
99
- ...ifDefined('referencedSchema', fk.referencedSchema),
101
+ referencedSchema: fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,
100
102
  ...ifDefined('name', fk.name),
101
103
  ...ifDefined('onDelete', fk.onDelete),
102
104
  ...ifDefined('onUpdate', fk.onUpdate),
@@ -1,10 +1,7 @@
1
1
  import type { Contract, ControlPolicy } from '@prisma-next/contract/types';
2
2
  import type { SqlSchemaDiffResult } from '@prisma-next/family-sql/control';
3
3
  import { buildNativeTypeExpander } from '@prisma-next/family-sql/control';
4
- import {
5
- classifyDiffSubjectGranularity,
6
- resolveSemanticSatisfaction,
7
- } from '@prisma-next/family-sql/diff';
4
+ import { classifyDiffSubjectGranularity } from '@prisma-next/family-sql/diff';
8
5
  import type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';
9
6
  import type { SchemaDiffIssue } from '@prisma-next/framework-components/control';
10
7
  import { diffSchemas } from '@prisma-next/framework-components/control';
@@ -15,7 +12,6 @@ import { ifDefined } from '@prisma-next/utils/defined';
15
12
  import type { PostgresContract } from '../postgres-schema';
16
13
  import { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';
17
14
  import { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';
18
- import { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';
19
15
  import {
20
16
  postgresDiffSubjectGranularity,
21
17
  type SqlSchemaDiffNode,
@@ -30,64 +26,6 @@ function ownedSchemaNames(expected: PostgresDatabaseSchemaNode): ReadonlySet<str
30
26
  return new Set([...policyNamespaces, ...expected.existingSchemas]);
31
27
  }
32
28
 
33
- /**
34
- * Applies the family's semantic-satisfaction normalization across a Postgres
35
- * tree pair: every actual table with an expected counterpart (paired by
36
- * namespace id, then table id) gets its unique/index child lists adjusted;
37
- * everything else passes through untouched.
38
- */
39
- export function normalizePostgresActualForDiff(
40
- expected: PostgresDatabaseSchemaNode,
41
- actual: PostgresDatabaseSchemaNode,
42
- ): PostgresDatabaseSchemaNode {
43
- const namespaces: Record<string, PostgresNamespaceSchemaNode> = {};
44
- for (const [nsId, actualNs] of Object.entries(actual.namespaces)) {
45
- const expectedNs = expected.namespaces[nsId];
46
- if (expectedNs === undefined) {
47
- namespaces[nsId] = actualNs;
48
- continue;
49
- }
50
- const tables: Record<string, PostgresTableSchemaNode> = {};
51
- for (const [tableName, actualTable] of Object.entries(actualNs.tables)) {
52
- const expectedTable = expectedNs.tables[tableName];
53
- if (expectedTable === undefined) {
54
- tables[tableName] = actualTable;
55
- continue;
56
- }
57
- const adjusted = resolveSemanticSatisfaction({
58
- expectedUniques: expectedTable.uniques,
59
- expectedIndexes: expectedTable.indexes,
60
- actualUniques: actualTable.uniques,
61
- actualIndexes: actualTable.indexes,
62
- });
63
- tables[tableName] = new PostgresTableSchemaNode({
64
- name: actualTable.name,
65
- columns: actualTable.columns,
66
- foreignKeys: actualTable.foreignKeys,
67
- uniques: adjusted.actualUniques,
68
- indexes: adjusted.actualIndexes,
69
- ...ifDefined('primaryKey', actualTable.primaryKey),
70
- ...ifDefined('annotations', actualTable.annotations),
71
- ...ifDefined('checks', actualTable.checks),
72
- policies: [...actualTable.policies],
73
- rlsEnabled: actualTable.rlsEnabled,
74
- });
75
- }
76
- namespaces[nsId] = new PostgresNamespaceSchemaNode({
77
- schemaName: actualNs.schemaName,
78
- tables,
79
- nativeEnumTypeNames: actualNs.nativeEnumTypeNames,
80
- nativeEnums: actualNs.nativeEnums,
81
- });
82
- }
83
- return new PostgresDatabaseSchemaNode({
84
- namespaces,
85
- roles: [...actual.roles],
86
- existingSchemas: [...actual.existingSchemas],
87
- pgVersion: actual.pgVersion,
88
- });
89
- }
90
-
91
29
  /**
92
30
  * Drops contract namespaces that declare no tables from the verdict-diff
93
31
  * expected tree and the relational owned-schema set. The legacy relational
@@ -135,9 +73,8 @@ function resolveControlPolicy(
135
73
  /**
136
74
  * The Postgres full-tree node diff for the family verify verdict: derive
137
75
  * the expected tree (resolved leaf values, expander threaded, FK schemas
138
- * resolved, table-less namespaces pruned), normalize the actual tree for
139
- * semantic satisfaction, run the generic
140
- * differ, and scope out `not-expected` findings under namespaces the
76
+ * resolved, table-less namespaces pruned), run the generic
77
+ * differ over the trees as derived, and scope out `not-expected` findings under namespaces the
141
78
  * contract does not own. Ownership is role-aware, mirroring the legacy
142
79
  * decomposition: relational extras check the PRUNED owned set (the legacy
143
80
  * per-namespace walk never visited a table-less namespace, so its live
@@ -166,10 +103,9 @@ export function diffPostgresSchema(input: {
166
103
  ...ifDefined('expandNativeType', expandNativeType),
167
104
  });
168
105
  const expected = pruneTableLessNamespaces(fullExpected);
169
- const normalizedActual = normalizePostgresActualForDiff(expected, actual);
170
106
  const relationalOwned = ownedSchemaNames(expected);
171
107
  const structuralOwned = ownedSchemaNames(fullExpected);
172
- const issues = diffSchemas(expected, normalizedActual).filter((issue) => {
108
+ const issues = diffSchemas(expected, actual).filter((issue) => {
173
109
  if (issue.reason !== 'not-expected') return true;
174
110
  const namespaceSegment = issue.path[1];
175
111
  if (namespaceSegment === undefined) return true;
@@ -227,7 +163,7 @@ function padActualNamespaces(
227
163
  export interface PostgresPlanDiff {
228
164
  /** The desired ("end") tree — resolved leaf values (incl. `codecRef`) on every column, table-less namespaces pruned. */
229
165
  readonly expected: PostgresDatabaseSchemaNode;
230
- /** The live ("start") tree, padded with empty namespaces and normalized for semantic satisfaction against `expected`. */
166
+ /** The live ("start") tree, padded with empty namespaces so a missing schema's tables pair. */
231
167
  readonly actual: PostgresDatabaseSchemaNode;
232
168
  /** The one node diff over the two trees: relational + policy drift, role-aware ownership filtered. */
233
169
  readonly issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[];
@@ -236,8 +172,8 @@ export interface PostgresPlanDiff {
236
172
  /**
237
173
  * The Postgres planner's diff input: the SAME tree-building
238
174
  * `diffPostgresSchema` uses (expander threaded, FK schemas resolved,
239
- * table-less namespaces pruned, actual normalized for semantic satisfaction,
240
- * role-aware ownership filter) plus actual namespace padding (so a missing
175
+ * table-less namespaces pruned, role-aware ownership filter) plus actual
176
+ * namespace padding (so a missing
241
177
  * schema's tables surface as `not-found` instead of a swallowed namespace
242
178
  * `not-found`). One differ drives both verify and plan; this is the
243
179
  * plan-side derivation. The single issue list covers tables / columns /
@@ -264,13 +200,12 @@ export function buildPostgresPlanDiff(input: {
264
200
  const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, projectionOptions);
265
201
  const expected = pruneTableLessNamespaces(fullExpected);
266
202
  const paddedActual = padActualNamespaces(expected, actual);
267
- const normalizedActual = normalizePostgresActualForDiff(expected, paddedActual);
268
203
  const relationalOwned = ownedSchemaNames(expected);
269
204
  const structuralOwned = ownedSchemaNames(fullExpected);
270
205
  const issues = blindCast<
271
206
  readonly SchemaDiffIssue<SqlSchemaDiffNode>[],
272
207
  'both trees are PostgresDatabaseSchemaNodes, so every diff-issue node is a SqlSchemaDiffNode'
273
- >(diffSchemas(expected, normalizedActual)).filter((issue) => {
208
+ >(diffSchemas(expected, paddedActual)).filter((issue) => {
274
209
  if (issue.reason !== 'not-expected') return true;
275
210
  const namespaceSegment = issue.path[1];
276
211
  if (namespaceSegment === undefined) return true;
@@ -278,5 +213,5 @@ export function buildPostgresPlanDiff(input: {
278
213
  const owned = granularity === 'structural' ? structuralOwned : relationalOwned;
279
214
  return owned.has(namespaceSegment);
280
215
  });
281
- return { expected, actual: normalizedActual, issues };
216
+ return { expected, actual: paddedActual, issues };
282
217
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"diff-database-schema-CWb-jeLy.mjs","names":[],"sources":["../src/core/migrations/contract-to-postgres-database-schema-node.ts","../src/core/migrations/diff-database-schema.ts"],"sourcesContent":["import type { ContractToSchemaIROptions } from '@prisma-next/family-sql/control';\nimport { contractNamespaceToSchemaIR } from '@prisma-next/family-sql/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport { SqlForeignKeyIR } from '@prisma-next/sql-schema-ir/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { PostgresRlsPolicy } from '../postgres-rls-policy';\nimport type { PostgresContract } from '../postgres-schema';\nimport { isPostgresSchema } from '../postgres-schema';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';\nimport { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';\nimport { PostgresRoleSchemaNode } from '../schema-ir/postgres-role-schema-node';\nimport { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';\nimport { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';\n\nfunction toPolicyNode(policy: PostgresRlsPolicy, namespaceId: string): PostgresPolicySchemaNode {\n return new PostgresPolicySchemaNode({\n name: policy.name,\n prefix: policy.prefix,\n tableName: policy.tableName,\n namespaceId,\n operation: policy.operation,\n roles: [...policy.roles],\n ...ifDefined('using', policy.using),\n ...ifDefined('withCheck', policy.withCheck),\n permissive: policy.permissive,\n });\n}\n\n/**\n * Projects a Postgres contract into the expected schema-diff tree: a\n * `PostgresDatabaseSchemaNode` root holding one `PostgresNamespaceSchemaNode`\n * per Postgres namespace, each holding its `PostgresTableSchemaNode`s with\n * their `PostgresPolicySchemaNode`s, plus the database roles on the root.\n *\n * Not a duplicate of the family's `contractToSchemaIR`: that builds a flat,\n * single `{ tables }` map (and throws on cross-namespace name collisions, with\n * no RLS/role concept) for SQLite's single-schema world. This is the\n * Postgres-specific *tree* shape — multi-schema, RLS-policy-aware, role-aware.\n * It reuses the family's per-namespace table conversion (`contractNamespaceToSchemaIR`)\n * for column/FK/index building and only adds the Postgres tree/policy/role shape.\n *\n * Tables are grouped by their owning namespace (resolved DDL schema name) so\n * the tree mirrors Postgres's object hierarchy. The DDL schema name is\n * resolved once per namespace.\n *\n * A policy that references a table absent from its namespace is a malformed\n * contract — the loop throws rather than fabricating a stub table.\n */\nexport function contractToPostgresDatabaseSchemaNode(\n contract: PostgresContract | null,\n options: ContractToSchemaIROptions,\n): PostgresDatabaseSchemaNode {\n if (contract === null) {\n return new PostgresDatabaseSchemaNode({\n namespaces: {},\n roles: [],\n existingSchemas: [],\n pgVersion: '',\n });\n }\n\n const namespaces: Record<string, PostgresNamespaceSchemaNode> = {};\n const roles: PostgresRoleSchemaNode[] = [];\n const ownedSchemas: string[] = [];\n\n for (const ns of Object.values(contract.storage.namespaces)) {\n if (!isPostgresSchema(ns)) continue;\n const ddlSchema = resolveDdlSchemaForNamespaceStorage(contract.storage, ns.id);\n ownedSchemas.push(ddlSchema);\n\n // Convert only THIS namespace's tables (passing the full storage for\n // type/value-set/enum resolution that spans namespaces), so the same table\n // name can exist in two schemas without colliding in a bare-keyed record.\n const sqlTables = contractNamespaceToSchemaIR(contract.storage, ns.id, options).tables;\n\n const policiesByTable = new Map<string, PostgresPolicySchemaNode[]>();\n for (const policy of Object.values(ns.policy)) {\n const list = policiesByTable.get(policy.tableName) ?? [];\n list.push(toPolicyNode(policy, ddlSchema));\n policiesByTable.set(policy.tableName, list);\n }\n\n const tables: Record<string, PostgresTableSchemaNode> = {};\n for (const tableName of Object.keys(ns.table)) {\n const sqlTable = sqlTables[tableName];\n if (sqlTable === undefined) continue;\n // The family conversion stamps `referencedSchema` with the FK target's\n // namespace id verbatim, which can be the unbound sentinel. Resolve it\n // to the real live DDL schema here — introspected FKs already carry the\n // live schema, so this is what lets an expected FK pair (by diff-node\n // id) with its introspected counterpart.\n const foreignKeys = sqlTable.foreignKeys.map(\n (fk) =>\n new SqlForeignKeyIR({\n columns: fk.columns,\n referencedTable: fk.referencedTable,\n referencedColumns: fk.referencedColumns,\n ...ifDefined('referencedSchema', fk.referencedSchema),\n ...ifDefined('name', fk.name),\n ...ifDefined('onDelete', fk.onDelete),\n ...ifDefined('onUpdate', fk.onUpdate),\n ...ifDefined('annotations', fk.annotations),\n resolvedReferencedNamespace: resolveDdlSchemaForNamespaceStorage(\n contract.storage,\n fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,\n ),\n }),\n );\n tables[tableName] = new PostgresTableSchemaNode({\n name: sqlTable.name,\n columns: sqlTable.columns,\n foreignKeys,\n uniques: sqlTable.uniques,\n indexes: sqlTable.indexes,\n ...ifDefined('primaryKey', sqlTable.primaryKey),\n ...ifDefined('annotations', sqlTable.annotations),\n ...ifDefined('checks', sqlTable.checks),\n policies: policiesByTable.get(tableName) ?? [],\n // Marker-driven, never derived from the policy set: the `rls` entry\n // is the single authored source of enablement.\n rlsEnabled: Object.hasOwn(ns.rls, tableName),\n });\n }\n\n for (const [tableName, tablePolicies] of policiesByTable) {\n if (!(tableName in tables)) {\n const policyName = tablePolicies[0]?.name ?? '(unknown)';\n throw new Error(\n `contract-to-postgres-database-schema-node: policy \"${policyName}\" references table \"${tableName}\" not present in namespace \"${ddlSchema}\"`,\n );\n }\n if (!Object.hasOwn(ns.rls, tableName)) {\n const policyPrefix = tablePolicies[0]?.prefix ?? '(unknown)';\n throw new Error(\n `contract-to-postgres-database-schema-node: policy \"${policyPrefix}\" targets table \"${tableName}\" in namespace \"${ddlSchema}\", which is not RLS-controlled. Mark the model with @@rls (entries.rls[\"${tableName}\"]) or remove the policy.`,\n );\n }\n }\n\n namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({\n schemaName: ddlSchema,\n tables,\n nativeEnumTypeNames: [],\n });\n\n for (const role of Object.values(ns.role)) {\n roles.push(new PostgresRoleSchemaNode({ name: role.name, namespaceId: role.namespaceId }));\n }\n }\n\n return new PostgresDatabaseSchemaNode({\n namespaces,\n roles,\n existingSchemas: ownedSchemas,\n pgVersion: '',\n });\n}\n","import type { Contract, ControlPolicy } from '@prisma-next/contract/types';\nimport type { SqlSchemaDiffResult } from '@prisma-next/family-sql/control';\nimport { buildNativeTypeExpander } from '@prisma-next/family-sql/control';\nimport {\n classifyDiffSubjectGranularity,\n resolveSemanticSatisfaction,\n} from '@prisma-next/family-sql/diff';\nimport type { TargetBoundComponentDescriptor } from '@prisma-next/framework-components/components';\nimport type { SchemaDiffIssue } from '@prisma-next/framework-components/control';\nimport { diffSchemas } from '@prisma-next/framework-components/control';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { SqlSchemaIRNode } from '@prisma-next/sql-schema-ir/types';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { PostgresContract } from '../postgres-schema';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresNamespaceSchemaNode } from '../schema-ir/postgres-namespace-schema-node';\nimport { PostgresTableSchemaNode } from '../schema-ir/postgres-table-schema-node';\nimport {\n postgresDiffSubjectGranularity,\n type SqlSchemaDiffNode,\n} from '../schema-ir/schema-node-kinds';\nimport { contractToPostgresDatabaseSchemaNode } from './contract-to-postgres-database-schema-node';\nimport { resolvePostgresNodeIssueControlPolicySubject } from './control-policy';\n\nfunction ownedSchemaNames(expected: PostgresDatabaseSchemaNode): ReadonlySet<string> {\n const policyNamespaces = Object.values(expected.namespaces).flatMap((ns) =>\n Object.values(ns.tables).flatMap((t) => t.policies.map((p) => p.namespaceId)),\n );\n return new Set([...policyNamespaces, ...expected.existingSchemas]);\n}\n\n/**\n * Applies the family's semantic-satisfaction normalization across a Postgres\n * tree pair: every actual table with an expected counterpart (paired by\n * namespace id, then table id) gets its unique/index child lists adjusted;\n * everything else passes through untouched.\n */\nexport function normalizePostgresActualForDiff(\n expected: PostgresDatabaseSchemaNode,\n actual: PostgresDatabaseSchemaNode,\n): PostgresDatabaseSchemaNode {\n const namespaces: Record<string, PostgresNamespaceSchemaNode> = {};\n for (const [nsId, actualNs] of Object.entries(actual.namespaces)) {\n const expectedNs = expected.namespaces[nsId];\n if (expectedNs === undefined) {\n namespaces[nsId] = actualNs;\n continue;\n }\n const tables: Record<string, PostgresTableSchemaNode> = {};\n for (const [tableName, actualTable] of Object.entries(actualNs.tables)) {\n const expectedTable = expectedNs.tables[tableName];\n if (expectedTable === undefined) {\n tables[tableName] = actualTable;\n continue;\n }\n const adjusted = resolveSemanticSatisfaction({\n expectedUniques: expectedTable.uniques,\n expectedIndexes: expectedTable.indexes,\n actualUniques: actualTable.uniques,\n actualIndexes: actualTable.indexes,\n });\n tables[tableName] = new PostgresTableSchemaNode({\n name: actualTable.name,\n columns: actualTable.columns,\n foreignKeys: actualTable.foreignKeys,\n uniques: adjusted.actualUniques,\n indexes: adjusted.actualIndexes,\n ...ifDefined('primaryKey', actualTable.primaryKey),\n ...ifDefined('annotations', actualTable.annotations),\n ...ifDefined('checks', actualTable.checks),\n policies: [...actualTable.policies],\n rlsEnabled: actualTable.rlsEnabled,\n });\n }\n namespaces[nsId] = new PostgresNamespaceSchemaNode({\n schemaName: actualNs.schemaName,\n tables,\n nativeEnumTypeNames: actualNs.nativeEnumTypeNames,\n nativeEnums: actualNs.nativeEnums,\n });\n }\n return new PostgresDatabaseSchemaNode({\n namespaces,\n roles: [...actual.roles],\n existingSchemas: [...actual.existingSchemas],\n pgVersion: actual.pgVersion,\n });\n}\n\n/**\n * Drops contract namespaces that declare no tables from the verdict-diff\n * expected tree and the relational owned-schema set. The legacy relational\n * walk skipped a table-less namespace (e.g. an enums-only schema) before\n * pairing, so neither its DDL schema's absence nor that schema's live\n * relational contents ever reached the verdict — the pruned tree\n * reproduces that. The prune loses no expected policies: policies attach\n * to tables, so a table-less namespace carries none (the projection\n * throws on a policy referencing an absent table). Live policies in a\n * pruned schema remain governed via the full owned set (see\n * {@link diffPostgresSchema}).\n */\nfunction pruneTableLessNamespaces(\n expected: PostgresDatabaseSchemaNode,\n): PostgresDatabaseSchemaNode {\n const namespaces = Object.fromEntries(\n Object.entries(expected.namespaces).filter(([, ns]) => Object.keys(ns.tables).length > 0),\n );\n return new PostgresDatabaseSchemaNode({\n namespaces,\n roles: [...expected.roles],\n existingSchemas: expected.existingSchemas.filter((s) => namespaces[s] !== undefined),\n pgVersion: expected.pgVersion,\n });\n}\n\n/**\n * Resolves a verdict-diff issue's subject table's declared control policy\n * directly from the contract, by delegating to the same node-typed resolver\n * ({@link resolvePostgresNodeIssueControlPolicySubject}) the planner uses to\n * gate DDL calls. `undefined` when the issue resolves to no contract table.\n */\nfunction resolveControlPolicy(\n issue: SchemaDiffIssue,\n contract: Contract<SqlStorage>,\n): ControlPolicy | undefined {\n const nodeIssue = blindCast<\n SchemaDiffIssue<SqlSchemaDiffNode>,\n 'every node in a Postgres schema diff tree is a SqlSchemaDiffNode'\n >(issue);\n return resolvePostgresNodeIssueControlPolicySubject(nodeIssue, contract)\n ?.explicitNodeControlPolicy;\n}\n\n/**\n * The Postgres full-tree node diff for the family verify verdict: derive\n * the expected tree (resolved leaf values, expander threaded, FK schemas\n * resolved, table-less namespaces pruned), normalize the actual tree for\n * semantic satisfaction, run the generic\n * differ, and scope out `not-expected` findings under namespaces the\n * contract does not own. Ownership is role-aware, mirroring the legacy\n * decomposition: relational extras check the PRUNED owned set (the legacy\n * per-namespace walk never visited a table-less namespace, so its live\n * relational contents are invisible), while `structural` extras (RLS\n * policies) check the FULL owned set (the legacy policy diff governed\n * every contract schema regardless of tables — RLS governance does not\n * shrink because a namespace declares no tables). The codec `verifyType`\n * hooks run once per contract namespace with tables against that\n * namespace's paired actual node (the hooks read namespace-scoped state\n * such as `nativeEnumTypeNames`).\n */\nexport function diffPostgresSchema(input: {\n readonly contract: Contract<SqlStorage>;\n readonly schema: SqlSchemaIRNode;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;\n}): SqlSchemaDiffResult {\n const postgresContract = blindCast<\n PostgresContract,\n 'diffPostgresSchema is only called with a postgres contract'\n >(input.contract);\n PostgresDatabaseSchemaNode.assert(input.schema);\n const actual = input.schema;\n const expandNativeType = buildNativeTypeExpander(input.frameworkComponents);\n const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, {\n annotationNamespace: 'pg',\n ...ifDefined('expandNativeType', expandNativeType),\n });\n const expected = pruneTableLessNamespaces(fullExpected);\n const normalizedActual = normalizePostgresActualForDiff(expected, actual);\n const relationalOwned = ownedSchemaNames(expected);\n const structuralOwned = ownedSchemaNames(fullExpected);\n const issues = diffSchemas(expected, normalizedActual).filter((issue) => {\n if (issue.reason !== 'not-expected') return true;\n const namespaceSegment = issue.path[1];\n if (namespaceSegment === undefined) return true;\n const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);\n const owned = granularity === 'structural' ? structuralOwned : relationalOwned;\n return owned.has(namespaceSegment);\n });\n const namespacePairs = Object.values(expected.namespaces).map((ns) => ({\n actual: actual.namespaces[ns.schemaName],\n }));\n return {\n issues,\n resolveControlPolicy: (issue) => resolveControlPolicy(issue, postgresContract),\n namespacePairs,\n };\n}\n\n/**\n * Adds an empty namespace node to the actual tree for every expected namespace\n * absent from it. The relational plan diff pairs on namespace: a contract\n * namespace whose live schema does not exist yet must surface each of its\n * tables as `not-found` (→ `CREATE TABLE`), NOT as a single namespace\n * `not-found` that subtree-coalescing would collapse (leaving `CREATE SCHEMA`\n * with no tables). Padding makes the namespaces pair, so only table/column/\n * policy drift surfaces; `CREATE SCHEMA` comes separately from the synthesized\n * namespace-presence stitch (`verifyPostgresNamespacePresence`), never from the\n * tree diff — matching the retired per-namespace-paired relational walk, which\n * paired a missing schema against an empty namespace node.\n */\nfunction padActualNamespaces(\n expected: PostgresDatabaseSchemaNode,\n actual: PostgresDatabaseSchemaNode,\n): PostgresDatabaseSchemaNode {\n const namespaces: Record<string, PostgresNamespaceSchemaNode> = { ...actual.namespaces };\n let padded = false;\n for (const schemaName of Object.keys(expected.namespaces)) {\n if (namespaces[schemaName] === undefined) {\n namespaces[schemaName] = new PostgresNamespaceSchemaNode({\n schemaName,\n tables: {},\n nativeEnumTypeNames: [],\n });\n padded = true;\n }\n }\n if (!padded) return actual;\n return new PostgresDatabaseSchemaNode({\n namespaces,\n roles: [...actual.roles],\n existingSchemas: [...actual.existingSchemas],\n pgVersion: actual.pgVersion,\n });\n}\n\nexport interface PostgresPlanDiff {\n /** The desired (\"end\") tree — resolved leaf values (incl. `codecRef`) on every column, table-less namespaces pruned. */\n readonly expected: PostgresDatabaseSchemaNode;\n /** The live (\"start\") tree, padded with empty namespaces and normalized for semantic satisfaction against `expected`. */\n readonly actual: PostgresDatabaseSchemaNode;\n /** The one node diff over the two trees: relational + policy drift, role-aware ownership filtered. */\n readonly issues: readonly SchemaDiffIssue<SqlSchemaDiffNode>[];\n}\n\n/**\n * The Postgres planner's diff input: the SAME tree-building\n * `diffPostgresSchema` uses (expander threaded, FK schemas resolved,\n * table-less namespaces pruned, actual normalized for semantic satisfaction,\n * role-aware ownership filter) plus actual namespace padding (so a missing\n * schema's tables surface as `not-found` instead of a swallowed namespace\n * `not-found`). One differ drives both verify and plan; this is the\n * plan-side derivation. The single issue list covers tables / columns /\n * constraints / indexes / defaults AND policies — the caller splits it\n * (relational → `mapNodeIssueToCall`; policy → RLS ops) and stitches in\n * `CREATE SCHEMA` separately.\n */\nexport function buildPostgresPlanDiff(input: {\n readonly contract: Contract<SqlStorage>;\n readonly actualSchema: SqlSchemaIRNode;\n readonly frameworkComponents: ReadonlyArray<TargetBoundComponentDescriptor<'sql', string>>;\n}): PostgresPlanDiff {\n const postgresContract = blindCast<\n PostgresContract,\n 'buildPostgresPlanDiff is only called with a postgres contract'\n >(input.contract);\n PostgresDatabaseSchemaNode.assert(input.actualSchema);\n const actual = input.actualSchema;\n const expandNativeType = buildNativeTypeExpander(input.frameworkComponents);\n const projectionOptions = {\n annotationNamespace: 'pg',\n ...ifDefined('expandNativeType', expandNativeType),\n };\n const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, projectionOptions);\n const expected = pruneTableLessNamespaces(fullExpected);\n const paddedActual = padActualNamespaces(expected, actual);\n const normalizedActual = normalizePostgresActualForDiff(expected, paddedActual);\n const relationalOwned = ownedSchemaNames(expected);\n const structuralOwned = ownedSchemaNames(fullExpected);\n const issues = blindCast<\n readonly SchemaDiffIssue<SqlSchemaDiffNode>[],\n 'both trees are PostgresDatabaseSchemaNodes, so every diff-issue node is a SqlSchemaDiffNode'\n >(diffSchemas(expected, normalizedActual)).filter((issue) => {\n if (issue.reason !== 'not-expected') return true;\n const namespaceSegment = issue.path[1];\n if (namespaceSegment === undefined) return true;\n const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);\n const owned = granularity === 'structural' ? structuralOwned : relationalOwned;\n return owned.has(namespaceSegment);\n });\n return { expected, actual: normalizedActual, issues };\n}\n"],"mappings":"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"}