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