@prisma-next/target-postgres 0.14.0-dev.49 → 0.14.0-dev.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/control-policy-DUFgy9jO.mjs +1103 -0
  2. package/dist/control-policy-DUFgy9jO.mjs.map +1 -0
  3. package/dist/control.d.mts.map +1 -1
  4. package/dist/control.mjs +19 -40
  5. package/dist/control.mjs.map +1 -1
  6. package/dist/default-normalizer.mjs +247 -1
  7. package/dist/default-normalizer.mjs.map +1 -0
  8. package/dist/diff-database-schema-BmSmWcaY.mjs +313 -0
  9. package/dist/diff-database-schema-BmSmWcaY.mjs.map +1 -0
  10. package/dist/diff-database-schema.d.mts +46 -0
  11. package/dist/diff-database-schema.d.mts.map +1 -0
  12. package/dist/diff-database-schema.mjs +2 -0
  13. package/dist/issue-planner.d.mts +34 -10
  14. package/dist/issue-planner.d.mts.map +1 -1
  15. package/dist/issue-planner.mjs +2 -2
  16. package/dist/migration.d.mts +2 -2
  17. package/dist/migration.mjs +2 -2
  18. package/dist/native-type-normalizer.mjs +37 -1
  19. package/dist/native-type-normalizer.mjs.map +1 -0
  20. package/dist/{op-factory-call-BuoGT5UI.mjs → op-factory-call-BKRnMgrB.mjs} +2 -2
  21. package/dist/op-factory-call-BKRnMgrB.mjs.map +1 -0
  22. package/dist/{op-factory-call-Caqus-Qg.d.mts → op-factory-call-Dt-Jx5JS.d.mts} +2 -2
  23. package/dist/{op-factory-call-Caqus-Qg.d.mts.map → op-factory-call-Dt-Jx5JS.d.mts.map} +1 -1
  24. package/dist/op-factory-call.d.mts +2 -2
  25. package/dist/op-factory-call.mjs +2 -2
  26. package/dist/planner-BMBRPzsv.mjs +367 -0
  27. package/dist/planner-BMBRPzsv.mjs.map +1 -0
  28. package/dist/planner-ddl-builders-B8Nn6nHN.mjs.map +1 -1
  29. package/dist/planner-ddl-builders.d.mts +2 -2
  30. package/dist/planner-ddl-builders.d.mts.map +1 -1
  31. package/dist/planner-identity-values-CJPha2Sz.mjs.map +1 -1
  32. package/dist/planner-identity-values.d.mts +7 -1
  33. package/dist/planner-identity-values.d.mts.map +1 -1
  34. package/dist/planner-identity-values.mjs +2 -2
  35. package/dist/{planner-produced-postgres-migration-CtJi4sgS.d.mts → planner-produced-postgres-migration-C0a7I1-e.d.mts} +2 -2
  36. package/dist/{planner-produced-postgres-migration-CtJi4sgS.d.mts.map → planner-produced-postgres-migration-C0a7I1-e.d.mts.map} +1 -1
  37. package/dist/{planner-produced-postgres-migration-C2_H6Vvl.mjs → planner-produced-postgres-migration-Dp6IO_gj.mjs} +2 -2
  38. package/dist/{planner-produced-postgres-migration-C2_H6Vvl.mjs.map → planner-produced-postgres-migration-Dp6IO_gj.mjs.map} +1 -1
  39. package/dist/planner-produced-postgres-migration.d.mts +1 -1
  40. package/dist/planner-produced-postgres-migration.mjs +1 -1
  41. package/dist/planner-sql-checks-Cze3vMfe.mjs.map +1 -1
  42. package/dist/planner-sql-checks.d.mts +1 -1
  43. package/dist/planner-sql-checks.d.mts.map +1 -1
  44. package/dist/planner.d.mts +27 -67
  45. package/dist/planner.d.mts.map +1 -1
  46. package/dist/planner.mjs +3 -2
  47. package/dist/{postgres-database-schema-node-os8ogEap.d.mts → postgres-database-schema-node-C9bTuMrd.d.mts} +14 -7
  48. package/dist/postgres-database-schema-node-C9bTuMrd.d.mts.map +1 -0
  49. package/dist/{postgres-migration-D0QIVE-p.mjs → postgres-migration-D0jwzYDT.mjs} +2 -2
  50. package/dist/{postgres-migration-D0QIVE-p.mjs.map → postgres-migration-D0jwzYDT.mjs.map} +1 -1
  51. package/dist/{postgres-migration-CfyKDT7b.d.mts → postgres-migration-DFaxuRyI.d.mts} +2 -2
  52. package/dist/{postgres-migration-CfyKDT7b.d.mts.map → postgres-migration-DFaxuRyI.d.mts.map} +1 -1
  53. package/dist/{postgres-table-schema-node-Clei_xel.mjs → postgres-table-schema-node-BgaSrxYN.mjs} +30 -29
  54. package/dist/postgres-table-schema-node-BgaSrxYN.mjs.map +1 -0
  55. package/dist/schema-node-kinds-ClScchhi.mjs +76 -0
  56. package/dist/schema-node-kinds-ClScchhi.mjs.map +1 -0
  57. package/dist/types.d.mts +1 -1
  58. package/dist/types.mjs +1 -1
  59. package/package.json +21 -20
  60. package/src/core/migrations/column-ddl-rendering.ts +117 -0
  61. package/src/core/migrations/contract-to-postgres-database-schema-node.ts +25 -1
  62. package/src/core/migrations/control-policy.ts +68 -48
  63. package/src/core/migrations/diff-database-schema.ts +242 -153
  64. package/src/core/migrations/issue-planner.ts +621 -667
  65. package/src/core/migrations/operations/constraints.ts +1 -1
  66. package/src/core/migrations/planner-ddl-builders.ts +5 -2
  67. package/src/core/migrations/planner-identity-values.ts +1 -1
  68. package/src/core/migrations/planner-sql-checks.ts +1 -1
  69. package/src/core/migrations/planner-strategies.ts +248 -208
  70. package/src/core/migrations/planner.ts +180 -74
  71. package/src/core/migrations/runner.ts +1 -4
  72. package/src/core/migrations/verify-postgres-namespaces.ts +26 -15
  73. package/src/core/postgres-schema-verifier.ts +10 -7
  74. package/src/core/psl-infer/infer-psl-contract.ts +5 -10
  75. package/src/core/schema-ir/postgres-database-schema-node.ts +3 -6
  76. package/src/core/schema-ir/postgres-namespace-schema-node.ts +8 -9
  77. package/src/core/schema-ir/postgres-policy-schema-node.ts +3 -6
  78. package/src/core/schema-ir/postgres-role-schema-node.ts +3 -6
  79. package/src/core/schema-ir/postgres-table-schema-node.ts +20 -9
  80. package/src/core/schema-ir/schema-node-kinds.ts +80 -2
  81. package/src/exports/control.ts +10 -43
  82. package/src/exports/diff-database-schema.ts +7 -0
  83. package/src/exports/issue-planner.ts +1 -1
  84. package/src/exports/op-factory-call.ts +1 -0
  85. package/src/exports/planner-identity-values.ts +4 -1
  86. package/src/exports/planner.ts +0 -1
  87. package/dist/default-normalizer-Dug8Fez9.mjs +0 -248
  88. package/dist/default-normalizer-Dug8Fez9.mjs.map +0 -1
  89. package/dist/issue-planner-Cbh-xTEr.mjs +0 -849
  90. package/dist/issue-planner-Cbh-xTEr.mjs.map +0 -1
  91. package/dist/native-type-normalizer-Bc9XJzWC.mjs +0 -38
  92. package/dist/native-type-normalizer-Bc9XJzWC.mjs.map +0 -1
  93. package/dist/op-factory-call-BuoGT5UI.mjs.map +0 -1
  94. package/dist/planner-Bq0Z2sVO.mjs +0 -641
  95. package/dist/planner-Bq0Z2sVO.mjs.map +0 -1
  96. package/dist/postgres-database-schema-node-os8ogEap.d.mts.map +0 -1
  97. package/dist/postgres-table-schema-node-Clei_xel.mjs.map +0 -1
@@ -1,641 +0,0 @@
1
- import { t as DEFAULT_NAMESPACE_ID } from "./namespace-ids-xtp_ZY86.mjs";
2
- import { p as PostgresRlsPolicy, r as isPostgresSchema } from "./postgres-schema-CajYAwny.mjs";
3
- import { a as PostgresDatabaseSchemaNode, i as PostgresNamespaceSchemaNode, n as PostgresRoleSchemaNode, r as PostgresPolicySchemaNode, t as PostgresTableSchemaNode } from "./postgres-table-schema-node-Clei_xel.mjs";
4
- import { t as resolveDdlSchemaForNamespaceStorage } from "./resolve-ddl-schema-Bv1ZvjX8.mjs";
5
- import { t as parsePostgresDefault } from "./default-normalizer-Dug8Fez9.mjs";
6
- import { t as normalizeSchemaNativeType } from "./native-type-normalizer-Bc9XJzWC.mjs";
7
- import { C as EnableRowLevelSecurityCall, d as CreatePostgresRlsPolicyCall, x as DropPostgresRlsPolicyCall } from "./op-factory-call-BuoGT5UI.mjs";
8
- import { n as postgresPlannerStrategies, t as planIssues } from "./issue-planner-Cbh-xTEr.mjs";
9
- import { t as TypeScriptRenderablePostgresMigration } from "./planner-produced-postgres-migration-C2_H6Vvl.mjs";
10
- import { UNBOUND_NAMESPACE_ID, entityAt } from "@prisma-next/framework-components/ir";
11
- import { blindCast } from "@prisma-next/utils/casts";
12
- import { contractNamespaceToSchemaIR, extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure } from "@prisma-next/family-sql/control";
13
- import { ifDefined } from "@prisma-next/utils/defined";
14
- import { verifySqlSchemaTree } from "@prisma-next/family-sql/diff";
15
- import { SchemaDiff, diffSchemas } from "@prisma-next/framework-components/control";
16
- //#region src/core/migrations/contract-to-postgres-database-schema-node.ts
17
- function toPolicyNode(policy, namespaceId) {
18
- return new PostgresPolicySchemaNode({
19
- name: policy.name,
20
- prefix: policy.prefix,
21
- tableName: policy.tableName,
22
- namespaceId,
23
- operation: policy.operation,
24
- roles: [...policy.roles],
25
- ...ifDefined("using", policy.using),
26
- ...ifDefined("withCheck", policy.withCheck),
27
- permissive: policy.permissive
28
- });
29
- }
30
- /**
31
- * Projects a Postgres contract into the expected schema-diff tree: a
32
- * `PostgresDatabaseSchemaNode` root holding one `PostgresNamespaceSchemaNode`
33
- * per Postgres namespace, each holding its `PostgresTableSchemaNode`s with
34
- * their `PostgresPolicySchemaNode`s, plus the database roles on the root.
35
- *
36
- * Not a duplicate of the family's `contractToSchemaIR`: that builds a flat,
37
- * single `{ tables }` map (and throws on cross-namespace name collisions, with
38
- * no RLS/role concept) for SQLite's single-schema world. This is the
39
- * Postgres-specific *tree* shape — multi-schema, RLS-policy-aware, role-aware.
40
- * It reuses the family's per-namespace table conversion (`contractNamespaceToSchemaIR`)
41
- * for column/FK/index building and only adds the Postgres tree/policy/role shape.
42
- *
43
- * Tables are grouped by their owning namespace (resolved DDL schema name) so
44
- * the tree mirrors Postgres's object hierarchy. The DDL schema name is
45
- * resolved once per namespace.
46
- *
47
- * A policy that references a table absent from its namespace is a malformed
48
- * contract — the loop throws rather than fabricating a stub table.
49
- */
50
- function contractToPostgresDatabaseSchemaNode(contract, options) {
51
- if (contract === null) return new PostgresDatabaseSchemaNode({
52
- namespaces: {},
53
- roles: [],
54
- existingSchemas: [],
55
- pgVersion: ""
56
- });
57
- const namespaces = {};
58
- const roles = [];
59
- const ownedSchemas = [];
60
- for (const ns of Object.values(contract.storage.namespaces)) {
61
- if (!isPostgresSchema(ns)) continue;
62
- const ddlSchema = resolveDdlSchemaForNamespaceStorage(contract.storage, ns.id);
63
- ownedSchemas.push(ddlSchema);
64
- const sqlTables = contractNamespaceToSchemaIR(contract.storage, ns.id, options).tables;
65
- const policiesByTable = /* @__PURE__ */ new Map();
66
- for (const policy of Object.values(ns.policy)) {
67
- const list = policiesByTable.get(policy.tableName) ?? [];
68
- list.push(toPolicyNode(policy, ddlSchema));
69
- policiesByTable.set(policy.tableName, list);
70
- }
71
- const tables = {};
72
- for (const tableName of Object.keys(ns.table)) {
73
- const sqlTable = sqlTables[tableName];
74
- if (sqlTable === void 0) continue;
75
- tables[tableName] = new PostgresTableSchemaNode({
76
- name: sqlTable.name,
77
- columns: sqlTable.columns,
78
- foreignKeys: sqlTable.foreignKeys,
79
- uniques: sqlTable.uniques,
80
- indexes: sqlTable.indexes,
81
- ...ifDefined("primaryKey", sqlTable.primaryKey),
82
- ...ifDefined("annotations", sqlTable.annotations),
83
- ...ifDefined("checks", sqlTable.checks),
84
- policies: policiesByTable.get(tableName) ?? []
85
- });
86
- }
87
- for (const [tableName, tablePolicies] of policiesByTable) if (!(tableName in tables)) {
88
- const policyName = tablePolicies[0]?.name ?? "(unknown)";
89
- throw new Error(`contract-to-postgres-database-schema-node: policy "${policyName}" references table "${tableName}" not present in namespace "${ddlSchema}"`);
90
- }
91
- namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({
92
- schemaName: ddlSchema,
93
- tables,
94
- nativeEnumTypeNames: []
95
- });
96
- for (const role of Object.values(ns.role)) roles.push(new PostgresRoleSchemaNode({
97
- name: role.name,
98
- namespaceId: role.namespaceId
99
- }));
100
- }
101
- return new PostgresDatabaseSchemaNode({
102
- namespaces,
103
- roles,
104
- existingSchemas: ownedSchemas,
105
- pgVersion: ""
106
- });
107
- }
108
- //#endregion
109
- //#region src/core/migrations/diff-database-schema.ts
110
- /**
111
- * The single combined database-schema comparison — the one computation the
112
- * migration planner and the family schema verify both consume. Composes,
113
- * once each:
114
- *
115
- * - the per-namespace-paired relational diff (`verifySqlSchemaTree`) → table /
116
- * column / constraint findings as framework `SchemaIssue`s (with the
117
- * verification-tree `root` and pass/warn/fail counts);
118
- * - the policy diff (`diffPostgresSchema` over the two trees) → RLS policy
119
- * presence as `SchemaDiffIssue`s, ownership-filtered to the contract's owned
120
- * schemas.
121
- *
122
- * `diffPostgresDatabaseSchema` and `verifyPostgresDatabaseSchema` both read
123
- * this single result, so the relational walk runs once per caller — never
124
- * once for the diff and again for the verify tree.
125
- */
126
- function computePostgresSchemaComparison(input) {
127
- const postgresContract = blindCast(input.contract);
128
- const relational = verifySqlSchemaTree({
129
- contract: input.contract,
130
- actualSchema: input.actualSchema,
131
- buildExpectedSchema: (scopedContract) => contractToPostgresDatabaseSchemaNode(blindCast(scopedContract), { annotationNamespace: "pg" }),
132
- strict: input.strict,
133
- typeMetadataRegistry: input.typeMetadataRegistry,
134
- frameworkComponents: input.frameworkComponents,
135
- normalizeDefault: parsePostgresDefault,
136
- normalizeNativeType: normalizeSchemaNativeType
137
- });
138
- PostgresDatabaseSchemaNode.assert(input.actualSchema);
139
- const expected = contractToPostgresDatabaseSchemaNode(postgresContract, { annotationNamespace: "pg" });
140
- return {
141
- relational,
142
- schemaDiffIssues: filterIssuesByOwnership(diffPostgresSchema(expected, input.actualSchema), ownedSchemaNames(expected))
143
- };
144
- }
145
- /**
146
- * The `SchemaDiffer` for Postgres: the target's black-box comparison,
147
- * projected to the two issue lists. Namespace presence (`missing_schema` →
148
- * `CREATE SCHEMA`) is intentionally NOT composed here: it is a planner-only
149
- * op-generation concern (verify rejects on the relational `missing_table` a
150
- * missing schema already produces), so the planner stitches it in around this
151
- * diff. Control-policy suppression of the policy issues is likewise a
152
- * per-consumer post-step (verify filters the issues; the planner filters the
153
- * calls).
154
- */
155
- function diffPostgresDatabaseSchema(input) {
156
- const { relational, schemaDiffIssues } = computePostgresSchemaComparison(input);
157
- return new SchemaDiff(relational.schema.issues, schemaDiffIssues);
158
- }
159
- /**
160
- * The same combined comparison as {@link diffPostgresDatabaseSchema}, wrapped
161
- * in the verify envelope (`ok`/`summary`/`code`/`target`/`timings`) plus the
162
- * pass/warn/fail tree the CLI renders — i.e. exactly the existing verify-result
163
- * schema shape, so nothing downstream changes.
164
- */
165
- function verifyPostgresDatabaseSchema(input) {
166
- const { relational, schemaDiffIssues } = computePostgresSchemaComparison(input);
167
- return {
168
- ...relational,
169
- schema: {
170
- ...relational.schema,
171
- schemaDiffIssues
172
- }
173
- };
174
- }
175
- function ownedSchemaNames(expected) {
176
- const policyNamespaces = Object.values(expected.namespaces).flatMap((ns) => Object.values(ns.tables).flatMap((t) => t.policies.map((p) => p.namespaceId)));
177
- return new Set([...policyNamespaces, ...expected.existingSchemas]);
178
- }
179
- function renderPostgresPolicyReference(policy) {
180
- return `policy "${policy.name}" on "${policy.namespaceId}"."${policy.tableName}"`;
181
- }
182
- /**
183
- * The policy node-diff — the structural half of the combined comparison above.
184
- * Computes RLS-policy drift between two derived schema trees:
185
- *
186
- * 1. Runs the framework total diff over the two `PostgresDatabaseSchemaNode`
187
- * roots (database → namespace → table → policy).
188
- * 2. Filters to policy-subject issues only — this is transitional: the generic
189
- * differ walks the whole tree, but the legacy relational verifier still owns
190
- * table/column drift, so non-policy issues are dropped here.
191
- * 3. Remaps the message to a human-readable policy reference.
192
- *
193
- * Both trees are `PostgresDatabaseSchemaNode`s, so every issue node is a
194
- * `SqlSchemaDiffNode` — narrow the framework's `SchemaDiffIssue<DiffableNode>`
195
- * output once here (the single boundary cast), so every downstream consumer
196
- * (the ownership filter, the planner) reads the concrete node with no cast.
197
- *
198
- * Ownership filtering (dropping `extra` issues in namespaces a contract doesn't
199
- * own) is the caller's responsibility — use `filterIssuesByOwnership`.
200
- */
201
- function diffPostgresSchema(expected, actual) {
202
- return blindCast(diffSchemas(expected, actual)).filter((i) => {
203
- const node = i.expected ?? i.actual;
204
- return node !== void 0 && PostgresPolicySchemaNode.is(node);
205
- }).map((i) => {
206
- const node = i.expected ?? i.actual;
207
- if (node === void 0 || !PostgresPolicySchemaNode.is(node)) return i;
208
- return {
209
- ...i,
210
- message: `${i.outcome}: ${renderPostgresPolicyReference(node)}`
211
- };
212
- });
213
- }
214
- /**
215
- * Filters `extra` policy issues to those in owned namespaces. Call after
216
- * `diffPostgresSchema` with the union of namespace ids from the expected tree's
217
- * policies and its `existingSchemas`.
218
- */
219
- function filterIssuesByOwnership(issues, ownedSchemaNameSet) {
220
- return issues.filter((i) => {
221
- if (i.outcome !== "extra") return true;
222
- if (i.actual === void 0) return false;
223
- return PostgresPolicySchemaNode.is(i.actual) && ownedSchemaNameSet.has(i.actual.namespaceId);
224
- });
225
- }
226
- //#endregion
227
- //#region src/core/migrations/control-policy.ts
228
- /**
229
- * Factory calls that create a whole, previously-absent top-level storage
230
- * object. Used to decide whether `tolerated` permits a call (it only allows
231
- * creating absent objects, never modifying existing ones).
232
- *
233
- * Deliberately an explicit, closed set rather than a `factoryName`
234
- * create/alter/drop classification: it answers exactly one yes/no question
235
- * and is fail-closed. Any call not listed here — including future or
236
- * extension-contributed factories — is treated as NOT object-creation, so it
237
- * is suppressed under `tolerated` rather than permissively emitted.
238
- */
239
- const OBJECT_CREATION_FACTORIES = new Set([
240
- "createTable",
241
- "createSchema",
242
- "createRlsPolicy",
243
- "enableRowLevelSecurity"
244
- ]);
245
- function createsNewTopLevelObject(call) {
246
- return OBJECT_CREATION_FACTORIES.has(call.factoryName);
247
- }
248
- function ddlSchemaNameForNamespace(contract, namespaceId) {
249
- const namespace = contract.storage.namespaces[namespaceId];
250
- return isPostgresSchema(namespace) ? namespace.ddlSchemaName(contract.storage) : namespaceId;
251
- }
252
- function resolveNamespaceIdForTable(contract, tableName, ddlSchemaName) {
253
- for (const namespaceId of Object.keys(contract.storage.namespaces)) {
254
- if (!entityAt(contract.storage, {
255
- namespaceId,
256
- entityKind: "table",
257
- entityName: tableName
258
- })) continue;
259
- if (ddlSchemaName === void 0 || ddlSchemaNameForNamespace(contract, namespaceId) === ddlSchemaName) return namespaceId;
260
- }
261
- return UNBOUND_NAMESPACE_ID;
262
- }
263
- function resolveNamespaceIdForDdlSchema(contract, ddlSchemaName) {
264
- for (const namespaceId of Object.keys(contract.storage.namespaces)) {
265
- const ns = contract.storage.namespaces[namespaceId];
266
- if (isPostgresSchema(ns) && ns.ddlSchemaName(contract.storage) === ddlSchemaName) return namespaceId;
267
- if (namespaceId === ddlSchemaName) return namespaceId;
268
- }
269
- return UNBOUND_NAMESPACE_ID;
270
- }
271
- function postgresCallFields(call) {
272
- return {
273
- ...ifDefined("schemaName", "schemaName" in call ? call.schemaName : void 0),
274
- ...ifDefined("tableName", "tableName" in call ? call.tableName : void 0),
275
- ...ifDefined("columnName", "columnName" in call ? call.columnName : void 0)
276
- };
277
- }
278
- function formatPostgresControlPolicySubjectLabel(factoryName, subject, contract) {
279
- if (subject?.table) return `${factoryName}(${ddlSchemaNameForNamespace(contract, subject.namespaceId)}.${subject.table})`;
280
- return factoryName;
281
- }
282
- function resolvePostgresCallControlPolicySubject(call, contract) {
283
- const callFields = postgresCallFields(call);
284
- const createsNewObject = createsNewTopLevelObject(call);
285
- if (call.factoryName === "createSchema" && callFields.schemaName) return {
286
- namespaceId: resolveNamespaceIdForDdlSchema(contract, callFields.schemaName),
287
- createsNewObject
288
- };
289
- if (callFields.tableName) {
290
- const namespaceId = resolveNamespaceIdForTable(contract, callFields.tableName, callFields.schemaName);
291
- const tableControlPolicy = entityAt(contract.storage, {
292
- namespaceId,
293
- entityKind: "table",
294
- entityName: callFields.tableName
295
- })?.control;
296
- return {
297
- namespaceId,
298
- ...ifDefined("explicitNodeControlPolicy", tableControlPolicy),
299
- table: callFields.tableName,
300
- ...ifDefined("column", callFields.columnName),
301
- createsNewObject
302
- };
303
- }
304
- if (callFields.schemaName) return {
305
- namespaceId: resolveNamespaceIdForDdlSchema(contract, callFields.schemaName),
306
- createsNewObject
307
- };
308
- }
309
- /**
310
- * Issue kinds that describe the absence of a whole, top-level Postgres
311
- * object — the same kinds `createsNewTopLevelObject` recognises for calls.
312
- * Used by {@link resolvePostgresIssueCreationFactoryName} to decide whether
313
- * a `tolerated` subject permits the issue to flow into the planner
314
- * (create-if-absent) and to seed the suppressed-subject warning's
315
- * `factoryName` when the planner is skipped.
316
- */
317
- const POSTGRES_ISSUE_CREATION_FACTORY = Object.freeze({
318
- missing_schema: "createSchema",
319
- missing_table: "createTable",
320
- missing_rls_policy: "createRlsPolicy"
321
- });
322
- function resolvePostgresIssueCreationFactoryName(issue) {
323
- return POSTGRES_ISSUE_CREATION_FACTORY[issue.kind];
324
- }
325
- /**
326
- * Resolve the control-policy subject coordinate for a single
327
- * {@link SchemaIssue}. Mirrors the resolution `resolvePostgresCallControlPolicySubject`
328
- * performs for a generated DDL call, but works *off the issue* — so the
329
- * planner can partition issues by effective policy before the diff engine
330
- * runs. `createsNewObject` is derived from the issue's kind: schema/table/
331
- * type-missing issues describe a brand-new top-level object; everything else
332
- * touches an existing object.
333
- *
334
- * An `extra_table` issue carries no contract namespace coordinate (the table
335
- * isn't in any contract namespace), so the subject's `namespaceId` falls
336
- * back to {@link UNBOUND_NAMESPACE_ID}; the call-side resolver does the same
337
- * for the `DropTableCall` it produces.
338
- */
339
- function resolvePostgresIssueControlPolicySubject(issue, contract) {
340
- const createsNewObject = POSTGRES_ISSUE_CREATION_FACTORY[issue.kind] !== void 0;
341
- if (issue.kind === "missing_schema" && issue.namespaceId) return {
342
- namespaceId: issue.namespaceId,
343
- createsNewObject
344
- };
345
- if ("table" in issue && issue.table) {
346
- const namespaceId = "namespaceId" in issue && issue.namespaceId ? issue.namespaceId : resolveNamespaceIdForTable(contract, issue.table, void 0);
347
- return {
348
- namespaceId,
349
- ...ifDefined("explicitNodeControlPolicy", entityAt(contract.storage, {
350
- namespaceId,
351
- entityKind: "table",
352
- entityName: issue.table
353
- })?.control),
354
- table: issue.table,
355
- ...ifDefined("column", "column" in issue ? issue.column : void 0),
356
- createsNewObject
357
- };
358
- }
359
- }
360
- //#endregion
361
- //#region src/core/migrations/verify-postgres-namespaces.ts
362
- /**
363
- * Resolves the live-database schema name for a given namespace
364
- * coordinate. Mirrors `resolveDdlSchemaForNamespace` in
365
- * `planner-strategies.ts` so the verifier's projection and the
366
- * planner's projection always agree — Postgres-aware namespaces (the
367
- * production path) dispatch to `ddlSchemaName(storage)`, and bare
368
- * object payloads (used by some tests) fall back to the coordinate
369
- * itself.
370
- */
371
- function resolveDdlSchemaName(storage, namespaceId) {
372
- const namespace = storage.namespaces[namespaceId];
373
- if (isPostgresSchema(namespace)) return namespace.ddlSchemaName(storage);
374
- return namespaceId;
375
- }
376
- /**
377
- * Reads the introspected list of schema names from the database-root schema
378
- * node. `existingSchemas` is database-level, so it lives on the
379
- * `PostgresDatabaseSchemaNode` root — not on the per-schema namespace nodes.
380
- *
381
- * Defaults to the always-present `public` schema when the node is not the
382
- * database root — a fresh Postgres database always carries `public` (unless an
383
- * operator dropped it manually), so any verifier path that runs without an
384
- * enriched introspection still suppresses the redundant `CREATE SCHEMA
385
- * "public"`.
386
- *
387
- * Production introspection (`PostgresControlAdapter.introspect`) is the
388
- * authoritative source: it queries `pg_namespace` and sets `existingSchemas`
389
- * on the returned root. Tests that want to assert against a richer initial
390
- * state construct a `PostgresDatabaseSchemaNode` explicitly.
391
- */
392
- function existingSchemasFromSchema(schema) {
393
- if (PostgresDatabaseSchemaNode.is(schema)) return schema.existingSchemas;
394
- return [DEFAULT_NAMESPACE_ID];
395
- }
396
- /**
397
- * Emits a `missing_schema` issue for every contract-declared Postgres
398
- * namespace whose live container does not yet exist. The planner's
399
- * `collectSchemaIssues` prepends these to the relational findings so a
400
- * multi-schema plan emits `CREATE SCHEMA` before the tables that need it —
401
- * a planner-only concern (verify already rejects via the `missing_table` a
402
- * missing schema produces), so this is not part of the shared diff.
403
- *
404
- * A namespace's live container is the schema returned by its
405
- * polymorphic `ddlSchemaName(storage)` method — named schemas resolve
406
- * to their own id; the unbound singleton returns `UNBOUND_NAMESPACE_ID`
407
- * and is skipped explicitly (late-bound namespaces have no fixed DDL
408
- * schema). Issues are emitted only when the resolved name is a real,
409
- * creatable schema (not the unbound sentinel) and is missing from the
410
- * introspected list. `public` is suppressed implicitly because the
411
- * introspection (or its sensible default) always carries it.
412
- *
413
- * Each emitted issue stamps `namespaceId` with the contract namespace
414
- * coordinate so the downstream `mapIssueToCall` re-resolves the DDL
415
- * schema name through the same polymorphic path — keeping the
416
- * coordinate, not the resolved name, as the issue's stable identity.
417
- */
418
- function verifyPostgresNamespacePresence(input) {
419
- const { contract, schema } = input;
420
- const existing = new Set(existingSchemasFromSchema(schema));
421
- const issues = [];
422
- const namespaceIds = Object.keys(contract.storage.namespaces).sort();
423
- for (const namespaceId of namespaceIds) {
424
- if (namespaceId === UNBOUND_NAMESPACE_ID) continue;
425
- const ddlName = resolveDdlSchemaName(contract.storage, namespaceId);
426
- if (ddlName === UNBOUND_NAMESPACE_ID) continue;
427
- if (existing.has(ddlName)) continue;
428
- issues.push({
429
- kind: "missing_schema",
430
- reason: "not-found",
431
- namespaceId,
432
- message: `Schema "${ddlName}" is missing from database`
433
- });
434
- }
435
- return issues;
436
- }
437
- //#endregion
438
- //#region src/core/migrations/planner.ts
439
- function createPostgresMigrationPlanner(lowerer) {
440
- return new PostgresMigrationPlanner(lowerer);
441
- }
442
- /**
443
- * Postgres migration planner — a thin wrapper over `planIssues`.
444
- *
445
- * `plan()` verifies the live schema against the target contract (producing
446
- * `SchemaIssue[]`) and delegates to `planIssues` with the unified
447
- * `postgresPlannerStrategies` list: enum-change, NOT-NULL backfill,
448
- * type-change, nullable-tightening, codec-hook storage types,
449
- * component-declared dependency installs, and shared-temp-default /
450
- * empty-table-guarded NOT-NULL add-column. The same strategy list runs for
451
- * `migration plan`, `db update`, and `db init`; behavior diverges purely on
452
- * `policy.allowedOperationClasses` (the data-safe strategies short-circuit
453
- * when `'data'` is excluded). The issue planner applies operation-class
454
- * policy gates and emits a single `PostgresOpFactoryCall[]` that drives both
455
- * the runtime-ops view (via `renderOps`) and the `renderTypeScript()`
456
- * authoring surface.
457
- */
458
- var PostgresMigrationPlanner = class {
459
- #lowerer;
460
- constructor(lowerer) {
461
- this.#lowerer = lowerer;
462
- }
463
- plan(options) {
464
- return this.planSql(options);
465
- }
466
- emptyMigration(context, spaceId) {
467
- return new TypeScriptRenderablePostgresMigration([], {
468
- from: context.fromHash,
469
- to: context.toHash
470
- }, spaceId, this.#lowerer);
471
- }
472
- planSql(options) {
473
- const schemaName = options.schemaName ?? Object.keys(options.contract.storage.namespaces).find((id) => id !== UNBOUND_NAMESPACE_ID) ?? UNBOUND_NAMESPACE_ID;
474
- const policyResult = this.ensureAdditivePolicy(options.policy);
475
- if (policyResult) return policyResult;
476
- PostgresDatabaseSchemaNode.assert(options.schema);
477
- const rawDiff = diffPostgresDatabaseSchema({
478
- contract: options.contract,
479
- actualSchema: options.schema,
480
- strict: options.policy.allowedOperationClasses.includes("widening") || options.policy.allowedOperationClasses.includes("destructive"),
481
- typeMetadataRegistry: /* @__PURE__ */ new Map(),
482
- frameworkComponents: options.frameworkComponents
483
- });
484
- const databaseDiff = options.keepDiffIssue ? rawDiff.filter(options.keepDiffIssue) : rawDiff;
485
- const schemaIssues = this.collectSchemaIssues(options, databaseDiff.issues);
486
- const codecHooks = extractCodecControlHooks(options.frameworkComponents);
487
- const storageTypes = options.contract.storage.types ?? {};
488
- const relationalSchema = relationalNamespaceNode(options.schema, schemaName);
489
- const issuePartition = partitionIssuesByControlPolicy({
490
- issues: schemaIssues,
491
- contract: options.contract,
492
- resolveControlPolicySubject: (issue) => resolvePostgresIssueControlPolicySubject(issue, options.contract),
493
- resolveCreationFactoryName: resolvePostgresIssueCreationFactoryName,
494
- formatSubjectLabel: (factoryName, subject) => formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract)
495
- });
496
- const result = planIssues({
497
- issues: issuePartition.plannable,
498
- toContract: options.contract,
499
- fromContract: options.fromContract,
500
- schemaName,
501
- codecHooks,
502
- storageTypes,
503
- ...ifDefined("schema", relationalSchema),
504
- policy: options.policy,
505
- frameworkComponents: options.frameworkComponents,
506
- strategies: postgresPlannerStrategies
507
- });
508
- if (!result.ok) return plannerFailure(result.failure);
509
- const schemaDiffPartition = partitionCallsByControlPolicy({
510
- calls: this.planPostgresSchemaDiff(options, databaseDiff.schemaDiffIssues),
511
- contract: options.contract,
512
- resolveControlPolicySubject: (call) => resolvePostgresCallControlPolicySubject(call, options.contract),
513
- resolveFactoryName: (call) => call.factoryName,
514
- formatSubjectLabel: (factoryName, subject) => formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract)
515
- });
516
- const fieldEventPartition = partitionCallsByControlPolicy({
517
- calls: blindCast(planFieldEventOperations({
518
- priorContract: options.fromContract,
519
- newContract: options.contract,
520
- codecHooks
521
- })),
522
- contract: options.contract,
523
- resolveControlPolicySubject: (call) => resolvePostgresCallControlPolicySubject(call, options.contract),
524
- resolveFactoryName: (call) => call.factoryName,
525
- formatSubjectLabel: (factoryName, subject) => formatPostgresControlPolicySubjectLabel(factoryName, subject, options.contract)
526
- });
527
- const calls = [
528
- ...result.value.calls,
529
- ...schemaDiffPartition.kept,
530
- ...fieldEventPartition.kept
531
- ];
532
- const warnings = [
533
- ...issuePartition.warnings,
534
- ...schemaDiffPartition.warnings,
535
- ...fieldEventPartition.warnings
536
- ];
537
- return Object.freeze({
538
- kind: "success",
539
- plan: new TypeScriptRenderablePostgresMigration(calls, {
540
- from: options.fromContract?.storage.storageHash ?? null,
541
- to: options.contract.storage.storageHash
542
- }, options.spaceId, this.#lowerer),
543
- ...warnings.length > 0 ? { warnings: Object.freeze(warnings) } : {}
544
- });
545
- }
546
- /**
547
- * Maps the RLS policy presence findings of the shared
548
- * `diffPostgresDatabaseSchema` (already ownership-filtered) into
549
- * `ENABLE RLS` / `CREATE POLICY` / `DROP POLICY` ops. It no longer re-diffs —
550
- * it consumes the `schemaDiffIssues` of the one combined diff.
551
- */
552
- planPostgresSchemaDiff(options, filteredDiffIssues) {
553
- const allowsDestructive = options.policy.allowedOperationClasses.includes("destructive");
554
- const calls = [];
555
- const seenEnableTables = /* @__PURE__ */ new Set();
556
- for (const issue of filteredDiffIssues) if (issue.outcome === "missing") {
557
- const expected = issue.expected;
558
- PostgresPolicySchemaNode.assert(expected);
559
- const schemaForTable = resolveDdlSchemaForNamespaceStorage(options.contract.storage, expected.namespaceId);
560
- const tableKey = `${schemaForTable}.${expected.tableName}`;
561
- if (!seenEnableTables.has(tableKey)) {
562
- seenEnableTables.add(tableKey);
563
- calls.push(new EnableRowLevelSecurityCall(schemaForTable, expected.tableName));
564
- }
565
- calls.push(new CreatePostgresRlsPolicyCall(schemaForTable, expected.tableName, policyNodeToContractPolicy(expected)));
566
- } else if (issue.outcome === "extra" && allowsDestructive) {
567
- const actual = issue.actual;
568
- PostgresPolicySchemaNode.assert(actual);
569
- const schemaForTable = resolveDdlSchemaForNamespaceStorage(options.contract.storage, actual.namespaceId);
570
- calls.push(new DropPostgresRlsPolicyCall(schemaForTable, actual.tableName, actual.name));
571
- }
572
- return calls;
573
- }
574
- ensureAdditivePolicy(policy) {
575
- if (!policy.allowedOperationClasses.includes("additive")) return plannerFailure([{
576
- kind: "unsupportedOperation",
577
- summary: "Migration planner requires additive operations be allowed",
578
- why: "The planner requires the \"additive\" operation class to be allowed in the policy."
579
- }]);
580
- return null;
581
- }
582
- /**
583
- * The structural issue list `planIssues` consumes: the relational findings
584
- * from the shared `diffPostgresDatabaseSchema` plus namespace presence.
585
- *
586
- * Schema presence (`missing_schema` → `CREATE SCHEMA`) is a planner-only
587
- * op-generation concern, so it is stitched in here rather than inside the
588
- * shared diff — verify never needs it (a missing schema already surfaces as
589
- * `missing_table` in the relational findings). It reads `existingSchemas` off
590
- * the database root, so it takes the whole tree. Policy drift is handled
591
- * separately via `planPostgresSchemaDiff` from the same shared diff's
592
- * `schemaDiffIssues`.
593
- */
594
- collectSchemaIssues(options, relationalIssues) {
595
- const namespaceIssues = verifyPostgresNamespacePresence({
596
- contract: options.contract,
597
- schema: options.schema
598
- });
599
- if (namespaceIssues.length === 0) return relationalIssues;
600
- return [...namespaceIssues, ...relationalIssues];
601
- }
602
- };
603
- /**
604
- * Returns the one namespace node whose tables the relational strategy layer
605
- * probes for live-table existence — the node matching the planner's resolved
606
- * schema name, or the first namespace when none matches. `undefined` when the
607
- * tree has no namespaces, so the strategy context uses its empty-schema default.
608
- *
609
- * The relational strategies key tables by bare name, so they can only probe one
610
- * namespace at a time; probing across every namespace at once is future work.
611
- */
612
- function relationalNamespaceNode(schema, schemaName) {
613
- const namespaceNodes = Object.values(schema.namespaces);
614
- return namespaceNodes.find((node) => node.schemaName === schemaName) ?? namespaceNodes[0];
615
- }
616
- /**
617
- * Rebuilds the `PostgresRlsPolicy` contract entity `CreatePostgresRlsPolicyCall`
618
- * carries (its `renderTypeScript`/`createRlsPolicy` paths serialize the whole
619
- * entity, `namespaceId` included). This reconstructs rather than looking the
620
- * original up in the contract on purpose: the diff node's `namespaceId` is the
621
- * *resolved DDL schema* (set when the expected tree was built), which is the
622
- * value the emitted op must carry; the contract-stored entity holds the raw,
623
- * pre-resolution coordinate, so a lookup would change the migration output.
624
- */
625
- function policyNodeToContractPolicy(node) {
626
- return new PostgresRlsPolicy({
627
- name: node.name,
628
- prefix: node.prefix,
629
- tableName: node.tableName,
630
- namespaceId: node.namespaceId,
631
- operation: node.operation,
632
- roles: [...node.roles],
633
- ...ifDefined("using", node.using),
634
- ...ifDefined("withCheck", node.withCheck),
635
- permissive: node.permissive
636
- });
637
- }
638
- //#endregion
639
- export { contractToPostgresDatabaseSchemaNode as i, diffPostgresDatabaseSchema as n, verifyPostgresDatabaseSchema as r, createPostgresMigrationPlanner as t };
640
-
641
- //# sourceMappingURL=planner-Bq0Z2sVO.mjs.map