@prisma-next/target-postgres
Advanced tools
| import { r as isPostgresSchema } from "./postgres-schema-CewLYHB7.mjs"; | ||
| import { i as quoteIdentifier } from "./sql-utils-SU4FDvIV.mjs"; | ||
| import { n as PostgresNativeEnumSchemaNode, o as postgresNodeEntityKind, r as PostgresSchemaNodeKind, t as PostgresTableSchemaNode } from "./postgres-table-schema-node-D6LvInCe.mjs"; | ||
| import { A as SetDefaultCall, C as DropNativeEnumTypeCall, D as EnableRowLevelSecurityCall, E as DropTableCall, M as postgresDefaultToDdlColumnDefault, N as buildTargetDetails, O as RawSqlCall, S as DropIndexCall, _ as DisableRowLevelSecurityCall, a as AddNotNullColumnDirectCall, b as DropConstraintCall, c as AddUniqueCall, d as CreateIndexCall, f as CreateNativeEnumTypeCall, g as DataTransformCall, h as CreateTableCall, i as AddNativeEnumValueCall, j as SetNotNullCall, l as AlterColumnTypeCall, m as CreateSchemaCall, n as AddColumnCall, o as AddNotNullColumnWithTempDefaultCall, r as AddForeignKeyCall, s as AddPrimaryKeyCall, t as AddCheckConstraintCall, v as DropCheckConstraintCall, w as DropNotNullCall, x as DropDefaultCall, y as DropColumnCall } from "./op-factory-call-PBYfcv8h.mjs"; | ||
| import { t as buildExpectedFormatType } from "./planner-sql-checks-BuB877wH.mjs"; | ||
| import { n as buildColumnTypeSql, t as buildColumnDefaultSql } from "./planner-ddl-builders-AIpFmG1i.mjs"; | ||
| import { n as resolveIdentityValue } from "./planner-identity-values-CJPha2Sz.mjs"; | ||
| import { i as hasUniqueConstraint, n as hasForeignKey, t as buildSchemaLookupMap } from "./planner-schema-lookup-CiVaAQP-.mjs"; | ||
| import { UNBOUND_NAMESPACE_ID, entityAt } from "@prisma-next/framework-components/ir"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { StorageTable } from "@prisma-next/sql-contract/types"; | ||
| import { resolveValueSetValues } from "@prisma-next/family-sql/control"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| import * as contractFree from "@prisma-next/sql-relational-core/contract-free"; | ||
| import { RelationalSchemaNodeKind, SqlSchemaIR } from "@prisma-next/sql-schema-ir/types"; | ||
| import { issueOutcome, orderIssuesByDependencies } from "@prisma-next/framework-components/control"; | ||
| import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming"; | ||
| import { notOk, ok } from "@prisma-next/utils/result"; | ||
| //#region src/core/schema-ir/node-storage-coordinate.ts | ||
| /** | ||
| * The storage-`entries` coordinate `(entityKind, entityName)` a Postgres diff | ||
| * node addresses — a table by its name, a native enum by its physical type | ||
| * name. `undefined` for a node that is not a whole storage entity (a namespace, | ||
| * a column, a policy). Lets ownership/subject resolution treat every entity kind | ||
| * uniformly, the node self-describing its storage identity. | ||
| */ | ||
| function postgresNodeStorageCoordinate(node) { | ||
| if (PostgresTableSchemaNode.is(node)) { | ||
| const entityKind = postgresNodeEntityKind(node.nodeKind); | ||
| return entityKind === void 0 ? void 0 : { | ||
| entityKind, | ||
| entityName: node.name | ||
| }; | ||
| } | ||
| if (PostgresNativeEnumSchemaNode.is(node)) { | ||
| const entityKind = postgresNodeEntityKind(node.nodeKind); | ||
| return entityKind === void 0 ? void 0 : { | ||
| entityKind, | ||
| entityName: node.typeName | ||
| }; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/column-ddl-rendering.ts | ||
| /** | ||
| * Reconstructs the `StorageColumn`-shaped fields the DDL builder functions | ||
| * (`buildColumnTypeSql`, `buildExpectedFormatType`, `resolveIdentityValue`) | ||
| * expect, from a column node's own stamped codec identity (`codecRef` / | ||
| * `codecBaseNativeType` / `codecNamedType`, Decision 5) — never the | ||
| * contract. The builders were written against `StorageColumn` and are | ||
| * unchanged here; only the shape feeding them moves from the contract to | ||
| * the node. An empty `storageTypes` catalog is passed alongside: the | ||
| * node's fields are already resolved past any `typeRef` indirection, so no | ||
| * live lookup is needed, and passing a non-empty catalog would risk a | ||
| * false `typeRef` hit against an unrelated storage type. | ||
| */ | ||
| function columnLike(column) { | ||
| if (column.codecRef === void 0 || column.codecBaseNativeType === void 0) throw new Error(`columnLike: expected column "${column.name}" carries no codec identity — the expected tree must be derived via contractToSchemaIR for planning`); | ||
| return { | ||
| nativeType: column.codecBaseNativeType, | ||
| codecId: column.codecRef.codecId, | ||
| nullable: column.nullable, | ||
| ...ifDefined("many", column.many ?? column.codecRef.many), | ||
| ...ifDefined("typeParams", column.codecRef.typeParams !== void 0 ? blindCast(column.codecRef.typeParams) : void 0), | ||
| ...column.codecNamedType ? { typeRef: "<resolved>" } : {}, | ||
| ...ifDefined("default", column.resolvedDefault) | ||
| }; | ||
| } | ||
| /** | ||
| * Builds the `CREATE TABLE` / `ADD COLUMN` DDL column for an expected column | ||
| * node, resolving type rendering from the node's codec identity against the | ||
| * codec hooks the caller holds — the same builder the pre-`plan(start, end)` | ||
| * op-path called, so the output is byte-identical. | ||
| */ | ||
| function renderColumnDdl(name, column, codecHooks) { | ||
| const like = columnLike(column); | ||
| const typeSql = buildColumnTypeSql(like, codecHooks, {}); | ||
| const ddlDefault = postgresDefaultToDdlColumnDefault(like.default); | ||
| return contractFree.col(name, typeSql, { | ||
| ...!column.nullable ? { notNull: true } : {}, | ||
| ...ifDefined("default", ddlDefault), | ||
| ...ifDefined("codecRef", column.codecRef) | ||
| }); | ||
| } | ||
| /** | ||
| * Builds the `ALTER COLUMN … TYPE` operands for an expected column node. | ||
| */ | ||
| function renderColumnAlterType(column, codecHooks) { | ||
| const like = columnLike(column); | ||
| return { | ||
| qualifiedTargetType: buildColumnTypeSql(like, codecHooks, {}, false), | ||
| formatTypeExpected: buildExpectedFormatType(like, codecHooks, {}) | ||
| }; | ||
| } | ||
| /** | ||
| * Resolves the identity value (monoid neutral element) SQL literal used as | ||
| * the temporary default when adding a NOT-NULL column with no contract | ||
| * default (`notNullAddColumnCallStrategy`'s shared-temp-default backfill). | ||
| * `null` when the column's type has no built-in/codec-provided identity | ||
| * value. | ||
| */ | ||
| function resolveColumnTemporaryDefault(column, codecHooks) { | ||
| return resolveIdentityValue(columnLike(column), codecHooks, {}); | ||
| } | ||
| /** | ||
| * The column's `SET DEFAULT` clause SQL, resolved from a column-default | ||
| * diff node. `''` when the node carries no resolved default. | ||
| */ | ||
| function renderColumnDefaultSql(defaultNode) { | ||
| if (defaultNode.resolved === void 0) return ""; | ||
| return buildColumnDefaultSql(defaultNode.resolved, { | ||
| nativeType: defaultNode.nativeTypeContext ?? "", | ||
| ...ifDefined("many", defaultNode.many) | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/planner-strategies.ts | ||
| /** | ||
| * Look up a storage table by its explicit namespace coordinate. Returns | ||
| * `undefined` when the namespace has no table by that name (or no such | ||
| * namespace exists). Callers that get `undefined` MUST treat it as an | ||
| * explicit conflict — never silently fall back to a global default | ||
| * schema or a name-only walk, because that footgun would resolve a | ||
| * stale or duplicate table name to whichever namespace the iteration | ||
| * order surfaced first (a real data-loss hazard in multi-namespace | ||
| * contracts where two namespaces can carry the same table name). | ||
| */ | ||
| function tableAt(storage, namespaceId, tableName) { | ||
| const ns = storage.namespaces[namespaceId]; | ||
| if (ns === void 0) return void 0; | ||
| return ns.entries.table?.[tableName]; | ||
| } | ||
| /** | ||
| * Resolve the DDL schema name for a namespace coordinate. Postgres-aware | ||
| * namespaces dispatch to their polymorphic `ddlSchemaName` override — | ||
| * named schemas return their own id; the unbound singleton returns | ||
| * `UNBOUND_NAMESPACE_ID`. Legacy single-namespace contracts whose | ||
| * `__unbound__` slot is the framework-default `SqlUnboundNamespace` | ||
| * (rather than the Postgres-aware `PostgresUnboundSchema`) flow the | ||
| * coordinate through unchanged so downstream `qualifyTableName` | ||
| * resolves polymorphically. | ||
| */ | ||
| function resolveDdlSchemaForNamespace(ctx, namespaceId) { | ||
| const namespace = ctx.toContract.storage.namespaces[namespaceId]; | ||
| if (isPostgresSchema(namespace)) return namespace.ddlSchemaName(ctx.toContract.storage); | ||
| return namespaceId; | ||
| } | ||
| /** | ||
| * Recovers the contract namespace id for a DDL schema name embedded in a | ||
| * diff-issue path (`path[1]`). The strategies need the CONTRACT column/table | ||
| * (for codec-hook reads and eligibility probes the retained subsystems still | ||
| * run) even though the issue itself only carries the resolved DDL schema — | ||
| * this is the reverse of `resolveDdlSchemaForNamespace`. | ||
| */ | ||
| function namespaceIdForDdlSchema(ctx, ddlSchemaName) { | ||
| return resolveNamespaceIdForDdlSchema(ctx.toContract, ddlSchemaName); | ||
| } | ||
| /** A `not-equal` column issue whose node pair is narrowed to `SqlColumnIR`. */ | ||
| function columnNodePair(issue) { | ||
| const node = issueNode(issue); | ||
| if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.column) return void 0; | ||
| if (issue.expected === void 0 || issue.actual === void 0) return void 0; | ||
| return { | ||
| expected: blindCast(issue.expected), | ||
| actual: blindCast(issue.actual) | ||
| }; | ||
| } | ||
| const notNullBackfillCallStrategy = (issues, ctx) => { | ||
| if (!ctx.policy.allowedOperationClasses.includes("data")) return { kind: "no_match" }; | ||
| const matched = []; | ||
| const calls = []; | ||
| for (const issue of issues) { | ||
| if (issueOutcome(issue) !== "not-found") continue; | ||
| const node = issueNode(issue); | ||
| if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.column) continue; | ||
| const expected = blindCast(issue.expected); | ||
| if (expected.nullable !== false || expected.resolvedDefault !== void 0) continue; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) continue; | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| matched.push(issue); | ||
| const ddl = renderColumnDdl(expected.name, expected, ctx.codecHooks); | ||
| const nullableSpec = contractFree.col(ddl.name, ddl.type, { ...ddl.codecRef !== void 0 ? { codecRef: ddl.codecRef } : {} }); | ||
| calls.push(new AddColumnCall(schemaName, tableName, nullableSpec), new DataTransformCall(`backfill-${tableName}-${expected.name}`, `backfill-${tableName}-${expected.name}:check`, `backfill-${tableName}-${expected.name}:run`), new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| } | ||
| if (matched.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((i) => !matched.includes(i)), | ||
| calls, | ||
| recipe: true | ||
| }; | ||
| }; | ||
| const SAFE_WIDENINGS = /* @__PURE__ */ new Set([ | ||
| "int2→int4", | ||
| "int2→int8", | ||
| "int4→int8", | ||
| "float4→float8" | ||
| ]); | ||
| /** | ||
| * Handles `not-equal` column issues whose TYPE differs. `fromContract` is | ||
| * only supplied by `migration plan` — for reconciliation (`db update` / | ||
| * `db init`, `fromContract === null`) this strategy never fires, mirroring | ||
| * the legacy `typeChangeCallStrategy`'s requirement of a prior contract: | ||
| * `mapNodeIssueToCall`'s in-place ALTER covers reconciliation directly. | ||
| * | ||
| * A single node issue can carry BOTH type and nullability drift (Postgres | ||
| * alters in place, so the differ emits one `not-equal` column issue where | ||
| * the legacy coordinate walk emitted two: `type_mismatch` + | ||
| * `nullability_mismatch`). When this strategy consumes the issue for its | ||
| * type portion, it also emits whatever nullability delta the same node pair | ||
| * carries — using the same construction `nullableTighteningCallStrategy` / | ||
| * the mapper's direct dispatch would have used — so the issue is never | ||
| * partially handled. | ||
| */ | ||
| const typeChangeCallStrategy = (issues, ctx) => { | ||
| if (ctx.fromContract === null) return { kind: "no_match" }; | ||
| const dataAllowed = ctx.policy.allowedOperationClasses.includes("data"); | ||
| const matched = []; | ||
| const calls = []; | ||
| for (const issue of issues) { | ||
| if (issueOutcome(issue) !== "not-equal") continue; | ||
| const pair = columnNodePair(issue); | ||
| if (pair === void 0) continue; | ||
| const { expected, actual } = pair; | ||
| if (!columnTypeChangedNativeOnly(expected, actual)) continue; | ||
| const fromType = actual.nativeType; | ||
| const toType = expected.nativeType; | ||
| const isSafeWidening = SAFE_WIDENINGS.has(`${fromType}→${toType}`); | ||
| if (!isSafeWidening && !dataAllowed) continue; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) continue; | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| matched.push(issue); | ||
| const { qualifiedTargetType, formatTypeExpected } = renderColumnAlterType(expected, ctx.codecHooks); | ||
| const alterOpts = { | ||
| qualifiedTargetType, | ||
| formatTypeExpected, | ||
| rawTargetTypeForLabel: qualifiedTargetType | ||
| }; | ||
| if (isSafeWidening) calls.push(new AlterColumnTypeCall(schemaName, tableName, expected.name, alterOpts)); | ||
| else calls.push(new DataTransformCall(`typechange-${tableName}-${expected.name}`, `typechange-${tableName}-${expected.name}:check`, `typechange-${tableName}-${expected.name}:run`), new AlterColumnTypeCall(schemaName, tableName, expected.name, alterOpts)); | ||
| if (expected.nullable !== actual.nullable) if (expected.nullable) calls.push(new DropNotNullCall(schemaName, tableName, expected.name)); | ||
| else if (dataAllowed) calls.push(new DataTransformCall(`handle-nulls-${tableName}-${expected.name}`, `handle-nulls-${tableName}-${expected.name}:check`, `handle-nulls-${tableName}-${expected.name}:run`), new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| else calls.push(new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| } | ||
| if (matched.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((i) => !matched.includes(i)), | ||
| calls, | ||
| recipe: true | ||
| }; | ||
| }; | ||
| /** | ||
| * Whether the raw (unresolved) native type differs — the SAME comparison | ||
| * `typeChangeCallStrategy` always used (`fromColumn.nativeType !== | ||
| * toColumn.nativeType`), which for the widenable numeric/float types | ||
| * `SAFE_WIDENINGS` lists is identical to the resolved comparison | ||
| * `columnTypeChanged` (in `issue-planner.ts`) performs. | ||
| */ | ||
| function columnTypeChangedNativeOnly(expected, actual) { | ||
| return expected.nativeType !== actual.nativeType; | ||
| } | ||
| /** | ||
| * Handles `not-equal` column issues whose type did NOT change but | ||
| * nullability tightened (contract requires NOT NULL, live column is | ||
| * nullable). A type-changed issue's nullability delta (if any) is already | ||
| * handled by `typeChangeCallStrategy`, which runs first — this strategy | ||
| * only ever sees issues that strategy left behind. | ||
| */ | ||
| const nullableTighteningCallStrategy = (issues, ctx) => { | ||
| if (!ctx.policy.allowedOperationClasses.includes("data")) return { kind: "no_match" }; | ||
| const matched = []; | ||
| const calls = []; | ||
| for (const issue of issues) { | ||
| if (issueOutcome(issue) !== "not-equal") continue; | ||
| const pair = columnNodePair(issue); | ||
| if (pair === void 0) continue; | ||
| const { expected, actual } = pair; | ||
| if (columnTypeChangedNativeOnly(expected, actual)) continue; | ||
| if (expected.nullable !== false || actual.nullable !== true) continue; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) continue; | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| matched.push(issue); | ||
| calls.push(new DataTransformCall(`handle-nulls-${tableName}-${expected.name}`, `handle-nulls-${tableName}-${expected.name}:check`, `handle-nulls-${tableName}-${expected.name}:run`), new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| } | ||
| if (matched.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((i) => !matched.includes(i)), | ||
| calls, | ||
| recipe: true | ||
| }; | ||
| }; | ||
| /** | ||
| * Collects every check constraint from a table in the contract storage. | ||
| * Returns an empty array when the table has no checks or the table is absent. | ||
| */ | ||
| function collectContractChecks(storage, namespaceId, tableName) { | ||
| const ns = storage.namespaces[namespaceId]; | ||
| const tableRaw = ns !== void 0 ? ns.entries.table?.[tableName] : void 0; | ||
| if (!(tableRaw instanceof StorageTable)) return []; | ||
| const checks = tableRaw.checks; | ||
| if (!checks || checks.length === 0) return []; | ||
| return checks.map((c) => ({ | ||
| name: c.name, | ||
| column: c.column, | ||
| permittedValues: resolveValueSetValues(c.valueSet, storage, `check "${c.name}" on "${tableName}"`) | ||
| })); | ||
| } | ||
| /** | ||
| * Compares two value arrays as unordered sets. | ||
| */ | ||
| function checkValueSetsEqual(a, b) { | ||
| if (a.length !== b.length) return false; | ||
| const bSet = new Set(b); | ||
| return a.every((v) => bSet.has(v)); | ||
| } | ||
| /** | ||
| * Plans check-constraint migrations for `enumType`-authored columns. | ||
| * | ||
| * Walks every namespace's tables in the target contract (the check nodes' | ||
| * resolved `permittedValues` are ultimately sourced from the same | ||
| * contract-declared value sets, so walking the contract directly is the | ||
| * simplest faithful port — the strategy's decisions never depend on which | ||
| * ISSUES happen to be in the input list, only on the contract + live schema | ||
| * shapes). For each table that carries `checks`, diffs the contract-expected | ||
| * checks against the live schema's checks: | ||
| * | ||
| * - Check in contract, absent from live DB → `AddCheckConstraintCall`. | ||
| * - Check in live DB, absent from contract → `DropCheckConstraintCall`. | ||
| * - Check on both sides but value sets differ → `DropCheckConstraintCall` | ||
| * then `AddCheckConstraintCall` (drop + recreate; a check predicate cannot | ||
| * be altered in place). | ||
| * | ||
| * Consumes every `sql-check-constraint` issue on a table this walk handles | ||
| * (not-found/not-expected/not-equal), leaving check issues on tables with | ||
| * NO contract checks to `mapNodeIssueToCall`'s `not-expected` fallback. | ||
| */ | ||
| const checkConstraintPlanCallStrategy = (issues, ctx) => { | ||
| const calls = []; | ||
| const handledIssueKeys = /* @__PURE__ */ new Set(); | ||
| for (const [namespaceId, ns] of Object.entries(ctx.toContract.storage.namespaces)) for (const tableName of Object.keys(ns.entries.table ?? {})) { | ||
| const contractChecks = collectContractChecks(ctx.toContract.storage, namespaceId, tableName); | ||
| if (contractChecks.length === 0) continue; | ||
| const liveChecks = ctx.schema.tables[tableName]?.checks ?? []; | ||
| const ddlSchema = resolveDdlSchemaForNamespace(ctx, namespaceId); | ||
| for (const contractCheck of contractChecks) { | ||
| const liveCheck = liveChecks.find((c) => c.name === contractCheck.name); | ||
| const issueKey = `${tableName} ${contractCheck.name}`; | ||
| if (!liveCheck) { | ||
| calls.push(new AddCheckConstraintCall(ddlSchema, tableName, contractCheck.name, contractCheck.column, contractCheck.permittedValues)); | ||
| handledIssueKeys.add(issueKey); | ||
| } else if (!checkValueSetsEqual(contractCheck.permittedValues, liveCheck.permittedValues)) { | ||
| calls.push(new DropCheckConstraintCall(ddlSchema, tableName, contractCheck.name), new AddCheckConstraintCall(ddlSchema, tableName, contractCheck.name, contractCheck.column, contractCheck.permittedValues)); | ||
| handledIssueKeys.add(issueKey); | ||
| } else handledIssueKeys.add(issueKey); | ||
| } | ||
| for (const liveCheck of liveChecks) if (!contractChecks.some((c) => c.name === liveCheck.name)) { | ||
| const issueKey = `${tableName} ${liveCheck.name}`; | ||
| calls.push(new DropCheckConstraintCall(ddlSchema, tableName, liveCheck.name)); | ||
| handledIssueKeys.add(issueKey); | ||
| } | ||
| } | ||
| if (calls.length === 0 && handledIssueKeys.size === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((issue) => { | ||
| const node = issueNode(issue); | ||
| if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.check) return true; | ||
| const tableName = issueTableName(issue); | ||
| if (tableName === void 0) return true; | ||
| const checkName = blindCast(node).name; | ||
| return !handledIssueKeys.has(`${tableName} ${checkName}`); | ||
| }), | ||
| calls | ||
| }; | ||
| }; | ||
| /** | ||
| * Dispatches codec-typed storage types through their codec's | ||
| * `planTypeOperations` hook (the authoritative source for codec-driven DDL | ||
| * such as custom type creation). Codec extension/type ops are not modeled as | ||
| * diff nodes — this strategy drives entirely off `ctx.toContract.storage.types` | ||
| * + codec hooks, consuming nothing from the node issue list (there is no | ||
| * node-vocabulary equivalent of `type_missing` / `enum_values_changed`). | ||
| */ | ||
| const storageTypePlanCallStrategy = (issues, ctx) => { | ||
| const storageTypes = ctx.toContract.storage.types ?? {}; | ||
| if (Object.keys(storageTypes).length === 0) return { kind: "no_match" }; | ||
| const calls = []; | ||
| for (const [typeName, typeInstance] of Object.entries(storageTypes).sort(([a], [b]) => a.localeCompare(b))) { | ||
| const codecInstance = typeInstance; | ||
| const hook = ctx.codecHooks.get(codecInstance.codecId); | ||
| if (!hook?.planTypeOperations) continue; | ||
| const planResult = hook.planTypeOperations({ | ||
| typeName, | ||
| typeInstance: codecInstance, | ||
| contract: ctx.toContract, | ||
| schema: ctx.schema, | ||
| schemaName: ctx.schemaName, | ||
| policy: ctx.policy | ||
| }); | ||
| if (!planResult) continue; | ||
| for (const op of planResult.operations) calls.push(new RawSqlCall({ | ||
| ...op, | ||
| target: { | ||
| id: op.target.id, | ||
| details: buildTargetDetails("type", typeName, ctx.schemaName) | ||
| } | ||
| })); | ||
| } | ||
| if (calls.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues, | ||
| calls | ||
| }; | ||
| }; | ||
| /** | ||
| * Handles `not-found` column issues for NOT NULL columns without a contract | ||
| * default. Replaces the legacy `buildAddColumnItem` non-default branches. | ||
| * | ||
| * Two shapes: | ||
| * - Shared-temp-default safe: emit a single atomic composite op (add | ||
| * nullable → backfill identity value → `SET NOT NULL` → `DROP DEFAULT`). | ||
| * The temp-default value is resolved from the column node's `codecRef` | ||
| * (`resolveColumnTemporaryDefault`, wrapping `resolveIdentityValue`). | ||
| * - Empty-table guarded: emit a hand-built op with a `tableIsEmptyCheck` | ||
| * precheck so the failure message is "table is not empty" rather than the | ||
| * raw PG NOT NULL violation. | ||
| * | ||
| * "Normal" not-found column cases (nullable or has a contract default) are | ||
| * left for `mapNodeIssueToCall`'s default `AddColumnCall` emission. | ||
| */ | ||
| const notNullAddColumnCallStrategy = (issues, ctx) => { | ||
| const matched = []; | ||
| const calls = []; | ||
| const schemaLookups = buildSchemaLookupMap(ctx.schema); | ||
| const mutableCodecHooks = ctx.codecHooks; | ||
| const mutableStorageTypes = ctx.storageTypes; | ||
| for (const issue of issues) { | ||
| if (issueOutcome(issue) !== "not-found") continue; | ||
| const node = issueNode(issue); | ||
| if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.column) continue; | ||
| const expected = blindCast(issue.expected); | ||
| if (expected.nullable !== false || expected.resolvedDefault !== void 0) continue; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) continue; | ||
| const namespaceId = namespaceIdForDdlSchema(ctx, ddlSchemaName); | ||
| const schemaName = namespaceId === UNBOUND_NAMESPACE_ID ? UNBOUND_NAMESPACE_ID : ddlSchemaName; | ||
| const contractTable = tableAt(ctx.toContract.storage, namespaceId, tableName); | ||
| const column = contractTable?.columns[expected.name]; | ||
| if (!contractTable || !column) continue; | ||
| const schemaTable = ctx.schema.tables[tableName]; | ||
| if (!schemaTable) continue; | ||
| const temporaryDefault = resolveColumnTemporaryDefault(expected, ctx.codecHooks); | ||
| const schemaLookup = schemaLookups.get(tableName); | ||
| const canUseSharedTempDefault = temporaryDefault !== null && canUseSharedTemporaryDefaultStrategy({ | ||
| table: contractTable, | ||
| schemaTable, | ||
| schemaLookup, | ||
| columnName: expected.name | ||
| }); | ||
| matched.push(issue); | ||
| if (canUseSharedTempDefault && temporaryDefault !== null) { | ||
| calls.push(new AddNotNullColumnWithTempDefaultCall({ | ||
| schemaName, | ||
| tableName, | ||
| columnName: expected.name, | ||
| column, | ||
| codecHooks: mutableCodecHooks, | ||
| storageTypes: mutableStorageTypes, | ||
| temporaryDefault | ||
| })); | ||
| continue; | ||
| } | ||
| calls.push(new AddNotNullColumnDirectCall(schemaName, tableName, expected.name, renderColumnDdl(expected.name, expected, ctx.codecHooks))); | ||
| } | ||
| if (matched.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((i) => !matched.includes(i)), | ||
| calls | ||
| }; | ||
| }; | ||
| function canUseSharedTemporaryDefaultStrategy(options) { | ||
| const { table, schemaTable, schemaLookup, columnName } = options; | ||
| if (table.primaryKey?.columns.includes(columnName) && !schemaTable.primaryKey) return false; | ||
| for (const unique of table.uniques) { | ||
| if (!unique.columns.includes(columnName)) continue; | ||
| if (!schemaLookup || !hasUniqueConstraint(schemaLookup, unique.columns)) return false; | ||
| } | ||
| for (const foreignKey of table.foreignKeys) { | ||
| if (!foreignKey.source.columns.includes(columnName)) continue; | ||
| if (!schemaLookup || !hasForeignKey(schemaLookup, foreignKey)) return false; | ||
| } | ||
| return true; | ||
| } | ||
| /** | ||
| * Ordered list of Postgres planner strategies, shared by `migration plan` | ||
| * and `db update` / `db init`. The issue planner runs each strategy in | ||
| * order, letting it consume any issues it handles, and routes whatever's | ||
| * left through `mapNodeIssueToCall`. Behavior diverges purely on | ||
| * `policy.allowedOperationClasses`: | ||
| * | ||
| * - When `'data'` is allowed (`migration plan`), the data-safe strategies | ||
| * (`notNullBackfillCallStrategy`, `typeChangeCallStrategy`, | ||
| * `nullableTighteningCallStrategy`) consume their matching issues and emit | ||
| * `DataTransformCall` placeholders or recipe ops. | ||
| * | ||
| * - When `'data'` is not allowed (`db update` / `db init`), the | ||
| * placeholder-emitting strategies short-circuit to `no_match`, leaving | ||
| * the issue for the downstream strategies (`storageTypePlanCallStrategy`, | ||
| * `notNullAddColumnCallStrategy`) or the `mapNodeIssueToCall` default to | ||
| * handle with direct DDL. | ||
| * | ||
| * Codec-typed storage type entries are dispatched through | ||
| * `storageTypePlanCallStrategy`. | ||
| */ | ||
| const postgresPlannerStrategies = [ | ||
| notNullBackfillCallStrategy, | ||
| typeChangeCallStrategy, | ||
| nullableTighteningCallStrategy, | ||
| checkConstraintPlanCallStrategy, | ||
| storageTypePlanCallStrategy, | ||
| notNullAddColumnCallStrategy | ||
| ]; | ||
| //#endregion | ||
| //#region src/core/migrations/issue-planner.ts | ||
| /** | ||
| * Deterministic name for the element-non-null CHECK constraint on a scalar-array | ||
| * column. Distinct `_elem_not_null` suffix avoids collision with the enum | ||
| * value-set `_check` constraints. Re-emitting the same schema produces the same | ||
| * name, so `pg_get_constraintdef`-based verify sees no drift. | ||
| */ | ||
| function elementNonNullCheckName(tableName, columnName) { | ||
| return `${tableName}_${columnName}_elem_not_null`; | ||
| } | ||
| /** | ||
| * Predicate enforcing that a scalar-array column carries no NULL element. The | ||
| * array column itself may be NULL (container nullability is the column's NOT NULL | ||
| * clause); `array_position` over a NULL array yields NULL, which a CHECK treats | ||
| * as satisfied, so a nullable array column is unaffected. | ||
| */ | ||
| function elementNonNullCheckExpression(columnName) { | ||
| return `array_position(${quoteIdentifier(columnName)}, NULL) IS NULL`; | ||
| } | ||
| function issueConflict(kind, summary, location) { | ||
| return { | ||
| kind, | ||
| summary, | ||
| why: "Use `migration new` to author a custom migration for this change.", | ||
| ...location ? { location } : {} | ||
| }; | ||
| } | ||
| /** | ||
| * Classifies calls into DDL sequencing buckets. The order matches the | ||
| * legacy walk-schema planner's emission order so `db init` and `db update` | ||
| * produce byte-identical plans for the shared shape (deps → drops → tables | ||
| * → columns → alters → PKs → uniques → indexes → FKs). | ||
| * | ||
| * `dropType` (DROP TYPE for a managed native enum) is its own bucket, ordered | ||
| * after the general drop bucket: a type can only be dropped once every table | ||
| * whose column uses it is gone. A column→enum edge would make this precise, | ||
| * but `coalesceSubtreeIssues` removes the column issue under a whole-table | ||
| * drop, so the edge never reaches the graph; the coarse bucket stands in until | ||
| * that edge lands (the coordinate work is a later slice). | ||
| */ | ||
| function classifyCall(call) { | ||
| switch (call.factoryName) { | ||
| case "createExtension": | ||
| case "createSchema": | ||
| case "createNativeEnumType": | ||
| case "addNativeEnumValue": return "dep"; | ||
| case "dropNativeEnumType": return "dropType"; | ||
| case "dropTable": | ||
| case "dropColumn": | ||
| case "dropConstraint": | ||
| case "dropCheckConstraint": | ||
| case "dropIndex": | ||
| case "dropDefault": return "drop"; | ||
| case "addCheckConstraint": return "unique"; | ||
| case "createTable": return "table"; | ||
| case "enableRowLevelSecurity": | ||
| case "disableRowLevelSecurity": return "rlsEnable"; | ||
| case "createRlsPolicy": return "rlsPolicy"; | ||
| case "dropRlsPolicy": return "drop"; | ||
| case "addColumn": return "column"; | ||
| case "alterColumnType": | ||
| case "setNotNull": | ||
| case "dropNotNull": | ||
| case "setDefault": return "alter"; | ||
| case "addPrimaryKey": return "primaryKey"; | ||
| case "addUnique": return "unique"; | ||
| case "createIndex": return "index"; | ||
| case "addForeignKey": return "foreignKey"; | ||
| case "rawSql": | ||
| if (call.op?.target?.details?.objectType === "type") return "dep"; | ||
| return "alter"; | ||
| default: return "alter"; | ||
| } | ||
| } | ||
| const DEFAULT_POLICY = { allowedOperationClasses: [ | ||
| "additive", | ||
| "widening", | ||
| "destructive", | ||
| "data" | ||
| ] }; | ||
| function emptySchemaIR() { | ||
| return new SqlSchemaIR({ tables: {} }); | ||
| } | ||
| function conflictKindForCall(call) { | ||
| switch (call.factoryName) { | ||
| case "alterColumnType": return "typeMismatch"; | ||
| case "setNotNull": | ||
| case "dropNotNull": return "nullabilityConflict"; | ||
| case "addForeignKey": | ||
| case "dropConstraint": return "foreignKeyConflict"; | ||
| case "createIndex": | ||
| case "dropIndex": return "indexIncompatible"; | ||
| default: return "missingButNonAdditive"; | ||
| } | ||
| } | ||
| function locationForCall(call) { | ||
| const anyCall = call; | ||
| const location = {}; | ||
| if (anyCall.tableName) { | ||
| location.entityKind = "table"; | ||
| location.entityName = anyCall.tableName; | ||
| } else if (anyCall.typeName) { | ||
| location.entityKind = "native_enum"; | ||
| location.entityName = anyCall.typeName; | ||
| } | ||
| if (anyCall.columnName) location.column = anyCall.columnName; | ||
| if (anyCall.indexName) location.index = anyCall.indexName; | ||
| if (anyCall.constraintName) location.constraint = anyCall.constraintName; | ||
| return Object.keys(location).length > 0 ? location : void 0; | ||
| } | ||
| function conflictForDisallowedCall(call, allowed) { | ||
| const summary = `Operation "${call.label}" requires class "${call.operationClass}", but policy allows only: ${allowed.join(", ")}`; | ||
| const location = locationForCall(call); | ||
| return { | ||
| kind: conflictKindForCall(call), | ||
| summary, | ||
| why: "Use `migration new` to author a custom migration for this change.", | ||
| ...location ? { location } : {} | ||
| }; | ||
| } | ||
| /** The diff node an issue concerns — expected when present, else the actual (extra) node. */ | ||
| function issueNode(issue) { | ||
| const node = issue.expected ?? issue.actual; | ||
| if (node === void 0) return void 0; | ||
| return blindCast(node); | ||
| } | ||
| /** DDL schema segment of a table-or-descendant issue path: `[database, ddlSchema, table, …]`. */ | ||
| function issueSchemaName(issue) { | ||
| return issue.path[1]; | ||
| } | ||
| /** Table segment of a table-or-descendant issue path: `[database, ddlSchema, table, …]`. */ | ||
| function issueTableName(issue) { | ||
| return issue.path[2]; | ||
| } | ||
| /** Column name embedded in a column/default issue path segment (`column:<name>`). */ | ||
| function issueColumnName(issue) { | ||
| const segment = issue.path[3]; | ||
| if (segment === void 0 || !segment.startsWith("column:")) return void 0; | ||
| return segment.slice(7); | ||
| } | ||
| /** | ||
| * The DDL schema name to use when EMITTING an op against `ddlSchemaName` (the | ||
| * diff tree's resolved physical schema, `issueSchemaName(issue)`). The | ||
| * unbound namespace's diff-tree identity resolves to `public` (a concrete | ||
| * physical default the differ needs in order to compare its tree against | ||
| * introspection — `resolveDdlSchemaForNamespaceStorage`), but DDL EMISSION | ||
| * must stay unqualified so the live connection's `search_path` resolves it | ||
| * at runtime (`boundSchema`). Recovers the logical namespace id via the | ||
| * contract and substitutes the unbound sentinel back in when it resolves | ||
| * there; every other namespace's `ddlSchemaName` already agrees between the | ||
| * two resolution paths, so it passes through unchanged. | ||
| */ | ||
| function emissionSchemaName(ctx, ddlSchemaName) { | ||
| return resolveNamespaceIdForDdlSchema(ctx.toContract, ddlSchemaName) === UNBOUND_NAMESPACE_ID ? UNBOUND_NAMESPACE_ID : ddlSchemaName; | ||
| } | ||
| /** | ||
| * Whether a column node is a scalar-array (`many: true`) column. The family | ||
| * converter (`contractToSchemaIR`'s `convertColumn`) never stamps `many` on | ||
| * the derived node — array-ness is folded into the `[]` suffix on | ||
| * `nativeType` instead — so the node-derived check reads the suffix; `.many` | ||
| * is still checked first for nodes a caller stamps directly (e.g. hand-built | ||
| * test fixtures, or an adapter that populates it at introspection). | ||
| */ | ||
| function isManyColumn(column) { | ||
| return column.many === true || column.nativeType.endsWith("[]"); | ||
| } | ||
| /** Whether the expected/actual native type (resolved, or raw+many fallback) differs — mirrors `SqlColumnIR.isEqualTo`'s type comparison. */ | ||
| function columnTypeChanged(expected, actual) { | ||
| if (expected.resolvedNativeType !== void 0 && actual.resolvedNativeType !== void 0) return expected.resolvedNativeType !== actual.resolvedNativeType; | ||
| return expected.nativeType !== actual.nativeType || Boolean(expected.many) !== Boolean(actual.many); | ||
| } | ||
| /** | ||
| * The generic differ is total: a missing/extra table (or column) emits an | ||
| * issue for itself AND for every node in its subtree. `CreateTable`/`DropTable` | ||
| * and `AddColumn`/`DropColumn` already account for the whole subtree, so the | ||
| * nested issues are redundant — coalescing drops any issue whose path is a | ||
| * strict descendant of a `not-found`/`not-expected` issue's path. Run over the | ||
| * relational subset ONLY (policy issues and synthesized namespace issues are | ||
| * handled on their own paths, never coalesced against tables). | ||
| */ | ||
| function coalesceSubtreeIssues(issues) { | ||
| const collapsingPaths = issues.filter((issue) => issueOutcome(issue) !== "not-equal").map((issue) => issue.path); | ||
| if (collapsingPaths.length === 0) return issues; | ||
| return issues.filter((issue) => !collapsingPaths.some((ancestor) => isStrictDescendantPath(issue.path, ancestor))); | ||
| } | ||
| function isStrictDescendantPath(path, ancestor) { | ||
| if (path.length <= ancestor.length) return false; | ||
| for (let i = 0; i < ancestor.length; i += 1) if (path[i] !== ancestor[i]) return false; | ||
| return true; | ||
| } | ||
| function fkSpecFromNode(fk, tableName) { | ||
| return { | ||
| name: fk.name ?? `${tableName}_${fk.columns.join("_")}_fkey`, | ||
| columns: [...fk.columns], | ||
| references: { | ||
| schema: fk.referencedSchema ?? "", | ||
| table: fk.referencedTable, | ||
| columns: [...fk.referencedColumns] | ||
| }, | ||
| ...ifDefined("onDelete", fk.onDelete), | ||
| ...ifDefined("onUpdate", fk.onUpdate) | ||
| }; | ||
| } | ||
| /** | ||
| * Builds the `CreateTable` + child `CreateIndex` / `AddForeignKey` / `AddUnique` | ||
| * calls for a newly-expected table, reading only the table node's children. The | ||
| * PK and element-non-null CHECKs go inline as table constraints; indexes | ||
| * (declared + FK-backing, already merged and ordered at derivation) and the | ||
| * FK / unique constraints are separate calls (re-bucketed downstream). Every | ||
| * column's DDL is resolved from its `codecRef` via `renderColumnDdl`. | ||
| */ | ||
| function buildCreateTableCallsFromNode(schemaName, ddlSchemaName, table, codecHooks) { | ||
| const ddlColumns = Object.values(table.columns).map((c) => renderColumnDdl(c.name, c, codecHooks)); | ||
| const primaryKeyConstraints = table.primaryKey ? [contractFree.primaryKey([...table.primaryKey.columns], { ...ifDefined("name", table.primaryKey.name) })] : []; | ||
| const elementNonNullChecks = Object.values(table.columns).filter((c) => isManyColumn(c)).map((c) => contractFree.checkExpression(elementNonNullCheckName(table.name, c.name), elementNonNullCheckExpression(c.name))); | ||
| const allTableConstraints = [...primaryKeyConstraints, ...elementNonNullChecks]; | ||
| const calls = [new CreateTableCall(schemaName, table.name, ddlColumns, allTableConstraints.length > 0 ? allTableConstraints : void 0)]; | ||
| for (const index of table.indexes) { | ||
| const indexName = index.name ?? defaultIndexName(table.name, index.columns); | ||
| const extras = {}; | ||
| if (index.type !== void 0) extras.type = index.type; | ||
| if (index.options !== void 0) extras.options = index.options; | ||
| calls.push(new CreateIndexCall(schemaName, table.name, indexName, [...index.columns], extras)); | ||
| } | ||
| for (const fk of table.foreignKeys) calls.push(new AddForeignKeyCall(schemaName, table.name, fkSpecFromNode(fk, table.name))); | ||
| for (const unique of table.uniques) { | ||
| const constraintName = unique.name ?? `${table.name}_${unique.columns.join("_")}_key`; | ||
| calls.push(new AddUniqueCall(schemaName, table.name, constraintName, [...unique.columns])); | ||
| } | ||
| if (table.rlsEnabled) calls.push(new EnableRowLevelSecurityCall(ddlSchemaName, table.name)); | ||
| return calls; | ||
| } | ||
| function nodeConflict(kind, message) { | ||
| return issueConflict(kind, message); | ||
| } | ||
| /** | ||
| * True when `actualMembers` (the live database's ordered members) is a | ||
| * strict, order-preserving prefix of `expectedMembers` (the contract's) — | ||
| * the database already carries every contract member, in declaration | ||
| * order, and the contract declares at least one member the database still | ||
| * lacks. Any other relationship — a renamed value, a removed value, a | ||
| * reordering, or the database holding members the contract lacks — is not | ||
| * a suffix append. | ||
| */ | ||
| function isNativeEnumSuffixAppend(actualMembers, expectedMembers) { | ||
| if (actualMembers.length >= expectedMembers.length) return false; | ||
| return actualMembers.every((member, index) => member === expectedMembers[index]); | ||
| } | ||
| /** Operator-worded refusal for a native-enum member change beyond a suffix append (design ruling — tests match this verbatim). */ | ||
| function nativeEnumMemberChangeRefusal(options) { | ||
| return `Native enum type "${options.ddlSchemaName}"."${options.typeName}" changed beyond appending new values (contract declares [${options.expectedMembers.join(", ")}], database has [${options.actualMembers.join(", ")}]). Prisma Next does not modify a native enum's existing values (rename, removal, reorder) — see https://pris.ly/d/postgres-native-enums. Author the change manually with \`migration new\`.`; | ||
| } | ||
| /** | ||
| * Managed native-enum issue -> op lowering. A missing declared type creates | ||
| * it; an unclaimed live type drops it (ownership-scoped upstream by | ||
| * `retainUnownedExtras`, destructiveness gated by the operation-class | ||
| * policy); a paired member-value mismatch lowers to one `ALTER TYPE ... ADD | ||
| * VALUE` per appended member when the database's members are a strict, | ||
| * order-preserving prefix of the contract's — any other change (rename, | ||
| * removal, reorder, or the database holding members the contract lacks) is | ||
| * refused with a NAMED diagnostic, never a silent no-op and never a | ||
| * drop-and-recreate. | ||
| */ | ||
| function mapNativeEnumNodeIssue(issue, ctx) { | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| if (ddlSchemaName === void 0) return notOk(nodeConflict("unsupportedOperation", `Enum issue has no schema in its path: ${issue.path.join("/")}`)); | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const expected = blindCast(issue.expected); | ||
| return ok([new CreateNativeEnumTypeCall(schemaName, expected.typeName, expected.members)]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropNativeEnumTypeCall(schemaName, blindCast(issue.actual).typeName)]); | ||
| const expected = blindCast(issue.expected); | ||
| const actual = blindCast(issue.actual); | ||
| if (isNativeEnumSuffixAppend(actual.members, expected.members)) return ok(expected.members.slice(actual.members.length).map((value) => new AddNativeEnumValueCall(schemaName, expected.typeName, value))); | ||
| return notOk(nodeConflict("unsupportedOperation", nativeEnumMemberChangeRefusal({ | ||
| ddlSchemaName, | ||
| typeName: expected.typeName, | ||
| expectedMembers: expected.members, | ||
| actualMembers: actual.members | ||
| }))); | ||
| } | ||
| function mapTableNodeIssue(issue, schemaName, ddlSchemaName, codecHooks) { | ||
| if (issueOutcome(issue) === "not-found") return ok(buildCreateTableCallsFromNode(schemaName, ddlSchemaName, blindCast(issue.expected), codecHooks)); | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropTableCall(schemaName, blindCast(issue.actual).name)]); | ||
| const expected = blindCast(issue.expected); | ||
| const actual = blindCast(issue.actual); | ||
| if (expected.rlsEnabled && !actual.rlsEnabled) return ok([new EnableRowLevelSecurityCall(ddlSchemaName, expected.name)]); | ||
| if (!expected.rlsEnabled && actual.rlsEnabled) return ok([new DisableRowLevelSecurityCall(ddlSchemaName, expected.name)]); | ||
| return notOk(nodeConflict("unsupportedOperation", `unhandled table-attribute drift on "${expected.name}": table not-equal with no rlsEnabled delta (a second table attribute drifted)`)); | ||
| } | ||
| function mapColumnNodeIssue(issue, schemaName, tableName, codecHooks) { | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const column = blindCast(issue.expected); | ||
| return ok([new AddColumnCall(schemaName, tableName, renderColumnDdl(column.name, column, codecHooks))]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropColumnCall(schemaName, tableName, blindCast(issue.actual).name)]); | ||
| const expected = blindCast(issue.expected); | ||
| const actual = blindCast(issue.actual); | ||
| const calls = []; | ||
| if (columnTypeChanged(expected, actual)) { | ||
| const { qualifiedTargetType, formatTypeExpected } = renderColumnAlterType(expected, codecHooks); | ||
| calls.push(new AlterColumnTypeCall(schemaName, tableName, expected.name, { | ||
| qualifiedTargetType, | ||
| formatTypeExpected, | ||
| rawTargetTypeForLabel: qualifiedTargetType | ||
| })); | ||
| } | ||
| if (expected.nullable !== actual.nullable) calls.push(expected.nullable ? new DropNotNullCall(schemaName, tableName, expected.name) : new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| return ok(calls); | ||
| } | ||
| function mapColumnDefaultNodeIssue(issue, schemaName, tableName, columnName) { | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropDefaultCall(schemaName, tableName, columnName)]); | ||
| if (issue.expected === void 0) return ok([]); | ||
| const defaultSql = renderColumnDefaultSql(blindCast(issue.expected)); | ||
| if (!defaultSql) return ok([]); | ||
| return ok([new SetDefaultCall(schemaName, tableName, columnName, defaultSql, issueOutcome(issue) === "not-equal" ? "widening" : "additive")]); | ||
| } | ||
| function mapPrimaryKeyNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const pk = blindCast(issue.expected); | ||
| return ok([new AddPrimaryKeyCall(schemaName, tableName, pk.name ?? `${tableName}_pkey`, [...pk.columns])]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropConstraintCall(schemaName, tableName, blindCast(issue.actual).name ?? `${tableName}_pkey`, "primaryKey")]); | ||
| return notOk(nodeConflict("indexIncompatible", issue.path.join("/"))); | ||
| } | ||
| function mapForeignKeyNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-found") return ok([new AddForeignKeyCall(schemaName, tableName, fkSpecFromNode(blindCast(issue.expected), tableName))]); | ||
| if (issueOutcome(issue) === "not-expected") { | ||
| const fk = blindCast(issue.actual); | ||
| return ok([new DropConstraintCall(schemaName, tableName, fk.name ?? `${tableName}_${fk.columns.join("_")}_fkey`, "foreignKey")]); | ||
| } | ||
| return notOk(nodeConflict("foreignKeyConflict", issue.path.join("/"))); | ||
| } | ||
| function mapUniqueNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const unique = blindCast(issue.expected); | ||
| return ok([new AddUniqueCall(schemaName, tableName, unique.name ?? `${tableName}_${unique.columns.join("_")}_key`, [...unique.columns])]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") { | ||
| const unique = blindCast(issue.actual); | ||
| return ok([new DropConstraintCall(schemaName, tableName, unique.name ?? `${tableName}_${unique.columns.join("_")}_key`, "unique")]); | ||
| } | ||
| return notOk(nodeConflict("indexIncompatible", issue.path.join("/"))); | ||
| } | ||
| function mapIndexNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const index = blindCast(issue.expected); | ||
| const indexName = index.name ?? defaultIndexName(tableName, index.columns); | ||
| const extras = {}; | ||
| if (index.type !== void 0) extras.type = index.type; | ||
| if (index.options !== void 0) extras.options = index.options; | ||
| return ok([new CreateIndexCall(schemaName, tableName, indexName, [...index.columns], extras)]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") { | ||
| const index = blindCast(issue.actual); | ||
| return ok([new DropIndexCall(schemaName, tableName, index.name ?? defaultIndexName(tableName, index.columns))]); | ||
| } | ||
| return notOk(nodeConflict("indexIncompatible", issue.path.join("/"))); | ||
| } | ||
| function mapCheckNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropCheckConstraintCall(schemaName, tableName, blindCast(issue.actual).name)]); | ||
| return notOk(nodeConflict("unsupportedOperation", `Check constraint drift on "${tableName}" — handled by checkConstraintPlanCallStrategy: ${issue.path.join("/")}`)); | ||
| } | ||
| /** | ||
| * Maps one node-typed diff issue to its migration call(s), dispatching on the | ||
| * node's `nodeKind` and the issue's presence-derived `issueOutcome`, reading | ||
| * nodes and resolving column DDL from `codecRef` via `column-ddl-rendering.ts`. | ||
| */ | ||
| function mapNodeIssueToCall(issue, ctx) { | ||
| const node = issueNode(issue); | ||
| if (node === void 0) return notOk(nodeConflict("unsupportedOperation", `Issue carries neither an expected nor an actual node: ${issue.path.join("/")}`)); | ||
| if (node.nodeKind === PostgresSchemaNodeKind.namespace) { | ||
| if (issueOutcome(issue) !== "not-found") return notOk(nodeConflict("unsupportedOperation", `Unexpected namespace drift: ${issue.path.join("/")}`)); | ||
| return ok([new CreateSchemaCall(blindCast(issue.expected).schemaName)]); | ||
| } | ||
| if (node.nodeKind === PostgresSchemaNodeKind.nativeEnum) return mapNativeEnumNodeIssue(issue, ctx); | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) return notOk(nodeConflict("unsupportedOperation", `Issue has no schema/table in its path: ${issue.path.join("/")}`)); | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| switch (node.nodeKind) { | ||
| case PostgresSchemaNodeKind.table: return mapTableNodeIssue(issue, schemaName, ddlSchemaName, ctx.codecHooks); | ||
| case RelationalSchemaNodeKind.column: return mapColumnNodeIssue(issue, schemaName, tableName, ctx.codecHooks); | ||
| case RelationalSchemaNodeKind.columnDefault: { | ||
| const columnName = issueColumnName(issue); | ||
| if (columnName === void 0) return notOk(nodeConflict("unsupportedOperation", `Default issue has no column in its path: ${issue.path.join("/")}`)); | ||
| return mapColumnDefaultNodeIssue(issue, schemaName, tableName, columnName); | ||
| } | ||
| case RelationalSchemaNodeKind.primaryKey: return mapPrimaryKeyNodeIssue(issue, schemaName, tableName); | ||
| case RelationalSchemaNodeKind.foreignKey: return mapForeignKeyNodeIssue(issue, schemaName, tableName); | ||
| case RelationalSchemaNodeKind.unique: return mapUniqueNodeIssue(issue, schemaName, tableName); | ||
| case RelationalSchemaNodeKind.index: return mapIndexNodeIssue(issue, schemaName, tableName); | ||
| case RelationalSchemaNodeKind.check: return mapCheckNodeIssue(issue, schemaName, tableName); | ||
| default: return notOk(nodeConflict("unsupportedOperation", `Unhandled node kind: ${node.nodeKind}`)); | ||
| } | ||
| } | ||
| /** | ||
| * Runs the ordered strategy list over the node-typed diff issues, maps | ||
| * leftover issues via {@link mapNodeIssueToCall}, applies operation-class | ||
| * policy gating, and buckets calls into the fixed DDL emission order (dep → | ||
| * drop → table → column → recipe → alter → primaryKey → unique → index → | ||
| * foreignKey). | ||
| */ | ||
| function planIssues(options) { | ||
| const policyProvided = options.policy !== void 0; | ||
| const policy = options.policy ?? DEFAULT_POLICY; | ||
| const schema = options.schema ?? emptySchemaIR(); | ||
| const frameworkComponents = options.frameworkComponents ?? []; | ||
| const context = { | ||
| toContract: options.toContract, | ||
| fromContract: options.fromContract, | ||
| schemaName: options.schemaName, | ||
| codecHooks: options.codecHooks, | ||
| storageTypes: options.storageTypes, | ||
| schema, | ||
| policy, | ||
| frameworkComponents | ||
| }; | ||
| const strategies = options.strategies ?? postgresPlannerStrategies; | ||
| let remaining = options.issues; | ||
| const recipeCalls = []; | ||
| const bucketablePatternCalls = []; | ||
| for (const strategy of strategies) { | ||
| const result = strategy(remaining, context); | ||
| if (result.kind === "match") { | ||
| remaining = result.issues; | ||
| if (result.recipe) recipeCalls.push(...result.calls); | ||
| else bucketablePatternCalls.push(...result.calls); | ||
| } | ||
| } | ||
| const sorted = orderIssuesByDependencies(remaining); | ||
| const defaultCalls = []; | ||
| const conflicts = []; | ||
| for (const issue of sorted) { | ||
| const result = mapNodeIssueToCall(issue, context); | ||
| if (result.ok) defaultCalls.push(...result.value); | ||
| else conflicts.push(result.failure); | ||
| } | ||
| const allowed = policy.allowedOperationClasses; | ||
| let gatedDefault = defaultCalls; | ||
| let gatedRecipe = recipeCalls; | ||
| let gatedBucketable = bucketablePatternCalls; | ||
| if (policyProvided) { | ||
| const keepIfAllowed = (bucket) => (call) => { | ||
| if (allowed.includes(call.operationClass)) { | ||
| bucket.push(call); | ||
| return; | ||
| } | ||
| conflicts.push(conflictForDisallowedCall(call, allowed)); | ||
| }; | ||
| const gatedDefaultBucket = []; | ||
| const gatedRecipeBucket = []; | ||
| const gatedBucketableBucket = []; | ||
| defaultCalls.forEach(keepIfAllowed(gatedDefaultBucket)); | ||
| recipeCalls.forEach(keepIfAllowed(gatedRecipeBucket)); | ||
| bucketablePatternCalls.forEach(keepIfAllowed(gatedBucketableBucket)); | ||
| gatedDefault = gatedDefaultBucket; | ||
| gatedRecipe = gatedRecipeBucket; | ||
| gatedBucketable = gatedBucketableBucket; | ||
| } | ||
| if (conflicts.length > 0) return notOk(conflicts); | ||
| const combinedBucketable = [...gatedDefault, ...gatedBucketable]; | ||
| const byCategory = (cat) => combinedBucketable.filter((c) => classifyCall(c) === cat); | ||
| return ok({ calls: [ | ||
| ...byCategory("dep"), | ||
| ...byCategory("drop"), | ||
| ...byCategory("dropType"), | ||
| ...byCategory("table"), | ||
| ...byCategory("column"), | ||
| ...gatedRecipe, | ||
| ...byCategory("alter"), | ||
| ...byCategory("primaryKey"), | ||
| ...byCategory("unique"), | ||
| ...byCategory("index"), | ||
| ...byCategory("foreignKey"), | ||
| ...byCategory("rlsEnable") | ||
| ] }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/control-policy.ts | ||
| /** | ||
| * Factory calls that create a whole, previously-absent top-level storage | ||
| * object. Used to decide whether `tolerated` permits a call (it only allows | ||
| * creating absent objects, never modifying existing ones). | ||
| * | ||
| * Deliberately an explicit, closed set rather than a `factoryName` | ||
| * create/alter/drop classification: it answers exactly one yes/no question | ||
| * and is fail-closed. Any call not listed here — including future or | ||
| * extension-contributed factories — is treated as NOT object-creation, so it | ||
| * is suppressed under `tolerated` rather than permissively emitted. | ||
| * | ||
| * Lists the creation factories that actually reach the call-side resolver | ||
| * (`resolvePostgresCallControlPolicySubject`) — today only RLS policy | ||
| * creation, from `planPostgresSchemaDiff`. Every other whole-object create | ||
| * (table, schema, native enum, and RLS enablement) is constructed inside | ||
| * `planIssues` and graded on the node side by | ||
| * `resolvePostgresNodeIssueCreationFactoryName`, so none of them are listed | ||
| * here. | ||
| */ | ||
| const OBJECT_CREATION_FACTORIES = /* @__PURE__ */ new Set(["createRlsPolicy"]); | ||
| function createsNewTopLevelObject(call) { | ||
| return OBJECT_CREATION_FACTORIES.has(call.factoryName); | ||
| } | ||
| function ddlSchemaNameForNamespace(contract, namespaceId) { | ||
| const namespace = contract.storage.namespaces[namespaceId]; | ||
| return isPostgresSchema(namespace) ? namespace.ddlSchemaName(contract.storage) : namespaceId; | ||
| } | ||
| /** | ||
| * Resolve the namespace a declared storage entity lives in by walking every | ||
| * namespace for one whose `entries` map actually contains the coordinate — | ||
| * a table by its name, a native enum by its physical type name (see | ||
| * {@link postgresNodeStorageCoordinate}). When `ddlSchemaName` is given, the | ||
| * match must also land in that DDL schema (disambiguates same-named entities | ||
| * declared under different namespaces); when omitted, the first namespace | ||
| * that declares the entity wins. Falls back to {@link UNBOUND_NAMESPACE_ID} | ||
| * when no namespace declares it — e.g. an extra/dropped entity the contract | ||
| * doesn't claim at all. | ||
| */ | ||
| function resolveNamespaceIdForEntity(contract, coordinate, ddlSchemaName) { | ||
| for (const namespaceId of Object.keys(contract.storage.namespaces)) { | ||
| if (!entityAt(contract.storage, { | ||
| namespaceId, | ||
| ...coordinate | ||
| })) continue; | ||
| if (ddlSchemaName === void 0 || ddlSchemaNameForNamespace(contract, namespaceId) === ddlSchemaName) return namespaceId; | ||
| } | ||
| return UNBOUND_NAMESPACE_ID; | ||
| } | ||
| function resolveNamespaceIdForTable(contract, tableName, ddlSchemaName) { | ||
| return resolveNamespaceIdForEntity(contract, { | ||
| entityKind: "table", | ||
| entityName: tableName | ||
| }, ddlSchemaName); | ||
| } | ||
| function resolveNamespaceIdForDdlSchema(contract, ddlSchemaName) { | ||
| for (const namespaceId of Object.keys(contract.storage.namespaces)) { | ||
| const ns = contract.storage.namespaces[namespaceId]; | ||
| if (isPostgresSchema(ns) && ns.ddlSchemaName(contract.storage) === ddlSchemaName) return namespaceId; | ||
| if (namespaceId === ddlSchemaName) return namespaceId; | ||
| } | ||
| return UNBOUND_NAMESPACE_ID; | ||
| } | ||
| function postgresCallFields(call) { | ||
| return { | ||
| ...ifDefined("schemaName", "schemaName" in call ? call.schemaName : void 0), | ||
| ...ifDefined("tableName", "tableName" in call ? call.tableName : void 0), | ||
| ...ifDefined("columnName", "columnName" in call ? call.columnName : void 0) | ||
| }; | ||
| } | ||
| function formatSuppressionSubjectLabel(subject, contract) { | ||
| if (subject === void 0) return "unknown"; | ||
| const ddlSchema = ddlSchemaNameForNamespace(contract, subject.namespaceId); | ||
| if (subject.entityKind !== void 0 && subject.entityName !== void 0) return `${subject.entityKind} "${ddlSchema}.${subject.entityName}"`; | ||
| return `namespace "${ddlSchema}"`; | ||
| } | ||
| function postgresSuppressionSummary(subjectLabel, subject, policy) { | ||
| const namespace = subject?.namespaceId ?? "unknown"; | ||
| const declared = subject?.explicitNodeControlPolicy; | ||
| if (policy === "external" && declared === "managed") return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control 'external' but declared 'managed'`; | ||
| return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control '${policy}'${declared ? ` but declared '${declared}'` : ""}`; | ||
| } | ||
| /** | ||
| * Render one family {@link SuppressionRecord} into a target `SqlPlannerConflict`. | ||
| * The family decides *that* a subject is suppressed and hands over the raw | ||
| * coordinate + policy; the label, message, and location are rendered here, | ||
| * driven entirely by the subject's own `(entityKind, entityName)` coordinate | ||
| * — no target-owned table-vs-enum vocabulary. | ||
| */ | ||
| function renderPostgresSuppression(record, contract) { | ||
| const subject = record.subject; | ||
| return { | ||
| kind: "controlPolicySuppressedCall", | ||
| summary: postgresSuppressionSummary(formatSuppressionSubjectLabel(subject, contract), subject, record.policy), | ||
| location: { | ||
| ...ifDefined("namespaceId", subject?.namespaceId), | ||
| ...ifDefined("entityKind", subject?.entityKind), | ||
| ...ifDefined("entityName", subject?.entityName), | ||
| ...ifDefined("column", subject?.column) | ||
| }, | ||
| meta: { | ||
| controlPolicy: record.policy, | ||
| ...ifDefined("factoryName", record.factoryName), | ||
| ...ifDefined("declaredControlPolicy", subject?.explicitNodeControlPolicy) | ||
| } | ||
| }; | ||
| } | ||
| function resolvePostgresCallControlPolicySubject(call, contract) { | ||
| const callFields = postgresCallFields(call); | ||
| const createsNewObject = createsNewTopLevelObject(call); | ||
| if (call.factoryName === "createSchema" && callFields.schemaName) return { | ||
| namespaceId: resolveNamespaceIdForDdlSchema(contract, callFields.schemaName), | ||
| createsNewObject | ||
| }; | ||
| if (callFields.tableName) { | ||
| const namespaceId = resolveNamespaceIdForTable(contract, callFields.tableName, callFields.schemaName); | ||
| const tableControlPolicy = entityAt(contract.storage, { | ||
| namespaceId, | ||
| entityKind: "table", | ||
| entityName: callFields.tableName | ||
| })?.control; | ||
| return { | ||
| namespaceId, | ||
| entityKind: "table", | ||
| entityName: callFields.tableName, | ||
| ...ifDefined("column", callFields.columnName), | ||
| ...ifDefined("explicitNodeControlPolicy", tableControlPolicy), | ||
| createsNewObject | ||
| }; | ||
| } | ||
| if (callFields.schemaName) return { | ||
| namespaceId: resolveNamespaceIdForDdlSchema(contract, callFields.schemaName), | ||
| createsNewObject | ||
| }; | ||
| } | ||
| /** | ||
| * Node kinds whose *absence* is the creation of a whole, top-level Postgres | ||
| * object: a namespace, a table, or a native enum. Used by | ||
| * {@link resolvePostgresNodeIssueCreationFactoryName} to decide whether a | ||
| * `tolerated` subject permits the issue to flow into the planner | ||
| * (create-if-absent) and to seed the suppressed-subject warning's | ||
| * `factoryName` when the planner is skipped. RLS policy creation is not | ||
| * listed here — policy issues never reach this issue-based partition (they | ||
| * are routed to `planPostgresSchemaDiff` and gated via the call-based | ||
| * {@link resolvePostgresCallControlPolicySubject} instead). | ||
| */ | ||
| const POSTGRES_NODE_CREATION_FACTORY = Object.freeze({ | ||
| [PostgresSchemaNodeKind.namespace]: "createSchema", | ||
| [PostgresSchemaNodeKind.table]: "createTable", | ||
| [PostgresSchemaNodeKind.nativeEnum]: "createNativeEnumType" | ||
| }); | ||
| /** | ||
| * A table `not-equal` issue whose `rlsEnabled` flips OFF→ON (expected on, | ||
| * actual off) is enablement toward `ENABLE ROW LEVEL SECURITY`. It is | ||
| * creation-class on the node side because `isEnablementCreationIssue` and | ||
| * {@link resolvePostgresNodeIssueCreationFactoryName} treat this OFF→ON delta | ||
| * as a creation: enabling RLS establishes the fail-closed guard the declared | ||
| * policy set attaches to, the same grant `tolerated` extends to creating the | ||
| * policies themselves. The opposite direction (`DISABLE`) is a modification | ||
| * and stays managed-only. Keying on the actual delta (not just the expected | ||
| * bit) keeps this correct if a second table attribute ever joins | ||
| * `isEqualTo`: a not-equal with no `rlsEnabled` delta is not enablement, so | ||
| * it is not admitted as creation-class here. | ||
| */ | ||
| function isEnablementCreationIssue(issue) { | ||
| if (issueOutcome(issue) !== "not-equal") return false; | ||
| const { expected, actual } = issue; | ||
| return expected !== void 0 && actual !== void 0 && PostgresTableSchemaNode.is(expected) && PostgresTableSchemaNode.is(actual) && expected.rlsEnabled === true && actual.rlsEnabled === false; | ||
| } | ||
| function resolvePostgresNodeIssueCreationFactoryName(issue) { | ||
| if (isEnablementCreationIssue(issue)) return "enableRowLevelSecurity"; | ||
| if (issueOutcome(issue) !== "not-found") return void 0; | ||
| const node = issue.expected ?? issue.actual; | ||
| if (node === void 0) return void 0; | ||
| return POSTGRES_NODE_CREATION_FACTORY[node.nodeKind]; | ||
| } | ||
| /** | ||
| * Resolves the control-policy subject for a node-typed {@link SchemaDiffIssue} | ||
| * (the issue-side mirror of `resolvePostgresCallControlPolicySubject`). | ||
| * Storage entities resolve off their node coordinate; a sub-entity issue | ||
| * resolves off the enclosing table read from the issue path; an unclaimed | ||
| * extra falls back to the unbound coordinate. A role always resolves | ||
| * `'external'` — roles are referenced, never owned. | ||
| */ | ||
| function resolvePostgresNodeIssueControlPolicySubject(issue, contract) { | ||
| const node = issue.expected ?? issue.actual; | ||
| if (node === void 0) return void 0; | ||
| if (node.nodeKind === PostgresSchemaNodeKind.namespace) return { | ||
| namespaceId: resolveNamespaceIdForDdlSchema(contract, blindCast(node).schemaName), | ||
| createsNewObject: issueOutcome(issue) === "not-found" | ||
| }; | ||
| if (node.nodeKind === PostgresSchemaNodeKind.role) { | ||
| const roleName = issue.path[1]; | ||
| return { | ||
| namespaceId: UNBOUND_NAMESPACE_ID, | ||
| explicitNodeControlPolicy: (roleName === void 0 ? void 0 : entityAt(contract.storage, { | ||
| namespaceId: UNBOUND_NAMESPACE_ID, | ||
| entityKind: "role", | ||
| entityName: roleName | ||
| }))?.control ?? "external", | ||
| createsNewObject: false | ||
| }; | ||
| } | ||
| const coordinate = postgresNodeStorageCoordinate(node); | ||
| if (coordinate !== void 0) { | ||
| const namespaceId = resolveNamespaceIdForEntity(contract, coordinate, issue.path[1]); | ||
| const entityControl = entityAt(contract.storage, { | ||
| namespaceId, | ||
| ...coordinate | ||
| })?.control; | ||
| return { | ||
| namespaceId, | ||
| ...coordinate, | ||
| ...ifDefined("column", issueColumnName(issue)), | ||
| ...ifDefined("explicitNodeControlPolicy", entityControl), | ||
| createsNewObject: resolvePostgresNodeIssueCreationFactoryName(issue) !== void 0 | ||
| }; | ||
| } | ||
| const tableName = issue.path[2]; | ||
| if (tableName === void 0) return void 0; | ||
| const ddlSchemaName = issue.path[1]; | ||
| const namespaceId = resolveNamespaceIdForTable(contract, tableName, ddlSchemaName); | ||
| const table = entityAt(contract.storage, { | ||
| namespaceId, | ||
| entityKind: "table", | ||
| entityName: tableName | ||
| }); | ||
| return { | ||
| namespaceId, | ||
| entityKind: "table", | ||
| entityName: tableName, | ||
| ...ifDefined("column", issueColumnName(issue)), | ||
| ...ifDefined("explicitNodeControlPolicy", table?.control), | ||
| createsNewObject: resolvePostgresNodeIssueCreationFactoryName(issue) !== void 0 | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { resolvePostgresNodeIssueCreationFactoryName as a, issueSchemaName as c, postgresNodeStorageCoordinate as d, resolvePostgresNodeIssueControlPolicySubject as i, planIssues as l, resolveNamespaceIdForDdlSchema as n, coalesceSubtreeIssues as o, resolvePostgresCallControlPolicySubject as r, issueNode as s, renderPostgresSuppression as t, postgresPlannerStrategies as u }; | ||
| //# sourceMappingURL=control-policy-BrcqeM82.mjs.map |
Sorry, the diff of this file is too big to display
| import { n as postgresResolveDefault } from "./default-normalizer-B9ZUiyUE.mjs"; | ||
| import { r as isPostgresSchema } from "./postgres-schema-CewLYHB7.mjs"; | ||
| import { a as postgresDiffSubjectGranularity, n as PostgresNativeEnumSchemaNode, r as PostgresSchemaNodeKind, t as PostgresTableSchemaNode } from "./postgres-table-schema-node-D6LvInCe.mjs"; | ||
| import { i as PostgresDatabaseSchemaNode, n as PostgresPolicySchemaNode, r as PostgresNamespaceSchemaNode, t as PostgresRoleSchemaNode } from "./postgres-role-schema-node-bg32e7I-.mjs"; | ||
| import { t as resolveDdlSchemaForNamespaceStorage } from "./resolve-ddl-schema-D0wi_P9Q.mjs"; | ||
| import { i as resolvePostgresNodeIssueControlPolicySubject } from "./control-policy-BrcqeM82.mjs"; | ||
| import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { buildNativeTypeExpander, contractNamespaceToSchemaIR } from "@prisma-next/family-sql/control"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| import { PrimaryKey, RelationalSchemaNodeKind, SqlForeignKeyIR, SqlIndexIR, SqlUniqueIR } from "@prisma-next/sql-schema-ir/types"; | ||
| import { classifyDiffSubjectGranularity } from "@prisma-next/family-sql/diff"; | ||
| import { diffSchemas, issueOutcome } from "@prisma-next/framework-components/control"; | ||
| //#region src/core/migrations/contract-to-postgres-database-schema-node.ts | ||
| /** The database root's fixed sentinel id (`PostgresDatabaseSchemaNode#id`). */ | ||
| function databaseStep() { | ||
| return { | ||
| nodeKind: PostgresSchemaNodeKind.database, | ||
| id: "database" | ||
| }; | ||
| } | ||
| function tableDependsOn(namespaceId, tableName) { | ||
| return [ | ||
| databaseStep(), | ||
| { | ||
| nodeKind: PostgresSchemaNodeKind.namespace, | ||
| id: namespaceId | ||
| }, | ||
| { | ||
| nodeKind: PostgresSchemaNodeKind.table, | ||
| id: tableName | ||
| } | ||
| ]; | ||
| } | ||
| function roleDependsOn(role) { | ||
| return [databaseStep(), { | ||
| nodeKind: PostgresSchemaNodeKind.role, | ||
| id: role | ||
| }]; | ||
| } | ||
| /** | ||
| * The chains from a table-child object (foreign key, index, unique, primary | ||
| * key) to each of the own columns it is built on, in the Postgres tree. | ||
| * Dropping a covered column auto-drops the object, so the object's drop must | ||
| * precede the column's; the graph derives that direction from these edges. | ||
| */ | ||
| function columnDependsOn(namespaceId, tableName, columns) { | ||
| return columns.map((column) => [...tableDependsOn(namespaceId, tableName), { | ||
| nodeKind: RelationalSchemaNodeKind.column, | ||
| id: `column:${column}` | ||
| }]); | ||
| } | ||
| function toPolicyNode(policy, namespaceId) { | ||
| return new PostgresPolicySchemaNode({ | ||
| name: policy.name, | ||
| prefix: policy.prefix, | ||
| tableName: policy.tableName, | ||
| namespaceId, | ||
| operation: policy.operation, | ||
| roles: [...policy.roles], | ||
| ...ifDefined("using", policy.using), | ||
| ...ifDefined("withCheck", policy.withCheck), | ||
| permissive: policy.permissive, | ||
| dependsOn: [tableDependsOn(namespaceId, policy.tableName), ...policy.roles.map(roleDependsOn)] | ||
| }); | ||
| } | ||
| /** | ||
| * Projects a Postgres contract into the expected schema-diff tree: a | ||
| * `PostgresDatabaseSchemaNode` root holding one `PostgresNamespaceSchemaNode` | ||
| * per Postgres namespace, each holding its `PostgresTableSchemaNode`s with | ||
| * their `PostgresPolicySchemaNode`s, plus the database roles on the root. | ||
| * | ||
| * Not a duplicate of the family's `contractToSchemaIR`: that builds a flat, | ||
| * single `{ tables }` map (and throws on cross-namespace name collisions, with | ||
| * no RLS/role concept) for SQLite's single-schema world. This is the | ||
| * Postgres-specific *tree* shape — multi-schema, RLS-policy-aware, role-aware. | ||
| * It reuses the family's per-namespace table conversion (`contractNamespaceToSchemaIR`) | ||
| * for column/FK/index building and only adds the Postgres tree/policy/role shape. | ||
| * | ||
| * Tables are grouped by their owning namespace (resolved DDL schema name) so | ||
| * the tree mirrors Postgres's object hierarchy. The DDL schema name is | ||
| * resolved once per namespace. | ||
| * | ||
| * A policy that references a table absent from its namespace is a malformed | ||
| * contract — the loop throws rather than fabricating a stub table. | ||
| */ | ||
| function contractToPostgresDatabaseSchemaNode(contract, options) { | ||
| if (contract === null) return new PostgresDatabaseSchemaNode({ | ||
| namespaces: {}, | ||
| roles: [], | ||
| existingSchemas: [], | ||
| pgVersion: "" | ||
| }); | ||
| const namespaces = {}; | ||
| const roles = []; | ||
| const ownedSchemas = []; | ||
| for (const ns of Object.values(contract.storage.namespaces)) { | ||
| if (!isPostgresSchema(ns)) continue; | ||
| for (const role of Object.values(ns.role)) roles.push(new PostgresRoleSchemaNode({ | ||
| name: role.name, | ||
| namespaceId: role.namespaceId | ||
| })); | ||
| if (ns.id === UNBOUND_NAMESPACE_ID) { | ||
| if (!Object.entries(ns.entries).some(([entriesKey, slot]) => entriesKey !== "role" && Object.keys(slot).length > 0)) continue; | ||
| } | ||
| const ddlSchema = resolveDdlSchemaForNamespaceStorage(contract.storage, ns.id); | ||
| ownedSchemas.push(ddlSchema); | ||
| const sqlTables = contractNamespaceToSchemaIR(contract.storage, ns.id, options).tables; | ||
| const policiesByTable = /* @__PURE__ */ new Map(); | ||
| for (const policy of Object.values(ns.policy)) { | ||
| const list = policiesByTable.get(policy.tableName) ?? []; | ||
| list.push(toPolicyNode(policy, ddlSchema)); | ||
| policiesByTable.set(policy.tableName, list); | ||
| } | ||
| const tables = {}; | ||
| for (const tableName of Object.keys(ns.table)) { | ||
| const sqlTable = sqlTables[tableName]; | ||
| if (sqlTable === void 0) continue; | ||
| const foreignKeys = sqlTable.foreignKeys.map((fk) => { | ||
| const resolvedReferencedNamespace = resolveDdlSchemaForNamespaceStorage(contract.storage, fk.referencedSchema ?? UNBOUND_NAMESPACE_ID); | ||
| return new SqlForeignKeyIR({ | ||
| columns: fk.columns, | ||
| referencedTable: fk.referencedTable, | ||
| referencedColumns: fk.referencedColumns, | ||
| referencedSchema: fk.referencedSchema ?? UNBOUND_NAMESPACE_ID, | ||
| ...ifDefined("name", fk.name), | ||
| ...ifDefined("onDelete", fk.onDelete), | ||
| ...ifDefined("onUpdate", fk.onUpdate), | ||
| ...ifDefined("annotations", fk.annotations), | ||
| resolvedReferencedNamespace, | ||
| dependsOn: [tableDependsOn(resolvedReferencedNamespace, fk.referencedTable), ...columnDependsOn(ddlSchema, tableName, fk.columns)] | ||
| }); | ||
| }); | ||
| const uniques = sqlTable.uniques.map((u) => new SqlUniqueIR({ | ||
| columns: u.columns, | ||
| ...ifDefined("name", u.name), | ||
| ...ifDefined("annotations", u.annotations), | ||
| dependsOn: columnDependsOn(ddlSchema, tableName, u.columns) | ||
| })); | ||
| const indexes = sqlTable.indexes.map((i) => new SqlIndexIR({ | ||
| columns: i.columns, | ||
| unique: i.unique, | ||
| partial: i.partial, | ||
| name: i.name, | ||
| type: i.type, | ||
| options: i.options, | ||
| annotations: i.annotations, | ||
| dependsOn: columnDependsOn(ddlSchema, tableName, i.columns) | ||
| })); | ||
| const primaryKey = sqlTable.primaryKey !== void 0 ? new PrimaryKey({ | ||
| columns: sqlTable.primaryKey.columns, | ||
| ...ifDefined("name", sqlTable.primaryKey.name), | ||
| dependsOn: columnDependsOn(ddlSchema, tableName, sqlTable.primaryKey.columns) | ||
| }) : void 0; | ||
| tables[tableName] = new PostgresTableSchemaNode({ | ||
| name: sqlTable.name, | ||
| columns: sqlTable.columns, | ||
| foreignKeys, | ||
| uniques, | ||
| indexes, | ||
| ...ifDefined("primaryKey", primaryKey), | ||
| ...ifDefined("annotations", sqlTable.annotations), | ||
| ...ifDefined("checks", sqlTable.checks), | ||
| policies: policiesByTable.get(tableName) ?? [], | ||
| rlsEnabled: Object.hasOwn(ns.rls, tableName) | ||
| }); | ||
| } | ||
| for (const [tableName, tablePolicies] of policiesByTable) { | ||
| if (!(tableName in tables)) { | ||
| const policyName = tablePolicies[0]?.name ?? "(unknown)"; | ||
| throw new Error(`contract-to-postgres-database-schema-node: policy "${policyName}" references table "${tableName}" not present in namespace "${ddlSchema}"`); | ||
| } | ||
| if (!Object.hasOwn(ns.rls, tableName)) { | ||
| const policyPrefix = tablePolicies[0]?.prefix ?? "(unknown)"; | ||
| throw new Error(`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.`); | ||
| } | ||
| } | ||
| namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({ | ||
| schemaName: ddlSchema, | ||
| tables, | ||
| nativeEnums: Object.values(ns.entries.native_enum ?? {}).map((entity) => new PostgresNativeEnumSchemaNode({ | ||
| typeName: entity.typeName, | ||
| namespaceId: ddlSchema, | ||
| members: entity.members, | ||
| ...ifDefined("control", entity.control) | ||
| })) | ||
| }); | ||
| } | ||
| return new PostgresDatabaseSchemaNode({ | ||
| namespaces, | ||
| roles, | ||
| existingSchemas: ownedSchemas, | ||
| pgVersion: "" | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/diff-database-schema.ts | ||
| /** | ||
| * Whether a diff issue's subject node is cluster-scoped — it carries its own | ||
| * `namespaceId` field (only role and policy diff nodes do) and that | ||
| * coordinate is the unbound sentinel, meaning the node is not owned by any | ||
| * schema/namespace. A role node is always cluster-scoped (roles are | ||
| * cluster-level objects); a policy node's `namespaceId` is always its | ||
| * table's resolved DDL schema, never the sentinel. Namespace-ownership | ||
| * scoping (which compares `issue.path[1]` against the set of DDL schemas | ||
| * the contract owns) makes no sense for a cluster-scoped subject — its path | ||
| * segment at that index is the object's own name, not a schema name — so | ||
| * such an issue bypasses that scoping entirely. | ||
| */ | ||
| function isClusterScopedIssue(issue) { | ||
| const node = issue.expected ?? issue.actual; | ||
| return node !== void 0 && nodeNamespaceId(node) === UNBOUND_NAMESPACE_ID; | ||
| } | ||
| function nodeNamespaceId(node) { | ||
| if (!Object.hasOwn(node, "namespaceId")) return void 0; | ||
| return blindCast(node).namespaceId; | ||
| } | ||
| function ownedSchemaNames(expected) { | ||
| const policyNamespaces = Object.values(expected.namespaces).flatMap((ns) => Object.values(ns.tables).flatMap((t) => t.policies.map((p) => p.namespaceId))); | ||
| return /* @__PURE__ */ new Set([...policyNamespaces, ...expected.existingSchemas]); | ||
| } | ||
| /** | ||
| * Drops contract namespaces that declare no tables from the verdict-diff | ||
| * expected tree and the relational owned-schema set. The legacy relational | ||
| * walk skipped a table-less namespace (e.g. an enums-only schema) before | ||
| * pairing, so neither its DDL schema's absence nor that schema's live | ||
| * relational contents ever reached the verdict — the pruned tree | ||
| * reproduces that. The prune loses no expected policies: policies attach | ||
| * to tables, so a table-less namespace carries none (the projection | ||
| * throws on a policy referencing an absent table). Live policies in a | ||
| * pruned schema remain governed via the full owned set (see | ||
| * {@link diffPostgresSchema}). | ||
| */ | ||
| function pruneTableLessNamespaces(expected) { | ||
| const namespaces = Object.fromEntries(Object.entries(expected.namespaces).filter(([, ns]) => Object.keys(ns.tables).length > 0)); | ||
| return new PostgresDatabaseSchemaNode({ | ||
| namespaces, | ||
| roles: [...expected.roles], | ||
| existingSchemas: expected.existingSchemas.filter((s) => namespaces[s] !== void 0), | ||
| pgVersion: expected.pgVersion | ||
| }); | ||
| } | ||
| /** | ||
| * Resolves a verdict-diff issue's subject's declared control policy directly | ||
| * from the contract, by delegating to the same node-typed resolver | ||
| * ({@link resolvePostgresNodeIssueControlPolicySubject}) the planner uses to | ||
| * gate DDL calls. `undefined` when the issue resolves to no contract subject. | ||
| * | ||
| * A role issue resolves through that same resolver to `external` | ||
| * unconditionally (see its role branch), regardless of the contract's own | ||
| * default policy: a role is referenced by the contract but not owned, and | ||
| * `external`'s existing semantics — a missing declared subject still fails, | ||
| * every extra is suppressed — are exactly the wanted asymmetric grading for | ||
| * a cluster object the framework does not own. | ||
| */ | ||
| function resolveControlPolicy(issue, contract) { | ||
| return resolvePostgresNodeIssueControlPolicySubject(blindCast(issue), contract)?.explicitNodeControlPolicy; | ||
| } | ||
| /** | ||
| * The Postgres full-tree node diff for the family verify verdict: derive | ||
| * the expected tree (resolved leaf values, expander threaded, FK schemas | ||
| * resolved, table-less namespaces pruned), run the generic | ||
| * differ over the trees as derived, and scope out `not-expected` findings under namespaces the | ||
| * contract does not own. Ownership scoping bypasses cluster-scoped subjects | ||
| * (roles today), mirroring the legacy decomposition: relational extras check | ||
| * the PRUNED owned set (the legacy | ||
| * per-namespace walk never visited a table-less namespace, so its live | ||
| * relational contents are invisible), while `structural` extras (RLS | ||
| * policies) check the FULL owned set (the legacy policy diff governed | ||
| * every contract schema regardless of tables — RLS governance does not | ||
| * shrink because a namespace declares no tables). The codec `verifyType` | ||
| * hooks run once per contract namespace with tables against that | ||
| * namespace's paired actual node (the hooks read namespace-scoped state | ||
| * such as `nativeEnums`). | ||
| */ | ||
| function diffPostgresSchema(input) { | ||
| const postgresContract = blindCast(input.contract); | ||
| PostgresDatabaseSchemaNode.assert(input.schema); | ||
| const actual = input.schema; | ||
| const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, { | ||
| annotationNamespace: "pg", | ||
| ...ifDefined("expandNativeType", buildNativeTypeExpander(input.frameworkComponents)), | ||
| resolveDefault: postgresResolveDefault | ||
| }); | ||
| const expected = pruneTableLessNamespaces(fullExpected); | ||
| const relationalOwned = ownedSchemaNames(expected); | ||
| const structuralOwned = ownedSchemaNames(fullExpected); | ||
| return { | ||
| issues: diffSchemas(expected, actual).filter((issue) => { | ||
| if (issueOutcome(issue) !== "not-expected") return true; | ||
| if (isClusterScopedIssue(issue)) return true; | ||
| const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity); | ||
| const namespaceSegment = issue.path[1]; | ||
| if (namespaceSegment === void 0) return true; | ||
| return (granularity === "structural" ? structuralOwned : relationalOwned).has(namespaceSegment); | ||
| }), | ||
| resolveControlPolicy: (issue) => resolveControlPolicy(issue, postgresContract), | ||
| namespacePairs: Object.values(expected.namespaces).map((ns) => ({ actual: actual.namespaces[ns.schemaName] })) | ||
| }; | ||
| } | ||
| /** | ||
| * Adds an empty namespace node to the actual tree for every expected namespace | ||
| * absent from it. The relational plan diff pairs on namespace: a contract | ||
| * namespace whose live schema does not exist yet must surface each of its | ||
| * tables as `not-found` (→ `CREATE TABLE`), NOT as a single namespace | ||
| * `not-found` that subtree-coalescing would collapse (leaving `CREATE SCHEMA` | ||
| * with no tables). Padding makes the namespaces pair, so only table/column/ | ||
| * policy drift surfaces; `CREATE SCHEMA` comes separately from the synthesized | ||
| * namespace-presence stitch (`verifyPostgresNamespacePresence`), never from the | ||
| * tree diff — matching the retired per-namespace-paired relational walk, which | ||
| * paired a missing schema against an empty namespace node. | ||
| */ | ||
| function padActualNamespaces(expected, actual) { | ||
| const namespaces = { ...actual.namespaces }; | ||
| let padded = false; | ||
| for (const schemaName of Object.keys(expected.namespaces)) if (namespaces[schemaName] === void 0) { | ||
| namespaces[schemaName] = new PostgresNamespaceSchemaNode({ | ||
| schemaName, | ||
| tables: {} | ||
| }); | ||
| padded = true; | ||
| } | ||
| if (!padded) return actual; | ||
| return new PostgresDatabaseSchemaNode({ | ||
| namespaces, | ||
| roles: [...actual.roles], | ||
| existingSchemas: [...actual.existingSchemas], | ||
| pgVersion: actual.pgVersion | ||
| }); | ||
| } | ||
| /** | ||
| * The Postgres planner's diff input: the SAME tree-building | ||
| * `diffPostgresSchema` uses (expander threaded, FK schemas resolved, | ||
| * table-less namespaces pruned, cluster-scope-aware ownership filter) plus | ||
| * actual namespace padding (so a missing | ||
| * schema's tables surface as `not-found` instead of a swallowed namespace | ||
| * `not-found`). One differ drives both verify and plan; this is the | ||
| * plan-side derivation. The single issue list covers tables / columns / | ||
| * constraints / indexes / defaults AND policies — the caller splits it | ||
| * (relational → `mapNodeIssueToCall`; policy → RLS ops) and stitches in | ||
| * `CREATE SCHEMA` separately. | ||
| */ | ||
| function buildPostgresPlanDiff(input) { | ||
| const postgresContract = blindCast(input.contract); | ||
| PostgresDatabaseSchemaNode.assert(input.actualSchema); | ||
| const actual = input.actualSchema; | ||
| const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, { | ||
| annotationNamespace: "pg", | ||
| ...ifDefined("expandNativeType", buildNativeTypeExpander(input.frameworkComponents)), | ||
| resolveDefault: postgresResolveDefault | ||
| }); | ||
| const expected = pruneTableLessNamespaces(fullExpected); | ||
| const paddedActual = padActualNamespaces(expected, actual); | ||
| const relationalOwned = ownedSchemaNames(expected); | ||
| const structuralOwned = ownedSchemaNames(fullExpected); | ||
| return { | ||
| expected, | ||
| actual: paddedActual, | ||
| issues: blindCast(diffSchemas(expected, paddedActual)).filter((issue) => { | ||
| if (issueOutcome(issue) !== "not-expected") return true; | ||
| if (isClusterScopedIssue(issue)) return true; | ||
| const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity); | ||
| const namespaceSegment = issue.path[1]; | ||
| if (namespaceSegment === void 0) return true; | ||
| return (granularity === "structural" ? structuralOwned : relationalOwned).has(namespaceSegment); | ||
| }) | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { diffPostgresSchema as n, contractToPostgresDatabaseSchemaNode as r, buildPostgresPlanDiff as t }; | ||
| //# sourceMappingURL=diff-database-schema-CK0FM2Z-.mjs.map |
| {"version":3,"file":"diff-database-schema-CK0FM2Z-.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 type { SchemaNodeRef } from '@prisma-next/framework-components/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport {\n PrimaryKey,\n RelationalSchemaNodeKind,\n SqlForeignKeyIR,\n SqlIndexIR,\n SqlUniqueIR,\n} 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 { PostgresNativeEnumSchemaNode } from '../schema-ir/postgres-native-enum-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 { PostgresSchemaNodeKind } from '../schema-ir/schema-node-kinds';\nimport { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';\n\n/** The database root's fixed sentinel id (`PostgresDatabaseSchemaNode#id`). */\nfunction databaseStep(): { readonly nodeKind: string; readonly id: string } {\n return { nodeKind: PostgresSchemaNodeKind.database, id: 'database' };\n}\n\nfunction tableDependsOn(namespaceId: string, tableName: string): SchemaNodeRef {\n return [\n databaseStep(),\n { nodeKind: PostgresSchemaNodeKind.namespace, id: namespaceId },\n { nodeKind: PostgresSchemaNodeKind.table, id: tableName },\n ];\n}\n\nfunction roleDependsOn(role: string): SchemaNodeRef {\n return [databaseStep(), { nodeKind: PostgresSchemaNodeKind.role, id: role }];\n}\n\n/**\n * The chains from a table-child object (foreign key, index, unique, primary\n * key) to each of the own columns it is built on, in the Postgres tree.\n * Dropping a covered column auto-drops the object, so the object's drop must\n * precede the column's; the graph derives that direction from these edges.\n */\nfunction columnDependsOn(\n namespaceId: string,\n tableName: string,\n columns: readonly string[],\n): readonly SchemaNodeRef[] {\n return columns.map((column) => [\n ...tableDependsOn(namespaceId, tableName),\n { nodeKind: RelationalSchemaNodeKind.column, id: `column:${column}` },\n ]);\n}\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 dependsOn: [tableDependsOn(namespaceId, policy.tableName), ...policy.roles.map(roleDependsOn)],\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\n // Role entries are root-level diff subjects: they hoist to the database\n // root from every slot and never count toward whether a namespace\n // materializes a schema node.\n for (const role of Object.values(ns.role)) {\n roles.push(new PostgresRoleSchemaNode({ name: role.name, namespaceId: role.namespaceId }));\n }\n\n // The unbound slot resolves its DDL schema to 'public', so it\n // materializes a schema node exactly when it has non-role content — a\n // late-binding contract keeps today's behavior (the slot carries the\n // tables), while a roles-only unbound slot alongside named namespaces\n // contributes only root roles and no node (which would otherwise be a\n // spurious empty 'public' node, clobbering a real bound 'public'\n // namespace's node keyed by the same resolved schema name).\n if (ns.id === UNBOUND_NAMESPACE_ID) {\n const hasNonRoleContent = Object.entries(ns.entries).some(\n ([entriesKey, slot]) => entriesKey !== 'role' && Object.keys(slot).length > 0,\n );\n if (!hasNonRoleContent) continue;\n }\n\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((fk) => {\n const resolvedReferencedNamespace = resolveDdlSchemaForNamespaceStorage(\n contract.storage,\n fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,\n );\n return 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,\n dependsOn: [\n tableDependsOn(resolvedReferencedNamespace, fk.referencedTable),\n ...columnDependsOn(ddlSchema, tableName, fk.columns),\n ],\n });\n });\n // The family stamped these children's own-column `dependsOn` with the\n // flat (single-schema) chain; the Postgres tree nests them under a\n // namespace, so re-stamp with the multi-schema chain that matches this\n // tree's paths. Every other field is carried through unchanged.\n const uniques = sqlTable.uniques.map(\n (u) =>\n new SqlUniqueIR({\n columns: u.columns,\n ...ifDefined('name', u.name),\n ...ifDefined('annotations', u.annotations),\n dependsOn: columnDependsOn(ddlSchema, tableName, u.columns),\n }),\n );\n const indexes = sqlTable.indexes.map(\n (i) =>\n new SqlIndexIR({\n columns: i.columns,\n unique: i.unique,\n partial: i.partial,\n name: i.name,\n type: i.type,\n options: i.options,\n annotations: i.annotations,\n dependsOn: columnDependsOn(ddlSchema, tableName, i.columns),\n }),\n );\n const primaryKey =\n sqlTable.primaryKey !== undefined\n ? new PrimaryKey({\n columns: sqlTable.primaryKey.columns,\n ...ifDefined('name', sqlTable.primaryKey.name),\n dependsOn: columnDependsOn(ddlSchema, tableName, sqlTable.primaryKey.columns),\n })\n : undefined;\n tables[tableName] = new PostgresTableSchemaNode({\n name: sqlTable.name,\n columns: sqlTable.columns,\n foreignKeys,\n uniques,\n indexes,\n ...ifDefined('primaryKey', 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 const nativeEnums = Object.values(ns.entries.native_enum ?? {}).map(\n (entity) =>\n new PostgresNativeEnumSchemaNode({\n typeName: entity.typeName,\n namespaceId: ddlSchema,\n members: entity.members,\n ...ifDefined('control', entity.control),\n }),\n );\n\n namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({\n schemaName: ddlSchema,\n tables,\n nativeEnums,\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 { DiffableNode, SchemaDiffIssue } from '@prisma-next/framework-components/control';\nimport { diffSchemas, issueOutcome } 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 { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { postgresResolveDefault } from '../default-normalizer';\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\n/**\n * Whether a diff issue's subject node is cluster-scoped — it carries its own\n * `namespaceId` field (only role and policy diff nodes do) and that\n * coordinate is the unbound sentinel, meaning the node is not owned by any\n * schema/namespace. A role node is always cluster-scoped (roles are\n * cluster-level objects); a policy node's `namespaceId` is always its\n * table's resolved DDL schema, never the sentinel. Namespace-ownership\n * scoping (which compares `issue.path[1]` against the set of DDL schemas\n * the contract owns) makes no sense for a cluster-scoped subject — its path\n * segment at that index is the object's own name, not a schema name — so\n * such an issue bypasses that scoping entirely.\n */\nfunction isClusterScopedIssue(issue: SchemaDiffIssue): boolean {\n const node = issue.expected ?? issue.actual;\n return node !== undefined && nodeNamespaceId(node) === UNBOUND_NAMESPACE_ID;\n}\n\nfunction nodeNamespaceId(node: DiffableNode): string | undefined {\n if (!Object.hasOwn(node, 'namespaceId')) return undefined;\n return blindCast<\n { namespaceId: string },\n 'presence of an own namespaceId field was just checked via Object.hasOwn'\n >(node).namespaceId;\n}\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's declared control policy directly\n * 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 subject.\n *\n * A role issue resolves through that same resolver to `external`\n * unconditionally (see its role branch), regardless of the contract's own\n * default policy: a role is referenced by the contract but not owned, and\n * `external`'s existing semantics — a missing declared subject still fails,\n * every extra is suppressed — are exactly the wanted asymmetric grading for\n * a cluster object the framework does not own.\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 scoping bypasses cluster-scoped subjects\n * (roles today), mirroring the legacy decomposition: relational extras check\n * 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 `nativeEnums`).\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 resolveDefault: postgresResolveDefault,\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 (issueOutcome(issue) !== 'not-expected') return true;\n if (isClusterScopedIssue(issue)) return true;\n const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);\n const namespaceSegment = issue.path[1];\n if (namespaceSegment === undefined) return true;\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 });\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, cluster-scope-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, cluster-scope-aware ownership filter) plus\n * 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 resolveDefault: postgresResolveDefault,\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 (issueOutcome(issue) !== 'not-expected') return true;\n if (isClusterScopedIssue(issue)) return true;\n const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);\n const namespaceSegment = issue.path[1];\n if (namespaceSegment === undefined) return true;\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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"} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { t as DEFAULT_NAMESPACE_ID } from "./namespace-ids-xtp_ZY86.mjs"; | ||
| import { g as PostgresRlsPolicy, r as isPostgresSchema } from "./postgres-schema-CewLYHB7.mjs"; | ||
| import { n as parseRlsPolicyWireName } from "./wire-name-DbQ9-2RC.mjs"; | ||
| import { i as PostgresDatabaseSchemaNode, n as PostgresPolicySchemaNode, r as PostgresNamespaceSchemaNode } from "./postgres-role-schema-node-bg32e7I-.mjs"; | ||
| import { t as resolveDdlSchemaForNamespaceStorage } from "./resolve-ddl-schema-D0wi_P9Q.mjs"; | ||
| import { t as buildPostgresPlanDiff } from "./diff-database-schema-CK0FM2Z-.mjs"; | ||
| import { a as resolvePostgresNodeIssueCreationFactoryName, c as issueSchemaName, d as postgresNodeStorageCoordinate, i as resolvePostgresNodeIssueControlPolicySubject, l as planIssues, n as resolveNamespaceIdForDdlSchema, o as coalesceSubtreeIssues, r as resolvePostgresCallControlPolicySubject, s as issueNode, t as renderPostgresSuppression, u as postgresPlannerStrategies } from "./control-policy-BrcqeM82.mjs"; | ||
| import { T as DropPostgresRlsPolicyCall, k as RenamePostgresRlsPolicyCall, p as CreatePostgresRlsPolicyCall } from "./op-factory-call-PBYfcv8h.mjs"; | ||
| import { t as TypeScriptRenderablePostgresMigration } from "./planner-produced-postgres-migration-PIgjJYrY.mjs"; | ||
| import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure } from "@prisma-next/family-sql/control"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| import { issueOutcome } from "@prisma-next/framework-components/control"; | ||
| //#region src/core/migrations/verify-postgres-namespaces.ts | ||
| /** | ||
| * Resolves the live-database schema name for a given namespace | ||
| * coordinate. Mirrors `resolveDdlSchemaForNamespace` in | ||
| * `planner-strategies.ts` so the verifier's projection and the | ||
| * planner's projection always agree — Postgres-aware namespaces (the | ||
| * production path) dispatch to `ddlSchemaName(storage)`, and bare | ||
| * object payloads (used by some tests) fall back to the coordinate | ||
| * itself. | ||
| */ | ||
| function resolveDdlSchemaName(storage, namespaceId) { | ||
| const namespace = storage.namespaces[namespaceId]; | ||
| if (isPostgresSchema(namespace)) return namespace.ddlSchemaName(storage); | ||
| return namespaceId; | ||
| } | ||
| /** | ||
| * Reads the introspected list of schema names from the database-root schema | ||
| * node. `existingSchemas` is database-level, so it lives on the | ||
| * `PostgresDatabaseSchemaNode` root — not on the per-schema namespace nodes. | ||
| * | ||
| * Defaults to the always-present `public` schema when the node is not the | ||
| * database root — a fresh Postgres database always carries `public` (unless an | ||
| * operator dropped it manually), so any verifier path that runs without an | ||
| * enriched introspection still suppresses the redundant `CREATE SCHEMA | ||
| * "public"`. | ||
| * | ||
| * Production introspection (`PostgresControlAdapter.introspect`) is the | ||
| * authoritative source: it queries `pg_namespace` and sets `existingSchemas` | ||
| * on the returned root. Tests that want to assert against a richer initial | ||
| * state construct a `PostgresDatabaseSchemaNode` explicitly. | ||
| */ | ||
| function existingSchemasFromSchema(schema) { | ||
| if (PostgresDatabaseSchemaNode.is(schema)) return schema.existingSchemas; | ||
| return [DEFAULT_NAMESPACE_ID]; | ||
| } | ||
| /** | ||
| * Emits a `postgres-namespace` `not-found` diff issue for every | ||
| * contract-declared Postgres namespace whose live container does not yet | ||
| * exist. The planner prepends these (node-typed, synthesized) to the | ||
| * relational diff issues so a multi-schema plan emits `CREATE SCHEMA` | ||
| * before the tables that need it — a planner-only concern (verify already | ||
| * rejects via the `not-found` table issues a missing schema already | ||
| * produces), so this is not part of the shared diff. | ||
| * | ||
| * A namespace's live container is the schema returned by its | ||
| * polymorphic `ddlSchemaName(storage)` method — named schemas resolve | ||
| * to their own id; the unbound singleton returns `UNBOUND_NAMESPACE_ID` | ||
| * and is skipped explicitly (late-bound namespaces have no fixed DDL | ||
| * schema). Issues are emitted only when the resolved name is a real, | ||
| * creatable schema (not the unbound sentinel) and is missing from the | ||
| * introspected list. `public` is suppressed implicitly because the | ||
| * introspection (or its sensible default) always carries it. | ||
| * | ||
| * Each emitted issue's path is `['database', ddlName]` — an ancestor of | ||
| * every table path under that schema, so it must never be run through | ||
| * `coalesceSubtreeIssues` alongside the table diff (it would swallow the | ||
| * table-level `not-found` issues that drive `CREATE TABLE`); the planner | ||
| * adds these AFTER coalescing the relational issues, and they are not | ||
| * subject to sibling-space ownership scoping (mirrors the retired | ||
| * coordinate walk, which prepended namespace issues after that filter). | ||
| */ | ||
| function verifyPostgresNamespacePresence(input) { | ||
| const { contract, schema } = input; | ||
| const existing = new Set(existingSchemasFromSchema(schema)); | ||
| const issues = []; | ||
| const namespaceIds = Object.keys(contract.storage.namespaces).sort(); | ||
| for (const namespaceId of namespaceIds) { | ||
| if (namespaceId === UNBOUND_NAMESPACE_ID) continue; | ||
| const ddlName = resolveDdlSchemaName(contract.storage, namespaceId); | ||
| if (ddlName === UNBOUND_NAMESPACE_ID) continue; | ||
| if (existing.has(ddlName)) continue; | ||
| const namespace = new PostgresNamespaceSchemaNode({ | ||
| schemaName: ddlName, | ||
| tables: {} | ||
| }); | ||
| issues.push({ | ||
| path: ["database", ddlName], | ||
| expected: namespace | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/planner.ts | ||
| function createPostgresMigrationPlanner(lowerer) { | ||
| return new PostgresMigrationPlanner(lowerer); | ||
| } | ||
| /** | ||
| * Postgres migration planner — a thin wrapper over `planIssues`. | ||
| * | ||
| * `plan()` diffs the target contract against the live schema via the one | ||
| * differ (`buildPostgresPlanDiff`, producing node-typed `SchemaDiffIssue[]`) | ||
| * and delegates to `planIssues` with the unified `postgresPlannerStrategies` | ||
| * list: NOT-NULL backfill, type-change, nullable-tightening, codec-hook | ||
| * storage types, component-declared dependency installs, and | ||
| * shared-temp-default / empty-table-guarded NOT-NULL add-column. The same | ||
| * strategy list runs for `migration plan`, `db update`, and `db init`; | ||
| * behavior diverges purely on `policy.allowedOperationClasses` (the | ||
| * data-safe strategies short-circuit when `'data'` is excluded). The issue | ||
| * planner applies operation-class policy gates and emits a single | ||
| * `PostgresOpFactoryCall[]` that drives both the runtime-ops view (via | ||
| * `renderOps`) and the `renderTypeScript()` authoring surface. RLS policy | ||
| * drift (the structural half of the same one-differ tree) is handled | ||
| * separately via `planPostgresSchemaDiff`. | ||
| */ | ||
| var PostgresMigrationPlanner = class { | ||
| #lowerer; | ||
| constructor(lowerer) { | ||
| this.#lowerer = lowerer; | ||
| } | ||
| plan(options) { | ||
| return this.planSql(options); | ||
| } | ||
| emptyMigration(context, spaceId) { | ||
| return new TypeScriptRenderablePostgresMigration([], { | ||
| from: context.fromHash, | ||
| to: context.toHash | ||
| }, spaceId, context.snapshotsImportPath, this.#lowerer); | ||
| } | ||
| planSql(options) { | ||
| const schemaName = options.schemaName ?? Object.keys(options.contract.storage.namespaces).find((id) => id !== UNBOUND_NAMESPACE_ID) ?? UNBOUND_NAMESPACE_ID; | ||
| const policyResult = this.ensureAdditivePolicy(options.policy); | ||
| if (policyResult) return policyResult; | ||
| PostgresDatabaseSchemaNode.assert(options.schema); | ||
| const { issues: rawIssues } = buildPostgresPlanDiff({ | ||
| contract: options.contract, | ||
| actualSchema: options.schema, | ||
| frameworkComponents: options.frameworkComponents | ||
| }); | ||
| const policyDiffIssues = rawIssues.filter((issue) => isPolicyDiffIssue(issue)); | ||
| const relationalDiffIssues = rawIssues.filter((issue) => !isPolicyDiffIssue(issue)); | ||
| const strict = options.policy.allowedOperationClasses.includes("widening") || options.policy.allowedOperationClasses.includes("destructive"); | ||
| const owned = retainUnownedExtras(coalesceSubtreeIssues(relationalDiffIssues), options.ownership, options.contract); | ||
| const gated = strict ? owned : owned.filter((issue) => issueOutcome(issue) !== "not-expected"); | ||
| const schemaIssues = [...verifyPostgresNamespacePresence({ | ||
| contract: options.contract, | ||
| schema: options.schema | ||
| }), ...gated]; | ||
| const codecHooks = extractCodecControlHooks(options.frameworkComponents); | ||
| const storageTypes = options.contract.storage.types ?? {}; | ||
| const relationalSchema = relationalNamespaceNode(options.schema, schemaName); | ||
| const issuePartition = partitionIssuesByControlPolicy({ | ||
| issues: schemaIssues, | ||
| contract: options.contract, | ||
| resolveControlPolicySubject: (issue) => resolvePostgresNodeIssueControlPolicySubject(issue, options.contract), | ||
| resolveCreationFactoryName: resolvePostgresNodeIssueCreationFactoryName | ||
| }); | ||
| const result = planIssues({ | ||
| issues: issuePartition.plannable, | ||
| toContract: options.contract, | ||
| fromContract: options.fromContract, | ||
| schemaName, | ||
| codecHooks, | ||
| storageTypes, | ||
| ...ifDefined("schema", relationalSchema), | ||
| policy: options.policy, | ||
| frameworkComponents: options.frameworkComponents, | ||
| strategies: postgresPlannerStrategies | ||
| }); | ||
| if (!result.ok) return plannerFailure(result.failure); | ||
| const schemaDiffPartition = partitionCallsByControlPolicy({ | ||
| calls: this.planPostgresSchemaDiff(options, policyDiffIssues), | ||
| contract: options.contract, | ||
| resolveControlPolicySubject: (call) => resolvePostgresCallControlPolicySubject(call, options.contract), | ||
| resolveFactoryName: (call) => call.factoryName | ||
| }); | ||
| const fieldEventPartition = partitionCallsByControlPolicy({ | ||
| calls: blindCast(planFieldEventOperations({ | ||
| priorContract: options.fromContract, | ||
| newContract: options.contract, | ||
| codecHooks | ||
| })), | ||
| contract: options.contract, | ||
| resolveControlPolicySubject: (call) => resolvePostgresCallControlPolicySubject(call, options.contract), | ||
| resolveFactoryName: (call) => call.factoryName | ||
| }); | ||
| const calls = [ | ||
| ...result.value.calls, | ||
| ...schemaDiffPartition.kept, | ||
| ...fieldEventPartition.kept | ||
| ]; | ||
| const warnings = [ | ||
| ...issuePartition.suppressions, | ||
| ...schemaDiffPartition.suppressions, | ||
| ...fieldEventPartition.suppressions | ||
| ].map((record) => renderPostgresSuppression(record, options.contract)); | ||
| return Object.freeze({ | ||
| kind: "success", | ||
| plan: new TypeScriptRenderablePostgresMigration(calls, { | ||
| from: options.fromContract?.storage.storageHash ?? null, | ||
| to: options.contract.storage.storageHash | ||
| }, options.spaceId, options.snapshotsImportPath, this.#lowerer), | ||
| ...warnings.length > 0 ? { warnings: Object.freeze(warnings) } : {} | ||
| }); | ||
| } | ||
| /** | ||
| * Maps the RLS policy presence findings of the one combined tree diff | ||
| * (`buildPostgresPlanDiff`, already ownership-filtered) into | ||
| * `CREATE POLICY` / `DROP POLICY` / `ALTER POLICY … RENAME TO` ops. It | ||
| * does not re-diff — it consumes exactly the policy-node subset of the | ||
| * shared diff's issues. Enablement is NOT decided here: `ENABLE`/`DISABLE | ||
| * ROW LEVEL SECURITY` derive from the table's marker-driven `rlsEnabled` | ||
| * attribute diff on the relational side. | ||
| * | ||
| * Rename post-pass: a `not-found` and a `not-expected` policy on the SAME | ||
| * table whose wire-name content hashes match but prefixes differ are one | ||
| * prefix-only rename, collapsed into a single non-destructive | ||
| * `RenamePostgresRlsPolicyCall`. Multi-candidate hash groups pair | ||
| * deterministically by sorted wire name; leftovers proceed as | ||
| * create/drop. Unparseable wire names never pair. | ||
| * | ||
| * The pairing runs only when the policy allows `widening` (rename's | ||
| * class). Without it (db-init's additive-only set), pairing degrades | ||
| * deliberately to the additive half: the new name is CREATEd and the old | ||
| * policy survives live until a widening/destructive-allowed plan runs — | ||
| * emitting an ungated widening rename would only fail at the runner's | ||
| * class re-enforcement. | ||
| */ | ||
| planPostgresSchemaDiff(options, filteredDiffIssues) { | ||
| const allowsDestructive = options.policy.allowedOperationClasses.includes("destructive"); | ||
| const allowsWidening = options.policy.allowedOperationClasses.includes("widening"); | ||
| const missing = []; | ||
| const extra = []; | ||
| for (const issue of filteredDiffIssues) if (issueOutcome(issue) === "not-found") { | ||
| const expected = issue.expected; | ||
| PostgresPolicySchemaNode.assert(expected); | ||
| missing.push({ | ||
| node: expected, | ||
| schemaForTable: resolveDdlSchemaForNamespaceStorage(options.contract.storage, expected.namespaceId) | ||
| }); | ||
| } else if (issueOutcome(issue) === "not-expected") { | ||
| const actual = issue.actual; | ||
| PostgresPolicySchemaNode.assert(actual); | ||
| extra.push({ | ||
| node: actual, | ||
| schemaForTable: resolveDdlSchemaForNamespaceStorage(options.contract.storage, actual.namespaceId) | ||
| }); | ||
| } | ||
| const calls = []; | ||
| const renamedExtras = /* @__PURE__ */ new Set(); | ||
| const renamedMissing = /* @__PURE__ */ new Set(); | ||
| const pairingKey = (finding, hash) => JSON.stringify([ | ||
| finding.schemaForTable, | ||
| finding.node.tableName, | ||
| hash | ||
| ]); | ||
| if (allowsWidening) { | ||
| const extrasByGroup = /* @__PURE__ */ new Map(); | ||
| for (const finding of extra) { | ||
| const parsed = parseRlsPolicyWireName(finding.node.name); | ||
| if (parsed === void 0) continue; | ||
| const key = pairingKey(finding, parsed.hash); | ||
| const group = extrasByGroup.get(key) ?? []; | ||
| group.push(finding); | ||
| extrasByGroup.set(key, group); | ||
| } | ||
| for (const group of extrasByGroup.values()) group.sort((a, b) => a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0); | ||
| const sortedMissing = [...missing].sort((a, b) => a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0); | ||
| for (const missingFinding of sortedMissing) { | ||
| const parsed = parseRlsPolicyWireName(missingFinding.node.name); | ||
| if (parsed === void 0) continue; | ||
| const candidate = extrasByGroup.get(pairingKey(missingFinding, parsed.hash))?.shift(); | ||
| if (candidate === void 0) continue; | ||
| renamedExtras.add(candidate); | ||
| renamedMissing.add(missingFinding); | ||
| calls.push(new RenamePostgresRlsPolicyCall(missingFinding.schemaForTable, missingFinding.node.tableName, candidate.node.name, missingFinding.node.name)); | ||
| } | ||
| } | ||
| for (const finding of missing) { | ||
| if (renamedMissing.has(finding)) continue; | ||
| calls.push(new CreatePostgresRlsPolicyCall(finding.schemaForTable, finding.node.tableName, policyNodeToContractPolicy(finding.node))); | ||
| } | ||
| if (allowsDestructive) for (const finding of extra) { | ||
| if (renamedExtras.has(finding)) continue; | ||
| calls.push(new DropPostgresRlsPolicyCall(finding.schemaForTable, finding.node.tableName, finding.node.name)); | ||
| } | ||
| return calls; | ||
| } | ||
| ensureAdditivePolicy(policy) { | ||
| if (!policy.allowedOperationClasses.includes("additive")) return plannerFailure([{ | ||
| kind: "unsupportedOperation", | ||
| summary: "Migration planner requires additive operations be allowed", | ||
| why: "The planner requires the \"additive\" operation class to be allowed in the policy." | ||
| }]); | ||
| return null; | ||
| } | ||
| }; | ||
| /** | ||
| * A diff issue whose node is an RLS policy — the structural half of the one | ||
| * combined tree diff, routed to `planPostgresSchemaDiff` instead of | ||
| * `planIssues`. | ||
| */ | ||
| function isPolicyDiffIssue(issue) { | ||
| const node = issue.expected ?? issue.actual; | ||
| return node !== void 0 && PostgresPolicySchemaNode.is(node); | ||
| } | ||
| /** | ||
| * Drops a `not-expected` issue when it is a whole extra storage entity (a table | ||
| * or a native enum) that some space in the composition owns. Each such node | ||
| * yields its own storage coordinate (see {@link postgresNodeStorageCoordinate}), | ||
| * so `declaresEntity` answers over the whole composition on one uniform | ||
| * coordinate: a positive answer means a sibling owns this entity here — leave | ||
| * it; a negative answer means a genuine orphan — drop it. A node with no storage | ||
| * coordinate (a namespace, or a deeper column/constraint drift on an owned | ||
| * table) is this space's own and is always kept. No oracle ⇒ keep everything. | ||
| */ | ||
| function retainUnownedExtras(issues, ownership, contract) { | ||
| if (ownership === void 0) return issues; | ||
| return issues.filter((issue) => { | ||
| if (issueOutcome(issue) !== "not-expected") return true; | ||
| const node = issueNode(issue); | ||
| if (node === void 0) return true; | ||
| const coordinate = postgresNodeStorageCoordinate(node); | ||
| if (coordinate === void 0) return true; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| if (ddlSchemaName === void 0) return true; | ||
| const namespaceId = resolveNamespaceIdForDdlSchema(contract, ddlSchemaName); | ||
| return !ownership.declaresEntity({ | ||
| namespaceId, | ||
| ...coordinate | ||
| }); | ||
| }); | ||
| } | ||
| /** | ||
| * Returns the one namespace node the relational strategy layer probes for | ||
| * live-table existence and reads codec-hook context off — the namespace | ||
| * matching the planner's resolved schema name, or the first namespace when | ||
| * none matches. `undefined` when the tree has no namespaces, so the strategy | ||
| * context uses its empty-schema default. | ||
| * | ||
| * The relational strategies key tables by bare name, so they can only probe one | ||
| * namespace at a time; probing across every namespace at once is future work. | ||
| * | ||
| * Returns the real `PostgresNamespaceSchemaNode` reference rather than a | ||
| * projection: `storageTypePlanCallStrategy` hands this same value to codec | ||
| * `planTypeOperations` hooks as `schema`, and hooks read the Postgres-specific | ||
| * `nativeEnums` field off it (via `PostgresNamespaceSchemaNode.is`) to decide | ||
| * whether a native enum type already exists — a projection that only copies | ||
| * `tables` would silently drop that signal. `StrategyContext.schema`'s | ||
| * declared type (`SqlSchemaIR`, shared with SQLite's flat shape) doesn't | ||
| * capture this, so the return is `blindCast` the same way `namespaceSchemaNodes` | ||
| * in the family's relational walk already treats a namespace node as a | ||
| * structurally-`SqlSchemaIR`-shaped value. | ||
| */ | ||
| function relationalNamespaceNode(schema, schemaName) { | ||
| const namespaceNodes = Object.values(schema.namespaces); | ||
| const namespaceNode = namespaceNodes.find((node) => node.schemaName === schemaName) ?? namespaceNodes[0]; | ||
| if (namespaceNode === void 0) return void 0; | ||
| return blindCast(namespaceNode); | ||
| } | ||
| /** | ||
| * Rebuilds the `PostgresRlsPolicy` contract entity `CreatePostgresRlsPolicyCall` | ||
| * carries (its `renderTypeScript`/`createRlsPolicy` paths serialize the whole | ||
| * entity, `namespaceId` included). This reconstructs rather than looking the | ||
| * original up in the contract on purpose: the diff node's `namespaceId` is the | ||
| * *resolved DDL schema* (set when the expected tree was built), which is the | ||
| * value the emitted op must carry; the contract-stored entity holds the raw, | ||
| * pre-resolution coordinate, so a lookup would change the migration output. | ||
| */ | ||
| function policyNodeToContractPolicy(node) { | ||
| return new PostgresRlsPolicy({ | ||
| name: node.name, | ||
| prefix: node.prefix, | ||
| tableName: node.tableName, | ||
| namespaceId: node.namespaceId, | ||
| operation: node.operation, | ||
| roles: [...node.roles], | ||
| ...ifDefined("using", node.using), | ||
| ...ifDefined("withCheck", node.withCheck), | ||
| permissive: node.permissive | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { createPostgresMigrationPlanner as t }; | ||
| //# sourceMappingURL=planner-CiXCo6BT.mjs.map |
| {"version":3,"file":"planner-CiXCo6BT.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 });\n issues.push({\n path: ['database', ddlName],\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 { issueOutcome } 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 { postgresNodeStorageCoordinate } from '../schema-ir/node-storage-coordinate';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';\nimport type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';\nimport {\n renderPostgresSuppression,\n resolveNamespaceIdForDdlSchema,\n resolvePostgresCallControlPolicySubject,\n resolvePostgresNodeIssueControlPolicySubject,\n resolvePostgresNodeIssueCreationFactoryName,\n} from './control-policy';\nimport { buildPostgresPlanDiff } from './diff-database-schema';\nimport { coalesceSubtreeIssues, issueNode, issueSchemaName, planIssues } 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 /**\n * POSIX-relative path from the migration package dir to\n * `migrations/snapshots` — see\n * {@link SqlMigrationPlannerPlanOptions.snapshotsImportPath}.\n */\n readonly snapshotsImportPath: string;\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 context.snapshotsImportPath,\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 // Role diff issues resolve to the `external` control policy (see\n // `resolvePostgresNodeIssueControlPolicySubject`'s role branch), so the\n // control-policy partition below suppresses them to zero ops on its own,\n // before `mapNodeIssueToCall` ever sees them — no separate exclusion\n // needed here.\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) => issueOutcome(issue) !== '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 });\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 });\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 });\n const calls = [...result.value.calls, ...schemaDiffPartition.kept, ...fieldEventPartition.kept];\n const warnings: SqlPlannerConflict[] = [\n ...issuePartition.suppressions,\n ...schemaDiffPartition.suppressions,\n ...fieldEventPartition.suppressions,\n ].map((record) => renderPostgresSuppression(record, options.contract));\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 options.snapshotsImportPath,\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 (issueOutcome(issue) === '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 (issueOutcome(issue) === '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 storage entity (a table\n * or a native enum) that some space in the composition owns. Each such node\n * yields its own storage coordinate (see {@link postgresNodeStorageCoordinate}),\n * so `declaresEntity` answers over the whole composition on one uniform\n * coordinate: a positive answer means a sibling owns this entity here — leave\n * it; a negative answer means a genuine orphan — drop it. A node with no storage\n * coordinate (a namespace, or a deeper column/constraint drift on an owned\n * table) is this space's own and is always kept. No oracle ⇒ keep everything.\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 (issueOutcome(issue) !== 'not-expected') return true;\n const node = issueNode(issue);\n if (node === undefined) return true;\n const coordinate = postgresNodeStorageCoordinate(node);\n if (coordinate === undefined) return true;\n const ddlSchemaName = issueSchemaName(issue);\n if (ddlSchemaName === undefined) return true;\n const namespaceId = resolveNamespaceIdForDdlSchema(contract, ddlSchemaName);\n return !ownership.declaresEntity({ namespaceId, ...coordinate });\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 * `nativeEnums` field off it (via `PostgresNamespaceSchemaNode.is`) to decide\n * whether a native enum type already exists — a projection that only copies\n * `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 (+ nativeEnums, 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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"} |
| import { n as isPgEnumParams } from "./codecs-DOgD_C1A.mjs"; | ||
| import { a as quoteQualifiedName, i as quoteIdentifier, n as escapeLiteral } from "./sql-utils-SU4FDvIV.mjs"; | ||
| import { t as resolveColumnTypeMetadata } from "./planner-type-resolution-Bt2f_q-F.mjs"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| //#region src/core/migrations/planner-ddl-builders.ts | ||
| /** | ||
| * Pattern for safe PostgreSQL type names. | ||
| * Allows letters, digits, underscores, spaces (for "double precision", "character varying"), | ||
| * and trailing [] for array types. | ||
| */ | ||
| const SAFE_NATIVE_TYPE_PATTERN = /^[a-zA-Z][a-zA-Z0-9_ ]*(\[\])?$/; | ||
| function assertSafeNativeType(nativeType) { | ||
| if (!SAFE_NATIVE_TYPE_PATTERN.test(nativeType)) throw new Error(`Unsafe native type name in contract: "${nativeType}". Native type names must match /^[a-zA-Z][a-zA-Z0-9_ ]*(\\[\\])?\$/`); | ||
| } | ||
| /** | ||
| * Sanity check against accidental SQL injection from malformed contract files. | ||
| * Rejects semicolons, SQL comment tokens, and dollar-quoting. | ||
| * Not a comprehensive security boundary — the contract is developer-authored. | ||
| */ | ||
| function assertSafeDefaultExpression(expression) { | ||
| if (expression.includes(";") || /--|\/\*|\$\$|\bSELECT\b/i.test(expression)) throw new Error(`Unsafe default expression in contract: "${expression}". Default expressions must not contain semicolons, SQL comment tokens, dollar-quoting, or subqueries.`); | ||
| } | ||
| /** | ||
| * Renders the SQL type for a column in DDL context. | ||
| * | ||
| * @param allowPseudoTypes - When true (default), autoincrement integer columns | ||
| * produce SERIAL/BIGSERIAL/SMALLSERIAL pseudo-types. Set to false for contexts | ||
| * like ALTER COLUMN TYPE where pseudo-types are invalid. | ||
| */ | ||
| function buildColumnTypeSql(column, codecHooks, storageTypes = {}, allowPseudoTypes = true) { | ||
| const resolved = resolveColumnTypeMetadata(column, storageTypes); | ||
| if (allowPseudoTypes) { | ||
| const columnDefault = column.default; | ||
| if (columnDefault?.kind === "function" && columnDefault.expression === "autoincrement()") { | ||
| if (resolved.nativeType === "int4" || resolved.nativeType === "integer") return "SERIAL"; | ||
| if (resolved.nativeType === "int8" || resolved.nativeType === "bigint") return "BIGSERIAL"; | ||
| if (resolved.nativeType === "int2" || resolved.nativeType === "smallint") return "SMALLSERIAL"; | ||
| } | ||
| } | ||
| if (isPgEnumParams(resolved.typeParams)) { | ||
| const quoted = quoteQualifiedName(resolved.nativeType); | ||
| return column.many ? `${quoted}[]` : quoted; | ||
| } | ||
| const expanded = expandParameterizedTypeSql(resolved, codecHooks); | ||
| if (expanded !== null) return column.many ? `${expanded}[]` : expanded; | ||
| if (column.typeRef) { | ||
| const base = quoteQualifiedName(resolved.nativeType); | ||
| return column.many ? `${base}[]` : base; | ||
| } | ||
| assertSafeNativeType(resolved.nativeType); | ||
| return column.many ? `${resolved.nativeType}[]` : resolved.nativeType; | ||
| } | ||
| function expandParameterizedTypeSql(column, codecHooks) { | ||
| if (!column.typeParams || Object.keys(column.typeParams).length === 0) return null; | ||
| if (!column.codecId) throw new Error(`Column declares typeParams for nativeType "${column.nativeType}" but has no codecId. Ensure the column is associated with a codec.`); | ||
| const hooks = codecHooks.get(column.codecId); | ||
| if (!hooks?.expandNativeType) { | ||
| if (hooks?.planTypeOperations) return null; | ||
| throw new Error(`Column declares typeParams for nativeType "${column.nativeType}" but no expandNativeType hook is registered for codecId "${column.codecId}". Ensure the extension providing this codec is included in extensions.`); | ||
| } | ||
| const expanded = hooks.expandNativeType({ | ||
| nativeType: column.nativeType, | ||
| codecId: column.codecId, | ||
| ...ifDefined("typeParams", column.typeParams) | ||
| }); | ||
| return expanded !== column.nativeType ? expanded : null; | ||
| } | ||
| /** Autoincrement columns use SERIAL types, so this returns empty for them. */ | ||
| function buildColumnDefaultSql(columnDefault, column) { | ||
| if (!columnDefault) return ""; | ||
| switch (columnDefault.kind) { | ||
| case "literal": return `DEFAULT ${renderDefaultLiteral(columnDefault.value, column)}`; | ||
| case "function": | ||
| if (columnDefault.expression === "autoincrement()") return ""; | ||
| assertSafeDefaultExpression(columnDefault.expression); | ||
| return `DEFAULT (${columnDefault.expression})`; | ||
| case "sequence": return `DEFAULT nextval('${escapeLiteral(quoteIdentifier(columnDefault.name))}'::regclass)`; | ||
| } | ||
| } | ||
| function renderDefaultLiteral(value, column) { | ||
| const isJsonColumn = column?.nativeType === "json" || column?.nativeType === "jsonb"; | ||
| if (column?.many && Array.isArray(value)) return renderArrayLiteralDefault(value); | ||
| if (value instanceof Date) return `'${escapeLiteral(value.toISOString())}'`; | ||
| if (typeof value === "string") return `'${escapeLiteral(value)}'`; | ||
| if (typeof value === "number" || typeof value === "boolean") return String(value); | ||
| if (value === null) return "NULL"; | ||
| const json = JSON.stringify(value); | ||
| if (isJsonColumn) return `'${escapeLiteral(json)}'::${column.nativeType}`; | ||
| return `'${escapeLiteral(json)}'`; | ||
| } | ||
| function renderArrayLiteralDefault(elements) { | ||
| if (elements.length === 0) return "'{}'"; | ||
| return `ARRAY[${elements.map((el) => renderDefaultLiteral(el)).join(", ")}]`; | ||
| } | ||
| //#endregion | ||
| export { buildColumnTypeSql as n, renderDefaultLiteral as r, buildColumnDefaultSql as t }; | ||
| //# sourceMappingURL=planner-ddl-builders-AIpFmG1i.mjs.map |
| {"version":3,"file":"planner-ddl-builders-AIpFmG1i.mjs","names":[],"sources":["../src/core/migrations/planner-ddl-builders.ts"],"sourcesContent":["import type { CodecControlHooks } from '@prisma-next/family-sql/control';\nimport type { StorageColumn, StorageTypeInstance } from '@prisma-next/sql-contract/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { isPgEnumParams } from '../codecs';\nimport { escapeLiteral, quoteIdentifier, quoteQualifiedName } from '../sql-utils';\nimport type { PostgresColumnDefault } from '../types';\nimport { resolveColumnTypeMetadata } from './planner-type-resolution';\n\n/**\n * Pattern for safe PostgreSQL type names.\n * Allows letters, digits, underscores, spaces (for \"double precision\", \"character varying\"),\n * and trailing [] for array types.\n */\nconst SAFE_NATIVE_TYPE_PATTERN = /^[a-zA-Z][a-zA-Z0-9_ ]*(\\[\\])?$/;\n\nfunction assertSafeNativeType(nativeType: string): void {\n if (!SAFE_NATIVE_TYPE_PATTERN.test(nativeType)) {\n throw new Error(\n `Unsafe native type name in contract: \"${nativeType}\". ` +\n 'Native type names must match /^[a-zA-Z][a-zA-Z0-9_ ]*(\\\\[\\\\])?$/',\n );\n }\n}\n\n/**\n * Sanity check against accidental SQL injection from malformed contract files.\n * Rejects semicolons, SQL comment tokens, and dollar-quoting.\n * Not a comprehensive security boundary — the contract is developer-authored.\n */\nfunction assertSafeDefaultExpression(expression: string): void {\n if (expression.includes(';') || /--|\\/\\*|\\$\\$|\\bSELECT\\b/i.test(expression)) {\n throw new Error(\n `Unsafe default expression in contract: \"${expression}\". ` +\n 'Default expressions must not contain semicolons, SQL comment tokens, dollar-quoting, or subqueries.',\n );\n }\n}\n\n/**\n * Renders the SQL type for a column in DDL context.\n *\n * @param allowPseudoTypes - When true (default), autoincrement integer columns\n * produce SERIAL/BIGSERIAL/SMALLSERIAL pseudo-types. Set to false for contexts\n * like ALTER COLUMN TYPE where pseudo-types are invalid.\n */\nexport function buildColumnTypeSql(\n column: StorageColumn,\n codecHooks: ReadonlyMap<string, CodecControlHooks>,\n storageTypes: Record<string, StorageTypeInstance> = {},\n allowPseudoTypes = true,\n): string {\n const resolved = resolveColumnTypeMetadata(column, storageTypes);\n\n if (allowPseudoTypes) {\n const columnDefault = column.default;\n if (columnDefault?.kind === 'function' && columnDefault.expression === 'autoincrement()') {\n if (resolved.nativeType === 'int4' || resolved.nativeType === 'integer') {\n return 'SERIAL';\n }\n if (resolved.nativeType === 'int8' || resolved.nativeType === 'bigint') {\n return 'BIGSERIAL';\n }\n if (resolved.nativeType === 'int2' || resolved.nativeType === 'smallint') {\n return 'SMALLSERIAL';\n }\n }\n }\n\n // A column whose codec supplied a `typeParams.typeName` references a named\n // database type (e.g. a native enum), not a parameterized builtin: render it\n // as its quoted, schema-qualified type-name identifier. DDL-render only — the\n // verify comparison value (`resolvedNativeType`) stays the bare name the\n // family expander produces, matching introspection.\n if (isPgEnumParams(resolved.typeParams)) {\n const quoted = quoteQualifiedName(resolved.nativeType);\n return column.many ? `${quoted}[]` : quoted;\n }\n\n const expanded = expandParameterizedTypeSql(resolved, codecHooks);\n if (expanded !== null) {\n return column.many ? `${expanded}[]` : expanded;\n }\n\n if (column.typeRef) {\n const base = quoteQualifiedName(resolved.nativeType);\n return column.many ? `${base}[]` : base;\n }\n\n assertSafeNativeType(resolved.nativeType);\n return column.many ? `${resolved.nativeType}[]` : resolved.nativeType;\n}\n\nfunction expandParameterizedTypeSql(\n column: Pick<StorageColumn, 'nativeType' | 'codecId' | 'typeParams'>,\n codecHooks: ReadonlyMap<string, CodecControlHooks>,\n): string | null {\n if (!column.typeParams || Object.keys(column.typeParams).length === 0) {\n return null;\n }\n\n if (!column.codecId) {\n throw new Error(\n `Column declares typeParams for nativeType \"${column.nativeType}\" but has no codecId. ` +\n 'Ensure the column is associated with a codec.',\n );\n }\n\n const hooks = codecHooks.get(column.codecId);\n if (!hooks?.expandNativeType) {\n if (hooks?.planTypeOperations) {\n return null;\n }\n throw new Error(\n `Column declares typeParams for nativeType \"${column.nativeType}\" ` +\n `but no expandNativeType hook is registered for codecId \"${column.codecId}\". ` +\n 'Ensure the extension providing this codec is included in extensions.',\n );\n }\n\n const expanded = hooks.expandNativeType({\n nativeType: column.nativeType,\n codecId: column.codecId,\n ...ifDefined('typeParams', column.typeParams),\n });\n\n return expanded !== column.nativeType ? expanded : null;\n}\n\n/** Autoincrement columns use SERIAL types, so this returns empty for them. */\nexport function buildColumnDefaultSql(\n columnDefault: PostgresColumnDefault | undefined,\n column?: Pick<StorageColumn, 'many' | 'nativeType'>,\n): string {\n if (!columnDefault) {\n return '';\n }\n\n switch (columnDefault.kind) {\n case 'literal':\n return `DEFAULT ${renderDefaultLiteral(columnDefault.value, column)}`;\n case 'function': {\n if (columnDefault.expression === 'autoincrement()') {\n return '';\n }\n assertSafeDefaultExpression(columnDefault.expression);\n return `DEFAULT (${columnDefault.expression})`;\n }\n case 'sequence':\n return `DEFAULT nextval('${escapeLiteral(quoteIdentifier(columnDefault.name))}'::regclass)`;\n }\n}\n\nexport function renderDefaultLiteral(\n value: unknown,\n column?: Pick<StorageColumn, 'many' | 'nativeType'>,\n): string {\n const isJsonColumn = column?.nativeType === 'json' || column?.nativeType === 'jsonb';\n\n if (column?.many && Array.isArray(value)) {\n return renderArrayLiteralDefault(value);\n }\n\n if (value instanceof Date) {\n return `'${escapeLiteral(value.toISOString())}'`;\n }\n if (typeof value === 'string') {\n return `'${escapeLiteral(value)}'`;\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (value === null) {\n return 'NULL';\n }\n const json = JSON.stringify(value);\n if (isJsonColumn) {\n return `'${escapeLiteral(json)}'::${column.nativeType}`;\n }\n return `'${escapeLiteral(json)}'`;\n}\n\nfunction renderArrayLiteralDefault(elements: unknown[]): string {\n if (elements.length === 0) {\n return \"'{}'\";\n }\n const rendered = elements.map((el) => renderDefaultLiteral(el)).join(', ');\n return `ARRAY[${rendered}]`;\n}\n"],"mappings":"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"} |
| import { t as PostgresMigration } from "./postgres-migration-BkIplcRz.mjs"; | ||
| import { t as renderOps } from "./render-ops-BREh1kHe.mjs"; | ||
| import { t as renderCallsToTypeScript } from "./render-typescript-Ta7_6CGf.mjs"; | ||
| //#region src/core/migrations/planner-produced-postgres-migration.ts | ||
| var TypeScriptRenderablePostgresMigration = class extends PostgresMigration { | ||
| #calls; | ||
| #meta; | ||
| #spaceId; | ||
| #snapshotsImportPath; | ||
| #lowerer; | ||
| #operationsCache; | ||
| constructor(calls, meta, spaceId, snapshotsImportPath, lowerer) { | ||
| super(); | ||
| this.#calls = calls; | ||
| this.#meta = meta; | ||
| this.#spaceId = spaceId; | ||
| this.#snapshotsImportPath = snapshotsImportPath; | ||
| this.#lowerer = lowerer; | ||
| } | ||
| get operations() { | ||
| this.#operationsCache ??= renderOps(this.#calls, this.#lowerer); | ||
| return this.#operationsCache; | ||
| } | ||
| describe() { | ||
| return this.#meta; | ||
| } | ||
| /** | ||
| * Contract space this planner-produced plan applies to. Threaded | ||
| * from the planner options so the runner keys the marker row by | ||
| * the right space when executing the plan. | ||
| */ | ||
| get spaceId() { | ||
| return this.#spaceId; | ||
| } | ||
| renderTypeScript() { | ||
| return renderCallsToTypeScript(this.#calls, { | ||
| from: this.#meta.from, | ||
| to: this.#meta.to, | ||
| snapshotsImportPath: this.#snapshotsImportPath | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { TypeScriptRenderablePostgresMigration as t }; | ||
| //# sourceMappingURL=planner-produced-postgres-migration-PIgjJYrY.mjs.map |
| {"version":3,"file":"planner-produced-postgres-migration-PIgjJYrY.mjs","names":["#calls","#meta","#spaceId","#snapshotsImportPath","#lowerer","#operationsCache"],"sources":["../src/core/migrations/planner-produced-postgres-migration.ts"],"sourcesContent":["/**\n * Planner-produced Postgres migration.\n *\n * Returned by `PostgresMigrationPlanner.plan(...)` and `emptyMigration(...)`.\n * Holds the migration IR (`PostgresOpFactoryCall[]`) alongside\n * `MigrationMeta` and exposes both the runtime-ops view (`get operations`)\n * and the TypeScript authoring view (`renderTypeScript()`). Satisfies\n * `MigrationPlanWithAuthoringSurface` so the CLI can uniformly serialize any\n * planner result back to `migration.ts`.\n *\n * Extends the family-level `SqlMigration` alias rather than the target-local\n * migration base directly — mirrors Mongo's `PlannerProducedMongoMigration`\n * shape and keeps CLI wiring one step removed from target internals.\n *\n * Placeholder-bearing plans: `renderTypeScript()` always succeeds and embeds\n * `() => placeholder(\"slot\")` at each stub. `operations`, in contrast, is\n * _not safe to enumerate_ on a stub-bearing plan — `DataTransformCall.toOp()`\n * throws `MIGRATION.UNFILLED_PLACEHOLDER` because a planner-stubbed closure cannot be lowered\n * to a runtime op. Callers that know a plan may carry stubs must render to\n * `migration.ts`, let the user fill the slots, and re-load the edited\n * migration before enumerating ops. The walk-schema planner does not emit\n * `DataTransformCall`s today, so this asymmetry is invisible until the\n * issue-planner integration lands in Phase 2.\n */\n\nimport type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';\nimport type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';\nimport type {\n MigrationPlanWithAuthoringSurface,\n OpFactoryCall,\n} from '@prisma-next/framework-components/control';\nimport type { MigrationMeta } from '@prisma-next/migration-tools/migration';\nimport type { PostgresPlanTargetDetails } from './planner-target-details';\nimport { PostgresMigration } from './postgres-migration';\nimport { renderOps } from './render-ops';\nimport { renderCallsToTypeScript } from './render-typescript';\n\nexport class TypeScriptRenderablePostgresMigration\n extends PostgresMigration\n implements MigrationPlanWithAuthoringSurface\n{\n readonly #calls: readonly OpFactoryCall[];\n readonly #meta: MigrationMeta;\n readonly #spaceId: string;\n readonly #snapshotsImportPath: string;\n readonly #lowerer: ExecuteRequestLowerer | undefined;\n #operationsCache:\n | readonly (\n | SqlMigrationPlanOperation<PostgresPlanTargetDetails>\n | Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>>\n )[]\n | undefined;\n\n constructor(\n calls: readonly OpFactoryCall[],\n meta: MigrationMeta,\n spaceId: string,\n snapshotsImportPath: string,\n lowerer?: ExecuteRequestLowerer,\n ) {\n super();\n this.#calls = calls;\n this.#meta = meta;\n this.#spaceId = spaceId;\n this.#snapshotsImportPath = snapshotsImportPath;\n this.#lowerer = lowerer;\n }\n\n override get operations(): readonly (\n | SqlMigrationPlanOperation<PostgresPlanTargetDetails>\n | Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>>\n )[] {\n this.#operationsCache ??= renderOps(this.#calls, this.#lowerer);\n return this.#operationsCache;\n }\n\n override describe(): MigrationMeta {\n return this.#meta;\n }\n\n /**\n * Contract space this planner-produced plan applies to. Threaded\n * from the planner options so the runner keys the marker row by\n * the right space when executing the plan.\n */\n get spaceId(): string {\n return this.#spaceId;\n }\n\n renderTypeScript(): string {\n return renderCallsToTypeScript(this.#calls, {\n from: this.#meta.from,\n to: this.#meta.to,\n snapshotsImportPath: this.#snapshotsImportPath,\n });\n }\n}\n"],"mappings":";;;;AAqCA,IAAa,wCAAb,cACU,kBAEV;CACE;CACA;CACA;CACA;CACA;CACA;CAOA,YACE,OACA,MACA,SACA,qBACA,SACA;EACA,MAAM;EACN,KAAKA,SAAS;EACd,KAAKC,QAAQ;EACb,KAAKC,WAAW;EAChB,KAAKC,uBAAuB;EAC5B,KAAKC,WAAW;CAClB;CAEA,IAAa,aAGT;EACF,KAAKC,qBAAqB,UAAU,KAAKL,QAAQ,KAAKI,QAAQ;EAC9D,OAAO,KAAKC;CACd;CAEA,WAAmC;EACjC,OAAO,KAAKJ;CACd;;;;;;CAOA,IAAI,UAAkB;EACpB,OAAO,KAAKC;CACd;CAEA,mBAA2B;EACzB,OAAO,wBAAwB,KAAKF,QAAQ;GAC1C,MAAM,KAAKC,MAAM;GACjB,IAAI,KAAKA,MAAM;GACf,qBAAqB,KAAKE;EAC5B,CAAC;CACH;AACF"} |
| import { g as PostgresRlsPolicy } from "./postgres-schema-CewLYHB7.mjs"; | ||
| import { A as SetDefaultCall, C as DropNativeEnumTypeCall, D as EnableRowLevelSecurityCall, E as DropTableCall, F as installExtension, S as DropIndexCall, T as DropPostgresRlsPolicyCall, _ as DisableRowLevelSecurityCall, b as DropConstraintCall, c as AddUniqueCall, d as CreateIndexCall, f as CreateNativeEnumTypeCall, h as CreateTableCall, i as AddNativeEnumValueCall, j as SetNotNullCall, k as RenamePostgresRlsPolicyCall, l as AlterColumnTypeCall, m as CreateSchemaCall, n as AddColumnCall, p as CreatePostgresRlsPolicyCall, r as AddForeignKeyCall, s as AddPrimaryKeyCall, t as AddCheckConstraintCall, v as DropCheckConstraintCall, w as DropNotNullCall, x as DropDefaultCall, y as DropColumnCall } from "./op-factory-call-PBYfcv8h.mjs"; | ||
| import { t as errorPostgresMigrationStackMissing } from "./errors-ByDS0xJL.mjs"; | ||
| import { t as PostgresContractView } from "./postgres-contract-view-DebHMqGR.mjs"; | ||
| import { t as dataTransform } from "./data-transform-BOWpliq8.mjs"; | ||
| import { Migration } from "@prisma-next/family-sql/migration"; | ||
| import { MigrationContractViews } from "@prisma-next/migration-tools/migration"; | ||
| //#region src/core/migrations/postgres-migration.ts | ||
| /** | ||
| * Target-owned base class for Postgres migrations. | ||
| * | ||
| * Fixes the `SqlMigration` generic to `PostgresPlanTargetDetails` and the | ||
| * abstract `targetId` to the Postgres target-id string literal, so both | ||
| * user-authored migrations and renderer-generated scaffolds (the output of | ||
| * `renderCallsToTypeScript`) can extend `PostgresMigration` directly without | ||
| * redeclaring target-local identity. | ||
| * | ||
| * Mirrors `MongoMigration` in `@prisma-next/family-mongo`: the renderer | ||
| * emits `extends Migration` against a facade re-export of this class | ||
| * from `@prisma-next/postgres/migration`, keeping the authoring surface | ||
| * target-scoped rather than family-scoped. | ||
| * | ||
| * The constructor materializes a single Postgres `SqlControlAdapter` from | ||
| * `stack.adapter.create(stack)` and stores it; the protected `dataTransform` | ||
| * instance method forwards to the free `dataTransform` factory with that | ||
| * stored adapter, so user migrations can write `this.dataTransform(...)` | ||
| * without threading the adapter through every call. | ||
| * | ||
| * Every method requires an explicit `schema`. Postgres migrations name their | ||
| * schema deliberately — there is no default and no `search_path`-relative | ||
| * option. A migration that left the schema unspecified would resolve against | ||
| * whatever `search_path` the connection happened to carry, and that ambiguity | ||
| * is an antipattern in a migration. (The unbound/unspecified namespace concept | ||
| * remains for SQLite, which has no schemas, and for Mongo's connection `db`.) | ||
| */ | ||
| var PostgresMigration = class extends Migration { | ||
| targetId = "postgres"; | ||
| /** | ||
| * Materialized Postgres control adapter, created once per migration | ||
| * instance from the injected stack. `undefined` only when the migration | ||
| * was instantiated without a stack (test fixtures); `controlAdapterFor` | ||
| * throws a MIGRATION.POSTGRES_CONTROL_STACK_MISSING in that case to surface the misuse. | ||
| */ | ||
| controlAdapter; | ||
| #endView = new MigrationContractViews(this, "PostgresMigration", (json) => PostgresContractView.fromJson(json)); | ||
| #startView = new MigrationContractViews(this, "PostgresMigration", (json) => PostgresContractView.fromJson(json)); | ||
| constructor(stack) { | ||
| super(stack); | ||
| this.controlAdapter = stack?.adapter ? stack.adapter.create(stack) : void 0; | ||
| } | ||
| /** | ||
| * Returns the materialized control adapter, or throws a MIGRATION.POSTGRES_CONTROL_STACK_MISSING naming | ||
| * `operation` when the migration was constructed without a `ControlStack`. | ||
| * Single home for the null-check that every DDL/DML method shares. | ||
| */ | ||
| controlAdapterFor(operation) { | ||
| if (!this.controlAdapter) throw errorPostgresMigrationStackMissing(operation); | ||
| return this.controlAdapter; | ||
| } | ||
| /** | ||
| * The typed, schema-qualified Postgres view over this migration's end-state | ||
| * contract — `this.endContract.namespace.<schema>.table.<name>`, etc. Throws | ||
| * if no `endContractJson` was provided. | ||
| */ | ||
| get endContract() { | ||
| return this.#endView.endContract; | ||
| } | ||
| /** | ||
| * The typed Postgres view over this migration's start-state contract, or | ||
| * `null` for a baseline migration (no `startContractJson`). | ||
| */ | ||
| get startContract() { | ||
| return this.#startView.startContract; | ||
| } | ||
| /** | ||
| * Instance-method wrapper around the free `dataTransform` factory that | ||
| * supplies the stored control adapter. Authors call this from inside | ||
| * `get operations()`; the adapter argument is hidden from the call site. | ||
| */ | ||
| dataTransform(contract, name, options) { | ||
| return dataTransform(contract, name, options, this.controlAdapterFor("dataTransform")); | ||
| } | ||
| /** | ||
| * Emit a `CREATE TABLE` migration operation. Builds a typed DDL node from | ||
| * the supplied options and lowers it through the stored control adapter. | ||
| * Throws if no adapter is present (i.e. migration instantiated without a stack). | ||
| */ | ||
| createTable(options) { | ||
| return new CreateTableCall(options.schema, options.table, options.columns, options.constraints).toOp(this.controlAdapterFor("createTable")); | ||
| } | ||
| /** | ||
| * Emit a `CREATE SCHEMA` migration operation. Builds a typed DDL node from | ||
| * the supplied options and lowers it through the stored control adapter. | ||
| * Throws if no adapter is present (i.e. migration instantiated without a stack). | ||
| */ | ||
| createSchema(options) { | ||
| return new CreateSchemaCall(options.schema).toOp(this.controlAdapterFor("createSchema")); | ||
| } | ||
| /** | ||
| * Emit a `CREATE TYPE ... AS ENUM (...)` migration operation for a managed | ||
| * native enum. Builds a typed DDL node and lowers it through the stored | ||
| * control adapter (members render in declaration order). Throws if no adapter | ||
| * is present. | ||
| */ | ||
| createNativeEnumType(options) { | ||
| return new CreateNativeEnumTypeCall(options.schema, options.typeName, options.members).toOp(this.controlAdapterFor("createNativeEnumType")); | ||
| } | ||
| /** | ||
| * Emit a `DROP TYPE` migration operation for a managed native enum, lowered | ||
| * through the stored control adapter. Throws if no adapter is present. | ||
| */ | ||
| dropNativeEnumType(options) { | ||
| return new DropNativeEnumTypeCall(options.schema, options.typeName).toOp(this.controlAdapterFor("dropNativeEnumType")); | ||
| } | ||
| /** | ||
| * Emit an `ALTER TYPE ... ADD VALUE` migration operation appending one | ||
| * member to a managed native enum, lowered through the stored control | ||
| * adapter. Throws if no adapter is present. Every appended value is its | ||
| * own operation — call this once per value to append more than one. | ||
| */ | ||
| addNativeEnumValue(options) { | ||
| return new AddNativeEnumValueCall(options.schema, options.typeName, options.value).toOp(this.controlAdapterFor("addNativeEnumValue")); | ||
| } | ||
| addColumn(options) { | ||
| return new AddColumnCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("addColumn")); | ||
| } | ||
| addPrimaryKey(options) { | ||
| return new AddPrimaryKeyCall(options.schema, options.table, options.constraint, options.columns).toOp(this.controlAdapterFor("addPrimaryKey")); | ||
| } | ||
| addUnique(options) { | ||
| return new AddUniqueCall(options.schema, options.table, options.constraint, options.columns).toOp(this.controlAdapterFor("addUnique")); | ||
| } | ||
| addForeignKey(options) { | ||
| return new AddForeignKeyCall(options.schema, options.table, options.foreignKey).toOp(this.controlAdapterFor("addForeignKey")); | ||
| } | ||
| addCheckConstraint(options) { | ||
| return new AddCheckConstraintCall(options.schema, options.table, options.constraint, options.column, options.values).toOp(this.controlAdapterFor("addCheckConstraint")); | ||
| } | ||
| dropCheckConstraint(options) { | ||
| return new DropCheckConstraintCall(options.schema, options.table, options.constraint).toOp(this.controlAdapterFor("dropCheckConstraint")); | ||
| } | ||
| dropConstraint(options) { | ||
| return new DropConstraintCall(options.schema, options.table, options.constraint, options.kind ?? "unique").toOp(this.controlAdapterFor("dropConstraint")); | ||
| } | ||
| dropTable(options) { | ||
| return new DropTableCall(options.schema, options.table).toOp(this.controlAdapterFor("dropTable")); | ||
| } | ||
| dropColumn(options) { | ||
| return new DropColumnCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("dropColumn")); | ||
| } | ||
| alterColumnType(options) { | ||
| return new AlterColumnTypeCall(options.schema, options.table, options.column, options.options).toOp(this.controlAdapterFor("alterColumnType")); | ||
| } | ||
| setNotNull(options) { | ||
| return new SetNotNullCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("setNotNull")); | ||
| } | ||
| dropNotNull(options) { | ||
| return new DropNotNullCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("dropNotNull")); | ||
| } | ||
| setDefault(options) { | ||
| return new SetDefaultCall(options.schema, options.table, options.column, options.defaultSql, options.operationClass).toOp(this.controlAdapterFor("setDefault")); | ||
| } | ||
| dropDefault(options) { | ||
| return new DropDefaultCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("dropDefault")); | ||
| } | ||
| createIndex(options) { | ||
| return new CreateIndexCall(options.schema, options.table, options.index, options.columns, options.extras).toOp(this.controlAdapterFor("createIndex")); | ||
| } | ||
| dropIndex(options) { | ||
| return new DropIndexCall(options.schema, options.table, options.index).toOp(this.controlAdapterFor("dropIndex")); | ||
| } | ||
| installExtension(options) { | ||
| return installExtension(options, this.controlAdapterFor("installExtension")); | ||
| } | ||
| enableRowLevelSecurity(options) { | ||
| return new EnableRowLevelSecurityCall(options.schema, options.table).toOp(this.controlAdapterFor("enableRowLevelSecurity")); | ||
| } | ||
| disableRowLevelSecurity(options) { | ||
| return new DisableRowLevelSecurityCall(options.schema, options.table).toOp(this.controlAdapterFor("disableRowLevelSecurity")); | ||
| } | ||
| createRlsPolicy(options) { | ||
| return new CreatePostgresRlsPolicyCall(options.schema, options.table, new PostgresRlsPolicy(options.policy)).toOp(this.controlAdapterFor("createRlsPolicy")); | ||
| } | ||
| dropRlsPolicy(options) { | ||
| return new DropPostgresRlsPolicyCall(options.schema, options.table, options.policy).toOp(this.controlAdapterFor("dropRlsPolicy")); | ||
| } | ||
| renameRlsPolicy(options) { | ||
| return new RenamePostgresRlsPolicyCall(options.schema, options.table, options.from, options.to).toOp(this.controlAdapterFor("renameRlsPolicy")); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { PostgresMigration as t }; | ||
| //# sourceMappingURL=postgres-migration-BkIplcRz.mjs.map |
| {"version":3,"file":"postgres-migration-BkIplcRz.mjs","names":["SqlMigration","#endView","#startView"],"sources":["../src/core/migrations/postgres-migration.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';\nimport type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';\nimport { Migration as SqlMigration } from '@prisma-next/family-sql/migration';\nimport type { ControlStack } from '@prisma-next/framework-components/control';\nimport { MigrationContractViews } from '@prisma-next/migration-tools/migration';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';\nimport { errorPostgresMigrationStackMissing } from '../errors';\nimport { PostgresContractView } from '../postgres-contract-view';\nimport { PostgresRlsPolicy, type PostgresRlsPolicyInput } from '../postgres-rls-policy';\nimport {\n AddCheckConstraintCall,\n AddColumnCall,\n AddForeignKeyCall,\n AddNativeEnumValueCall,\n AddPrimaryKeyCall,\n AddUniqueCall,\n AlterColumnTypeCall,\n type AlterColumnTypeOptions,\n CreateIndexCall,\n CreateNativeEnumTypeCall,\n CreatePostgresRlsPolicyCall,\n CreateSchemaCall,\n CreateTableCall,\n DisableRowLevelSecurityCall,\n DropCheckConstraintCall,\n DropColumnCall,\n DropConstraintCall,\n DropDefaultCall,\n DropIndexCall,\n DropNativeEnumTypeCall,\n DropNotNullCall,\n DropPostgresRlsPolicyCall,\n DropTableCall,\n EnableRowLevelSecurityCall,\n RenamePostgresRlsPolicyCall,\n SetDefaultCall,\n SetNotNullCall,\n} from './op-factory-call';\nimport { type DataTransformOptions, dataTransform } from './operations/data-transform';\nimport { installExtension } from './operations/dependencies';\nimport type { CreateIndexExtras } from './operations/indexes';\nimport type { ForeignKeySpec } from './operations/shared';\nimport type { PostgresPlanTargetDetails } from './planner-target-details';\n\n/**\n * Target-owned base class for Postgres migrations.\n *\n * Fixes the `SqlMigration` generic to `PostgresPlanTargetDetails` and the\n * abstract `targetId` to the Postgres target-id string literal, so both\n * user-authored migrations and renderer-generated scaffolds (the output of\n * `renderCallsToTypeScript`) can extend `PostgresMigration` directly without\n * redeclaring target-local identity.\n *\n * Mirrors `MongoMigration` in `@prisma-next/family-mongo`: the renderer\n * emits `extends Migration` against a facade re-export of this class\n * from `@prisma-next/postgres/migration`, keeping the authoring surface\n * target-scoped rather than family-scoped.\n *\n * The constructor materializes a single Postgres `SqlControlAdapter` from\n * `stack.adapter.create(stack)` and stores it; the protected `dataTransform`\n * instance method forwards to the free `dataTransform` factory with that\n * stored adapter, so user migrations can write `this.dataTransform(...)`\n * without threading the adapter through every call.\n *\n * Every method requires an explicit `schema`. Postgres migrations name their\n * schema deliberately — there is no default and no `search_path`-relative\n * option. A migration that left the schema unspecified would resolve against\n * whatever `search_path` the connection happened to carry, and that ambiguity\n * is an antipattern in a migration. (The unbound/unspecified namespace concept\n * remains for SQLite, which has no schemas, and for Mongo's connection `db`.)\n */\nexport abstract class PostgresMigration<\n Start extends Contract<SqlStorage> = Contract<SqlStorage>,\n End extends Contract<SqlStorage> = Contract<SqlStorage>,\n> extends SqlMigration<PostgresPlanTargetDetails, 'postgres', Start, End> {\n readonly targetId = 'postgres' as const;\n\n /**\n * Materialized Postgres control adapter, created once per migration\n * instance from the injected stack. `undefined` only when the migration\n * was instantiated without a stack (test fixtures); `controlAdapterFor`\n * throws a MIGRATION.POSTGRES_CONTROL_STACK_MISSING in that case to surface the misuse.\n */\n protected readonly controlAdapter: SqlControlAdapter<'postgres'> | undefined;\n\n #endView = new MigrationContractViews<PostgresContractView<End>>(\n this,\n 'PostgresMigration',\n (json) => PostgresContractView.fromJson<End>(json),\n );\n #startView = new MigrationContractViews<PostgresContractView<Start>>(\n this,\n 'PostgresMigration',\n (json) => PostgresContractView.fromJson<Start>(json),\n );\n\n constructor(stack?: ControlStack<'sql', 'postgres'>) {\n super(stack);\n // The descriptor `create()` is typed as the wider `ControlAdapterInstance`;\n // the Postgres descriptor concretely returns a `SqlControlAdapter<'postgres'>`,\n // so the cast holds for any Postgres-target stack assembled at runtime.\n this.controlAdapter = stack?.adapter\n ? (stack.adapter.create(stack) as SqlControlAdapter<'postgres'>)\n : undefined;\n }\n\n /**\n * Returns the materialized control adapter, or throws a MIGRATION.POSTGRES_CONTROL_STACK_MISSING naming\n * `operation` when the migration was constructed without a `ControlStack`.\n * Single home for the null-check that every DDL/DML method shares.\n */\n private controlAdapterFor(operation: string): SqlControlAdapter<'postgres'> {\n if (!this.controlAdapter) {\n throw errorPostgresMigrationStackMissing(operation);\n }\n return this.controlAdapter;\n }\n\n /**\n * The typed, schema-qualified Postgres view over this migration's end-state\n * contract — `this.endContract.namespace.<schema>.table.<name>`, etc. Throws\n * if no `endContractJson` was provided.\n */\n get endContract(): PostgresContractView<End> {\n return this.#endView.endContract;\n }\n\n /**\n * The typed Postgres view over this migration's start-state contract, or\n * `null` for a baseline migration (no `startContractJson`).\n */\n get startContract(): PostgresContractView<Start> | null {\n return this.#startView.startContract;\n }\n\n /**\n * Instance-method wrapper around the free `dataTransform` factory that\n * supplies the stored control adapter. Authors call this from inside\n * `get operations()`; the adapter argument is hidden from the call site.\n */\n protected dataTransform<TContract extends Contract<SqlStorage>>(\n contract: TContract,\n name: string,\n options: DataTransformOptions,\n ): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return dataTransform(contract, name, options, this.controlAdapterFor('dataTransform'));\n }\n\n /**\n * Emit a `CREATE TABLE` migration operation. Builds a typed DDL node from\n * the supplied options and lowers it through the stored control adapter.\n * Throws if no adapter is present (i.e. migration instantiated without a stack).\n */\n protected createTable(options: {\n readonly schema: string;\n readonly table: string;\n readonly ifNotExists?: boolean;\n readonly columns: readonly DdlColumn[];\n readonly constraints?: readonly DdlTableConstraint[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreateTableCall(\n options.schema,\n options.table,\n options.columns,\n options.constraints,\n ).toOp(this.controlAdapterFor('createTable'));\n }\n\n /**\n * Emit a `CREATE SCHEMA` migration operation. Builds a typed DDL node from\n * the supplied options and lowers it through the stored control adapter.\n * Throws if no adapter is present (i.e. migration instantiated without a stack).\n */\n protected createSchema(options: {\n readonly schema: string;\n readonly ifNotExists?: boolean;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreateSchemaCall(options.schema).toOp(this.controlAdapterFor('createSchema'));\n }\n\n /**\n * Emit a `CREATE TYPE ... AS ENUM (...)` migration operation for a managed\n * native enum. Builds a typed DDL node and lowers it through the stored\n * control adapter (members render in declaration order). Throws if no adapter\n * is present.\n */\n protected createNativeEnumType(options: {\n readonly schema: string;\n readonly typeName: string;\n readonly members: readonly string[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreateNativeEnumTypeCall(options.schema, options.typeName, options.members).toOp(\n this.controlAdapterFor('createNativeEnumType'),\n );\n }\n\n /**\n * Emit a `DROP TYPE` migration operation for a managed native enum, lowered\n * through the stored control adapter. Throws if no adapter is present.\n */\n protected dropNativeEnumType(options: {\n readonly schema: string;\n readonly typeName: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropNativeEnumTypeCall(options.schema, options.typeName).toOp(\n this.controlAdapterFor('dropNativeEnumType'),\n );\n }\n\n /**\n * Emit an `ALTER TYPE ... ADD VALUE` migration operation appending one\n * member to a managed native enum, lowered through the stored control\n * adapter. Throws if no adapter is present. Every appended value is its\n * own operation — call this once per value to append more than one.\n */\n protected addNativeEnumValue(options: {\n readonly schema: string;\n readonly typeName: string;\n readonly value: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddNativeEnumValueCall(options.schema, options.typeName, options.value).toOp(\n this.controlAdapterFor('addNativeEnumValue'),\n );\n }\n\n protected addColumn(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: DdlColumn;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddColumnCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('addColumn'),\n );\n }\n\n protected addPrimaryKey(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n readonly columns: readonly string[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddPrimaryKeyCall(\n options.schema,\n options.table,\n options.constraint,\n options.columns,\n ).toOp(this.controlAdapterFor('addPrimaryKey'));\n }\n\n protected addUnique(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n readonly columns: readonly string[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddUniqueCall(\n options.schema,\n options.table,\n options.constraint,\n options.columns,\n ).toOp(this.controlAdapterFor('addUnique'));\n }\n\n protected addForeignKey(options: {\n readonly schema: string;\n readonly table: string;\n readonly foreignKey: ForeignKeySpec;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddForeignKeyCall(options.schema, options.table, options.foreignKey).toOp(\n this.controlAdapterFor('addForeignKey'),\n );\n }\n\n protected addCheckConstraint(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n readonly column: string;\n readonly values: readonly string[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddCheckConstraintCall(\n options.schema,\n options.table,\n options.constraint,\n options.column,\n options.values,\n ).toOp(this.controlAdapterFor('addCheckConstraint'));\n }\n\n protected dropCheckConstraint(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropCheckConstraintCall(options.schema, options.table, options.constraint).toOp(\n this.controlAdapterFor('dropCheckConstraint'),\n );\n }\n\n protected dropConstraint(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n readonly kind?: 'foreignKey' | 'unique' | 'primaryKey';\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropConstraintCall(\n options.schema,\n options.table,\n options.constraint,\n options.kind ?? 'unique',\n ).toOp(this.controlAdapterFor('dropConstraint'));\n }\n\n protected dropTable(options: {\n readonly schema: string;\n readonly table: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropTableCall(options.schema, options.table).toOp(\n this.controlAdapterFor('dropTable'),\n );\n }\n\n protected dropColumn(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropColumnCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('dropColumn'),\n );\n }\n\n protected alterColumnType(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n readonly options: AlterColumnTypeOptions;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AlterColumnTypeCall(\n options.schema,\n options.table,\n options.column,\n options.options,\n ).toOp(this.controlAdapterFor('alterColumnType'));\n }\n\n protected setNotNull(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new SetNotNullCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('setNotNull'),\n );\n }\n\n protected dropNotNull(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropNotNullCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('dropNotNull'),\n );\n }\n\n protected setDefault(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n readonly defaultSql: string;\n readonly operationClass?: 'additive' | 'widening';\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new SetDefaultCall(\n options.schema,\n options.table,\n options.column,\n options.defaultSql,\n options.operationClass,\n ).toOp(this.controlAdapterFor('setDefault'));\n }\n\n protected dropDefault(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropDefaultCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('dropDefault'),\n );\n }\n\n protected createIndex(options: {\n readonly schema: string;\n readonly table: string;\n readonly index: string;\n readonly columns: readonly string[];\n readonly extras?: CreateIndexExtras;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreateIndexCall(\n options.schema,\n options.table,\n options.index,\n options.columns,\n options.extras,\n ).toOp(this.controlAdapterFor('createIndex'));\n }\n\n protected dropIndex(options: {\n readonly schema: string;\n readonly table: string;\n readonly index: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropIndexCall(options.schema, options.table, options.index).toOp(\n this.controlAdapterFor('dropIndex'),\n );\n }\n\n protected installExtension(options: {\n readonly extensionName: string;\n readonly invariantId: string;\n readonly id: string;\n readonly label?: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return installExtension(options, this.controlAdapterFor('installExtension'));\n }\n\n protected enableRowLevelSecurity(options: {\n readonly schema: string;\n readonly table: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new EnableRowLevelSecurityCall(options.schema, options.table).toOp(\n this.controlAdapterFor('enableRowLevelSecurity'),\n );\n }\n\n protected disableRowLevelSecurity(options: {\n readonly schema: string;\n readonly table: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DisableRowLevelSecurityCall(options.schema, options.table).toOp(\n this.controlAdapterFor('disableRowLevelSecurity'),\n );\n }\n\n protected createRlsPolicy(options: {\n readonly schema: string;\n readonly table: string;\n readonly policy: PostgresRlsPolicyInput;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreatePostgresRlsPolicyCall(\n options.schema,\n options.table,\n new PostgresRlsPolicy(options.policy),\n ).toOp(this.controlAdapterFor('createRlsPolicy'));\n }\n\n protected dropRlsPolicy(options: {\n readonly schema: string;\n readonly table: string;\n readonly policy: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropPostgresRlsPolicyCall(options.schema, options.table, options.policy).toOp(\n this.controlAdapterFor('dropRlsPolicy'),\n );\n }\n\n protected renameRlsPolicy(options: {\n readonly schema: string;\n readonly table: string;\n readonly from: string;\n readonly to: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new RenamePostgresRlsPolicyCall(\n options.schema,\n options.table,\n options.from,\n options.to,\n ).toOp(this.controlAdapterFor('renameRlsPolicy'));\n }\n}\n"],"mappings":"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"} |
+4
-4
@@ -5,6 +5,6 @@ import { n as postgresResolveDefault } from "./default-normalizer-B9ZUiyUE.mjs"; | ||
| import { i as PostgresDatabaseSchemaNode } from "./postgres-role-schema-node-bg32e7I-.mjs"; | ||
| import { n as diffPostgresSchema, r as contractToPostgresDatabaseSchemaNode } from "./diff-database-schema-iiZgpBJ4.mjs"; | ||
| import { r as renderDefaultLiteral } from "./planner-ddl-builders-CI1CpBfG.mjs"; | ||
| import { n as diffPostgresSchema, r as contractToPostgresDatabaseSchemaNode } from "./diff-database-schema-CK0FM2Z-.mjs"; | ||
| import { r as renderDefaultLiteral } from "./planner-ddl-builders-AIpFmG1i.mjs"; | ||
| import { n as PostgresContractSerializer } from "./postgres-contract-view-DebHMqGR.mjs"; | ||
| import { t as createPostgresMigrationPlanner } from "./planner-eM-kNm0O.mjs"; | ||
| import { t as createPostgresMigrationPlanner } from "./planner-CiXCo6BT.mjs"; | ||
| import { UNBOUND_NAMESPACE_ID, coordinateKey, elementCoordinates } from "@prisma-next/framework-components/ir"; | ||
@@ -982,3 +982,3 @@ import { blindCast } from "@prisma-next/utils/casts"; | ||
| return `"${fieldNames.join(", ")}" -> "${target}"`; | ||
| }).join(", ")} exists in the database, but its target schema is outside the introspected scope, so no relation field was generated. If the target schema is described by an extension pack, add it to extensionPacks and re-run infer.`; | ||
| }).join(", ")} exists in the database, but its target schema is outside the introspected scope, so no relation field was generated. If the target schema is described by an extension pack, add it to extensions and re-run infer.`; | ||
| } | ||
@@ -985,0 +985,0 @@ function buildScalarField(column, table, typeMap, enumNameMap, fieldNameMap, namedTypes, defaultMapping, rawDefaultParser, pkColumns, isSinglePk, singlePkConstraintName, uniqueColumns) { |
@@ -1,2 +0,2 @@ | ||
| import { t as buildPostgresPlanDiff } from "./diff-database-schema-iiZgpBJ4.mjs"; | ||
| import { t as buildPostgresPlanDiff } from "./diff-database-schema-CK0FM2Z-.mjs"; | ||
| export { buildPostgresPlanDiff }; |
@@ -1,2 +0,2 @@ | ||
| import { l as planIssues, o as coalesceSubtreeIssues } from "./control-policy-vqI4KbAN.mjs"; | ||
| import { l as planIssues, o as coalesceSubtreeIssues } from "./control-policy-BrcqeM82.mjs"; | ||
| export { coalesceSubtreeIssues, planIssues }; |
@@ -1,4 +0,4 @@ | ||
| import { P as createExtension } from "./op-factory-call-DMb7FTR6.mjs"; | ||
| import { P as createExtension } from "./op-factory-call-PBYfcv8h.mjs"; | ||
| import { t as dataTransform } from "./data-transform-BOWpliq8.mjs"; | ||
| import { t as PostgresMigration } from "./postgres-migration-CB767OIG.mjs"; | ||
| import { t as PostgresMigration } from "./postgres-migration-BkIplcRz.mjs"; | ||
| import { checkExpression, col, fn, foreignKey, lit, primaryKey, unique } from "@prisma-next/sql-relational-core/contract-free"; | ||
@@ -5,0 +5,0 @@ import { placeholder } from "@prisma-next/errors/migration"; |
@@ -1,2 +0,2 @@ | ||
| import { A as SetDefaultCall, C as DropNativeEnumTypeCall, D as EnableRowLevelSecurityCall, E as DropTableCall, O as RawSqlCall, S as DropIndexCall, T as DropPostgresRlsPolicyCall, _ as DisableRowLevelSecurityCall, b as DropConstraintCall, c as AddUniqueCall, d as CreateIndexCall, f as CreateNativeEnumTypeCall, g as DataTransformCall, h as CreateTableCall, i as AddNativeEnumValueCall, j as SetNotNullCall, k as RenamePostgresRlsPolicyCall, l as AlterColumnTypeCall, m as CreateSchemaCall, n as AddColumnCall, o as AddNotNullColumnWithTempDefaultCall, p as CreatePostgresRlsPolicyCall, r as AddForeignKeyCall, s as AddPrimaryKeyCall, t as AddCheckConstraintCall, u as CreateExtensionCall, v as DropCheckConstraintCall, w as DropNotNullCall, x as DropDefaultCall, y as DropColumnCall } from "./op-factory-call-DMb7FTR6.mjs"; | ||
| import { A as SetDefaultCall, C as DropNativeEnumTypeCall, D as EnableRowLevelSecurityCall, E as DropTableCall, O as RawSqlCall, S as DropIndexCall, T as DropPostgresRlsPolicyCall, _ as DisableRowLevelSecurityCall, b as DropConstraintCall, c as AddUniqueCall, d as CreateIndexCall, f as CreateNativeEnumTypeCall, g as DataTransformCall, h as CreateTableCall, i as AddNativeEnumValueCall, j as SetNotNullCall, k as RenamePostgresRlsPolicyCall, l as AlterColumnTypeCall, m as CreateSchemaCall, n as AddColumnCall, o as AddNotNullColumnWithTempDefaultCall, p as CreatePostgresRlsPolicyCall, r as AddForeignKeyCall, s as AddPrimaryKeyCall, t as AddCheckConstraintCall, u as CreateExtensionCall, v as DropCheckConstraintCall, w as DropNotNullCall, x as DropDefaultCall, y as DropColumnCall } from "./op-factory-call-PBYfcv8h.mjs"; | ||
| export { AddCheckConstraintCall, AddColumnCall, AddForeignKeyCall, AddNativeEnumValueCall, AddNotNullColumnWithTempDefaultCall, AddPrimaryKeyCall, AddUniqueCall, AlterColumnTypeCall, CreateExtensionCall, CreateIndexCall, CreateNativeEnumTypeCall, CreatePostgresRlsPolicyCall, CreateSchemaCall, CreateTableCall, DataTransformCall, DisableRowLevelSecurityCall, DropCheckConstraintCall, DropColumnCall, DropConstraintCall, DropDefaultCall, DropIndexCall, DropNativeEnumTypeCall, DropNotNullCall, DropPostgresRlsPolicyCall, DropTableCall, EnableRowLevelSecurityCall, RawSqlCall, RenamePostgresRlsPolicyCall, SetDefaultCall, SetNotNullCall }; |
@@ -1,2 +0,2 @@ | ||
| import { n as buildColumnTypeSql, r as renderDefaultLiteral, t as buildColumnDefaultSql } from "./planner-ddl-builders-CI1CpBfG.mjs"; | ||
| import { n as buildColumnTypeSql, r as renderDefaultLiteral, t as buildColumnDefaultSql } from "./planner-ddl-builders-AIpFmG1i.mjs"; | ||
| export { buildColumnDefaultSql, buildColumnTypeSql, renderDefaultLiteral }; |
@@ -1,2 +0,2 @@ | ||
| import { t as TypeScriptRenderablePostgresMigration } from "./planner-produced-postgres-migration-Db57IpMZ.mjs"; | ||
| import { t as TypeScriptRenderablePostgresMigration } from "./planner-produced-postgres-migration-PIgjJYrY.mjs"; | ||
| export { TypeScriptRenderablePostgresMigration }; |
+2
-2
@@ -1,3 +0,3 @@ | ||
| import { r as contractToPostgresDatabaseSchemaNode } from "./diff-database-schema-iiZgpBJ4.mjs"; | ||
| import { t as createPostgresMigrationPlanner } from "./planner-eM-kNm0O.mjs"; | ||
| import { r as contractToPostgresDatabaseSchemaNode } from "./diff-database-schema-CK0FM2Z-.mjs"; | ||
| import { t as createPostgresMigrationPlanner } from "./planner-CiXCo6BT.mjs"; | ||
| export { contractToPostgresDatabaseSchemaNode, createPostgresMigrationPlanner }; |
+20
-20
| { | ||
| "name": "@prisma-next/target-postgres", | ||
| "version": "0.16.0-dev.19", | ||
| "version": "0.16.0-dev.22", | ||
| "license": "Apache-2.0", | ||
@@ -9,16 +9,16 @@ "type": "module", | ||
| "dependencies": { | ||
| "@prisma-next/cli": "0.16.0-dev.19", | ||
| "@prisma-next/contract": "0.16.0-dev.19", | ||
| "@prisma-next/errors": "0.16.0-dev.19", | ||
| "@prisma-next/family-sql": "0.16.0-dev.19", | ||
| "@prisma-next/framework-components": "0.16.0-dev.19", | ||
| "@prisma-next/migration-tools": "0.16.0-dev.19", | ||
| "@prisma-next/psl-parser": "0.16.0-dev.19", | ||
| "@prisma-next/sql-contract": "0.16.0-dev.19", | ||
| "@prisma-next/sql-errors": "0.16.0-dev.19", | ||
| "@prisma-next/sql-operations": "0.16.0-dev.19", | ||
| "@prisma-next/sql-relational-core": "0.16.0-dev.19", | ||
| "@prisma-next/sql-schema-ir": "0.16.0-dev.19", | ||
| "@prisma-next/ts-render": "0.16.0-dev.19", | ||
| "@prisma-next/utils": "0.16.0-dev.19", | ||
| "@prisma-next/cli": "0.16.0-dev.22", | ||
| "@prisma-next/contract": "0.16.0-dev.22", | ||
| "@prisma-next/errors": "0.16.0-dev.22", | ||
| "@prisma-next/family-sql": "0.16.0-dev.22", | ||
| "@prisma-next/framework-components": "0.16.0-dev.22", | ||
| "@prisma-next/migration-tools": "0.16.0-dev.22", | ||
| "@prisma-next/psl-parser": "0.16.0-dev.22", | ||
| "@prisma-next/sql-contract": "0.16.0-dev.22", | ||
| "@prisma-next/sql-errors": "0.16.0-dev.22", | ||
| "@prisma-next/sql-operations": "0.16.0-dev.22", | ||
| "@prisma-next/sql-relational-core": "0.16.0-dev.22", | ||
| "@prisma-next/sql-schema-ir": "0.16.0-dev.22", | ||
| "@prisma-next/ts-render": "0.16.0-dev.22", | ||
| "@prisma-next/utils": "0.16.0-dev.22", | ||
| "@standard-schema/spec": "^1.1.0", | ||
@@ -29,7 +29,7 @@ "arktype": "^2.2.2", | ||
| "devDependencies": { | ||
| "@prisma-next/psl-printer": "0.16.0-dev.19", | ||
| "@prisma-next/sql-contract-psl": "0.16.0-dev.19", | ||
| "@prisma-next/test-utils": "0.16.0-dev.19", | ||
| "@prisma-next/tsconfig": "0.16.0-dev.19", | ||
| "@prisma-next/tsdown": "0.16.0-dev.19", | ||
| "@prisma-next/psl-printer": "0.16.0-dev.22", | ||
| "@prisma-next/sql-contract-psl": "0.16.0-dev.22", | ||
| "@prisma-next/test-utils": "0.16.0-dev.22", | ||
| "@prisma-next/tsconfig": "0.16.0-dev.22", | ||
| "@prisma-next/tsdown": "0.16.0-dev.22", | ||
| "tsdown": "0.22.8", | ||
@@ -36,0 +36,0 @@ "typescript": "5.9.3", |
+1
-1
@@ -110,3 +110,3 @@ # @prisma-next/target-postgres | ||
| target: postgresPack, | ||
| extensionPacks: { pgvector }, | ||
| extensions: { pgvector }, | ||
| }); | ||
@@ -113,0 +113,0 @@ ``` |
@@ -116,3 +116,3 @@ import type { CodecControlHooks } from '@prisma-next/family-sql/control'; | ||
| `but no expandNativeType hook is registered for codecId "${column.codecId}". ` + | ||
| 'Ensure the extension providing this codec is included in extensionPacks.', | ||
| 'Ensure the extension providing this codec is included in extensions.', | ||
| ); | ||
@@ -119,0 +119,0 @@ } |
@@ -808,3 +808,3 @@ import type { ColumnDefault } from '@prisma-next/contract/types'; | ||
| 'outside the introspected scope, so no relation field was generated. If the target schema ' + | ||
| 'is described by an extension pack, add it to extensionPacks and re-run infer.' | ||
| 'is described by an extension pack, add it to extensions and re-run infer.' | ||
| ); | ||
@@ -811,0 +811,0 @@ } |
| import { r as isPostgresSchema } from "./postgres-schema-CewLYHB7.mjs"; | ||
| import { i as quoteIdentifier } from "./sql-utils-SU4FDvIV.mjs"; | ||
| import { n as PostgresNativeEnumSchemaNode, o as postgresNodeEntityKind, r as PostgresSchemaNodeKind, t as PostgresTableSchemaNode } from "./postgres-table-schema-node-D6LvInCe.mjs"; | ||
| import { A as SetDefaultCall, C as DropNativeEnumTypeCall, D as EnableRowLevelSecurityCall, E as DropTableCall, M as postgresDefaultToDdlColumnDefault, N as buildTargetDetails, O as RawSqlCall, S as DropIndexCall, _ as DisableRowLevelSecurityCall, a as AddNotNullColumnDirectCall, b as DropConstraintCall, c as AddUniqueCall, d as CreateIndexCall, f as CreateNativeEnumTypeCall, g as DataTransformCall, h as CreateTableCall, i as AddNativeEnumValueCall, j as SetNotNullCall, l as AlterColumnTypeCall, m as CreateSchemaCall, n as AddColumnCall, o as AddNotNullColumnWithTempDefaultCall, r as AddForeignKeyCall, s as AddPrimaryKeyCall, t as AddCheckConstraintCall, v as DropCheckConstraintCall, w as DropNotNullCall, x as DropDefaultCall, y as DropColumnCall } from "./op-factory-call-DMb7FTR6.mjs"; | ||
| import { t as buildExpectedFormatType } from "./planner-sql-checks-BuB877wH.mjs"; | ||
| import { n as buildColumnTypeSql, t as buildColumnDefaultSql } from "./planner-ddl-builders-CI1CpBfG.mjs"; | ||
| import { n as resolveIdentityValue } from "./planner-identity-values-CJPha2Sz.mjs"; | ||
| import { i as hasUniqueConstraint, n as hasForeignKey, t as buildSchemaLookupMap } from "./planner-schema-lookup-CiVaAQP-.mjs"; | ||
| import { UNBOUND_NAMESPACE_ID, entityAt } from "@prisma-next/framework-components/ir"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { StorageTable } from "@prisma-next/sql-contract/types"; | ||
| import { resolveValueSetValues } from "@prisma-next/family-sql/control"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| import * as contractFree from "@prisma-next/sql-relational-core/contract-free"; | ||
| import { RelationalSchemaNodeKind, SqlSchemaIR } from "@prisma-next/sql-schema-ir/types"; | ||
| import { issueOutcome, orderIssuesByDependencies } from "@prisma-next/framework-components/control"; | ||
| import { defaultIndexName } from "@prisma-next/sql-schema-ir/naming"; | ||
| import { notOk, ok } from "@prisma-next/utils/result"; | ||
| //#region src/core/schema-ir/node-storage-coordinate.ts | ||
| /** | ||
| * The storage-`entries` coordinate `(entityKind, entityName)` a Postgres diff | ||
| * node addresses — a table by its name, a native enum by its physical type | ||
| * name. `undefined` for a node that is not a whole storage entity (a namespace, | ||
| * a column, a policy). Lets ownership/subject resolution treat every entity kind | ||
| * uniformly, the node self-describing its storage identity. | ||
| */ | ||
| function postgresNodeStorageCoordinate(node) { | ||
| if (PostgresTableSchemaNode.is(node)) { | ||
| const entityKind = postgresNodeEntityKind(node.nodeKind); | ||
| return entityKind === void 0 ? void 0 : { | ||
| entityKind, | ||
| entityName: node.name | ||
| }; | ||
| } | ||
| if (PostgresNativeEnumSchemaNode.is(node)) { | ||
| const entityKind = postgresNodeEntityKind(node.nodeKind); | ||
| return entityKind === void 0 ? void 0 : { | ||
| entityKind, | ||
| entityName: node.typeName | ||
| }; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/column-ddl-rendering.ts | ||
| /** | ||
| * Reconstructs the `StorageColumn`-shaped fields the DDL builder functions | ||
| * (`buildColumnTypeSql`, `buildExpectedFormatType`, `resolveIdentityValue`) | ||
| * expect, from a column node's own stamped codec identity (`codecRef` / | ||
| * `codecBaseNativeType` / `codecNamedType`, Decision 5) — never the | ||
| * contract. The builders were written against `StorageColumn` and are | ||
| * unchanged here; only the shape feeding them moves from the contract to | ||
| * the node. An empty `storageTypes` catalog is passed alongside: the | ||
| * node's fields are already resolved past any `typeRef` indirection, so no | ||
| * live lookup is needed, and passing a non-empty catalog would risk a | ||
| * false `typeRef` hit against an unrelated storage type. | ||
| */ | ||
| function columnLike(column) { | ||
| if (column.codecRef === void 0 || column.codecBaseNativeType === void 0) throw new Error(`columnLike: expected column "${column.name}" carries no codec identity — the expected tree must be derived via contractToSchemaIR for planning`); | ||
| return { | ||
| nativeType: column.codecBaseNativeType, | ||
| codecId: column.codecRef.codecId, | ||
| nullable: column.nullable, | ||
| ...ifDefined("many", column.many ?? column.codecRef.many), | ||
| ...ifDefined("typeParams", column.codecRef.typeParams !== void 0 ? blindCast(column.codecRef.typeParams) : void 0), | ||
| ...column.codecNamedType ? { typeRef: "<resolved>" } : {}, | ||
| ...ifDefined("default", column.resolvedDefault) | ||
| }; | ||
| } | ||
| /** | ||
| * Builds the `CREATE TABLE` / `ADD COLUMN` DDL column for an expected column | ||
| * node, resolving type rendering from the node's codec identity against the | ||
| * codec hooks the caller holds — the same builder the pre-`plan(start, end)` | ||
| * op-path called, so the output is byte-identical. | ||
| */ | ||
| function renderColumnDdl(name, column, codecHooks) { | ||
| const like = columnLike(column); | ||
| const typeSql = buildColumnTypeSql(like, codecHooks, {}); | ||
| const ddlDefault = postgresDefaultToDdlColumnDefault(like.default); | ||
| return contractFree.col(name, typeSql, { | ||
| ...!column.nullable ? { notNull: true } : {}, | ||
| ...ifDefined("default", ddlDefault), | ||
| ...ifDefined("codecRef", column.codecRef) | ||
| }); | ||
| } | ||
| /** | ||
| * Builds the `ALTER COLUMN … TYPE` operands for an expected column node. | ||
| */ | ||
| function renderColumnAlterType(column, codecHooks) { | ||
| const like = columnLike(column); | ||
| return { | ||
| qualifiedTargetType: buildColumnTypeSql(like, codecHooks, {}, false), | ||
| formatTypeExpected: buildExpectedFormatType(like, codecHooks, {}) | ||
| }; | ||
| } | ||
| /** | ||
| * Resolves the identity value (monoid neutral element) SQL literal used as | ||
| * the temporary default when adding a NOT-NULL column with no contract | ||
| * default (`notNullAddColumnCallStrategy`'s shared-temp-default backfill). | ||
| * `null` when the column's type has no built-in/codec-provided identity | ||
| * value. | ||
| */ | ||
| function resolveColumnTemporaryDefault(column, codecHooks) { | ||
| return resolveIdentityValue(columnLike(column), codecHooks, {}); | ||
| } | ||
| /** | ||
| * The column's `SET DEFAULT` clause SQL, resolved from a column-default | ||
| * diff node. `''` when the node carries no resolved default. | ||
| */ | ||
| function renderColumnDefaultSql(defaultNode) { | ||
| if (defaultNode.resolved === void 0) return ""; | ||
| return buildColumnDefaultSql(defaultNode.resolved, { | ||
| nativeType: defaultNode.nativeTypeContext ?? "", | ||
| ...ifDefined("many", defaultNode.many) | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/planner-strategies.ts | ||
| /** | ||
| * Look up a storage table by its explicit namespace coordinate. Returns | ||
| * `undefined` when the namespace has no table by that name (or no such | ||
| * namespace exists). Callers that get `undefined` MUST treat it as an | ||
| * explicit conflict — never silently fall back to a global default | ||
| * schema or a name-only walk, because that footgun would resolve a | ||
| * stale or duplicate table name to whichever namespace the iteration | ||
| * order surfaced first (a real data-loss hazard in multi-namespace | ||
| * contracts where two namespaces can carry the same table name). | ||
| */ | ||
| function tableAt(storage, namespaceId, tableName) { | ||
| const ns = storage.namespaces[namespaceId]; | ||
| if (ns === void 0) return void 0; | ||
| return ns.entries.table?.[tableName]; | ||
| } | ||
| /** | ||
| * Resolve the DDL schema name for a namespace coordinate. Postgres-aware | ||
| * namespaces dispatch to their polymorphic `ddlSchemaName` override — | ||
| * named schemas return their own id; the unbound singleton returns | ||
| * `UNBOUND_NAMESPACE_ID`. Legacy single-namespace contracts whose | ||
| * `__unbound__` slot is the framework-default `SqlUnboundNamespace` | ||
| * (rather than the Postgres-aware `PostgresUnboundSchema`) flow the | ||
| * coordinate through unchanged so downstream `qualifyTableName` | ||
| * resolves polymorphically. | ||
| */ | ||
| function resolveDdlSchemaForNamespace(ctx, namespaceId) { | ||
| const namespace = ctx.toContract.storage.namespaces[namespaceId]; | ||
| if (isPostgresSchema(namespace)) return namespace.ddlSchemaName(ctx.toContract.storage); | ||
| return namespaceId; | ||
| } | ||
| /** | ||
| * Recovers the contract namespace id for a DDL schema name embedded in a | ||
| * diff-issue path (`path[1]`). The strategies need the CONTRACT column/table | ||
| * (for codec-hook reads and eligibility probes the retained subsystems still | ||
| * run) even though the issue itself only carries the resolved DDL schema — | ||
| * this is the reverse of `resolveDdlSchemaForNamespace`. | ||
| */ | ||
| function namespaceIdForDdlSchema(ctx, ddlSchemaName) { | ||
| return resolveNamespaceIdForDdlSchema(ctx.toContract, ddlSchemaName); | ||
| } | ||
| /** A `not-equal` column issue whose node pair is narrowed to `SqlColumnIR`. */ | ||
| function columnNodePair(issue) { | ||
| const node = issueNode(issue); | ||
| if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.column) return void 0; | ||
| if (issue.expected === void 0 || issue.actual === void 0) return void 0; | ||
| return { | ||
| expected: blindCast(issue.expected), | ||
| actual: blindCast(issue.actual) | ||
| }; | ||
| } | ||
| const notNullBackfillCallStrategy = (issues, ctx) => { | ||
| if (!ctx.policy.allowedOperationClasses.includes("data")) return { kind: "no_match" }; | ||
| const matched = []; | ||
| const calls = []; | ||
| for (const issue of issues) { | ||
| if (issueOutcome(issue) !== "not-found") continue; | ||
| const node = issueNode(issue); | ||
| if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.column) continue; | ||
| const expected = blindCast(issue.expected); | ||
| if (expected.nullable !== false || expected.resolvedDefault !== void 0) continue; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) continue; | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| matched.push(issue); | ||
| const ddl = renderColumnDdl(expected.name, expected, ctx.codecHooks); | ||
| const nullableSpec = contractFree.col(ddl.name, ddl.type, { ...ddl.codecRef !== void 0 ? { codecRef: ddl.codecRef } : {} }); | ||
| calls.push(new AddColumnCall(schemaName, tableName, nullableSpec), new DataTransformCall(`backfill-${tableName}-${expected.name}`, `backfill-${tableName}-${expected.name}:check`, `backfill-${tableName}-${expected.name}:run`), new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| } | ||
| if (matched.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((i) => !matched.includes(i)), | ||
| calls, | ||
| recipe: true | ||
| }; | ||
| }; | ||
| const SAFE_WIDENINGS = /* @__PURE__ */ new Set([ | ||
| "int2→int4", | ||
| "int2→int8", | ||
| "int4→int8", | ||
| "float4→float8" | ||
| ]); | ||
| /** | ||
| * Handles `not-equal` column issues whose TYPE differs. `fromContract` is | ||
| * only supplied by `migration plan` — for reconciliation (`db update` / | ||
| * `db init`, `fromContract === null`) this strategy never fires, mirroring | ||
| * the legacy `typeChangeCallStrategy`'s requirement of a prior contract: | ||
| * `mapNodeIssueToCall`'s in-place ALTER covers reconciliation directly. | ||
| * | ||
| * A single node issue can carry BOTH type and nullability drift (Postgres | ||
| * alters in place, so the differ emits one `not-equal` column issue where | ||
| * the legacy coordinate walk emitted two: `type_mismatch` + | ||
| * `nullability_mismatch`). When this strategy consumes the issue for its | ||
| * type portion, it also emits whatever nullability delta the same node pair | ||
| * carries — using the same construction `nullableTighteningCallStrategy` / | ||
| * the mapper's direct dispatch would have used — so the issue is never | ||
| * partially handled. | ||
| */ | ||
| const typeChangeCallStrategy = (issues, ctx) => { | ||
| if (ctx.fromContract === null) return { kind: "no_match" }; | ||
| const dataAllowed = ctx.policy.allowedOperationClasses.includes("data"); | ||
| const matched = []; | ||
| const calls = []; | ||
| for (const issue of issues) { | ||
| if (issueOutcome(issue) !== "not-equal") continue; | ||
| const pair = columnNodePair(issue); | ||
| if (pair === void 0) continue; | ||
| const { expected, actual } = pair; | ||
| if (!columnTypeChangedNativeOnly(expected, actual)) continue; | ||
| const fromType = actual.nativeType; | ||
| const toType = expected.nativeType; | ||
| const isSafeWidening = SAFE_WIDENINGS.has(`${fromType}→${toType}`); | ||
| if (!isSafeWidening && !dataAllowed) continue; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) continue; | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| matched.push(issue); | ||
| const { qualifiedTargetType, formatTypeExpected } = renderColumnAlterType(expected, ctx.codecHooks); | ||
| const alterOpts = { | ||
| qualifiedTargetType, | ||
| formatTypeExpected, | ||
| rawTargetTypeForLabel: qualifiedTargetType | ||
| }; | ||
| if (isSafeWidening) calls.push(new AlterColumnTypeCall(schemaName, tableName, expected.name, alterOpts)); | ||
| else calls.push(new DataTransformCall(`typechange-${tableName}-${expected.name}`, `typechange-${tableName}-${expected.name}:check`, `typechange-${tableName}-${expected.name}:run`), new AlterColumnTypeCall(schemaName, tableName, expected.name, alterOpts)); | ||
| if (expected.nullable !== actual.nullable) if (expected.nullable) calls.push(new DropNotNullCall(schemaName, tableName, expected.name)); | ||
| else if (dataAllowed) calls.push(new DataTransformCall(`handle-nulls-${tableName}-${expected.name}`, `handle-nulls-${tableName}-${expected.name}:check`, `handle-nulls-${tableName}-${expected.name}:run`), new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| else calls.push(new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| } | ||
| if (matched.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((i) => !matched.includes(i)), | ||
| calls, | ||
| recipe: true | ||
| }; | ||
| }; | ||
| /** | ||
| * Whether the raw (unresolved) native type differs — the SAME comparison | ||
| * `typeChangeCallStrategy` always used (`fromColumn.nativeType !== | ||
| * toColumn.nativeType`), which for the widenable numeric/float types | ||
| * `SAFE_WIDENINGS` lists is identical to the resolved comparison | ||
| * `columnTypeChanged` (in `issue-planner.ts`) performs. | ||
| */ | ||
| function columnTypeChangedNativeOnly(expected, actual) { | ||
| return expected.nativeType !== actual.nativeType; | ||
| } | ||
| /** | ||
| * Handles `not-equal` column issues whose type did NOT change but | ||
| * nullability tightened (contract requires NOT NULL, live column is | ||
| * nullable). A type-changed issue's nullability delta (if any) is already | ||
| * handled by `typeChangeCallStrategy`, which runs first — this strategy | ||
| * only ever sees issues that strategy left behind. | ||
| */ | ||
| const nullableTighteningCallStrategy = (issues, ctx) => { | ||
| if (!ctx.policy.allowedOperationClasses.includes("data")) return { kind: "no_match" }; | ||
| const matched = []; | ||
| const calls = []; | ||
| for (const issue of issues) { | ||
| if (issueOutcome(issue) !== "not-equal") continue; | ||
| const pair = columnNodePair(issue); | ||
| if (pair === void 0) continue; | ||
| const { expected, actual } = pair; | ||
| if (columnTypeChangedNativeOnly(expected, actual)) continue; | ||
| if (expected.nullable !== false || actual.nullable !== true) continue; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) continue; | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| matched.push(issue); | ||
| calls.push(new DataTransformCall(`handle-nulls-${tableName}-${expected.name}`, `handle-nulls-${tableName}-${expected.name}:check`, `handle-nulls-${tableName}-${expected.name}:run`), new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| } | ||
| if (matched.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((i) => !matched.includes(i)), | ||
| calls, | ||
| recipe: true | ||
| }; | ||
| }; | ||
| /** | ||
| * Collects every check constraint from a table in the contract storage. | ||
| * Returns an empty array when the table has no checks or the table is absent. | ||
| */ | ||
| function collectContractChecks(storage, namespaceId, tableName) { | ||
| const ns = storage.namespaces[namespaceId]; | ||
| const tableRaw = ns !== void 0 ? ns.entries.table?.[tableName] : void 0; | ||
| if (!(tableRaw instanceof StorageTable)) return []; | ||
| const checks = tableRaw.checks; | ||
| if (!checks || checks.length === 0) return []; | ||
| return checks.map((c) => ({ | ||
| name: c.name, | ||
| column: c.column, | ||
| permittedValues: resolveValueSetValues(c.valueSet, storage, `check "${c.name}" on "${tableName}"`) | ||
| })); | ||
| } | ||
| /** | ||
| * Compares two value arrays as unordered sets. | ||
| */ | ||
| function checkValueSetsEqual(a, b) { | ||
| if (a.length !== b.length) return false; | ||
| const bSet = new Set(b); | ||
| return a.every((v) => bSet.has(v)); | ||
| } | ||
| /** | ||
| * Plans check-constraint migrations for `enumType`-authored columns. | ||
| * | ||
| * Walks every namespace's tables in the target contract (the check nodes' | ||
| * resolved `permittedValues` are ultimately sourced from the same | ||
| * contract-declared value sets, so walking the contract directly is the | ||
| * simplest faithful port — the strategy's decisions never depend on which | ||
| * ISSUES happen to be in the input list, only on the contract + live schema | ||
| * shapes). For each table that carries `checks`, diffs the contract-expected | ||
| * checks against the live schema's checks: | ||
| * | ||
| * - Check in contract, absent from live DB → `AddCheckConstraintCall`. | ||
| * - Check in live DB, absent from contract → `DropCheckConstraintCall`. | ||
| * - Check on both sides but value sets differ → `DropCheckConstraintCall` | ||
| * then `AddCheckConstraintCall` (drop + recreate; a check predicate cannot | ||
| * be altered in place). | ||
| * | ||
| * Consumes every `sql-check-constraint` issue on a table this walk handles | ||
| * (not-found/not-expected/not-equal), leaving check issues on tables with | ||
| * NO contract checks to `mapNodeIssueToCall`'s `not-expected` fallback. | ||
| */ | ||
| const checkConstraintPlanCallStrategy = (issues, ctx) => { | ||
| const calls = []; | ||
| const handledIssueKeys = /* @__PURE__ */ new Set(); | ||
| for (const [namespaceId, ns] of Object.entries(ctx.toContract.storage.namespaces)) for (const tableName of Object.keys(ns.entries.table ?? {})) { | ||
| const contractChecks = collectContractChecks(ctx.toContract.storage, namespaceId, tableName); | ||
| if (contractChecks.length === 0) continue; | ||
| const liveChecks = ctx.schema.tables[tableName]?.checks ?? []; | ||
| const ddlSchema = resolveDdlSchemaForNamespace(ctx, namespaceId); | ||
| for (const contractCheck of contractChecks) { | ||
| const liveCheck = liveChecks.find((c) => c.name === contractCheck.name); | ||
| const issueKey = `${tableName} ${contractCheck.name}`; | ||
| if (!liveCheck) { | ||
| calls.push(new AddCheckConstraintCall(ddlSchema, tableName, contractCheck.name, contractCheck.column, contractCheck.permittedValues)); | ||
| handledIssueKeys.add(issueKey); | ||
| } else if (!checkValueSetsEqual(contractCheck.permittedValues, liveCheck.permittedValues)) { | ||
| calls.push(new DropCheckConstraintCall(ddlSchema, tableName, contractCheck.name), new AddCheckConstraintCall(ddlSchema, tableName, contractCheck.name, contractCheck.column, contractCheck.permittedValues)); | ||
| handledIssueKeys.add(issueKey); | ||
| } else handledIssueKeys.add(issueKey); | ||
| } | ||
| for (const liveCheck of liveChecks) if (!contractChecks.some((c) => c.name === liveCheck.name)) { | ||
| const issueKey = `${tableName} ${liveCheck.name}`; | ||
| calls.push(new DropCheckConstraintCall(ddlSchema, tableName, liveCheck.name)); | ||
| handledIssueKeys.add(issueKey); | ||
| } | ||
| } | ||
| if (calls.length === 0 && handledIssueKeys.size === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((issue) => { | ||
| const node = issueNode(issue); | ||
| if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.check) return true; | ||
| const tableName = issueTableName(issue); | ||
| if (tableName === void 0) return true; | ||
| const checkName = blindCast(node).name; | ||
| return !handledIssueKeys.has(`${tableName} ${checkName}`); | ||
| }), | ||
| calls | ||
| }; | ||
| }; | ||
| /** | ||
| * Dispatches codec-typed storage types through their codec's | ||
| * `planTypeOperations` hook (the authoritative source for codec-driven DDL | ||
| * such as custom type creation). Codec extension/type ops are not modeled as | ||
| * diff nodes — this strategy drives entirely off `ctx.toContract.storage.types` | ||
| * + codec hooks, consuming nothing from the node issue list (there is no | ||
| * node-vocabulary equivalent of `type_missing` / `enum_values_changed`). | ||
| */ | ||
| const storageTypePlanCallStrategy = (issues, ctx) => { | ||
| const storageTypes = ctx.toContract.storage.types ?? {}; | ||
| if (Object.keys(storageTypes).length === 0) return { kind: "no_match" }; | ||
| const calls = []; | ||
| for (const [typeName, typeInstance] of Object.entries(storageTypes).sort(([a], [b]) => a.localeCompare(b))) { | ||
| const codecInstance = typeInstance; | ||
| const hook = ctx.codecHooks.get(codecInstance.codecId); | ||
| if (!hook?.planTypeOperations) continue; | ||
| const planResult = hook.planTypeOperations({ | ||
| typeName, | ||
| typeInstance: codecInstance, | ||
| contract: ctx.toContract, | ||
| schema: ctx.schema, | ||
| schemaName: ctx.schemaName, | ||
| policy: ctx.policy | ||
| }); | ||
| if (!planResult) continue; | ||
| for (const op of planResult.operations) calls.push(new RawSqlCall({ | ||
| ...op, | ||
| target: { | ||
| id: op.target.id, | ||
| details: buildTargetDetails("type", typeName, ctx.schemaName) | ||
| } | ||
| })); | ||
| } | ||
| if (calls.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues, | ||
| calls | ||
| }; | ||
| }; | ||
| /** | ||
| * Handles `not-found` column issues for NOT NULL columns without a contract | ||
| * default. Replaces the legacy `buildAddColumnItem` non-default branches. | ||
| * | ||
| * Two shapes: | ||
| * - Shared-temp-default safe: emit a single atomic composite op (add | ||
| * nullable → backfill identity value → `SET NOT NULL` → `DROP DEFAULT`). | ||
| * The temp-default value is resolved from the column node's `codecRef` | ||
| * (`resolveColumnTemporaryDefault`, wrapping `resolveIdentityValue`). | ||
| * - Empty-table guarded: emit a hand-built op with a `tableIsEmptyCheck` | ||
| * precheck so the failure message is "table is not empty" rather than the | ||
| * raw PG NOT NULL violation. | ||
| * | ||
| * "Normal" not-found column cases (nullable or has a contract default) are | ||
| * left for `mapNodeIssueToCall`'s default `AddColumnCall` emission. | ||
| */ | ||
| const notNullAddColumnCallStrategy = (issues, ctx) => { | ||
| const matched = []; | ||
| const calls = []; | ||
| const schemaLookups = buildSchemaLookupMap(ctx.schema); | ||
| const mutableCodecHooks = ctx.codecHooks; | ||
| const mutableStorageTypes = ctx.storageTypes; | ||
| for (const issue of issues) { | ||
| if (issueOutcome(issue) !== "not-found") continue; | ||
| const node = issueNode(issue); | ||
| if (node === void 0 || node.nodeKind !== RelationalSchemaNodeKind.column) continue; | ||
| const expected = blindCast(issue.expected); | ||
| if (expected.nullable !== false || expected.resolvedDefault !== void 0) continue; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) continue; | ||
| const namespaceId = namespaceIdForDdlSchema(ctx, ddlSchemaName); | ||
| const schemaName = namespaceId === UNBOUND_NAMESPACE_ID ? UNBOUND_NAMESPACE_ID : ddlSchemaName; | ||
| const contractTable = tableAt(ctx.toContract.storage, namespaceId, tableName); | ||
| const column = contractTable?.columns[expected.name]; | ||
| if (!contractTable || !column) continue; | ||
| const schemaTable = ctx.schema.tables[tableName]; | ||
| if (!schemaTable) continue; | ||
| const temporaryDefault = resolveColumnTemporaryDefault(expected, ctx.codecHooks); | ||
| const schemaLookup = schemaLookups.get(tableName); | ||
| const canUseSharedTempDefault = temporaryDefault !== null && canUseSharedTemporaryDefaultStrategy({ | ||
| table: contractTable, | ||
| schemaTable, | ||
| schemaLookup, | ||
| columnName: expected.name | ||
| }); | ||
| matched.push(issue); | ||
| if (canUseSharedTempDefault && temporaryDefault !== null) { | ||
| calls.push(new AddNotNullColumnWithTempDefaultCall({ | ||
| schemaName, | ||
| tableName, | ||
| columnName: expected.name, | ||
| column, | ||
| codecHooks: mutableCodecHooks, | ||
| storageTypes: mutableStorageTypes, | ||
| temporaryDefault | ||
| })); | ||
| continue; | ||
| } | ||
| calls.push(new AddNotNullColumnDirectCall(schemaName, tableName, expected.name, renderColumnDdl(expected.name, expected, ctx.codecHooks))); | ||
| } | ||
| if (matched.length === 0) return { kind: "no_match" }; | ||
| return { | ||
| kind: "match", | ||
| issues: issues.filter((i) => !matched.includes(i)), | ||
| calls | ||
| }; | ||
| }; | ||
| function canUseSharedTemporaryDefaultStrategy(options) { | ||
| const { table, schemaTable, schemaLookup, columnName } = options; | ||
| if (table.primaryKey?.columns.includes(columnName) && !schemaTable.primaryKey) return false; | ||
| for (const unique of table.uniques) { | ||
| if (!unique.columns.includes(columnName)) continue; | ||
| if (!schemaLookup || !hasUniqueConstraint(schemaLookup, unique.columns)) return false; | ||
| } | ||
| for (const foreignKey of table.foreignKeys) { | ||
| if (!foreignKey.source.columns.includes(columnName)) continue; | ||
| if (!schemaLookup || !hasForeignKey(schemaLookup, foreignKey)) return false; | ||
| } | ||
| return true; | ||
| } | ||
| /** | ||
| * Ordered list of Postgres planner strategies, shared by `migration plan` | ||
| * and `db update` / `db init`. The issue planner runs each strategy in | ||
| * order, letting it consume any issues it handles, and routes whatever's | ||
| * left through `mapNodeIssueToCall`. Behavior diverges purely on | ||
| * `policy.allowedOperationClasses`: | ||
| * | ||
| * - When `'data'` is allowed (`migration plan`), the data-safe strategies | ||
| * (`notNullBackfillCallStrategy`, `typeChangeCallStrategy`, | ||
| * `nullableTighteningCallStrategy`) consume their matching issues and emit | ||
| * `DataTransformCall` placeholders or recipe ops. | ||
| * | ||
| * - When `'data'` is not allowed (`db update` / `db init`), the | ||
| * placeholder-emitting strategies short-circuit to `no_match`, leaving | ||
| * the issue for the downstream strategies (`storageTypePlanCallStrategy`, | ||
| * `notNullAddColumnCallStrategy`) or the `mapNodeIssueToCall` default to | ||
| * handle with direct DDL. | ||
| * | ||
| * Codec-typed storage type entries are dispatched through | ||
| * `storageTypePlanCallStrategy`. | ||
| */ | ||
| const postgresPlannerStrategies = [ | ||
| notNullBackfillCallStrategy, | ||
| typeChangeCallStrategy, | ||
| nullableTighteningCallStrategy, | ||
| checkConstraintPlanCallStrategy, | ||
| storageTypePlanCallStrategy, | ||
| notNullAddColumnCallStrategy | ||
| ]; | ||
| //#endregion | ||
| //#region src/core/migrations/issue-planner.ts | ||
| /** | ||
| * Deterministic name for the element-non-null CHECK constraint on a scalar-array | ||
| * column. Distinct `_elem_not_null` suffix avoids collision with the enum | ||
| * value-set `_check` constraints. Re-emitting the same schema produces the same | ||
| * name, so `pg_get_constraintdef`-based verify sees no drift. | ||
| */ | ||
| function elementNonNullCheckName(tableName, columnName) { | ||
| return `${tableName}_${columnName}_elem_not_null`; | ||
| } | ||
| /** | ||
| * Predicate enforcing that a scalar-array column carries no NULL element. The | ||
| * array column itself may be NULL (container nullability is the column's NOT NULL | ||
| * clause); `array_position` over a NULL array yields NULL, which a CHECK treats | ||
| * as satisfied, so a nullable array column is unaffected. | ||
| */ | ||
| function elementNonNullCheckExpression(columnName) { | ||
| return `array_position(${quoteIdentifier(columnName)}, NULL) IS NULL`; | ||
| } | ||
| function issueConflict(kind, summary, location) { | ||
| return { | ||
| kind, | ||
| summary, | ||
| why: "Use `migration new` to author a custom migration for this change.", | ||
| ...location ? { location } : {} | ||
| }; | ||
| } | ||
| /** | ||
| * Classifies calls into DDL sequencing buckets. The order matches the | ||
| * legacy walk-schema planner's emission order so `db init` and `db update` | ||
| * produce byte-identical plans for the shared shape (deps → drops → tables | ||
| * → columns → alters → PKs → uniques → indexes → FKs). | ||
| * | ||
| * `dropType` (DROP TYPE for a managed native enum) is its own bucket, ordered | ||
| * after the general drop bucket: a type can only be dropped once every table | ||
| * whose column uses it is gone. A column→enum edge would make this precise, | ||
| * but `coalesceSubtreeIssues` removes the column issue under a whole-table | ||
| * drop, so the edge never reaches the graph; the coarse bucket stands in until | ||
| * that edge lands (the coordinate work is a later slice). | ||
| */ | ||
| function classifyCall(call) { | ||
| switch (call.factoryName) { | ||
| case "createExtension": | ||
| case "createSchema": | ||
| case "createNativeEnumType": | ||
| case "addNativeEnumValue": return "dep"; | ||
| case "dropNativeEnumType": return "dropType"; | ||
| case "dropTable": | ||
| case "dropColumn": | ||
| case "dropConstraint": | ||
| case "dropCheckConstraint": | ||
| case "dropIndex": | ||
| case "dropDefault": return "drop"; | ||
| case "addCheckConstraint": return "unique"; | ||
| case "createTable": return "table"; | ||
| case "enableRowLevelSecurity": | ||
| case "disableRowLevelSecurity": return "rlsEnable"; | ||
| case "createRlsPolicy": return "rlsPolicy"; | ||
| case "dropRlsPolicy": return "drop"; | ||
| case "addColumn": return "column"; | ||
| case "alterColumnType": | ||
| case "setNotNull": | ||
| case "dropNotNull": | ||
| case "setDefault": return "alter"; | ||
| case "addPrimaryKey": return "primaryKey"; | ||
| case "addUnique": return "unique"; | ||
| case "createIndex": return "index"; | ||
| case "addForeignKey": return "foreignKey"; | ||
| case "rawSql": | ||
| if (call.op?.target?.details?.objectType === "type") return "dep"; | ||
| return "alter"; | ||
| default: return "alter"; | ||
| } | ||
| } | ||
| const DEFAULT_POLICY = { allowedOperationClasses: [ | ||
| "additive", | ||
| "widening", | ||
| "destructive", | ||
| "data" | ||
| ] }; | ||
| function emptySchemaIR() { | ||
| return new SqlSchemaIR({ tables: {} }); | ||
| } | ||
| function conflictKindForCall(call) { | ||
| switch (call.factoryName) { | ||
| case "alterColumnType": return "typeMismatch"; | ||
| case "setNotNull": | ||
| case "dropNotNull": return "nullabilityConflict"; | ||
| case "addForeignKey": | ||
| case "dropConstraint": return "foreignKeyConflict"; | ||
| case "createIndex": | ||
| case "dropIndex": return "indexIncompatible"; | ||
| default: return "missingButNonAdditive"; | ||
| } | ||
| } | ||
| function locationForCall(call) { | ||
| const anyCall = call; | ||
| const location = {}; | ||
| if (anyCall.tableName) { | ||
| location.entityKind = "table"; | ||
| location.entityName = anyCall.tableName; | ||
| } else if (anyCall.typeName) { | ||
| location.entityKind = "native_enum"; | ||
| location.entityName = anyCall.typeName; | ||
| } | ||
| if (anyCall.columnName) location.column = anyCall.columnName; | ||
| if (anyCall.indexName) location.index = anyCall.indexName; | ||
| if (anyCall.constraintName) location.constraint = anyCall.constraintName; | ||
| return Object.keys(location).length > 0 ? location : void 0; | ||
| } | ||
| function conflictForDisallowedCall(call, allowed) { | ||
| const summary = `Operation "${call.label}" requires class "${call.operationClass}", but policy allows only: ${allowed.join(", ")}`; | ||
| const location = locationForCall(call); | ||
| return { | ||
| kind: conflictKindForCall(call), | ||
| summary, | ||
| why: "Use `migration new` to author a custom migration for this change.", | ||
| ...location ? { location } : {} | ||
| }; | ||
| } | ||
| /** The diff node an issue concerns — expected when present, else the actual (extra) node. */ | ||
| function issueNode(issue) { | ||
| const node = issue.expected ?? issue.actual; | ||
| if (node === void 0) return void 0; | ||
| return blindCast(node); | ||
| } | ||
| /** DDL schema segment of a table-or-descendant issue path: `[database, ddlSchema, table, …]`. */ | ||
| function issueSchemaName(issue) { | ||
| return issue.path[1]; | ||
| } | ||
| /** Table segment of a table-or-descendant issue path: `[database, ddlSchema, table, …]`. */ | ||
| function issueTableName(issue) { | ||
| return issue.path[2]; | ||
| } | ||
| /** Column name embedded in a column/default issue path segment (`column:<name>`). */ | ||
| function issueColumnName(issue) { | ||
| const segment = issue.path[3]; | ||
| if (segment === void 0 || !segment.startsWith("column:")) return void 0; | ||
| return segment.slice(7); | ||
| } | ||
| /** | ||
| * The DDL schema name to use when EMITTING an op against `ddlSchemaName` (the | ||
| * diff tree's resolved physical schema, `issueSchemaName(issue)`). The | ||
| * unbound namespace's diff-tree identity resolves to `public` (a concrete | ||
| * physical default the differ needs in order to compare its tree against | ||
| * introspection — `resolveDdlSchemaForNamespaceStorage`), but DDL EMISSION | ||
| * must stay unqualified so the live connection's `search_path` resolves it | ||
| * at runtime (`boundSchema`). Recovers the logical namespace id via the | ||
| * contract and substitutes the unbound sentinel back in when it resolves | ||
| * there; every other namespace's `ddlSchemaName` already agrees between the | ||
| * two resolution paths, so it passes through unchanged. | ||
| */ | ||
| function emissionSchemaName(ctx, ddlSchemaName) { | ||
| return resolveNamespaceIdForDdlSchema(ctx.toContract, ddlSchemaName) === UNBOUND_NAMESPACE_ID ? UNBOUND_NAMESPACE_ID : ddlSchemaName; | ||
| } | ||
| /** | ||
| * Whether a column node is a scalar-array (`many: true`) column. The family | ||
| * converter (`contractToSchemaIR`'s `convertColumn`) never stamps `many` on | ||
| * the derived node — array-ness is folded into the `[]` suffix on | ||
| * `nativeType` instead — so the node-derived check reads the suffix; `.many` | ||
| * is still checked first for nodes a caller stamps directly (e.g. hand-built | ||
| * test fixtures, or an adapter that populates it at introspection). | ||
| */ | ||
| function isManyColumn(column) { | ||
| return column.many === true || column.nativeType.endsWith("[]"); | ||
| } | ||
| /** Whether the expected/actual native type (resolved, or raw+many fallback) differs — mirrors `SqlColumnIR.isEqualTo`'s type comparison. */ | ||
| function columnTypeChanged(expected, actual) { | ||
| if (expected.resolvedNativeType !== void 0 && actual.resolvedNativeType !== void 0) return expected.resolvedNativeType !== actual.resolvedNativeType; | ||
| return expected.nativeType !== actual.nativeType || Boolean(expected.many) !== Boolean(actual.many); | ||
| } | ||
| /** | ||
| * The generic differ is total: a missing/extra table (or column) emits an | ||
| * issue for itself AND for every node in its subtree. `CreateTable`/`DropTable` | ||
| * and `AddColumn`/`DropColumn` already account for the whole subtree, so the | ||
| * nested issues are redundant — coalescing drops any issue whose path is a | ||
| * strict descendant of a `not-found`/`not-expected` issue's path. Run over the | ||
| * relational subset ONLY (policy issues and synthesized namespace issues are | ||
| * handled on their own paths, never coalesced against tables). | ||
| */ | ||
| function coalesceSubtreeIssues(issues) { | ||
| const collapsingPaths = issues.filter((issue) => issueOutcome(issue) !== "not-equal").map((issue) => issue.path); | ||
| if (collapsingPaths.length === 0) return issues; | ||
| return issues.filter((issue) => !collapsingPaths.some((ancestor) => isStrictDescendantPath(issue.path, ancestor))); | ||
| } | ||
| function isStrictDescendantPath(path, ancestor) { | ||
| if (path.length <= ancestor.length) return false; | ||
| for (let i = 0; i < ancestor.length; i += 1) if (path[i] !== ancestor[i]) return false; | ||
| return true; | ||
| } | ||
| function fkSpecFromNode(fk, tableName) { | ||
| return { | ||
| name: fk.name ?? `${tableName}_${fk.columns.join("_")}_fkey`, | ||
| columns: [...fk.columns], | ||
| references: { | ||
| schema: fk.referencedSchema ?? "", | ||
| table: fk.referencedTable, | ||
| columns: [...fk.referencedColumns] | ||
| }, | ||
| ...ifDefined("onDelete", fk.onDelete), | ||
| ...ifDefined("onUpdate", fk.onUpdate) | ||
| }; | ||
| } | ||
| /** | ||
| * Builds the `CreateTable` + child `CreateIndex` / `AddForeignKey` / `AddUnique` | ||
| * calls for a newly-expected table, reading only the table node's children. The | ||
| * PK and element-non-null CHECKs go inline as table constraints; indexes | ||
| * (declared + FK-backing, already merged and ordered at derivation) and the | ||
| * FK / unique constraints are separate calls (re-bucketed downstream). Every | ||
| * column's DDL is resolved from its `codecRef` via `renderColumnDdl`. | ||
| */ | ||
| function buildCreateTableCallsFromNode(schemaName, ddlSchemaName, table, codecHooks) { | ||
| const ddlColumns = Object.values(table.columns).map((c) => renderColumnDdl(c.name, c, codecHooks)); | ||
| const primaryKeyConstraints = table.primaryKey ? [contractFree.primaryKey([...table.primaryKey.columns], { ...ifDefined("name", table.primaryKey.name) })] : []; | ||
| const elementNonNullChecks = Object.values(table.columns).filter((c) => isManyColumn(c)).map((c) => contractFree.checkExpression(elementNonNullCheckName(table.name, c.name), elementNonNullCheckExpression(c.name))); | ||
| const allTableConstraints = [...primaryKeyConstraints, ...elementNonNullChecks]; | ||
| const calls = [new CreateTableCall(schemaName, table.name, ddlColumns, allTableConstraints.length > 0 ? allTableConstraints : void 0)]; | ||
| for (const index of table.indexes) { | ||
| const indexName = index.name ?? defaultIndexName(table.name, index.columns); | ||
| const extras = {}; | ||
| if (index.type !== void 0) extras.type = index.type; | ||
| if (index.options !== void 0) extras.options = index.options; | ||
| calls.push(new CreateIndexCall(schemaName, table.name, indexName, [...index.columns], extras)); | ||
| } | ||
| for (const fk of table.foreignKeys) calls.push(new AddForeignKeyCall(schemaName, table.name, fkSpecFromNode(fk, table.name))); | ||
| for (const unique of table.uniques) { | ||
| const constraintName = unique.name ?? `${table.name}_${unique.columns.join("_")}_key`; | ||
| calls.push(new AddUniqueCall(schemaName, table.name, constraintName, [...unique.columns])); | ||
| } | ||
| if (table.rlsEnabled) calls.push(new EnableRowLevelSecurityCall(ddlSchemaName, table.name)); | ||
| return calls; | ||
| } | ||
| function nodeConflict(kind, message) { | ||
| return issueConflict(kind, message); | ||
| } | ||
| /** | ||
| * True when `actualMembers` (the live database's ordered members) is a | ||
| * strict, order-preserving prefix of `expectedMembers` (the contract's) — | ||
| * the database already carries every contract member, in declaration | ||
| * order, and the contract declares at least one member the database still | ||
| * lacks. Any other relationship — a renamed value, a removed value, a | ||
| * reordering, or the database holding members the contract lacks — is not | ||
| * a suffix append. | ||
| */ | ||
| function isNativeEnumSuffixAppend(actualMembers, expectedMembers) { | ||
| if (actualMembers.length >= expectedMembers.length) return false; | ||
| return actualMembers.every((member, index) => member === expectedMembers[index]); | ||
| } | ||
| /** Operator-worded refusal for a native-enum member change beyond a suffix append (design ruling — tests match this verbatim). */ | ||
| function nativeEnumMemberChangeRefusal(options) { | ||
| return `Native enum type "${options.ddlSchemaName}"."${options.typeName}" changed beyond appending new values (contract declares [${options.expectedMembers.join(", ")}], database has [${options.actualMembers.join(", ")}]). Prisma Next does not modify a native enum's existing values (rename, removal, reorder) — see https://pris.ly/d/postgres-native-enums. Author the change manually with \`migration new\`.`; | ||
| } | ||
| /** | ||
| * Managed native-enum issue -> op lowering. A missing declared type creates | ||
| * it; an unclaimed live type drops it (ownership-scoped upstream by | ||
| * `retainUnownedExtras`, destructiveness gated by the operation-class | ||
| * policy); a paired member-value mismatch lowers to one `ALTER TYPE ... ADD | ||
| * VALUE` per appended member when the database's members are a strict, | ||
| * order-preserving prefix of the contract's — any other change (rename, | ||
| * removal, reorder, or the database holding members the contract lacks) is | ||
| * refused with a NAMED diagnostic, never a silent no-op and never a | ||
| * drop-and-recreate. | ||
| */ | ||
| function mapNativeEnumNodeIssue(issue, ctx) { | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| if (ddlSchemaName === void 0) return notOk(nodeConflict("unsupportedOperation", `Enum issue has no schema in its path: ${issue.path.join("/")}`)); | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const expected = blindCast(issue.expected); | ||
| return ok([new CreateNativeEnumTypeCall(schemaName, expected.typeName, expected.members)]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropNativeEnumTypeCall(schemaName, blindCast(issue.actual).typeName)]); | ||
| const expected = blindCast(issue.expected); | ||
| const actual = blindCast(issue.actual); | ||
| if (isNativeEnumSuffixAppend(actual.members, expected.members)) return ok(expected.members.slice(actual.members.length).map((value) => new AddNativeEnumValueCall(schemaName, expected.typeName, value))); | ||
| return notOk(nodeConflict("unsupportedOperation", nativeEnumMemberChangeRefusal({ | ||
| ddlSchemaName, | ||
| typeName: expected.typeName, | ||
| expectedMembers: expected.members, | ||
| actualMembers: actual.members | ||
| }))); | ||
| } | ||
| function mapTableNodeIssue(issue, schemaName, ddlSchemaName, codecHooks) { | ||
| if (issueOutcome(issue) === "not-found") return ok(buildCreateTableCallsFromNode(schemaName, ddlSchemaName, blindCast(issue.expected), codecHooks)); | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropTableCall(schemaName, blindCast(issue.actual).name)]); | ||
| const expected = blindCast(issue.expected); | ||
| const actual = blindCast(issue.actual); | ||
| if (expected.rlsEnabled && !actual.rlsEnabled) return ok([new EnableRowLevelSecurityCall(ddlSchemaName, expected.name)]); | ||
| if (!expected.rlsEnabled && actual.rlsEnabled) return ok([new DisableRowLevelSecurityCall(ddlSchemaName, expected.name)]); | ||
| return notOk(nodeConflict("unsupportedOperation", `unhandled table-attribute drift on "${expected.name}": table not-equal with no rlsEnabled delta (a second table attribute drifted)`)); | ||
| } | ||
| function mapColumnNodeIssue(issue, schemaName, tableName, codecHooks) { | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const column = blindCast(issue.expected); | ||
| return ok([new AddColumnCall(schemaName, tableName, renderColumnDdl(column.name, column, codecHooks))]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropColumnCall(schemaName, tableName, blindCast(issue.actual).name)]); | ||
| const expected = blindCast(issue.expected); | ||
| const actual = blindCast(issue.actual); | ||
| const calls = []; | ||
| if (columnTypeChanged(expected, actual)) { | ||
| const { qualifiedTargetType, formatTypeExpected } = renderColumnAlterType(expected, codecHooks); | ||
| calls.push(new AlterColumnTypeCall(schemaName, tableName, expected.name, { | ||
| qualifiedTargetType, | ||
| formatTypeExpected, | ||
| rawTargetTypeForLabel: qualifiedTargetType | ||
| })); | ||
| } | ||
| if (expected.nullable !== actual.nullable) calls.push(expected.nullable ? new DropNotNullCall(schemaName, tableName, expected.name) : new SetNotNullCall(schemaName, tableName, expected.name)); | ||
| return ok(calls); | ||
| } | ||
| function mapColumnDefaultNodeIssue(issue, schemaName, tableName, columnName) { | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropDefaultCall(schemaName, tableName, columnName)]); | ||
| if (issue.expected === void 0) return ok([]); | ||
| const defaultSql = renderColumnDefaultSql(blindCast(issue.expected)); | ||
| if (!defaultSql) return ok([]); | ||
| return ok([new SetDefaultCall(schemaName, tableName, columnName, defaultSql, issueOutcome(issue) === "not-equal" ? "widening" : "additive")]); | ||
| } | ||
| function mapPrimaryKeyNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const pk = blindCast(issue.expected); | ||
| return ok([new AddPrimaryKeyCall(schemaName, tableName, pk.name ?? `${tableName}_pkey`, [...pk.columns])]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropConstraintCall(schemaName, tableName, blindCast(issue.actual).name ?? `${tableName}_pkey`, "primaryKey")]); | ||
| return notOk(nodeConflict("indexIncompatible", issue.path.join("/"))); | ||
| } | ||
| function mapForeignKeyNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-found") return ok([new AddForeignKeyCall(schemaName, tableName, fkSpecFromNode(blindCast(issue.expected), tableName))]); | ||
| if (issueOutcome(issue) === "not-expected") { | ||
| const fk = blindCast(issue.actual); | ||
| return ok([new DropConstraintCall(schemaName, tableName, fk.name ?? `${tableName}_${fk.columns.join("_")}_fkey`, "foreignKey")]); | ||
| } | ||
| return notOk(nodeConflict("foreignKeyConflict", issue.path.join("/"))); | ||
| } | ||
| function mapUniqueNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const unique = blindCast(issue.expected); | ||
| return ok([new AddUniqueCall(schemaName, tableName, unique.name ?? `${tableName}_${unique.columns.join("_")}_key`, [...unique.columns])]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") { | ||
| const unique = blindCast(issue.actual); | ||
| return ok([new DropConstraintCall(schemaName, tableName, unique.name ?? `${tableName}_${unique.columns.join("_")}_key`, "unique")]); | ||
| } | ||
| return notOk(nodeConflict("indexIncompatible", issue.path.join("/"))); | ||
| } | ||
| function mapIndexNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-found") { | ||
| const index = blindCast(issue.expected); | ||
| const indexName = index.name ?? defaultIndexName(tableName, index.columns); | ||
| const extras = {}; | ||
| if (index.type !== void 0) extras.type = index.type; | ||
| if (index.options !== void 0) extras.options = index.options; | ||
| return ok([new CreateIndexCall(schemaName, tableName, indexName, [...index.columns], extras)]); | ||
| } | ||
| if (issueOutcome(issue) === "not-expected") { | ||
| const index = blindCast(issue.actual); | ||
| return ok([new DropIndexCall(schemaName, tableName, index.name ?? defaultIndexName(tableName, index.columns))]); | ||
| } | ||
| return notOk(nodeConflict("indexIncompatible", issue.path.join("/"))); | ||
| } | ||
| function mapCheckNodeIssue(issue, schemaName, tableName) { | ||
| if (issueOutcome(issue) === "not-expected") return ok([new DropCheckConstraintCall(schemaName, tableName, blindCast(issue.actual).name)]); | ||
| return notOk(nodeConflict("unsupportedOperation", `Check constraint drift on "${tableName}" — handled by checkConstraintPlanCallStrategy: ${issue.path.join("/")}`)); | ||
| } | ||
| /** | ||
| * Maps one node-typed diff issue to its migration call(s), dispatching on the | ||
| * node's `nodeKind` and the issue's presence-derived `issueOutcome`, reading | ||
| * nodes and resolving column DDL from `codecRef` via `column-ddl-rendering.ts`. | ||
| */ | ||
| function mapNodeIssueToCall(issue, ctx) { | ||
| const node = issueNode(issue); | ||
| if (node === void 0) return notOk(nodeConflict("unsupportedOperation", `Issue carries neither an expected nor an actual node: ${issue.path.join("/")}`)); | ||
| if (node.nodeKind === PostgresSchemaNodeKind.namespace) { | ||
| if (issueOutcome(issue) !== "not-found") return notOk(nodeConflict("unsupportedOperation", `Unexpected namespace drift: ${issue.path.join("/")}`)); | ||
| return ok([new CreateSchemaCall(blindCast(issue.expected).schemaName)]); | ||
| } | ||
| if (node.nodeKind === PostgresSchemaNodeKind.nativeEnum) return mapNativeEnumNodeIssue(issue, ctx); | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| const tableName = issueTableName(issue); | ||
| if (ddlSchemaName === void 0 || tableName === void 0) return notOk(nodeConflict("unsupportedOperation", `Issue has no schema/table in its path: ${issue.path.join("/")}`)); | ||
| const schemaName = emissionSchemaName(ctx, ddlSchemaName); | ||
| switch (node.nodeKind) { | ||
| case PostgresSchemaNodeKind.table: return mapTableNodeIssue(issue, schemaName, ddlSchemaName, ctx.codecHooks); | ||
| case RelationalSchemaNodeKind.column: return mapColumnNodeIssue(issue, schemaName, tableName, ctx.codecHooks); | ||
| case RelationalSchemaNodeKind.columnDefault: { | ||
| const columnName = issueColumnName(issue); | ||
| if (columnName === void 0) return notOk(nodeConflict("unsupportedOperation", `Default issue has no column in its path: ${issue.path.join("/")}`)); | ||
| return mapColumnDefaultNodeIssue(issue, schemaName, tableName, columnName); | ||
| } | ||
| case RelationalSchemaNodeKind.primaryKey: return mapPrimaryKeyNodeIssue(issue, schemaName, tableName); | ||
| case RelationalSchemaNodeKind.foreignKey: return mapForeignKeyNodeIssue(issue, schemaName, tableName); | ||
| case RelationalSchemaNodeKind.unique: return mapUniqueNodeIssue(issue, schemaName, tableName); | ||
| case RelationalSchemaNodeKind.index: return mapIndexNodeIssue(issue, schemaName, tableName); | ||
| case RelationalSchemaNodeKind.check: return mapCheckNodeIssue(issue, schemaName, tableName); | ||
| default: return notOk(nodeConflict("unsupportedOperation", `Unhandled node kind: ${node.nodeKind}`)); | ||
| } | ||
| } | ||
| /** | ||
| * Runs the ordered strategy list over the node-typed diff issues, maps | ||
| * leftover issues via {@link mapNodeIssueToCall}, applies operation-class | ||
| * policy gating, and buckets calls into the fixed DDL emission order (dep → | ||
| * drop → table → column → recipe → alter → primaryKey → unique → index → | ||
| * foreignKey). | ||
| */ | ||
| function planIssues(options) { | ||
| const policyProvided = options.policy !== void 0; | ||
| const policy = options.policy ?? DEFAULT_POLICY; | ||
| const schema = options.schema ?? emptySchemaIR(); | ||
| const frameworkComponents = options.frameworkComponents ?? []; | ||
| const context = { | ||
| toContract: options.toContract, | ||
| fromContract: options.fromContract, | ||
| schemaName: options.schemaName, | ||
| codecHooks: options.codecHooks, | ||
| storageTypes: options.storageTypes, | ||
| schema, | ||
| policy, | ||
| frameworkComponents | ||
| }; | ||
| const strategies = options.strategies ?? postgresPlannerStrategies; | ||
| let remaining = options.issues; | ||
| const recipeCalls = []; | ||
| const bucketablePatternCalls = []; | ||
| for (const strategy of strategies) { | ||
| const result = strategy(remaining, context); | ||
| if (result.kind === "match") { | ||
| remaining = result.issues; | ||
| if (result.recipe) recipeCalls.push(...result.calls); | ||
| else bucketablePatternCalls.push(...result.calls); | ||
| } | ||
| } | ||
| const sorted = orderIssuesByDependencies(remaining); | ||
| const defaultCalls = []; | ||
| const conflicts = []; | ||
| for (const issue of sorted) { | ||
| const result = mapNodeIssueToCall(issue, context); | ||
| if (result.ok) defaultCalls.push(...result.value); | ||
| else conflicts.push(result.failure); | ||
| } | ||
| const allowed = policy.allowedOperationClasses; | ||
| let gatedDefault = defaultCalls; | ||
| let gatedRecipe = recipeCalls; | ||
| let gatedBucketable = bucketablePatternCalls; | ||
| if (policyProvided) { | ||
| const keepIfAllowed = (bucket) => (call) => { | ||
| if (allowed.includes(call.operationClass)) { | ||
| bucket.push(call); | ||
| return; | ||
| } | ||
| conflicts.push(conflictForDisallowedCall(call, allowed)); | ||
| }; | ||
| const gatedDefaultBucket = []; | ||
| const gatedRecipeBucket = []; | ||
| const gatedBucketableBucket = []; | ||
| defaultCalls.forEach(keepIfAllowed(gatedDefaultBucket)); | ||
| recipeCalls.forEach(keepIfAllowed(gatedRecipeBucket)); | ||
| bucketablePatternCalls.forEach(keepIfAllowed(gatedBucketableBucket)); | ||
| gatedDefault = gatedDefaultBucket; | ||
| gatedRecipe = gatedRecipeBucket; | ||
| gatedBucketable = gatedBucketableBucket; | ||
| } | ||
| if (conflicts.length > 0) return notOk(conflicts); | ||
| const combinedBucketable = [...gatedDefault, ...gatedBucketable]; | ||
| const byCategory = (cat) => combinedBucketable.filter((c) => classifyCall(c) === cat); | ||
| return ok({ calls: [ | ||
| ...byCategory("dep"), | ||
| ...byCategory("drop"), | ||
| ...byCategory("dropType"), | ||
| ...byCategory("table"), | ||
| ...byCategory("column"), | ||
| ...gatedRecipe, | ||
| ...byCategory("alter"), | ||
| ...byCategory("primaryKey"), | ||
| ...byCategory("unique"), | ||
| ...byCategory("index"), | ||
| ...byCategory("foreignKey"), | ||
| ...byCategory("rlsEnable") | ||
| ] }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/control-policy.ts | ||
| /** | ||
| * Factory calls that create a whole, previously-absent top-level storage | ||
| * object. Used to decide whether `tolerated` permits a call (it only allows | ||
| * creating absent objects, never modifying existing ones). | ||
| * | ||
| * Deliberately an explicit, closed set rather than a `factoryName` | ||
| * create/alter/drop classification: it answers exactly one yes/no question | ||
| * and is fail-closed. Any call not listed here — including future or | ||
| * extension-contributed factories — is treated as NOT object-creation, so it | ||
| * is suppressed under `tolerated` rather than permissively emitted. | ||
| * | ||
| * Lists the creation factories that actually reach the call-side resolver | ||
| * (`resolvePostgresCallControlPolicySubject`) — today only RLS policy | ||
| * creation, from `planPostgresSchemaDiff`. Every other whole-object create | ||
| * (table, schema, native enum, and RLS enablement) is constructed inside | ||
| * `planIssues` and graded on the node side by | ||
| * `resolvePostgresNodeIssueCreationFactoryName`, so none of them are listed | ||
| * here. | ||
| */ | ||
| const OBJECT_CREATION_FACTORIES = /* @__PURE__ */ new Set(["createRlsPolicy"]); | ||
| function createsNewTopLevelObject(call) { | ||
| return OBJECT_CREATION_FACTORIES.has(call.factoryName); | ||
| } | ||
| function ddlSchemaNameForNamespace(contract, namespaceId) { | ||
| const namespace = contract.storage.namespaces[namespaceId]; | ||
| return isPostgresSchema(namespace) ? namespace.ddlSchemaName(contract.storage) : namespaceId; | ||
| } | ||
| /** | ||
| * Resolve the namespace a declared storage entity lives in by walking every | ||
| * namespace for one whose `entries` map actually contains the coordinate — | ||
| * a table by its name, a native enum by its physical type name (see | ||
| * {@link postgresNodeStorageCoordinate}). When `ddlSchemaName` is given, the | ||
| * match must also land in that DDL schema (disambiguates same-named entities | ||
| * declared under different namespaces); when omitted, the first namespace | ||
| * that declares the entity wins. Falls back to {@link UNBOUND_NAMESPACE_ID} | ||
| * when no namespace declares it — e.g. an extra/dropped entity the contract | ||
| * doesn't claim at all. | ||
| */ | ||
| function resolveNamespaceIdForEntity(contract, coordinate, ddlSchemaName) { | ||
| for (const namespaceId of Object.keys(contract.storage.namespaces)) { | ||
| if (!entityAt(contract.storage, { | ||
| namespaceId, | ||
| ...coordinate | ||
| })) continue; | ||
| if (ddlSchemaName === void 0 || ddlSchemaNameForNamespace(contract, namespaceId) === ddlSchemaName) return namespaceId; | ||
| } | ||
| return UNBOUND_NAMESPACE_ID; | ||
| } | ||
| function resolveNamespaceIdForTable(contract, tableName, ddlSchemaName) { | ||
| return resolveNamespaceIdForEntity(contract, { | ||
| entityKind: "table", | ||
| entityName: tableName | ||
| }, ddlSchemaName); | ||
| } | ||
| function resolveNamespaceIdForDdlSchema(contract, ddlSchemaName) { | ||
| for (const namespaceId of Object.keys(contract.storage.namespaces)) { | ||
| const ns = contract.storage.namespaces[namespaceId]; | ||
| if (isPostgresSchema(ns) && ns.ddlSchemaName(contract.storage) === ddlSchemaName) return namespaceId; | ||
| if (namespaceId === ddlSchemaName) return namespaceId; | ||
| } | ||
| return UNBOUND_NAMESPACE_ID; | ||
| } | ||
| function postgresCallFields(call) { | ||
| return { | ||
| ...ifDefined("schemaName", "schemaName" in call ? call.schemaName : void 0), | ||
| ...ifDefined("tableName", "tableName" in call ? call.tableName : void 0), | ||
| ...ifDefined("columnName", "columnName" in call ? call.columnName : void 0) | ||
| }; | ||
| } | ||
| function formatSuppressionSubjectLabel(subject, contract) { | ||
| if (subject === void 0) return "unknown"; | ||
| const ddlSchema = ddlSchemaNameForNamespace(contract, subject.namespaceId); | ||
| if (subject.entityKind !== void 0 && subject.entityName !== void 0) return `${subject.entityKind} "${ddlSchema}.${subject.entityName}"`; | ||
| return `namespace "${ddlSchema}"`; | ||
| } | ||
| function postgresSuppressionSummary(subjectLabel, subject, policy) { | ||
| const namespace = subject?.namespaceId ?? "unknown"; | ||
| const declared = subject?.explicitNodeControlPolicy; | ||
| if (policy === "external" && declared === "managed") return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control 'external' but declared 'managed'`; | ||
| return `control policy suppressed: ${subjectLabel} — namespace '${namespace}' has effective control '${policy}'${declared ? ` but declared '${declared}'` : ""}`; | ||
| } | ||
| /** | ||
| * Render one family {@link SuppressionRecord} into a target `SqlPlannerConflict`. | ||
| * The family decides *that* a subject is suppressed and hands over the raw | ||
| * coordinate + policy; the label, message, and location are rendered here, | ||
| * driven entirely by the subject's own `(entityKind, entityName)` coordinate | ||
| * — no target-owned table-vs-enum vocabulary. | ||
| */ | ||
| function renderPostgresSuppression(record, contract) { | ||
| const subject = record.subject; | ||
| return { | ||
| kind: "controlPolicySuppressedCall", | ||
| summary: postgresSuppressionSummary(formatSuppressionSubjectLabel(subject, contract), subject, record.policy), | ||
| location: { | ||
| ...ifDefined("namespaceId", subject?.namespaceId), | ||
| ...ifDefined("entityKind", subject?.entityKind), | ||
| ...ifDefined("entityName", subject?.entityName), | ||
| ...ifDefined("column", subject?.column) | ||
| }, | ||
| meta: { | ||
| controlPolicy: record.policy, | ||
| ...ifDefined("factoryName", record.factoryName), | ||
| ...ifDefined("declaredControlPolicy", subject?.explicitNodeControlPolicy) | ||
| } | ||
| }; | ||
| } | ||
| function resolvePostgresCallControlPolicySubject(call, contract) { | ||
| const callFields = postgresCallFields(call); | ||
| const createsNewObject = createsNewTopLevelObject(call); | ||
| if (call.factoryName === "createSchema" && callFields.schemaName) return { | ||
| namespaceId: resolveNamespaceIdForDdlSchema(contract, callFields.schemaName), | ||
| createsNewObject | ||
| }; | ||
| if (callFields.tableName) { | ||
| const namespaceId = resolveNamespaceIdForTable(contract, callFields.tableName, callFields.schemaName); | ||
| const tableControlPolicy = entityAt(contract.storage, { | ||
| namespaceId, | ||
| entityKind: "table", | ||
| entityName: callFields.tableName | ||
| })?.control; | ||
| return { | ||
| namespaceId, | ||
| entityKind: "table", | ||
| entityName: callFields.tableName, | ||
| ...ifDefined("column", callFields.columnName), | ||
| ...ifDefined("explicitNodeControlPolicy", tableControlPolicy), | ||
| createsNewObject | ||
| }; | ||
| } | ||
| if (callFields.schemaName) return { | ||
| namespaceId: resolveNamespaceIdForDdlSchema(contract, callFields.schemaName), | ||
| createsNewObject | ||
| }; | ||
| } | ||
| /** | ||
| * Node kinds whose *absence* is the creation of a whole, top-level Postgres | ||
| * object: a namespace, a table, or a native enum. Used by | ||
| * {@link resolvePostgresNodeIssueCreationFactoryName} to decide whether a | ||
| * `tolerated` subject permits the issue to flow into the planner | ||
| * (create-if-absent) and to seed the suppressed-subject warning's | ||
| * `factoryName` when the planner is skipped. RLS policy creation is not | ||
| * listed here — policy issues never reach this issue-based partition (they | ||
| * are routed to `planPostgresSchemaDiff` and gated via the call-based | ||
| * {@link resolvePostgresCallControlPolicySubject} instead). | ||
| */ | ||
| const POSTGRES_NODE_CREATION_FACTORY = Object.freeze({ | ||
| [PostgresSchemaNodeKind.namespace]: "createSchema", | ||
| [PostgresSchemaNodeKind.table]: "createTable", | ||
| [PostgresSchemaNodeKind.nativeEnum]: "createNativeEnumType" | ||
| }); | ||
| /** | ||
| * A table `not-equal` issue whose `rlsEnabled` flips OFF→ON (expected on, | ||
| * actual off) is enablement toward `ENABLE ROW LEVEL SECURITY`. It is | ||
| * creation-class on the node side because `isEnablementCreationIssue` and | ||
| * {@link resolvePostgresNodeIssueCreationFactoryName} treat this OFF→ON delta | ||
| * as a creation: enabling RLS establishes the fail-closed guard the declared | ||
| * policy set attaches to, the same grant `tolerated` extends to creating the | ||
| * policies themselves. The opposite direction (`DISABLE`) is a modification | ||
| * and stays managed-only. Keying on the actual delta (not just the expected | ||
| * bit) keeps this correct if a second table attribute ever joins | ||
| * `isEqualTo`: a not-equal with no `rlsEnabled` delta is not enablement, so | ||
| * it is not admitted as creation-class here. | ||
| */ | ||
| function isEnablementCreationIssue(issue) { | ||
| if (issueOutcome(issue) !== "not-equal") return false; | ||
| const { expected, actual } = issue; | ||
| return expected !== void 0 && actual !== void 0 && PostgresTableSchemaNode.is(expected) && PostgresTableSchemaNode.is(actual) && expected.rlsEnabled === true && actual.rlsEnabled === false; | ||
| } | ||
| function resolvePostgresNodeIssueCreationFactoryName(issue) { | ||
| if (isEnablementCreationIssue(issue)) return "enableRowLevelSecurity"; | ||
| if (issueOutcome(issue) !== "not-found") return void 0; | ||
| const node = issue.expected ?? issue.actual; | ||
| if (node === void 0) return void 0; | ||
| return POSTGRES_NODE_CREATION_FACTORY[node.nodeKind]; | ||
| } | ||
| /** | ||
| * Resolves the control-policy subject for a node-typed {@link SchemaDiffIssue} | ||
| * (the issue-side mirror of `resolvePostgresCallControlPolicySubject`). | ||
| * Storage entities resolve off their node coordinate; a sub-entity issue | ||
| * resolves off the enclosing table read from the issue path; an unclaimed | ||
| * extra falls back to the unbound coordinate. A role always resolves | ||
| * `'external'` — roles are referenced, never owned. | ||
| */ | ||
| function resolvePostgresNodeIssueControlPolicySubject(issue, contract) { | ||
| const node = issue.expected ?? issue.actual; | ||
| if (node === void 0) return void 0; | ||
| if (node.nodeKind === PostgresSchemaNodeKind.namespace) return { | ||
| namespaceId: resolveNamespaceIdForDdlSchema(contract, blindCast(node).schemaName), | ||
| createsNewObject: issueOutcome(issue) === "not-found" | ||
| }; | ||
| if (node.nodeKind === PostgresSchemaNodeKind.role) { | ||
| const roleName = issue.path[1]; | ||
| return { | ||
| namespaceId: UNBOUND_NAMESPACE_ID, | ||
| explicitNodeControlPolicy: (roleName === void 0 ? void 0 : entityAt(contract.storage, { | ||
| namespaceId: UNBOUND_NAMESPACE_ID, | ||
| entityKind: "role", | ||
| entityName: roleName | ||
| }))?.control ?? "external", | ||
| createsNewObject: false | ||
| }; | ||
| } | ||
| const coordinate = postgresNodeStorageCoordinate(node); | ||
| if (coordinate !== void 0) { | ||
| const namespaceId = resolveNamespaceIdForEntity(contract, coordinate, issue.path[1]); | ||
| const entityControl = entityAt(contract.storage, { | ||
| namespaceId, | ||
| ...coordinate | ||
| })?.control; | ||
| return { | ||
| namespaceId, | ||
| ...coordinate, | ||
| ...ifDefined("column", issueColumnName(issue)), | ||
| ...ifDefined("explicitNodeControlPolicy", entityControl), | ||
| createsNewObject: resolvePostgresNodeIssueCreationFactoryName(issue) !== void 0 | ||
| }; | ||
| } | ||
| const tableName = issue.path[2]; | ||
| if (tableName === void 0) return void 0; | ||
| const ddlSchemaName = issue.path[1]; | ||
| const namespaceId = resolveNamespaceIdForTable(contract, tableName, ddlSchemaName); | ||
| const table = entityAt(contract.storage, { | ||
| namespaceId, | ||
| entityKind: "table", | ||
| entityName: tableName | ||
| }); | ||
| return { | ||
| namespaceId, | ||
| entityKind: "table", | ||
| entityName: tableName, | ||
| ...ifDefined("column", issueColumnName(issue)), | ||
| ...ifDefined("explicitNodeControlPolicy", table?.control), | ||
| createsNewObject: resolvePostgresNodeIssueCreationFactoryName(issue) !== void 0 | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { resolvePostgresNodeIssueCreationFactoryName as a, issueSchemaName as c, postgresNodeStorageCoordinate as d, resolvePostgresNodeIssueControlPolicySubject as i, planIssues as l, resolveNamespaceIdForDdlSchema as n, coalesceSubtreeIssues as o, resolvePostgresCallControlPolicySubject as r, issueNode as s, renderPostgresSuppression as t, postgresPlannerStrategies as u }; | ||
| //# sourceMappingURL=control-policy-vqI4KbAN.mjs.map |
Sorry, the diff of this file is too big to display
| import { n as postgresResolveDefault } from "./default-normalizer-B9ZUiyUE.mjs"; | ||
| import { r as isPostgresSchema } from "./postgres-schema-CewLYHB7.mjs"; | ||
| import { a as postgresDiffSubjectGranularity, n as PostgresNativeEnumSchemaNode, r as PostgresSchemaNodeKind, t as PostgresTableSchemaNode } from "./postgres-table-schema-node-D6LvInCe.mjs"; | ||
| import { i as PostgresDatabaseSchemaNode, n as PostgresPolicySchemaNode, r as PostgresNamespaceSchemaNode, t as PostgresRoleSchemaNode } from "./postgres-role-schema-node-bg32e7I-.mjs"; | ||
| import { t as resolveDdlSchemaForNamespaceStorage } from "./resolve-ddl-schema-D0wi_P9Q.mjs"; | ||
| import { i as resolvePostgresNodeIssueControlPolicySubject } from "./control-policy-vqI4KbAN.mjs"; | ||
| import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { buildNativeTypeExpander, contractNamespaceToSchemaIR } from "@prisma-next/family-sql/control"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| import { PrimaryKey, RelationalSchemaNodeKind, SqlForeignKeyIR, SqlIndexIR, SqlUniqueIR } from "@prisma-next/sql-schema-ir/types"; | ||
| import { classifyDiffSubjectGranularity } from "@prisma-next/family-sql/diff"; | ||
| import { diffSchemas, issueOutcome } from "@prisma-next/framework-components/control"; | ||
| //#region src/core/migrations/contract-to-postgres-database-schema-node.ts | ||
| /** The database root's fixed sentinel id (`PostgresDatabaseSchemaNode#id`). */ | ||
| function databaseStep() { | ||
| return { | ||
| nodeKind: PostgresSchemaNodeKind.database, | ||
| id: "database" | ||
| }; | ||
| } | ||
| function tableDependsOn(namespaceId, tableName) { | ||
| return [ | ||
| databaseStep(), | ||
| { | ||
| nodeKind: PostgresSchemaNodeKind.namespace, | ||
| id: namespaceId | ||
| }, | ||
| { | ||
| nodeKind: PostgresSchemaNodeKind.table, | ||
| id: tableName | ||
| } | ||
| ]; | ||
| } | ||
| function roleDependsOn(role) { | ||
| return [databaseStep(), { | ||
| nodeKind: PostgresSchemaNodeKind.role, | ||
| id: role | ||
| }]; | ||
| } | ||
| /** | ||
| * The chains from a table-child object (foreign key, index, unique, primary | ||
| * key) to each of the own columns it is built on, in the Postgres tree. | ||
| * Dropping a covered column auto-drops the object, so the object's drop must | ||
| * precede the column's; the graph derives that direction from these edges. | ||
| */ | ||
| function columnDependsOn(namespaceId, tableName, columns) { | ||
| return columns.map((column) => [...tableDependsOn(namespaceId, tableName), { | ||
| nodeKind: RelationalSchemaNodeKind.column, | ||
| id: `column:${column}` | ||
| }]); | ||
| } | ||
| function toPolicyNode(policy, namespaceId) { | ||
| return new PostgresPolicySchemaNode({ | ||
| name: policy.name, | ||
| prefix: policy.prefix, | ||
| tableName: policy.tableName, | ||
| namespaceId, | ||
| operation: policy.operation, | ||
| roles: [...policy.roles], | ||
| ...ifDefined("using", policy.using), | ||
| ...ifDefined("withCheck", policy.withCheck), | ||
| permissive: policy.permissive, | ||
| dependsOn: [tableDependsOn(namespaceId, policy.tableName), ...policy.roles.map(roleDependsOn)] | ||
| }); | ||
| } | ||
| /** | ||
| * Projects a Postgres contract into the expected schema-diff tree: a | ||
| * `PostgresDatabaseSchemaNode` root holding one `PostgresNamespaceSchemaNode` | ||
| * per Postgres namespace, each holding its `PostgresTableSchemaNode`s with | ||
| * their `PostgresPolicySchemaNode`s, plus the database roles on the root. | ||
| * | ||
| * Not a duplicate of the family's `contractToSchemaIR`: that builds a flat, | ||
| * single `{ tables }` map (and throws on cross-namespace name collisions, with | ||
| * no RLS/role concept) for SQLite's single-schema world. This is the | ||
| * Postgres-specific *tree* shape — multi-schema, RLS-policy-aware, role-aware. | ||
| * It reuses the family's per-namespace table conversion (`contractNamespaceToSchemaIR`) | ||
| * for column/FK/index building and only adds the Postgres tree/policy/role shape. | ||
| * | ||
| * Tables are grouped by their owning namespace (resolved DDL schema name) so | ||
| * the tree mirrors Postgres's object hierarchy. The DDL schema name is | ||
| * resolved once per namespace. | ||
| * | ||
| * A policy that references a table absent from its namespace is a malformed | ||
| * contract — the loop throws rather than fabricating a stub table. | ||
| */ | ||
| function contractToPostgresDatabaseSchemaNode(contract, options) { | ||
| if (contract === null) return new PostgresDatabaseSchemaNode({ | ||
| namespaces: {}, | ||
| roles: [], | ||
| existingSchemas: [], | ||
| pgVersion: "" | ||
| }); | ||
| const namespaces = {}; | ||
| const roles = []; | ||
| const ownedSchemas = []; | ||
| for (const ns of Object.values(contract.storage.namespaces)) { | ||
| if (!isPostgresSchema(ns)) continue; | ||
| for (const role of Object.values(ns.role)) roles.push(new PostgresRoleSchemaNode({ | ||
| name: role.name, | ||
| namespaceId: role.namespaceId | ||
| })); | ||
| if (ns.id === UNBOUND_NAMESPACE_ID) { | ||
| if (!Object.entries(ns.entries).some(([entriesKey, slot]) => entriesKey !== "role" && Object.keys(slot).length > 0)) continue; | ||
| } | ||
| const ddlSchema = resolveDdlSchemaForNamespaceStorage(contract.storage, ns.id); | ||
| ownedSchemas.push(ddlSchema); | ||
| const sqlTables = contractNamespaceToSchemaIR(contract.storage, ns.id, options).tables; | ||
| const policiesByTable = /* @__PURE__ */ new Map(); | ||
| for (const policy of Object.values(ns.policy)) { | ||
| const list = policiesByTable.get(policy.tableName) ?? []; | ||
| list.push(toPolicyNode(policy, ddlSchema)); | ||
| policiesByTable.set(policy.tableName, list); | ||
| } | ||
| const tables = {}; | ||
| for (const tableName of Object.keys(ns.table)) { | ||
| const sqlTable = sqlTables[tableName]; | ||
| if (sqlTable === void 0) continue; | ||
| const foreignKeys = sqlTable.foreignKeys.map((fk) => { | ||
| const resolvedReferencedNamespace = resolveDdlSchemaForNamespaceStorage(contract.storage, fk.referencedSchema ?? UNBOUND_NAMESPACE_ID); | ||
| return new SqlForeignKeyIR({ | ||
| columns: fk.columns, | ||
| referencedTable: fk.referencedTable, | ||
| referencedColumns: fk.referencedColumns, | ||
| referencedSchema: fk.referencedSchema ?? UNBOUND_NAMESPACE_ID, | ||
| ...ifDefined("name", fk.name), | ||
| ...ifDefined("onDelete", fk.onDelete), | ||
| ...ifDefined("onUpdate", fk.onUpdate), | ||
| ...ifDefined("annotations", fk.annotations), | ||
| resolvedReferencedNamespace, | ||
| dependsOn: [tableDependsOn(resolvedReferencedNamespace, fk.referencedTable), ...columnDependsOn(ddlSchema, tableName, fk.columns)] | ||
| }); | ||
| }); | ||
| const uniques = sqlTable.uniques.map((u) => new SqlUniqueIR({ | ||
| columns: u.columns, | ||
| ...ifDefined("name", u.name), | ||
| ...ifDefined("annotations", u.annotations), | ||
| dependsOn: columnDependsOn(ddlSchema, tableName, u.columns) | ||
| })); | ||
| const indexes = sqlTable.indexes.map((i) => new SqlIndexIR({ | ||
| columns: i.columns, | ||
| unique: i.unique, | ||
| partial: i.partial, | ||
| name: i.name, | ||
| type: i.type, | ||
| options: i.options, | ||
| annotations: i.annotations, | ||
| dependsOn: columnDependsOn(ddlSchema, tableName, i.columns) | ||
| })); | ||
| const primaryKey = sqlTable.primaryKey !== void 0 ? new PrimaryKey({ | ||
| columns: sqlTable.primaryKey.columns, | ||
| ...ifDefined("name", sqlTable.primaryKey.name), | ||
| dependsOn: columnDependsOn(ddlSchema, tableName, sqlTable.primaryKey.columns) | ||
| }) : void 0; | ||
| tables[tableName] = new PostgresTableSchemaNode({ | ||
| name: sqlTable.name, | ||
| columns: sqlTable.columns, | ||
| foreignKeys, | ||
| uniques, | ||
| indexes, | ||
| ...ifDefined("primaryKey", primaryKey), | ||
| ...ifDefined("annotations", sqlTable.annotations), | ||
| ...ifDefined("checks", sqlTable.checks), | ||
| policies: policiesByTable.get(tableName) ?? [], | ||
| rlsEnabled: Object.hasOwn(ns.rls, tableName) | ||
| }); | ||
| } | ||
| for (const [tableName, tablePolicies] of policiesByTable) { | ||
| if (!(tableName in tables)) { | ||
| const policyName = tablePolicies[0]?.name ?? "(unknown)"; | ||
| throw new Error(`contract-to-postgres-database-schema-node: policy "${policyName}" references table "${tableName}" not present in namespace "${ddlSchema}"`); | ||
| } | ||
| if (!Object.hasOwn(ns.rls, tableName)) { | ||
| const policyPrefix = tablePolicies[0]?.prefix ?? "(unknown)"; | ||
| throw new Error(`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.`); | ||
| } | ||
| } | ||
| namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({ | ||
| schemaName: ddlSchema, | ||
| tables, | ||
| nativeEnums: Object.values(ns.entries.native_enum ?? {}).map((entity) => new PostgresNativeEnumSchemaNode({ | ||
| typeName: entity.typeName, | ||
| namespaceId: ddlSchema, | ||
| members: entity.members, | ||
| ...ifDefined("control", entity.control) | ||
| })) | ||
| }); | ||
| } | ||
| return new PostgresDatabaseSchemaNode({ | ||
| namespaces, | ||
| roles, | ||
| existingSchemas: ownedSchemas, | ||
| pgVersion: "" | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/diff-database-schema.ts | ||
| /** | ||
| * Whether a diff issue's subject node is cluster-scoped — it carries its own | ||
| * `namespaceId` field (only role and policy diff nodes do) and that | ||
| * coordinate is the unbound sentinel, meaning the node is not owned by any | ||
| * schema/namespace. A role node is always cluster-scoped (roles are | ||
| * cluster-level objects); a policy node's `namespaceId` is always its | ||
| * table's resolved DDL schema, never the sentinel. Namespace-ownership | ||
| * scoping (which compares `issue.path[1]` against the set of DDL schemas | ||
| * the contract owns) makes no sense for a cluster-scoped subject — its path | ||
| * segment at that index is the object's own name, not a schema name — so | ||
| * such an issue bypasses that scoping entirely. | ||
| */ | ||
| function isClusterScopedIssue(issue) { | ||
| const node = issue.expected ?? issue.actual; | ||
| return node !== void 0 && nodeNamespaceId(node) === UNBOUND_NAMESPACE_ID; | ||
| } | ||
| function nodeNamespaceId(node) { | ||
| if (!Object.hasOwn(node, "namespaceId")) return void 0; | ||
| return blindCast(node).namespaceId; | ||
| } | ||
| function ownedSchemaNames(expected) { | ||
| const policyNamespaces = Object.values(expected.namespaces).flatMap((ns) => Object.values(ns.tables).flatMap((t) => t.policies.map((p) => p.namespaceId))); | ||
| return /* @__PURE__ */ new Set([...policyNamespaces, ...expected.existingSchemas]); | ||
| } | ||
| /** | ||
| * Drops contract namespaces that declare no tables from the verdict-diff | ||
| * expected tree and the relational owned-schema set. The legacy relational | ||
| * walk skipped a table-less namespace (e.g. an enums-only schema) before | ||
| * pairing, so neither its DDL schema's absence nor that schema's live | ||
| * relational contents ever reached the verdict — the pruned tree | ||
| * reproduces that. The prune loses no expected policies: policies attach | ||
| * to tables, so a table-less namespace carries none (the projection | ||
| * throws on a policy referencing an absent table). Live policies in a | ||
| * pruned schema remain governed via the full owned set (see | ||
| * {@link diffPostgresSchema}). | ||
| */ | ||
| function pruneTableLessNamespaces(expected) { | ||
| const namespaces = Object.fromEntries(Object.entries(expected.namespaces).filter(([, ns]) => Object.keys(ns.tables).length > 0)); | ||
| return new PostgresDatabaseSchemaNode({ | ||
| namespaces, | ||
| roles: [...expected.roles], | ||
| existingSchemas: expected.existingSchemas.filter((s) => namespaces[s] !== void 0), | ||
| pgVersion: expected.pgVersion | ||
| }); | ||
| } | ||
| /** | ||
| * Resolves a verdict-diff issue's subject's declared control policy directly | ||
| * from the contract, by delegating to the same node-typed resolver | ||
| * ({@link resolvePostgresNodeIssueControlPolicySubject}) the planner uses to | ||
| * gate DDL calls. `undefined` when the issue resolves to no contract subject. | ||
| * | ||
| * A role issue resolves through that same resolver to `external` | ||
| * unconditionally (see its role branch), regardless of the contract's own | ||
| * default policy: a role is referenced by the contract but not owned, and | ||
| * `external`'s existing semantics — a missing declared subject still fails, | ||
| * every extra is suppressed — are exactly the wanted asymmetric grading for | ||
| * a cluster object the framework does not own. | ||
| */ | ||
| function resolveControlPolicy(issue, contract) { | ||
| return resolvePostgresNodeIssueControlPolicySubject(blindCast(issue), contract)?.explicitNodeControlPolicy; | ||
| } | ||
| /** | ||
| * The Postgres full-tree node diff for the family verify verdict: derive | ||
| * the expected tree (resolved leaf values, expander threaded, FK schemas | ||
| * resolved, table-less namespaces pruned), run the generic | ||
| * differ over the trees as derived, and scope out `not-expected` findings under namespaces the | ||
| * contract does not own. Ownership scoping bypasses cluster-scoped subjects | ||
| * (roles today), mirroring the legacy decomposition: relational extras check | ||
| * the PRUNED owned set (the legacy | ||
| * per-namespace walk never visited a table-less namespace, so its live | ||
| * relational contents are invisible), while `structural` extras (RLS | ||
| * policies) check the FULL owned set (the legacy policy diff governed | ||
| * every contract schema regardless of tables — RLS governance does not | ||
| * shrink because a namespace declares no tables). The codec `verifyType` | ||
| * hooks run once per contract namespace with tables against that | ||
| * namespace's paired actual node (the hooks read namespace-scoped state | ||
| * such as `nativeEnums`). | ||
| */ | ||
| function diffPostgresSchema(input) { | ||
| const postgresContract = blindCast(input.contract); | ||
| PostgresDatabaseSchemaNode.assert(input.schema); | ||
| const actual = input.schema; | ||
| const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, { | ||
| annotationNamespace: "pg", | ||
| ...ifDefined("expandNativeType", buildNativeTypeExpander(input.frameworkComponents)), | ||
| resolveDefault: postgresResolveDefault | ||
| }); | ||
| const expected = pruneTableLessNamespaces(fullExpected); | ||
| const relationalOwned = ownedSchemaNames(expected); | ||
| const structuralOwned = ownedSchemaNames(fullExpected); | ||
| return { | ||
| issues: diffSchemas(expected, actual).filter((issue) => { | ||
| if (issueOutcome(issue) !== "not-expected") return true; | ||
| if (isClusterScopedIssue(issue)) return true; | ||
| const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity); | ||
| const namespaceSegment = issue.path[1]; | ||
| if (namespaceSegment === void 0) return true; | ||
| return (granularity === "structural" ? structuralOwned : relationalOwned).has(namespaceSegment); | ||
| }), | ||
| resolveControlPolicy: (issue) => resolveControlPolicy(issue, postgresContract), | ||
| namespacePairs: Object.values(expected.namespaces).map((ns) => ({ actual: actual.namespaces[ns.schemaName] })) | ||
| }; | ||
| } | ||
| /** | ||
| * Adds an empty namespace node to the actual tree for every expected namespace | ||
| * absent from it. The relational plan diff pairs on namespace: a contract | ||
| * namespace whose live schema does not exist yet must surface each of its | ||
| * tables as `not-found` (→ `CREATE TABLE`), NOT as a single namespace | ||
| * `not-found` that subtree-coalescing would collapse (leaving `CREATE SCHEMA` | ||
| * with no tables). Padding makes the namespaces pair, so only table/column/ | ||
| * policy drift surfaces; `CREATE SCHEMA` comes separately from the synthesized | ||
| * namespace-presence stitch (`verifyPostgresNamespacePresence`), never from the | ||
| * tree diff — matching the retired per-namespace-paired relational walk, which | ||
| * paired a missing schema against an empty namespace node. | ||
| */ | ||
| function padActualNamespaces(expected, actual) { | ||
| const namespaces = { ...actual.namespaces }; | ||
| let padded = false; | ||
| for (const schemaName of Object.keys(expected.namespaces)) if (namespaces[schemaName] === void 0) { | ||
| namespaces[schemaName] = new PostgresNamespaceSchemaNode({ | ||
| schemaName, | ||
| tables: {} | ||
| }); | ||
| padded = true; | ||
| } | ||
| if (!padded) return actual; | ||
| return new PostgresDatabaseSchemaNode({ | ||
| namespaces, | ||
| roles: [...actual.roles], | ||
| existingSchemas: [...actual.existingSchemas], | ||
| pgVersion: actual.pgVersion | ||
| }); | ||
| } | ||
| /** | ||
| * The Postgres planner's diff input: the SAME tree-building | ||
| * `diffPostgresSchema` uses (expander threaded, FK schemas resolved, | ||
| * table-less namespaces pruned, cluster-scope-aware ownership filter) plus | ||
| * actual namespace padding (so a missing | ||
| * schema's tables surface as `not-found` instead of a swallowed namespace | ||
| * `not-found`). One differ drives both verify and plan; this is the | ||
| * plan-side derivation. The single issue list covers tables / columns / | ||
| * constraints / indexes / defaults AND policies — the caller splits it | ||
| * (relational → `mapNodeIssueToCall`; policy → RLS ops) and stitches in | ||
| * `CREATE SCHEMA` separately. | ||
| */ | ||
| function buildPostgresPlanDiff(input) { | ||
| const postgresContract = blindCast(input.contract); | ||
| PostgresDatabaseSchemaNode.assert(input.actualSchema); | ||
| const actual = input.actualSchema; | ||
| const fullExpected = contractToPostgresDatabaseSchemaNode(postgresContract, { | ||
| annotationNamespace: "pg", | ||
| ...ifDefined("expandNativeType", buildNativeTypeExpander(input.frameworkComponents)), | ||
| resolveDefault: postgresResolveDefault | ||
| }); | ||
| const expected = pruneTableLessNamespaces(fullExpected); | ||
| const paddedActual = padActualNamespaces(expected, actual); | ||
| const relationalOwned = ownedSchemaNames(expected); | ||
| const structuralOwned = ownedSchemaNames(fullExpected); | ||
| return { | ||
| expected, | ||
| actual: paddedActual, | ||
| issues: blindCast(diffSchemas(expected, paddedActual)).filter((issue) => { | ||
| if (issueOutcome(issue) !== "not-expected") return true; | ||
| if (isClusterScopedIssue(issue)) return true; | ||
| const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity); | ||
| const namespaceSegment = issue.path[1]; | ||
| if (namespaceSegment === void 0) return true; | ||
| return (granularity === "structural" ? structuralOwned : relationalOwned).has(namespaceSegment); | ||
| }) | ||
| }; | ||
| } | ||
| //#endregion | ||
| export { diffPostgresSchema as n, contractToPostgresDatabaseSchemaNode as r, buildPostgresPlanDiff as t }; | ||
| //# sourceMappingURL=diff-database-schema-iiZgpBJ4.mjs.map |
| {"version":3,"file":"diff-database-schema-iiZgpBJ4.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 type { SchemaNodeRef } from '@prisma-next/framework-components/control';\nimport { UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport {\n PrimaryKey,\n RelationalSchemaNodeKind,\n SqlForeignKeyIR,\n SqlIndexIR,\n SqlUniqueIR,\n} 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 { PostgresNativeEnumSchemaNode } from '../schema-ir/postgres-native-enum-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 { PostgresSchemaNodeKind } from '../schema-ir/schema-node-kinds';\nimport { resolveDdlSchemaForNamespaceStorage } from './resolve-ddl-schema';\n\n/** The database root's fixed sentinel id (`PostgresDatabaseSchemaNode#id`). */\nfunction databaseStep(): { readonly nodeKind: string; readonly id: string } {\n return { nodeKind: PostgresSchemaNodeKind.database, id: 'database' };\n}\n\nfunction tableDependsOn(namespaceId: string, tableName: string): SchemaNodeRef {\n return [\n databaseStep(),\n { nodeKind: PostgresSchemaNodeKind.namespace, id: namespaceId },\n { nodeKind: PostgresSchemaNodeKind.table, id: tableName },\n ];\n}\n\nfunction roleDependsOn(role: string): SchemaNodeRef {\n return [databaseStep(), { nodeKind: PostgresSchemaNodeKind.role, id: role }];\n}\n\n/**\n * The chains from a table-child object (foreign key, index, unique, primary\n * key) to each of the own columns it is built on, in the Postgres tree.\n * Dropping a covered column auto-drops the object, so the object's drop must\n * precede the column's; the graph derives that direction from these edges.\n */\nfunction columnDependsOn(\n namespaceId: string,\n tableName: string,\n columns: readonly string[],\n): readonly SchemaNodeRef[] {\n return columns.map((column) => [\n ...tableDependsOn(namespaceId, tableName),\n { nodeKind: RelationalSchemaNodeKind.column, id: `column:${column}` },\n ]);\n}\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 dependsOn: [tableDependsOn(namespaceId, policy.tableName), ...policy.roles.map(roleDependsOn)],\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\n // Role entries are root-level diff subjects: they hoist to the database\n // root from every slot and never count toward whether a namespace\n // materializes a schema node.\n for (const role of Object.values(ns.role)) {\n roles.push(new PostgresRoleSchemaNode({ name: role.name, namespaceId: role.namespaceId }));\n }\n\n // The unbound slot resolves its DDL schema to 'public', so it\n // materializes a schema node exactly when it has non-role content — a\n // late-binding contract keeps today's behavior (the slot carries the\n // tables), while a roles-only unbound slot alongside named namespaces\n // contributes only root roles and no node (which would otherwise be a\n // spurious empty 'public' node, clobbering a real bound 'public'\n // namespace's node keyed by the same resolved schema name).\n if (ns.id === UNBOUND_NAMESPACE_ID) {\n const hasNonRoleContent = Object.entries(ns.entries).some(\n ([entriesKey, slot]) => entriesKey !== 'role' && Object.keys(slot).length > 0,\n );\n if (!hasNonRoleContent) continue;\n }\n\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((fk) => {\n const resolvedReferencedNamespace = resolveDdlSchemaForNamespaceStorage(\n contract.storage,\n fk.referencedSchema ?? UNBOUND_NAMESPACE_ID,\n );\n return 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,\n dependsOn: [\n tableDependsOn(resolvedReferencedNamespace, fk.referencedTable),\n ...columnDependsOn(ddlSchema, tableName, fk.columns),\n ],\n });\n });\n // The family stamped these children's own-column `dependsOn` with the\n // flat (single-schema) chain; the Postgres tree nests them under a\n // namespace, so re-stamp with the multi-schema chain that matches this\n // tree's paths. Every other field is carried through unchanged.\n const uniques = sqlTable.uniques.map(\n (u) =>\n new SqlUniqueIR({\n columns: u.columns,\n ...ifDefined('name', u.name),\n ...ifDefined('annotations', u.annotations),\n dependsOn: columnDependsOn(ddlSchema, tableName, u.columns),\n }),\n );\n const indexes = sqlTable.indexes.map(\n (i) =>\n new SqlIndexIR({\n columns: i.columns,\n unique: i.unique,\n partial: i.partial,\n name: i.name,\n type: i.type,\n options: i.options,\n annotations: i.annotations,\n dependsOn: columnDependsOn(ddlSchema, tableName, i.columns),\n }),\n );\n const primaryKey =\n sqlTable.primaryKey !== undefined\n ? new PrimaryKey({\n columns: sqlTable.primaryKey.columns,\n ...ifDefined('name', sqlTable.primaryKey.name),\n dependsOn: columnDependsOn(ddlSchema, tableName, sqlTable.primaryKey.columns),\n })\n : undefined;\n tables[tableName] = new PostgresTableSchemaNode({\n name: sqlTable.name,\n columns: sqlTable.columns,\n foreignKeys,\n uniques,\n indexes,\n ...ifDefined('primaryKey', 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 const nativeEnums = Object.values(ns.entries.native_enum ?? {}).map(\n (entity) =>\n new PostgresNativeEnumSchemaNode({\n typeName: entity.typeName,\n namespaceId: ddlSchema,\n members: entity.members,\n ...ifDefined('control', entity.control),\n }),\n );\n\n namespaces[ddlSchema] = new PostgresNamespaceSchemaNode({\n schemaName: ddlSchema,\n tables,\n nativeEnums,\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 { DiffableNode, SchemaDiffIssue } from '@prisma-next/framework-components/control';\nimport { diffSchemas, issueOutcome } 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 { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { postgresResolveDefault } from '../default-normalizer';\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\n/**\n * Whether a diff issue's subject node is cluster-scoped — it carries its own\n * `namespaceId` field (only role and policy diff nodes do) and that\n * coordinate is the unbound sentinel, meaning the node is not owned by any\n * schema/namespace. A role node is always cluster-scoped (roles are\n * cluster-level objects); a policy node's `namespaceId` is always its\n * table's resolved DDL schema, never the sentinel. Namespace-ownership\n * scoping (which compares `issue.path[1]` against the set of DDL schemas\n * the contract owns) makes no sense for a cluster-scoped subject — its path\n * segment at that index is the object's own name, not a schema name — so\n * such an issue bypasses that scoping entirely.\n */\nfunction isClusterScopedIssue(issue: SchemaDiffIssue): boolean {\n const node = issue.expected ?? issue.actual;\n return node !== undefined && nodeNamespaceId(node) === UNBOUND_NAMESPACE_ID;\n}\n\nfunction nodeNamespaceId(node: DiffableNode): string | undefined {\n if (!Object.hasOwn(node, 'namespaceId')) return undefined;\n return blindCast<\n { namespaceId: string },\n 'presence of an own namespaceId field was just checked via Object.hasOwn'\n >(node).namespaceId;\n}\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's declared control policy directly\n * 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 subject.\n *\n * A role issue resolves through that same resolver to `external`\n * unconditionally (see its role branch), regardless of the contract's own\n * default policy: a role is referenced by the contract but not owned, and\n * `external`'s existing semantics — a missing declared subject still fails,\n * every extra is suppressed — are exactly the wanted asymmetric grading for\n * a cluster object the framework does not own.\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 scoping bypasses cluster-scoped subjects\n * (roles today), mirroring the legacy decomposition: relational extras check\n * 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 `nativeEnums`).\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 resolveDefault: postgresResolveDefault,\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 (issueOutcome(issue) !== 'not-expected') return true;\n if (isClusterScopedIssue(issue)) return true;\n const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);\n const namespaceSegment = issue.path[1];\n if (namespaceSegment === undefined) return true;\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 });\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, cluster-scope-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, cluster-scope-aware ownership filter) plus\n * 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 resolveDefault: postgresResolveDefault,\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 (issueOutcome(issue) !== 'not-expected') return true;\n if (isClusterScopedIssue(issue)) return true;\n const granularity = classifyDiffSubjectGranularity(issue, postgresDiffSubjectGranularity);\n const namespaceSegment = issue.path[1];\n if (namespaceSegment === undefined) return true;\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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"} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { n as isPgEnumParams } from "./codecs-DOgD_C1A.mjs"; | ||
| import { a as quoteQualifiedName, i as quoteIdentifier, n as escapeLiteral } from "./sql-utils-SU4FDvIV.mjs"; | ||
| import { t as resolveColumnTypeMetadata } from "./planner-type-resolution-Bt2f_q-F.mjs"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| //#region src/core/migrations/planner-ddl-builders.ts | ||
| /** | ||
| * Pattern for safe PostgreSQL type names. | ||
| * Allows letters, digits, underscores, spaces (for "double precision", "character varying"), | ||
| * and trailing [] for array types. | ||
| */ | ||
| const SAFE_NATIVE_TYPE_PATTERN = /^[a-zA-Z][a-zA-Z0-9_ ]*(\[\])?$/; | ||
| function assertSafeNativeType(nativeType) { | ||
| if (!SAFE_NATIVE_TYPE_PATTERN.test(nativeType)) throw new Error(`Unsafe native type name in contract: "${nativeType}". Native type names must match /^[a-zA-Z][a-zA-Z0-9_ ]*(\\[\\])?\$/`); | ||
| } | ||
| /** | ||
| * Sanity check against accidental SQL injection from malformed contract files. | ||
| * Rejects semicolons, SQL comment tokens, and dollar-quoting. | ||
| * Not a comprehensive security boundary — the contract is developer-authored. | ||
| */ | ||
| function assertSafeDefaultExpression(expression) { | ||
| if (expression.includes(";") || /--|\/\*|\$\$|\bSELECT\b/i.test(expression)) throw new Error(`Unsafe default expression in contract: "${expression}". Default expressions must not contain semicolons, SQL comment tokens, dollar-quoting, or subqueries.`); | ||
| } | ||
| /** | ||
| * Renders the SQL type for a column in DDL context. | ||
| * | ||
| * @param allowPseudoTypes - When true (default), autoincrement integer columns | ||
| * produce SERIAL/BIGSERIAL/SMALLSERIAL pseudo-types. Set to false for contexts | ||
| * like ALTER COLUMN TYPE where pseudo-types are invalid. | ||
| */ | ||
| function buildColumnTypeSql(column, codecHooks, storageTypes = {}, allowPseudoTypes = true) { | ||
| const resolved = resolveColumnTypeMetadata(column, storageTypes); | ||
| if (allowPseudoTypes) { | ||
| const columnDefault = column.default; | ||
| if (columnDefault?.kind === "function" && columnDefault.expression === "autoincrement()") { | ||
| if (resolved.nativeType === "int4" || resolved.nativeType === "integer") return "SERIAL"; | ||
| if (resolved.nativeType === "int8" || resolved.nativeType === "bigint") return "BIGSERIAL"; | ||
| if (resolved.nativeType === "int2" || resolved.nativeType === "smallint") return "SMALLSERIAL"; | ||
| } | ||
| } | ||
| if (isPgEnumParams(resolved.typeParams)) { | ||
| const quoted = quoteQualifiedName(resolved.nativeType); | ||
| return column.many ? `${quoted}[]` : quoted; | ||
| } | ||
| const expanded = expandParameterizedTypeSql(resolved, codecHooks); | ||
| if (expanded !== null) return column.many ? `${expanded}[]` : expanded; | ||
| if (column.typeRef) { | ||
| const base = quoteQualifiedName(resolved.nativeType); | ||
| return column.many ? `${base}[]` : base; | ||
| } | ||
| assertSafeNativeType(resolved.nativeType); | ||
| return column.many ? `${resolved.nativeType}[]` : resolved.nativeType; | ||
| } | ||
| function expandParameterizedTypeSql(column, codecHooks) { | ||
| if (!column.typeParams || Object.keys(column.typeParams).length === 0) return null; | ||
| if (!column.codecId) throw new Error(`Column declares typeParams for nativeType "${column.nativeType}" but has no codecId. Ensure the column is associated with a codec.`); | ||
| const hooks = codecHooks.get(column.codecId); | ||
| if (!hooks?.expandNativeType) { | ||
| if (hooks?.planTypeOperations) return null; | ||
| throw new Error(`Column declares typeParams for nativeType "${column.nativeType}" but no expandNativeType hook is registered for codecId "${column.codecId}". Ensure the extension providing this codec is included in extensionPacks.`); | ||
| } | ||
| const expanded = hooks.expandNativeType({ | ||
| nativeType: column.nativeType, | ||
| codecId: column.codecId, | ||
| ...ifDefined("typeParams", column.typeParams) | ||
| }); | ||
| return expanded !== column.nativeType ? expanded : null; | ||
| } | ||
| /** Autoincrement columns use SERIAL types, so this returns empty for them. */ | ||
| function buildColumnDefaultSql(columnDefault, column) { | ||
| if (!columnDefault) return ""; | ||
| switch (columnDefault.kind) { | ||
| case "literal": return `DEFAULT ${renderDefaultLiteral(columnDefault.value, column)}`; | ||
| case "function": | ||
| if (columnDefault.expression === "autoincrement()") return ""; | ||
| assertSafeDefaultExpression(columnDefault.expression); | ||
| return `DEFAULT (${columnDefault.expression})`; | ||
| case "sequence": return `DEFAULT nextval('${escapeLiteral(quoteIdentifier(columnDefault.name))}'::regclass)`; | ||
| } | ||
| } | ||
| function renderDefaultLiteral(value, column) { | ||
| const isJsonColumn = column?.nativeType === "json" || column?.nativeType === "jsonb"; | ||
| if (column?.many && Array.isArray(value)) return renderArrayLiteralDefault(value); | ||
| if (value instanceof Date) return `'${escapeLiteral(value.toISOString())}'`; | ||
| if (typeof value === "string") return `'${escapeLiteral(value)}'`; | ||
| if (typeof value === "number" || typeof value === "boolean") return String(value); | ||
| if (value === null) return "NULL"; | ||
| const json = JSON.stringify(value); | ||
| if (isJsonColumn) return `'${escapeLiteral(json)}'::${column.nativeType}`; | ||
| return `'${escapeLiteral(json)}'`; | ||
| } | ||
| function renderArrayLiteralDefault(elements) { | ||
| if (elements.length === 0) return "'{}'"; | ||
| return `ARRAY[${elements.map((el) => renderDefaultLiteral(el)).join(", ")}]`; | ||
| } | ||
| //#endregion | ||
| export { buildColumnTypeSql as n, renderDefaultLiteral as r, buildColumnDefaultSql as t }; | ||
| //# sourceMappingURL=planner-ddl-builders-CI1CpBfG.mjs.map |
| {"version":3,"file":"planner-ddl-builders-CI1CpBfG.mjs","names":[],"sources":["../src/core/migrations/planner-ddl-builders.ts"],"sourcesContent":["import type { CodecControlHooks } from '@prisma-next/family-sql/control';\nimport type { StorageColumn, StorageTypeInstance } from '@prisma-next/sql-contract/types';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { isPgEnumParams } from '../codecs';\nimport { escapeLiteral, quoteIdentifier, quoteQualifiedName } from '../sql-utils';\nimport type { PostgresColumnDefault } from '../types';\nimport { resolveColumnTypeMetadata } from './planner-type-resolution';\n\n/**\n * Pattern for safe PostgreSQL type names.\n * Allows letters, digits, underscores, spaces (for \"double precision\", \"character varying\"),\n * and trailing [] for array types.\n */\nconst SAFE_NATIVE_TYPE_PATTERN = /^[a-zA-Z][a-zA-Z0-9_ ]*(\\[\\])?$/;\n\nfunction assertSafeNativeType(nativeType: string): void {\n if (!SAFE_NATIVE_TYPE_PATTERN.test(nativeType)) {\n throw new Error(\n `Unsafe native type name in contract: \"${nativeType}\". ` +\n 'Native type names must match /^[a-zA-Z][a-zA-Z0-9_ ]*(\\\\[\\\\])?$/',\n );\n }\n}\n\n/**\n * Sanity check against accidental SQL injection from malformed contract files.\n * Rejects semicolons, SQL comment tokens, and dollar-quoting.\n * Not a comprehensive security boundary — the contract is developer-authored.\n */\nfunction assertSafeDefaultExpression(expression: string): void {\n if (expression.includes(';') || /--|\\/\\*|\\$\\$|\\bSELECT\\b/i.test(expression)) {\n throw new Error(\n `Unsafe default expression in contract: \"${expression}\". ` +\n 'Default expressions must not contain semicolons, SQL comment tokens, dollar-quoting, or subqueries.',\n );\n }\n}\n\n/**\n * Renders the SQL type for a column in DDL context.\n *\n * @param allowPseudoTypes - When true (default), autoincrement integer columns\n * produce SERIAL/BIGSERIAL/SMALLSERIAL pseudo-types. Set to false for contexts\n * like ALTER COLUMN TYPE where pseudo-types are invalid.\n */\nexport function buildColumnTypeSql(\n column: StorageColumn,\n codecHooks: ReadonlyMap<string, CodecControlHooks>,\n storageTypes: Record<string, StorageTypeInstance> = {},\n allowPseudoTypes = true,\n): string {\n const resolved = resolveColumnTypeMetadata(column, storageTypes);\n\n if (allowPseudoTypes) {\n const columnDefault = column.default;\n if (columnDefault?.kind === 'function' && columnDefault.expression === 'autoincrement()') {\n if (resolved.nativeType === 'int4' || resolved.nativeType === 'integer') {\n return 'SERIAL';\n }\n if (resolved.nativeType === 'int8' || resolved.nativeType === 'bigint') {\n return 'BIGSERIAL';\n }\n if (resolved.nativeType === 'int2' || resolved.nativeType === 'smallint') {\n return 'SMALLSERIAL';\n }\n }\n }\n\n // A column whose codec supplied a `typeParams.typeName` references a named\n // database type (e.g. a native enum), not a parameterized builtin: render it\n // as its quoted, schema-qualified type-name identifier. DDL-render only — the\n // verify comparison value (`resolvedNativeType`) stays the bare name the\n // family expander produces, matching introspection.\n if (isPgEnumParams(resolved.typeParams)) {\n const quoted = quoteQualifiedName(resolved.nativeType);\n return column.many ? `${quoted}[]` : quoted;\n }\n\n const expanded = expandParameterizedTypeSql(resolved, codecHooks);\n if (expanded !== null) {\n return column.many ? `${expanded}[]` : expanded;\n }\n\n if (column.typeRef) {\n const base = quoteQualifiedName(resolved.nativeType);\n return column.many ? `${base}[]` : base;\n }\n\n assertSafeNativeType(resolved.nativeType);\n return column.many ? `${resolved.nativeType}[]` : resolved.nativeType;\n}\n\nfunction expandParameterizedTypeSql(\n column: Pick<StorageColumn, 'nativeType' | 'codecId' | 'typeParams'>,\n codecHooks: ReadonlyMap<string, CodecControlHooks>,\n): string | null {\n if (!column.typeParams || Object.keys(column.typeParams).length === 0) {\n return null;\n }\n\n if (!column.codecId) {\n throw new Error(\n `Column declares typeParams for nativeType \"${column.nativeType}\" but has no codecId. ` +\n 'Ensure the column is associated with a codec.',\n );\n }\n\n const hooks = codecHooks.get(column.codecId);\n if (!hooks?.expandNativeType) {\n if (hooks?.planTypeOperations) {\n return null;\n }\n throw new Error(\n `Column declares typeParams for nativeType \"${column.nativeType}\" ` +\n `but no expandNativeType hook is registered for codecId \"${column.codecId}\". ` +\n 'Ensure the extension providing this codec is included in extensionPacks.',\n );\n }\n\n const expanded = hooks.expandNativeType({\n nativeType: column.nativeType,\n codecId: column.codecId,\n ...ifDefined('typeParams', column.typeParams),\n });\n\n return expanded !== column.nativeType ? expanded : null;\n}\n\n/** Autoincrement columns use SERIAL types, so this returns empty for them. */\nexport function buildColumnDefaultSql(\n columnDefault: PostgresColumnDefault | undefined,\n column?: Pick<StorageColumn, 'many' | 'nativeType'>,\n): string {\n if (!columnDefault) {\n return '';\n }\n\n switch (columnDefault.kind) {\n case 'literal':\n return `DEFAULT ${renderDefaultLiteral(columnDefault.value, column)}`;\n case 'function': {\n if (columnDefault.expression === 'autoincrement()') {\n return '';\n }\n assertSafeDefaultExpression(columnDefault.expression);\n return `DEFAULT (${columnDefault.expression})`;\n }\n case 'sequence':\n return `DEFAULT nextval('${escapeLiteral(quoteIdentifier(columnDefault.name))}'::regclass)`;\n }\n}\n\nexport function renderDefaultLiteral(\n value: unknown,\n column?: Pick<StorageColumn, 'many' | 'nativeType'>,\n): string {\n const isJsonColumn = column?.nativeType === 'json' || column?.nativeType === 'jsonb';\n\n if (column?.many && Array.isArray(value)) {\n return renderArrayLiteralDefault(value);\n }\n\n if (value instanceof Date) {\n return `'${escapeLiteral(value.toISOString())}'`;\n }\n if (typeof value === 'string') {\n return `'${escapeLiteral(value)}'`;\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (value === null) {\n return 'NULL';\n }\n const json = JSON.stringify(value);\n if (isJsonColumn) {\n return `'${escapeLiteral(json)}'::${column.nativeType}`;\n }\n return `'${escapeLiteral(json)}'`;\n}\n\nfunction renderArrayLiteralDefault(elements: unknown[]): string {\n if (elements.length === 0) {\n return \"'{}'\";\n }\n const rendered = elements.map((el) => renderDefaultLiteral(el)).join(', ');\n return `ARRAY[${rendered}]`;\n}\n"],"mappings":"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"} |
| import { t as DEFAULT_NAMESPACE_ID } from "./namespace-ids-xtp_ZY86.mjs"; | ||
| import { g as PostgresRlsPolicy, r as isPostgresSchema } from "./postgres-schema-CewLYHB7.mjs"; | ||
| import { n as parseRlsPolicyWireName } from "./wire-name-DbQ9-2RC.mjs"; | ||
| import { i as PostgresDatabaseSchemaNode, n as PostgresPolicySchemaNode, r as PostgresNamespaceSchemaNode } from "./postgres-role-schema-node-bg32e7I-.mjs"; | ||
| import { t as resolveDdlSchemaForNamespaceStorage } from "./resolve-ddl-schema-D0wi_P9Q.mjs"; | ||
| import { t as buildPostgresPlanDiff } from "./diff-database-schema-iiZgpBJ4.mjs"; | ||
| import { a as resolvePostgresNodeIssueCreationFactoryName, c as issueSchemaName, d as postgresNodeStorageCoordinate, i as resolvePostgresNodeIssueControlPolicySubject, l as planIssues, n as resolveNamespaceIdForDdlSchema, o as coalesceSubtreeIssues, r as resolvePostgresCallControlPolicySubject, s as issueNode, t as renderPostgresSuppression, u as postgresPlannerStrategies } from "./control-policy-vqI4KbAN.mjs"; | ||
| import { T as DropPostgresRlsPolicyCall, k as RenamePostgresRlsPolicyCall, p as CreatePostgresRlsPolicyCall } from "./op-factory-call-DMb7FTR6.mjs"; | ||
| import { t as TypeScriptRenderablePostgresMigration } from "./planner-produced-postgres-migration-Db57IpMZ.mjs"; | ||
| import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { extractCodecControlHooks, partitionCallsByControlPolicy, partitionIssuesByControlPolicy, planFieldEventOperations, plannerFailure } from "@prisma-next/family-sql/control"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| import { issueOutcome } from "@prisma-next/framework-components/control"; | ||
| //#region src/core/migrations/verify-postgres-namespaces.ts | ||
| /** | ||
| * Resolves the live-database schema name for a given namespace | ||
| * coordinate. Mirrors `resolveDdlSchemaForNamespace` in | ||
| * `planner-strategies.ts` so the verifier's projection and the | ||
| * planner's projection always agree — Postgres-aware namespaces (the | ||
| * production path) dispatch to `ddlSchemaName(storage)`, and bare | ||
| * object payloads (used by some tests) fall back to the coordinate | ||
| * itself. | ||
| */ | ||
| function resolveDdlSchemaName(storage, namespaceId) { | ||
| const namespace = storage.namespaces[namespaceId]; | ||
| if (isPostgresSchema(namespace)) return namespace.ddlSchemaName(storage); | ||
| return namespaceId; | ||
| } | ||
| /** | ||
| * Reads the introspected list of schema names from the database-root schema | ||
| * node. `existingSchemas` is database-level, so it lives on the | ||
| * `PostgresDatabaseSchemaNode` root — not on the per-schema namespace nodes. | ||
| * | ||
| * Defaults to the always-present `public` schema when the node is not the | ||
| * database root — a fresh Postgres database always carries `public` (unless an | ||
| * operator dropped it manually), so any verifier path that runs without an | ||
| * enriched introspection still suppresses the redundant `CREATE SCHEMA | ||
| * "public"`. | ||
| * | ||
| * Production introspection (`PostgresControlAdapter.introspect`) is the | ||
| * authoritative source: it queries `pg_namespace` and sets `existingSchemas` | ||
| * on the returned root. Tests that want to assert against a richer initial | ||
| * state construct a `PostgresDatabaseSchemaNode` explicitly. | ||
| */ | ||
| function existingSchemasFromSchema(schema) { | ||
| if (PostgresDatabaseSchemaNode.is(schema)) return schema.existingSchemas; | ||
| return [DEFAULT_NAMESPACE_ID]; | ||
| } | ||
| /** | ||
| * Emits a `postgres-namespace` `not-found` diff issue for every | ||
| * contract-declared Postgres namespace whose live container does not yet | ||
| * exist. The planner prepends these (node-typed, synthesized) to the | ||
| * relational diff issues so a multi-schema plan emits `CREATE SCHEMA` | ||
| * before the tables that need it — a planner-only concern (verify already | ||
| * rejects via the `not-found` table issues a missing schema already | ||
| * produces), so this is not part of the shared diff. | ||
| * | ||
| * A namespace's live container is the schema returned by its | ||
| * polymorphic `ddlSchemaName(storage)` method — named schemas resolve | ||
| * to their own id; the unbound singleton returns `UNBOUND_NAMESPACE_ID` | ||
| * and is skipped explicitly (late-bound namespaces have no fixed DDL | ||
| * schema). Issues are emitted only when the resolved name is a real, | ||
| * creatable schema (not the unbound sentinel) and is missing from the | ||
| * introspected list. `public` is suppressed implicitly because the | ||
| * introspection (or its sensible default) always carries it. | ||
| * | ||
| * Each emitted issue's path is `['database', ddlName]` — an ancestor of | ||
| * every table path under that schema, so it must never be run through | ||
| * `coalesceSubtreeIssues` alongside the table diff (it would swallow the | ||
| * table-level `not-found` issues that drive `CREATE TABLE`); the planner | ||
| * adds these AFTER coalescing the relational issues, and they are not | ||
| * subject to sibling-space ownership scoping (mirrors the retired | ||
| * coordinate walk, which prepended namespace issues after that filter). | ||
| */ | ||
| function verifyPostgresNamespacePresence(input) { | ||
| const { contract, schema } = input; | ||
| const existing = new Set(existingSchemasFromSchema(schema)); | ||
| const issues = []; | ||
| const namespaceIds = Object.keys(contract.storage.namespaces).sort(); | ||
| for (const namespaceId of namespaceIds) { | ||
| if (namespaceId === UNBOUND_NAMESPACE_ID) continue; | ||
| const ddlName = resolveDdlSchemaName(contract.storage, namespaceId); | ||
| if (ddlName === UNBOUND_NAMESPACE_ID) continue; | ||
| if (existing.has(ddlName)) continue; | ||
| const namespace = new PostgresNamespaceSchemaNode({ | ||
| schemaName: ddlName, | ||
| tables: {} | ||
| }); | ||
| issues.push({ | ||
| path: ["database", ddlName], | ||
| expected: namespace | ||
| }); | ||
| } | ||
| return issues; | ||
| } | ||
| //#endregion | ||
| //#region src/core/migrations/planner.ts | ||
| function createPostgresMigrationPlanner(lowerer) { | ||
| return new PostgresMigrationPlanner(lowerer); | ||
| } | ||
| /** | ||
| * Postgres migration planner — a thin wrapper over `planIssues`. | ||
| * | ||
| * `plan()` diffs the target contract against the live schema via the one | ||
| * differ (`buildPostgresPlanDiff`, producing node-typed `SchemaDiffIssue[]`) | ||
| * and delegates to `planIssues` with the unified `postgresPlannerStrategies` | ||
| * list: NOT-NULL backfill, type-change, nullable-tightening, codec-hook | ||
| * storage types, component-declared dependency installs, and | ||
| * shared-temp-default / empty-table-guarded NOT-NULL add-column. The same | ||
| * strategy list runs for `migration plan`, `db update`, and `db init`; | ||
| * behavior diverges purely on `policy.allowedOperationClasses` (the | ||
| * data-safe strategies short-circuit when `'data'` is excluded). The issue | ||
| * planner applies operation-class policy gates and emits a single | ||
| * `PostgresOpFactoryCall[]` that drives both the runtime-ops view (via | ||
| * `renderOps`) and the `renderTypeScript()` authoring surface. RLS policy | ||
| * drift (the structural half of the same one-differ tree) is handled | ||
| * separately via `planPostgresSchemaDiff`. | ||
| */ | ||
| var PostgresMigrationPlanner = class { | ||
| #lowerer; | ||
| constructor(lowerer) { | ||
| this.#lowerer = lowerer; | ||
| } | ||
| plan(options) { | ||
| return this.planSql(options); | ||
| } | ||
| emptyMigration(context, spaceId) { | ||
| return new TypeScriptRenderablePostgresMigration([], { | ||
| from: context.fromHash, | ||
| to: context.toHash | ||
| }, spaceId, context.snapshotsImportPath, this.#lowerer); | ||
| } | ||
| planSql(options) { | ||
| const schemaName = options.schemaName ?? Object.keys(options.contract.storage.namespaces).find((id) => id !== UNBOUND_NAMESPACE_ID) ?? UNBOUND_NAMESPACE_ID; | ||
| const policyResult = this.ensureAdditivePolicy(options.policy); | ||
| if (policyResult) return policyResult; | ||
| PostgresDatabaseSchemaNode.assert(options.schema); | ||
| const { issues: rawIssues } = buildPostgresPlanDiff({ | ||
| contract: options.contract, | ||
| actualSchema: options.schema, | ||
| frameworkComponents: options.frameworkComponents | ||
| }); | ||
| const policyDiffIssues = rawIssues.filter((issue) => isPolicyDiffIssue(issue)); | ||
| const relationalDiffIssues = rawIssues.filter((issue) => !isPolicyDiffIssue(issue)); | ||
| const strict = options.policy.allowedOperationClasses.includes("widening") || options.policy.allowedOperationClasses.includes("destructive"); | ||
| const owned = retainUnownedExtras(coalesceSubtreeIssues(relationalDiffIssues), options.ownership, options.contract); | ||
| const gated = strict ? owned : owned.filter((issue) => issueOutcome(issue) !== "not-expected"); | ||
| const schemaIssues = [...verifyPostgresNamespacePresence({ | ||
| contract: options.contract, | ||
| schema: options.schema | ||
| }), ...gated]; | ||
| const codecHooks = extractCodecControlHooks(options.frameworkComponents); | ||
| const storageTypes = options.contract.storage.types ?? {}; | ||
| const relationalSchema = relationalNamespaceNode(options.schema, schemaName); | ||
| const issuePartition = partitionIssuesByControlPolicy({ | ||
| issues: schemaIssues, | ||
| contract: options.contract, | ||
| resolveControlPolicySubject: (issue) => resolvePostgresNodeIssueControlPolicySubject(issue, options.contract), | ||
| resolveCreationFactoryName: resolvePostgresNodeIssueCreationFactoryName | ||
| }); | ||
| const result = planIssues({ | ||
| issues: issuePartition.plannable, | ||
| toContract: options.contract, | ||
| fromContract: options.fromContract, | ||
| schemaName, | ||
| codecHooks, | ||
| storageTypes, | ||
| ...ifDefined("schema", relationalSchema), | ||
| policy: options.policy, | ||
| frameworkComponents: options.frameworkComponents, | ||
| strategies: postgresPlannerStrategies | ||
| }); | ||
| if (!result.ok) return plannerFailure(result.failure); | ||
| const schemaDiffPartition = partitionCallsByControlPolicy({ | ||
| calls: this.planPostgresSchemaDiff(options, policyDiffIssues), | ||
| contract: options.contract, | ||
| resolveControlPolicySubject: (call) => resolvePostgresCallControlPolicySubject(call, options.contract), | ||
| resolveFactoryName: (call) => call.factoryName | ||
| }); | ||
| const fieldEventPartition = partitionCallsByControlPolicy({ | ||
| calls: blindCast(planFieldEventOperations({ | ||
| priorContract: options.fromContract, | ||
| newContract: options.contract, | ||
| codecHooks | ||
| })), | ||
| contract: options.contract, | ||
| resolveControlPolicySubject: (call) => resolvePostgresCallControlPolicySubject(call, options.contract), | ||
| resolveFactoryName: (call) => call.factoryName | ||
| }); | ||
| const calls = [ | ||
| ...result.value.calls, | ||
| ...schemaDiffPartition.kept, | ||
| ...fieldEventPartition.kept | ||
| ]; | ||
| const warnings = [ | ||
| ...issuePartition.suppressions, | ||
| ...schemaDiffPartition.suppressions, | ||
| ...fieldEventPartition.suppressions | ||
| ].map((record) => renderPostgresSuppression(record, options.contract)); | ||
| return Object.freeze({ | ||
| kind: "success", | ||
| plan: new TypeScriptRenderablePostgresMigration(calls, { | ||
| from: options.fromContract?.storage.storageHash ?? null, | ||
| to: options.contract.storage.storageHash | ||
| }, options.spaceId, options.snapshotsImportPath, this.#lowerer), | ||
| ...warnings.length > 0 ? { warnings: Object.freeze(warnings) } : {} | ||
| }); | ||
| } | ||
| /** | ||
| * Maps the RLS policy presence findings of the one combined tree diff | ||
| * (`buildPostgresPlanDiff`, already ownership-filtered) into | ||
| * `CREATE POLICY` / `DROP POLICY` / `ALTER POLICY … RENAME TO` ops. It | ||
| * does not re-diff — it consumes exactly the policy-node subset of the | ||
| * shared diff's issues. Enablement is NOT decided here: `ENABLE`/`DISABLE | ||
| * ROW LEVEL SECURITY` derive from the table's marker-driven `rlsEnabled` | ||
| * attribute diff on the relational side. | ||
| * | ||
| * Rename post-pass: a `not-found` and a `not-expected` policy on the SAME | ||
| * table whose wire-name content hashes match but prefixes differ are one | ||
| * prefix-only rename, collapsed into a single non-destructive | ||
| * `RenamePostgresRlsPolicyCall`. Multi-candidate hash groups pair | ||
| * deterministically by sorted wire name; leftovers proceed as | ||
| * create/drop. Unparseable wire names never pair. | ||
| * | ||
| * The pairing runs only when the policy allows `widening` (rename's | ||
| * class). Without it (db-init's additive-only set), pairing degrades | ||
| * deliberately to the additive half: the new name is CREATEd and the old | ||
| * policy survives live until a widening/destructive-allowed plan runs — | ||
| * emitting an ungated widening rename would only fail at the runner's | ||
| * class re-enforcement. | ||
| */ | ||
| planPostgresSchemaDiff(options, filteredDiffIssues) { | ||
| const allowsDestructive = options.policy.allowedOperationClasses.includes("destructive"); | ||
| const allowsWidening = options.policy.allowedOperationClasses.includes("widening"); | ||
| const missing = []; | ||
| const extra = []; | ||
| for (const issue of filteredDiffIssues) if (issueOutcome(issue) === "not-found") { | ||
| const expected = issue.expected; | ||
| PostgresPolicySchemaNode.assert(expected); | ||
| missing.push({ | ||
| node: expected, | ||
| schemaForTable: resolveDdlSchemaForNamespaceStorage(options.contract.storage, expected.namespaceId) | ||
| }); | ||
| } else if (issueOutcome(issue) === "not-expected") { | ||
| const actual = issue.actual; | ||
| PostgresPolicySchemaNode.assert(actual); | ||
| extra.push({ | ||
| node: actual, | ||
| schemaForTable: resolveDdlSchemaForNamespaceStorage(options.contract.storage, actual.namespaceId) | ||
| }); | ||
| } | ||
| const calls = []; | ||
| const renamedExtras = /* @__PURE__ */ new Set(); | ||
| const renamedMissing = /* @__PURE__ */ new Set(); | ||
| const pairingKey = (finding, hash) => JSON.stringify([ | ||
| finding.schemaForTable, | ||
| finding.node.tableName, | ||
| hash | ||
| ]); | ||
| if (allowsWidening) { | ||
| const extrasByGroup = /* @__PURE__ */ new Map(); | ||
| for (const finding of extra) { | ||
| const parsed = parseRlsPolicyWireName(finding.node.name); | ||
| if (parsed === void 0) continue; | ||
| const key = pairingKey(finding, parsed.hash); | ||
| const group = extrasByGroup.get(key) ?? []; | ||
| group.push(finding); | ||
| extrasByGroup.set(key, group); | ||
| } | ||
| for (const group of extrasByGroup.values()) group.sort((a, b) => a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0); | ||
| const sortedMissing = [...missing].sort((a, b) => a.node.name < b.node.name ? -1 : a.node.name > b.node.name ? 1 : 0); | ||
| for (const missingFinding of sortedMissing) { | ||
| const parsed = parseRlsPolicyWireName(missingFinding.node.name); | ||
| if (parsed === void 0) continue; | ||
| const candidate = extrasByGroup.get(pairingKey(missingFinding, parsed.hash))?.shift(); | ||
| if (candidate === void 0) continue; | ||
| renamedExtras.add(candidate); | ||
| renamedMissing.add(missingFinding); | ||
| calls.push(new RenamePostgresRlsPolicyCall(missingFinding.schemaForTable, missingFinding.node.tableName, candidate.node.name, missingFinding.node.name)); | ||
| } | ||
| } | ||
| for (const finding of missing) { | ||
| if (renamedMissing.has(finding)) continue; | ||
| calls.push(new CreatePostgresRlsPolicyCall(finding.schemaForTable, finding.node.tableName, policyNodeToContractPolicy(finding.node))); | ||
| } | ||
| if (allowsDestructive) for (const finding of extra) { | ||
| if (renamedExtras.has(finding)) continue; | ||
| calls.push(new DropPostgresRlsPolicyCall(finding.schemaForTable, finding.node.tableName, finding.node.name)); | ||
| } | ||
| return calls; | ||
| } | ||
| ensureAdditivePolicy(policy) { | ||
| if (!policy.allowedOperationClasses.includes("additive")) return plannerFailure([{ | ||
| kind: "unsupportedOperation", | ||
| summary: "Migration planner requires additive operations be allowed", | ||
| why: "The planner requires the \"additive\" operation class to be allowed in the policy." | ||
| }]); | ||
| return null; | ||
| } | ||
| }; | ||
| /** | ||
| * A diff issue whose node is an RLS policy — the structural half of the one | ||
| * combined tree diff, routed to `planPostgresSchemaDiff` instead of | ||
| * `planIssues`. | ||
| */ | ||
| function isPolicyDiffIssue(issue) { | ||
| const node = issue.expected ?? issue.actual; | ||
| return node !== void 0 && PostgresPolicySchemaNode.is(node); | ||
| } | ||
| /** | ||
| * Drops a `not-expected` issue when it is a whole extra storage entity (a table | ||
| * or a native enum) that some space in the composition owns. Each such node | ||
| * yields its own storage coordinate (see {@link postgresNodeStorageCoordinate}), | ||
| * so `declaresEntity` answers over the whole composition on one uniform | ||
| * coordinate: a positive answer means a sibling owns this entity here — leave | ||
| * it; a negative answer means a genuine orphan — drop it. A node with no storage | ||
| * coordinate (a namespace, or a deeper column/constraint drift on an owned | ||
| * table) is this space's own and is always kept. No oracle ⇒ keep everything. | ||
| */ | ||
| function retainUnownedExtras(issues, ownership, contract) { | ||
| if (ownership === void 0) return issues; | ||
| return issues.filter((issue) => { | ||
| if (issueOutcome(issue) !== "not-expected") return true; | ||
| const node = issueNode(issue); | ||
| if (node === void 0) return true; | ||
| const coordinate = postgresNodeStorageCoordinate(node); | ||
| if (coordinate === void 0) return true; | ||
| const ddlSchemaName = issueSchemaName(issue); | ||
| if (ddlSchemaName === void 0) return true; | ||
| const namespaceId = resolveNamespaceIdForDdlSchema(contract, ddlSchemaName); | ||
| return !ownership.declaresEntity({ | ||
| namespaceId, | ||
| ...coordinate | ||
| }); | ||
| }); | ||
| } | ||
| /** | ||
| * Returns the one namespace node the relational strategy layer probes for | ||
| * live-table existence and reads codec-hook context off — the namespace | ||
| * matching the planner's resolved schema name, or the first namespace when | ||
| * none matches. `undefined` when the tree has no namespaces, so the strategy | ||
| * context uses its empty-schema default. | ||
| * | ||
| * The relational strategies key tables by bare name, so they can only probe one | ||
| * namespace at a time; probing across every namespace at once is future work. | ||
| * | ||
| * Returns the real `PostgresNamespaceSchemaNode` reference rather than a | ||
| * projection: `storageTypePlanCallStrategy` hands this same value to codec | ||
| * `planTypeOperations` hooks as `schema`, and hooks read the Postgres-specific | ||
| * `nativeEnums` field off it (via `PostgresNamespaceSchemaNode.is`) to decide | ||
| * whether a native enum type already exists — a projection that only copies | ||
| * `tables` would silently drop that signal. `StrategyContext.schema`'s | ||
| * declared type (`SqlSchemaIR`, shared with SQLite's flat shape) doesn't | ||
| * capture this, so the return is `blindCast` the same way `namespaceSchemaNodes` | ||
| * in the family's relational walk already treats a namespace node as a | ||
| * structurally-`SqlSchemaIR`-shaped value. | ||
| */ | ||
| function relationalNamespaceNode(schema, schemaName) { | ||
| const namespaceNodes = Object.values(schema.namespaces); | ||
| const namespaceNode = namespaceNodes.find((node) => node.schemaName === schemaName) ?? namespaceNodes[0]; | ||
| if (namespaceNode === void 0) return void 0; | ||
| return blindCast(namespaceNode); | ||
| } | ||
| /** | ||
| * Rebuilds the `PostgresRlsPolicy` contract entity `CreatePostgresRlsPolicyCall` | ||
| * carries (its `renderTypeScript`/`createRlsPolicy` paths serialize the whole | ||
| * entity, `namespaceId` included). This reconstructs rather than looking the | ||
| * original up in the contract on purpose: the diff node's `namespaceId` is the | ||
| * *resolved DDL schema* (set when the expected tree was built), which is the | ||
| * value the emitted op must carry; the contract-stored entity holds the raw, | ||
| * pre-resolution coordinate, so a lookup would change the migration output. | ||
| */ | ||
| function policyNodeToContractPolicy(node) { | ||
| return new PostgresRlsPolicy({ | ||
| name: node.name, | ||
| prefix: node.prefix, | ||
| tableName: node.tableName, | ||
| namespaceId: node.namespaceId, | ||
| operation: node.operation, | ||
| roles: [...node.roles], | ||
| ...ifDefined("using", node.using), | ||
| ...ifDefined("withCheck", node.withCheck), | ||
| permissive: node.permissive | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { createPostgresMigrationPlanner as t }; | ||
| //# sourceMappingURL=planner-eM-kNm0O.mjs.map |
| {"version":3,"file":"planner-eM-kNm0O.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 });\n issues.push({\n path: ['database', ddlName],\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 { issueOutcome } 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 { postgresNodeStorageCoordinate } from '../schema-ir/node-storage-coordinate';\nimport { PostgresDatabaseSchemaNode } from '../schema-ir/postgres-database-schema-node';\nimport { PostgresPolicySchemaNode } from '../schema-ir/postgres-policy-schema-node';\nimport type { SqlSchemaDiffNode } from '../schema-ir/schema-node-kinds';\nimport {\n renderPostgresSuppression,\n resolveNamespaceIdForDdlSchema,\n resolvePostgresCallControlPolicySubject,\n resolvePostgresNodeIssueControlPolicySubject,\n resolvePostgresNodeIssueCreationFactoryName,\n} from './control-policy';\nimport { buildPostgresPlanDiff } from './diff-database-schema';\nimport { coalesceSubtreeIssues, issueNode, issueSchemaName, planIssues } 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 /**\n * POSIX-relative path from the migration package dir to\n * `migrations/snapshots` — see\n * {@link SqlMigrationPlannerPlanOptions.snapshotsImportPath}.\n */\n readonly snapshotsImportPath: string;\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 context.snapshotsImportPath,\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 // Role diff issues resolve to the `external` control policy (see\n // `resolvePostgresNodeIssueControlPolicySubject`'s role branch), so the\n // control-policy partition below suppresses them to zero ops on its own,\n // before `mapNodeIssueToCall` ever sees them — no separate exclusion\n // needed here.\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) => issueOutcome(issue) !== '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 });\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 });\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 });\n const calls = [...result.value.calls, ...schemaDiffPartition.kept, ...fieldEventPartition.kept];\n const warnings: SqlPlannerConflict[] = [\n ...issuePartition.suppressions,\n ...schemaDiffPartition.suppressions,\n ...fieldEventPartition.suppressions,\n ].map((record) => renderPostgresSuppression(record, options.contract));\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 options.snapshotsImportPath,\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 (issueOutcome(issue) === '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 (issueOutcome(issue) === '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 storage entity (a table\n * or a native enum) that some space in the composition owns. Each such node\n * yields its own storage coordinate (see {@link postgresNodeStorageCoordinate}),\n * so `declaresEntity` answers over the whole composition on one uniform\n * coordinate: a positive answer means a sibling owns this entity here — leave\n * it; a negative answer means a genuine orphan — drop it. A node with no storage\n * coordinate (a namespace, or a deeper column/constraint drift on an owned\n * table) is this space's own and is always kept. No oracle ⇒ keep everything.\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 (issueOutcome(issue) !== 'not-expected') return true;\n const node = issueNode(issue);\n if (node === undefined) return true;\n const coordinate = postgresNodeStorageCoordinate(node);\n if (coordinate === undefined) return true;\n const ddlSchemaName = issueSchemaName(issue);\n if (ddlSchemaName === undefined) return true;\n const namespaceId = resolveNamespaceIdForDdlSchema(contract, ddlSchemaName);\n return !ownership.declaresEntity({ namespaceId, ...coordinate });\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 * `nativeEnums` field off it (via `PostgresNamespaceSchemaNode.is`) to decide\n * whether a native enum type already exists — a projection that only copies\n * `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 (+ nativeEnums, 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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"} |
| import { t as PostgresMigration } from "./postgres-migration-CB767OIG.mjs"; | ||
| import { t as renderOps } from "./render-ops-BREh1kHe.mjs"; | ||
| import { t as renderCallsToTypeScript } from "./render-typescript-Ta7_6CGf.mjs"; | ||
| //#region src/core/migrations/planner-produced-postgres-migration.ts | ||
| var TypeScriptRenderablePostgresMigration = class extends PostgresMigration { | ||
| #calls; | ||
| #meta; | ||
| #spaceId; | ||
| #snapshotsImportPath; | ||
| #lowerer; | ||
| #operationsCache; | ||
| constructor(calls, meta, spaceId, snapshotsImportPath, lowerer) { | ||
| super(); | ||
| this.#calls = calls; | ||
| this.#meta = meta; | ||
| this.#spaceId = spaceId; | ||
| this.#snapshotsImportPath = snapshotsImportPath; | ||
| this.#lowerer = lowerer; | ||
| } | ||
| get operations() { | ||
| this.#operationsCache ??= renderOps(this.#calls, this.#lowerer); | ||
| return this.#operationsCache; | ||
| } | ||
| describe() { | ||
| return this.#meta; | ||
| } | ||
| /** | ||
| * Contract space this planner-produced plan applies to. Threaded | ||
| * from the planner options so the runner keys the marker row by | ||
| * the right space when executing the plan. | ||
| */ | ||
| get spaceId() { | ||
| return this.#spaceId; | ||
| } | ||
| renderTypeScript() { | ||
| return renderCallsToTypeScript(this.#calls, { | ||
| from: this.#meta.from, | ||
| to: this.#meta.to, | ||
| snapshotsImportPath: this.#snapshotsImportPath | ||
| }); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { TypeScriptRenderablePostgresMigration as t }; | ||
| //# sourceMappingURL=planner-produced-postgres-migration-Db57IpMZ.mjs.map |
| {"version":3,"file":"planner-produced-postgres-migration-Db57IpMZ.mjs","names":["#calls","#meta","#spaceId","#snapshotsImportPath","#lowerer","#operationsCache"],"sources":["../src/core/migrations/planner-produced-postgres-migration.ts"],"sourcesContent":["/**\n * Planner-produced Postgres migration.\n *\n * Returned by `PostgresMigrationPlanner.plan(...)` and `emptyMigration(...)`.\n * Holds the migration IR (`PostgresOpFactoryCall[]`) alongside\n * `MigrationMeta` and exposes both the runtime-ops view (`get operations`)\n * and the TypeScript authoring view (`renderTypeScript()`). Satisfies\n * `MigrationPlanWithAuthoringSurface` so the CLI can uniformly serialize any\n * planner result back to `migration.ts`.\n *\n * Extends the family-level `SqlMigration` alias rather than the target-local\n * migration base directly — mirrors Mongo's `PlannerProducedMongoMigration`\n * shape and keeps CLI wiring one step removed from target internals.\n *\n * Placeholder-bearing plans: `renderTypeScript()` always succeeds and embeds\n * `() => placeholder(\"slot\")` at each stub. `operations`, in contrast, is\n * _not safe to enumerate_ on a stub-bearing plan — `DataTransformCall.toOp()`\n * throws `MIGRATION.UNFILLED_PLACEHOLDER` because a planner-stubbed closure cannot be lowered\n * to a runtime op. Callers that know a plan may carry stubs must render to\n * `migration.ts`, let the user fill the slots, and re-load the edited\n * migration before enumerating ops. The walk-schema planner does not emit\n * `DataTransformCall`s today, so this asymmetry is invisible until the\n * issue-planner integration lands in Phase 2.\n */\n\nimport type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';\nimport type { ExecuteRequestLowerer } from '@prisma-next/family-sql/control-adapter';\nimport type {\n MigrationPlanWithAuthoringSurface,\n OpFactoryCall,\n} from '@prisma-next/framework-components/control';\nimport type { MigrationMeta } from '@prisma-next/migration-tools/migration';\nimport type { PostgresPlanTargetDetails } from './planner-target-details';\nimport { PostgresMigration } from './postgres-migration';\nimport { renderOps } from './render-ops';\nimport { renderCallsToTypeScript } from './render-typescript';\n\nexport class TypeScriptRenderablePostgresMigration\n extends PostgresMigration\n implements MigrationPlanWithAuthoringSurface\n{\n readonly #calls: readonly OpFactoryCall[];\n readonly #meta: MigrationMeta;\n readonly #spaceId: string;\n readonly #snapshotsImportPath: string;\n readonly #lowerer: ExecuteRequestLowerer | undefined;\n #operationsCache:\n | readonly (\n | SqlMigrationPlanOperation<PostgresPlanTargetDetails>\n | Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>>\n )[]\n | undefined;\n\n constructor(\n calls: readonly OpFactoryCall[],\n meta: MigrationMeta,\n spaceId: string,\n snapshotsImportPath: string,\n lowerer?: ExecuteRequestLowerer,\n ) {\n super();\n this.#calls = calls;\n this.#meta = meta;\n this.#spaceId = spaceId;\n this.#snapshotsImportPath = snapshotsImportPath;\n this.#lowerer = lowerer;\n }\n\n override get operations(): readonly (\n | SqlMigrationPlanOperation<PostgresPlanTargetDetails>\n | Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>>\n )[] {\n this.#operationsCache ??= renderOps(this.#calls, this.#lowerer);\n return this.#operationsCache;\n }\n\n override describe(): MigrationMeta {\n return this.#meta;\n }\n\n /**\n * Contract space this planner-produced plan applies to. Threaded\n * from the planner options so the runner keys the marker row by\n * the right space when executing the plan.\n */\n get spaceId(): string {\n return this.#spaceId;\n }\n\n renderTypeScript(): string {\n return renderCallsToTypeScript(this.#calls, {\n from: this.#meta.from,\n to: this.#meta.to,\n snapshotsImportPath: this.#snapshotsImportPath,\n });\n }\n}\n"],"mappings":";;;;AAqCA,IAAa,wCAAb,cACU,kBAEV;CACE;CACA;CACA;CACA;CACA;CACA;CAOA,YACE,OACA,MACA,SACA,qBACA,SACA;EACA,MAAM;EACN,KAAKA,SAAS;EACd,KAAKC,QAAQ;EACb,KAAKC,WAAW;EAChB,KAAKC,uBAAuB;EAC5B,KAAKC,WAAW;CAClB;CAEA,IAAa,aAGT;EACF,KAAKC,qBAAqB,UAAU,KAAKL,QAAQ,KAAKI,QAAQ;EAC9D,OAAO,KAAKC;CACd;CAEA,WAAmC;EACjC,OAAO,KAAKJ;CACd;;;;;;CAOA,IAAI,UAAkB;EACpB,OAAO,KAAKC;CACd;CAEA,mBAA2B;EACzB,OAAO,wBAAwB,KAAKF,QAAQ;GAC1C,MAAM,KAAKC,MAAM;GACjB,IAAI,KAAKA,MAAM;GACf,qBAAqB,KAAKE;EAC5B,CAAC;CACH;AACF"} |
| import { g as PostgresRlsPolicy } from "./postgres-schema-CewLYHB7.mjs"; | ||
| import { A as SetDefaultCall, C as DropNativeEnumTypeCall, D as EnableRowLevelSecurityCall, E as DropTableCall, F as installExtension, S as DropIndexCall, T as DropPostgresRlsPolicyCall, _ as DisableRowLevelSecurityCall, b as DropConstraintCall, c as AddUniqueCall, d as CreateIndexCall, f as CreateNativeEnumTypeCall, h as CreateTableCall, i as AddNativeEnumValueCall, j as SetNotNullCall, k as RenamePostgresRlsPolicyCall, l as AlterColumnTypeCall, m as CreateSchemaCall, n as AddColumnCall, p as CreatePostgresRlsPolicyCall, r as AddForeignKeyCall, s as AddPrimaryKeyCall, t as AddCheckConstraintCall, v as DropCheckConstraintCall, w as DropNotNullCall, x as DropDefaultCall, y as DropColumnCall } from "./op-factory-call-DMb7FTR6.mjs"; | ||
| import { t as errorPostgresMigrationStackMissing } from "./errors-ByDS0xJL.mjs"; | ||
| import { t as PostgresContractView } from "./postgres-contract-view-DebHMqGR.mjs"; | ||
| import { t as dataTransform } from "./data-transform-BOWpliq8.mjs"; | ||
| import { Migration } from "@prisma-next/family-sql/migration"; | ||
| import { MigrationContractViews } from "@prisma-next/migration-tools/migration"; | ||
| //#region src/core/migrations/postgres-migration.ts | ||
| /** | ||
| * Target-owned base class for Postgres migrations. | ||
| * | ||
| * Fixes the `SqlMigration` generic to `PostgresPlanTargetDetails` and the | ||
| * abstract `targetId` to the Postgres target-id string literal, so both | ||
| * user-authored migrations and renderer-generated scaffolds (the output of | ||
| * `renderCallsToTypeScript`) can extend `PostgresMigration` directly without | ||
| * redeclaring target-local identity. | ||
| * | ||
| * Mirrors `MongoMigration` in `@prisma-next/family-mongo`: the renderer | ||
| * emits `extends Migration` against a facade re-export of this class | ||
| * from `@prisma-next/postgres/migration`, keeping the authoring surface | ||
| * target-scoped rather than family-scoped. | ||
| * | ||
| * The constructor materializes a single Postgres `SqlControlAdapter` from | ||
| * `stack.adapter.create(stack)` and stores it; the protected `dataTransform` | ||
| * instance method forwards to the free `dataTransform` factory with that | ||
| * stored adapter, so user migrations can write `this.dataTransform(...)` | ||
| * without threading the adapter through every call. | ||
| * | ||
| * Every method requires an explicit `schema`. Postgres migrations name their | ||
| * schema deliberately — there is no default and no `search_path`-relative | ||
| * option. A migration that left the schema unspecified would resolve against | ||
| * whatever `search_path` the connection happened to carry, and that ambiguity | ||
| * is an antipattern in a migration. (The unbound/unspecified namespace concept | ||
| * remains for SQLite, which has no schemas, and for Mongo's connection `db`.) | ||
| */ | ||
| var PostgresMigration = class extends Migration { | ||
| targetId = "postgres"; | ||
| /** | ||
| * Materialized Postgres control adapter, created once per migration | ||
| * instance from the injected stack. `undefined` only when the migration | ||
| * was instantiated without a stack (test fixtures); `controlAdapterFor` | ||
| * throws a MIGRATION.POSTGRES_CONTROL_STACK_MISSING in that case to surface the misuse. | ||
| */ | ||
| controlAdapter; | ||
| #endView = new MigrationContractViews(this, "PostgresMigration", (json) => PostgresContractView.fromJson(json)); | ||
| #startView = new MigrationContractViews(this, "PostgresMigration", (json) => PostgresContractView.fromJson(json)); | ||
| constructor(stack) { | ||
| super(stack); | ||
| this.controlAdapter = stack?.adapter ? stack.adapter.create(stack) : void 0; | ||
| } | ||
| /** | ||
| * Returns the materialized control adapter, or throws a MIGRATION.POSTGRES_CONTROL_STACK_MISSING naming | ||
| * `operation` when the migration was constructed without a `ControlStack`. | ||
| * Single home for the null-check that every DDL/DML method shares. | ||
| */ | ||
| controlAdapterFor(operation) { | ||
| if (!this.controlAdapter) throw errorPostgresMigrationStackMissing(operation); | ||
| return this.controlAdapter; | ||
| } | ||
| /** | ||
| * The typed, schema-qualified Postgres view over this migration's end-state | ||
| * contract — `this.endContract.namespace.<schema>.table.<name>`, etc. Throws | ||
| * if no `endContractJson` was provided. | ||
| */ | ||
| get endContract() { | ||
| return this.#endView.endContract; | ||
| } | ||
| /** | ||
| * The typed Postgres view over this migration's start-state contract, or | ||
| * `null` for a baseline migration (no `startContractJson`). | ||
| */ | ||
| get startContract() { | ||
| return this.#startView.startContract; | ||
| } | ||
| /** | ||
| * Instance-method wrapper around the free `dataTransform` factory that | ||
| * supplies the stored control adapter. Authors call this from inside | ||
| * `get operations()`; the adapter argument is hidden from the call site. | ||
| */ | ||
| dataTransform(contract, name, options) { | ||
| return dataTransform(contract, name, options, this.controlAdapterFor("dataTransform")); | ||
| } | ||
| /** | ||
| * Emit a `CREATE TABLE` migration operation. Builds a typed DDL node from | ||
| * the supplied options and lowers it through the stored control adapter. | ||
| * Throws if no adapter is present (i.e. migration instantiated without a stack). | ||
| */ | ||
| createTable(options) { | ||
| return new CreateTableCall(options.schema, options.table, options.columns, options.constraints).toOp(this.controlAdapterFor("createTable")); | ||
| } | ||
| /** | ||
| * Emit a `CREATE SCHEMA` migration operation. Builds a typed DDL node from | ||
| * the supplied options and lowers it through the stored control adapter. | ||
| * Throws if no adapter is present (i.e. migration instantiated without a stack). | ||
| */ | ||
| createSchema(options) { | ||
| return new CreateSchemaCall(options.schema).toOp(this.controlAdapterFor("createSchema")); | ||
| } | ||
| /** | ||
| * Emit a `CREATE TYPE ... AS ENUM (...)` migration operation for a managed | ||
| * native enum. Builds a typed DDL node and lowers it through the stored | ||
| * control adapter (members render in declaration order). Throws if no adapter | ||
| * is present. | ||
| */ | ||
| createNativeEnumType(options) { | ||
| return new CreateNativeEnumTypeCall(options.schema, options.typeName, options.members).toOp(this.controlAdapterFor("createNativeEnumType")); | ||
| } | ||
| /** | ||
| * Emit a `DROP TYPE` migration operation for a managed native enum, lowered | ||
| * through the stored control adapter. Throws if no adapter is present. | ||
| */ | ||
| dropNativeEnumType(options) { | ||
| return new DropNativeEnumTypeCall(options.schema, options.typeName).toOp(this.controlAdapterFor("dropNativeEnumType")); | ||
| } | ||
| /** | ||
| * Emit an `ALTER TYPE ... ADD VALUE` migration operation appending one | ||
| * member to a managed native enum, lowered through the stored control | ||
| * adapter. Throws if no adapter is present. Every appended value is its | ||
| * own operation — call this once per value to append more than one. | ||
| */ | ||
| addNativeEnumValue(options) { | ||
| return new AddNativeEnumValueCall(options.schema, options.typeName, options.value).toOp(this.controlAdapterFor("addNativeEnumValue")); | ||
| } | ||
| addColumn(options) { | ||
| return new AddColumnCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("addColumn")); | ||
| } | ||
| addPrimaryKey(options) { | ||
| return new AddPrimaryKeyCall(options.schema, options.table, options.constraint, options.columns).toOp(this.controlAdapterFor("addPrimaryKey")); | ||
| } | ||
| addUnique(options) { | ||
| return new AddUniqueCall(options.schema, options.table, options.constraint, options.columns).toOp(this.controlAdapterFor("addUnique")); | ||
| } | ||
| addForeignKey(options) { | ||
| return new AddForeignKeyCall(options.schema, options.table, options.foreignKey).toOp(this.controlAdapterFor("addForeignKey")); | ||
| } | ||
| addCheckConstraint(options) { | ||
| return new AddCheckConstraintCall(options.schema, options.table, options.constraint, options.column, options.values).toOp(this.controlAdapterFor("addCheckConstraint")); | ||
| } | ||
| dropCheckConstraint(options) { | ||
| return new DropCheckConstraintCall(options.schema, options.table, options.constraint).toOp(this.controlAdapterFor("dropCheckConstraint")); | ||
| } | ||
| dropConstraint(options) { | ||
| return new DropConstraintCall(options.schema, options.table, options.constraint, options.kind ?? "unique").toOp(this.controlAdapterFor("dropConstraint")); | ||
| } | ||
| dropTable(options) { | ||
| return new DropTableCall(options.schema, options.table).toOp(this.controlAdapterFor("dropTable")); | ||
| } | ||
| dropColumn(options) { | ||
| return new DropColumnCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("dropColumn")); | ||
| } | ||
| alterColumnType(options) { | ||
| return new AlterColumnTypeCall(options.schema, options.table, options.column, options.options).toOp(this.controlAdapterFor("alterColumnType")); | ||
| } | ||
| setNotNull(options) { | ||
| return new SetNotNullCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("setNotNull")); | ||
| } | ||
| dropNotNull(options) { | ||
| return new DropNotNullCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("dropNotNull")); | ||
| } | ||
| setDefault(options) { | ||
| return new SetDefaultCall(options.schema, options.table, options.column, options.defaultSql, options.operationClass).toOp(this.controlAdapterFor("setDefault")); | ||
| } | ||
| dropDefault(options) { | ||
| return new DropDefaultCall(options.schema, options.table, options.column).toOp(this.controlAdapterFor("dropDefault")); | ||
| } | ||
| createIndex(options) { | ||
| return new CreateIndexCall(options.schema, options.table, options.index, options.columns, options.extras).toOp(this.controlAdapterFor("createIndex")); | ||
| } | ||
| dropIndex(options) { | ||
| return new DropIndexCall(options.schema, options.table, options.index).toOp(this.controlAdapterFor("dropIndex")); | ||
| } | ||
| installExtension(options) { | ||
| return installExtension(options, this.controlAdapterFor("installExtension")); | ||
| } | ||
| enableRowLevelSecurity(options) { | ||
| return new EnableRowLevelSecurityCall(options.schema, options.table).toOp(this.controlAdapterFor("enableRowLevelSecurity")); | ||
| } | ||
| disableRowLevelSecurity(options) { | ||
| return new DisableRowLevelSecurityCall(options.schema, options.table).toOp(this.controlAdapterFor("disableRowLevelSecurity")); | ||
| } | ||
| createRlsPolicy(options) { | ||
| return new CreatePostgresRlsPolicyCall(options.schema, options.table, new PostgresRlsPolicy(options.policy)).toOp(this.controlAdapterFor("createRlsPolicy")); | ||
| } | ||
| dropRlsPolicy(options) { | ||
| return new DropPostgresRlsPolicyCall(options.schema, options.table, options.policy).toOp(this.controlAdapterFor("dropRlsPolicy")); | ||
| } | ||
| renameRlsPolicy(options) { | ||
| return new RenamePostgresRlsPolicyCall(options.schema, options.table, options.from, options.to).toOp(this.controlAdapterFor("renameRlsPolicy")); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { PostgresMigration as t }; | ||
| //# sourceMappingURL=postgres-migration-CB767OIG.mjs.map |
| {"version":3,"file":"postgres-migration-CB767OIG.mjs","names":["SqlMigration","#endView","#startView"],"sources":["../src/core/migrations/postgres-migration.ts"],"sourcesContent":["import type { Contract } from '@prisma-next/contract/types';\nimport type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';\nimport type { SqlControlAdapter } from '@prisma-next/family-sql/control-adapter';\nimport { Migration as SqlMigration } from '@prisma-next/family-sql/migration';\nimport type { ControlStack } from '@prisma-next/framework-components/control';\nimport { MigrationContractViews } from '@prisma-next/migration-tools/migration';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport type { DdlColumn, DdlTableConstraint } from '@prisma-next/sql-relational-core/ast';\nimport { errorPostgresMigrationStackMissing } from '../errors';\nimport { PostgresContractView } from '../postgres-contract-view';\nimport { PostgresRlsPolicy, type PostgresRlsPolicyInput } from '../postgres-rls-policy';\nimport {\n AddCheckConstraintCall,\n AddColumnCall,\n AddForeignKeyCall,\n AddNativeEnumValueCall,\n AddPrimaryKeyCall,\n AddUniqueCall,\n AlterColumnTypeCall,\n type AlterColumnTypeOptions,\n CreateIndexCall,\n CreateNativeEnumTypeCall,\n CreatePostgresRlsPolicyCall,\n CreateSchemaCall,\n CreateTableCall,\n DisableRowLevelSecurityCall,\n DropCheckConstraintCall,\n DropColumnCall,\n DropConstraintCall,\n DropDefaultCall,\n DropIndexCall,\n DropNativeEnumTypeCall,\n DropNotNullCall,\n DropPostgresRlsPolicyCall,\n DropTableCall,\n EnableRowLevelSecurityCall,\n RenamePostgresRlsPolicyCall,\n SetDefaultCall,\n SetNotNullCall,\n} from './op-factory-call';\nimport { type DataTransformOptions, dataTransform } from './operations/data-transform';\nimport { installExtension } from './operations/dependencies';\nimport type { CreateIndexExtras } from './operations/indexes';\nimport type { ForeignKeySpec } from './operations/shared';\nimport type { PostgresPlanTargetDetails } from './planner-target-details';\n\n/**\n * Target-owned base class for Postgres migrations.\n *\n * Fixes the `SqlMigration` generic to `PostgresPlanTargetDetails` and the\n * abstract `targetId` to the Postgres target-id string literal, so both\n * user-authored migrations and renderer-generated scaffolds (the output of\n * `renderCallsToTypeScript`) can extend `PostgresMigration` directly without\n * redeclaring target-local identity.\n *\n * Mirrors `MongoMigration` in `@prisma-next/family-mongo`: the renderer\n * emits `extends Migration` against a facade re-export of this class\n * from `@prisma-next/postgres/migration`, keeping the authoring surface\n * target-scoped rather than family-scoped.\n *\n * The constructor materializes a single Postgres `SqlControlAdapter` from\n * `stack.adapter.create(stack)` and stores it; the protected `dataTransform`\n * instance method forwards to the free `dataTransform` factory with that\n * stored adapter, so user migrations can write `this.dataTransform(...)`\n * without threading the adapter through every call.\n *\n * Every method requires an explicit `schema`. Postgres migrations name their\n * schema deliberately — there is no default and no `search_path`-relative\n * option. A migration that left the schema unspecified would resolve against\n * whatever `search_path` the connection happened to carry, and that ambiguity\n * is an antipattern in a migration. (The unbound/unspecified namespace concept\n * remains for SQLite, which has no schemas, and for Mongo's connection `db`.)\n */\nexport abstract class PostgresMigration<\n Start extends Contract<SqlStorage> = Contract<SqlStorage>,\n End extends Contract<SqlStorage> = Contract<SqlStorage>,\n> extends SqlMigration<PostgresPlanTargetDetails, 'postgres', Start, End> {\n readonly targetId = 'postgres' as const;\n\n /**\n * Materialized Postgres control adapter, created once per migration\n * instance from the injected stack. `undefined` only when the migration\n * was instantiated without a stack (test fixtures); `controlAdapterFor`\n * throws a MIGRATION.POSTGRES_CONTROL_STACK_MISSING in that case to surface the misuse.\n */\n protected readonly controlAdapter: SqlControlAdapter<'postgres'> | undefined;\n\n #endView = new MigrationContractViews<PostgresContractView<End>>(\n this,\n 'PostgresMigration',\n (json) => PostgresContractView.fromJson<End>(json),\n );\n #startView = new MigrationContractViews<PostgresContractView<Start>>(\n this,\n 'PostgresMigration',\n (json) => PostgresContractView.fromJson<Start>(json),\n );\n\n constructor(stack?: ControlStack<'sql', 'postgres'>) {\n super(stack);\n // The descriptor `create()` is typed as the wider `ControlAdapterInstance`;\n // the Postgres descriptor concretely returns a `SqlControlAdapter<'postgres'>`,\n // so the cast holds for any Postgres-target stack assembled at runtime.\n this.controlAdapter = stack?.adapter\n ? (stack.adapter.create(stack) as SqlControlAdapter<'postgres'>)\n : undefined;\n }\n\n /**\n * Returns the materialized control adapter, or throws a MIGRATION.POSTGRES_CONTROL_STACK_MISSING naming\n * `operation` when the migration was constructed without a `ControlStack`.\n * Single home for the null-check that every DDL/DML method shares.\n */\n private controlAdapterFor(operation: string): SqlControlAdapter<'postgres'> {\n if (!this.controlAdapter) {\n throw errorPostgresMigrationStackMissing(operation);\n }\n return this.controlAdapter;\n }\n\n /**\n * The typed, schema-qualified Postgres view over this migration's end-state\n * contract — `this.endContract.namespace.<schema>.table.<name>`, etc. Throws\n * if no `endContractJson` was provided.\n */\n get endContract(): PostgresContractView<End> {\n return this.#endView.endContract;\n }\n\n /**\n * The typed Postgres view over this migration's start-state contract, or\n * `null` for a baseline migration (no `startContractJson`).\n */\n get startContract(): PostgresContractView<Start> | null {\n return this.#startView.startContract;\n }\n\n /**\n * Instance-method wrapper around the free `dataTransform` factory that\n * supplies the stored control adapter. Authors call this from inside\n * `get operations()`; the adapter argument is hidden from the call site.\n */\n protected dataTransform<TContract extends Contract<SqlStorage>>(\n contract: TContract,\n name: string,\n options: DataTransformOptions,\n ): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return dataTransform(contract, name, options, this.controlAdapterFor('dataTransform'));\n }\n\n /**\n * Emit a `CREATE TABLE` migration operation. Builds a typed DDL node from\n * the supplied options and lowers it through the stored control adapter.\n * Throws if no adapter is present (i.e. migration instantiated without a stack).\n */\n protected createTable(options: {\n readonly schema: string;\n readonly table: string;\n readonly ifNotExists?: boolean;\n readonly columns: readonly DdlColumn[];\n readonly constraints?: readonly DdlTableConstraint[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreateTableCall(\n options.schema,\n options.table,\n options.columns,\n options.constraints,\n ).toOp(this.controlAdapterFor('createTable'));\n }\n\n /**\n * Emit a `CREATE SCHEMA` migration operation. Builds a typed DDL node from\n * the supplied options and lowers it through the stored control adapter.\n * Throws if no adapter is present (i.e. migration instantiated without a stack).\n */\n protected createSchema(options: {\n readonly schema: string;\n readonly ifNotExists?: boolean;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreateSchemaCall(options.schema).toOp(this.controlAdapterFor('createSchema'));\n }\n\n /**\n * Emit a `CREATE TYPE ... AS ENUM (...)` migration operation for a managed\n * native enum. Builds a typed DDL node and lowers it through the stored\n * control adapter (members render in declaration order). Throws if no adapter\n * is present.\n */\n protected createNativeEnumType(options: {\n readonly schema: string;\n readonly typeName: string;\n readonly members: readonly string[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreateNativeEnumTypeCall(options.schema, options.typeName, options.members).toOp(\n this.controlAdapterFor('createNativeEnumType'),\n );\n }\n\n /**\n * Emit a `DROP TYPE` migration operation for a managed native enum, lowered\n * through the stored control adapter. Throws if no adapter is present.\n */\n protected dropNativeEnumType(options: {\n readonly schema: string;\n readonly typeName: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropNativeEnumTypeCall(options.schema, options.typeName).toOp(\n this.controlAdapterFor('dropNativeEnumType'),\n );\n }\n\n /**\n * Emit an `ALTER TYPE ... ADD VALUE` migration operation appending one\n * member to a managed native enum, lowered through the stored control\n * adapter. Throws if no adapter is present. Every appended value is its\n * own operation — call this once per value to append more than one.\n */\n protected addNativeEnumValue(options: {\n readonly schema: string;\n readonly typeName: string;\n readonly value: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddNativeEnumValueCall(options.schema, options.typeName, options.value).toOp(\n this.controlAdapterFor('addNativeEnumValue'),\n );\n }\n\n protected addColumn(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: DdlColumn;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddColumnCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('addColumn'),\n );\n }\n\n protected addPrimaryKey(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n readonly columns: readonly string[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddPrimaryKeyCall(\n options.schema,\n options.table,\n options.constraint,\n options.columns,\n ).toOp(this.controlAdapterFor('addPrimaryKey'));\n }\n\n protected addUnique(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n readonly columns: readonly string[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddUniqueCall(\n options.schema,\n options.table,\n options.constraint,\n options.columns,\n ).toOp(this.controlAdapterFor('addUnique'));\n }\n\n protected addForeignKey(options: {\n readonly schema: string;\n readonly table: string;\n readonly foreignKey: ForeignKeySpec;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddForeignKeyCall(options.schema, options.table, options.foreignKey).toOp(\n this.controlAdapterFor('addForeignKey'),\n );\n }\n\n protected addCheckConstraint(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n readonly column: string;\n readonly values: readonly string[];\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AddCheckConstraintCall(\n options.schema,\n options.table,\n options.constraint,\n options.column,\n options.values,\n ).toOp(this.controlAdapterFor('addCheckConstraint'));\n }\n\n protected dropCheckConstraint(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropCheckConstraintCall(options.schema, options.table, options.constraint).toOp(\n this.controlAdapterFor('dropCheckConstraint'),\n );\n }\n\n protected dropConstraint(options: {\n readonly schema: string;\n readonly table: string;\n readonly constraint: string;\n readonly kind?: 'foreignKey' | 'unique' | 'primaryKey';\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropConstraintCall(\n options.schema,\n options.table,\n options.constraint,\n options.kind ?? 'unique',\n ).toOp(this.controlAdapterFor('dropConstraint'));\n }\n\n protected dropTable(options: {\n readonly schema: string;\n readonly table: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropTableCall(options.schema, options.table).toOp(\n this.controlAdapterFor('dropTable'),\n );\n }\n\n protected dropColumn(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropColumnCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('dropColumn'),\n );\n }\n\n protected alterColumnType(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n readonly options: AlterColumnTypeOptions;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new AlterColumnTypeCall(\n options.schema,\n options.table,\n options.column,\n options.options,\n ).toOp(this.controlAdapterFor('alterColumnType'));\n }\n\n protected setNotNull(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new SetNotNullCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('setNotNull'),\n );\n }\n\n protected dropNotNull(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropNotNullCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('dropNotNull'),\n );\n }\n\n protected setDefault(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n readonly defaultSql: string;\n readonly operationClass?: 'additive' | 'widening';\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new SetDefaultCall(\n options.schema,\n options.table,\n options.column,\n options.defaultSql,\n options.operationClass,\n ).toOp(this.controlAdapterFor('setDefault'));\n }\n\n protected dropDefault(options: {\n readonly schema: string;\n readonly table: string;\n readonly column: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropDefaultCall(options.schema, options.table, options.column).toOp(\n this.controlAdapterFor('dropDefault'),\n );\n }\n\n protected createIndex(options: {\n readonly schema: string;\n readonly table: string;\n readonly index: string;\n readonly columns: readonly string[];\n readonly extras?: CreateIndexExtras;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreateIndexCall(\n options.schema,\n options.table,\n options.index,\n options.columns,\n options.extras,\n ).toOp(this.controlAdapterFor('createIndex'));\n }\n\n protected dropIndex(options: {\n readonly schema: string;\n readonly table: string;\n readonly index: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropIndexCall(options.schema, options.table, options.index).toOp(\n this.controlAdapterFor('dropIndex'),\n );\n }\n\n protected installExtension(options: {\n readonly extensionName: string;\n readonly invariantId: string;\n readonly id: string;\n readonly label?: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return installExtension(options, this.controlAdapterFor('installExtension'));\n }\n\n protected enableRowLevelSecurity(options: {\n readonly schema: string;\n readonly table: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new EnableRowLevelSecurityCall(options.schema, options.table).toOp(\n this.controlAdapterFor('enableRowLevelSecurity'),\n );\n }\n\n protected disableRowLevelSecurity(options: {\n readonly schema: string;\n readonly table: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DisableRowLevelSecurityCall(options.schema, options.table).toOp(\n this.controlAdapterFor('disableRowLevelSecurity'),\n );\n }\n\n protected createRlsPolicy(options: {\n readonly schema: string;\n readonly table: string;\n readonly policy: PostgresRlsPolicyInput;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new CreatePostgresRlsPolicyCall(\n options.schema,\n options.table,\n new PostgresRlsPolicy(options.policy),\n ).toOp(this.controlAdapterFor('createRlsPolicy'));\n }\n\n protected dropRlsPolicy(options: {\n readonly schema: string;\n readonly table: string;\n readonly policy: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new DropPostgresRlsPolicyCall(options.schema, options.table, options.policy).toOp(\n this.controlAdapterFor('dropRlsPolicy'),\n );\n }\n\n protected renameRlsPolicy(options: {\n readonly schema: string;\n readonly table: string;\n readonly from: string;\n readonly to: string;\n }): Promise<SqlMigrationPlanOperation<PostgresPlanTargetDetails>> {\n return new RenamePostgresRlsPolicyCall(\n options.schema,\n options.table,\n options.from,\n options.to,\n ).toOp(this.controlAdapterFor('renameRlsPolicy'));\n }\n}\n"],"mappings":"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"} |
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
2117555
0+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed