@prisma-next/sql-schema-ir
Advanced tools
| import { createHash } from "node:crypto"; | ||
| import { structuredError } from "@prisma-next/utils/structured-error"; | ||
| //#region src/naming.ts | ||
| function defaultIndexName(tableName, columns) { | ||
| return `${tableName}_${columns.join("_")}_idx`; | ||
| } | ||
| const WIRE_NAME_PATTERN = /^(.+)_([0-9a-f]{8})$/; | ||
| /** | ||
| * Assembles a wire name from its user-supplied prefix and its 8-hex | ||
| * content-hash suffix. This module owns the `<prefix>_<hash>` format on both | ||
| * sides — construction here and parsing in {@link parseWireName} — so the two | ||
| * never drift. | ||
| */ | ||
| function formatWireName(prefix, hash) { | ||
| return `${prefix}_${hash}`; | ||
| } | ||
| /** | ||
| * Splits a wire name (`<prefix>_<8hex>`) into its prefix and content-hash | ||
| * suffix. Returns `undefined` when the name does not follow the wire-name | ||
| * shape (e.g. an object created outside the toolchain) — callers treat such | ||
| * names as all-prefix. Consumed by introspection (prefix extraction) and by | ||
| * rename pairing (same hash, different prefix). | ||
| */ | ||
| function parseWireName(name) { | ||
| const match = WIRE_NAME_PATTERN.exec(name); | ||
| const prefix = match?.[1]; | ||
| const hash = match?.[2]; | ||
| if (prefix === void 0 || hash === void 0) return void 0; | ||
| return { | ||
| prefix, | ||
| hash | ||
| }; | ||
| } | ||
| /** | ||
| * Stabilizes an authored SQL body (index expression, partial-index predicate, | ||
| * RLS policy predicate) for hashing: trim, and collapse runs of internal | ||
| * whitespace to a single space. | ||
| * | ||
| * This is deliberately minimal. The content hash is the equivalence relation | ||
| * for a wire-named object, and the wire name (prefix + hash) is the only | ||
| * thing ever compared — the hash is never recomputed from an introspected | ||
| * body, so there is no need to match the database's reprinted form. Minimal | ||
| * normalization also protects the no-collision property: aggressive rewriting | ||
| * (lowercasing, paren-stripping, cast-alias folding) risks collapsing two | ||
| * distinct bodies onto one hash. | ||
| * | ||
| * The normalizer is a stability commitment: any change re-suffixes all wire names. | ||
| */ | ||
| function normalizeSqlBody(sql) { | ||
| return sql.replace(/\s+/g, " ").trim(); | ||
| } | ||
| /** | ||
| * Returns the first 8 lowercase hex characters of the SHA-256 digest over the | ||
| * canonical content tuple for an index: | ||
| * | ||
| * [normalizeSqlBody(expression), normalizeSqlBody(where), columns, unique, type, sortedOptions] | ||
| * | ||
| * Columns hash in authored order — column order is semantic in an index. | ||
| * Option values are `String()`-coerced (matching the loose option equality | ||
| * used for diffing) so a hash computed from typed contract values agrees with | ||
| * one recomputed from introspected reloptions strings. The prefix, schema, | ||
| * and table are excluded (they are orthogonal to index equivalence). | ||
| * | ||
| * The tuple order and encoding are a stability commitment with the same | ||
| * status as the RLS tuple: any change re-suffixes every wire name. | ||
| */ | ||
| /** | ||
| * Canonicalizes one index option VALUE to the `on`/`off` boolean spelling: | ||
| * JS booleans and the common catalog spellings (`pg_class.reloptions` | ||
| * stores whatever spelling the DDL used, so a live index may carry | ||
| * `'true'`/`'false'` or `'on'`/`'off'`) all map to one form; everything | ||
| * else via `String()` (fully specified for numbers, so no platform | ||
| * variance). Shared by the wire-name hash tuple, the node's option | ||
| * equality, and the DDL renderer, so an authored `{ fastupdate: true }` | ||
| * agrees with a live index created under any boolean spelling. | ||
| */ | ||
| function normalizeIndexOptionValue(value) { | ||
| if (value === true || value === "true" || value === "on") return "on"; | ||
| if (value === false || value === "false" || value === "off") return "off"; | ||
| return String(value); | ||
| } | ||
| function computeIndexContentHash(parts) { | ||
| const sortedOptions = Object.entries(parts.options ?? {}).map(([key, value]) => [key, normalizeIndexOptionValue(value)]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0); | ||
| const tuple = JSON.stringify([ | ||
| normalizeSqlBody(parts.expression ?? ""), | ||
| normalizeSqlBody(parts.where ?? ""), | ||
| parts.columns ?? [], | ||
| parts.unique, | ||
| parts.type ?? "", | ||
| sortedOptions | ||
| ]); | ||
| return createHash("sha256").update(tuple).digest("hex").slice(0, 8); | ||
| } | ||
| /** | ||
| * Postgres identifiers cap at 63 characters and the wire name appends a | ||
| * 9-character `_<8hex>` suffix, so an authored prefix is bounded at 54. | ||
| */ | ||
| const WIRE_NAME_PREFIX_MAX_LENGTH = 54; | ||
| /** | ||
| * Rejects a wire-name prefix over {@link WIRE_NAME_PREFIX_MAX_LENGTH}. | ||
| * `subject` opens the error message (e.g. `defineContract: policy prefix`). | ||
| */ | ||
| function assertWireNamePrefixLength(prefix, subject) { | ||
| if (prefix.length > 54) throw structuredError("CONTRACT.WIRE_NAME_PREFIX_TOO_LONG", `${subject} "${prefix}" exceeds the 54-character maximum (Postgres identifiers cap at 63 characters and the wire name appends a 9-character hash suffix).`, { meta: { | ||
| prefix, | ||
| maxLength: 54 | ||
| } }); | ||
| } | ||
| //#endregion | ||
| export { formatWireName as a, parseWireName as c, defaultIndexName as i, assertWireNamePrefixLength as n, normalizeIndexOptionValue as o, computeIndexContentHash as r, normalizeSqlBody as s, WIRE_NAME_PREFIX_MAX_LENGTH as t }; | ||
| //# sourceMappingURL=naming-BD7Y-Fq1.mjs.map |
| {"version":3,"file":"naming-BD7Y-Fq1.mjs","names":[],"sources":["../src/naming.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { structuredError } from '@prisma-next/utils/structured-error';\n\nexport function defaultIndexName(tableName: string, columns: readonly string[]): string {\n return `${tableName}_${columns.join('_')}_idx`;\n}\n\nexport interface WireName {\n /** The user-supplied part before the `_<8hex>` suffix. */\n readonly prefix: string;\n /** The 8-lowercase-hex content-hash suffix. */\n readonly hash: string;\n}\n\nconst WIRE_NAME_PATTERN = /^(.+)_([0-9a-f]{8})$/;\n\n/**\n * Assembles a wire name from its user-supplied prefix and its 8-hex\n * content-hash suffix. This module owns the `<prefix>_<hash>` format on both\n * sides — construction here and parsing in {@link parseWireName} — so the two\n * never drift.\n */\nexport function formatWireName(prefix: string, hash: string): string {\n return `${prefix}_${hash}`;\n}\n\n/**\n * Splits a wire name (`<prefix>_<8hex>`) into its prefix and content-hash\n * suffix. Returns `undefined` when the name does not follow the wire-name\n * shape (e.g. an object created outside the toolchain) — callers treat such\n * names as all-prefix. Consumed by introspection (prefix extraction) and by\n * rename pairing (same hash, different prefix).\n */\nexport function parseWireName(name: string): WireName | undefined {\n const match = WIRE_NAME_PATTERN.exec(name);\n const prefix = match?.[1];\n const hash = match?.[2];\n if (prefix === undefined || hash === undefined) return undefined;\n return { prefix, hash };\n}\n\n/**\n * Stabilizes an authored SQL body (index expression, partial-index predicate,\n * RLS policy predicate) for hashing: trim, and collapse runs of internal\n * whitespace to a single space.\n *\n * This is deliberately minimal. The content hash is the equivalence relation\n * for a wire-named object, and the wire name (prefix + hash) is the only\n * thing ever compared — the hash is never recomputed from an introspected\n * body, so there is no need to match the database's reprinted form. Minimal\n * normalization also protects the no-collision property: aggressive rewriting\n * (lowercasing, paren-stripping, cast-alias folding) risks collapsing two\n * distinct bodies onto one hash.\n *\n * The normalizer is a stability commitment: any change re-suffixes all wire names.\n */\nexport function normalizeSqlBody(sql: string): string {\n return sql.replace(/\\s+/g, ' ').trim();\n}\n\nexport interface IndexContentHashParts {\n readonly expression?: string;\n readonly where?: string;\n readonly columns?: readonly string[];\n readonly unique: boolean;\n readonly type?: string;\n readonly options?: Record<string, unknown>;\n}\n\n/**\n * Returns the first 8 lowercase hex characters of the SHA-256 digest over the\n * canonical content tuple for an index:\n *\n * [normalizeSqlBody(expression), normalizeSqlBody(where), columns, unique, type, sortedOptions]\n *\n * Columns hash in authored order — column order is semantic in an index.\n * Option values are `String()`-coerced (matching the loose option equality\n * used for diffing) so a hash computed from typed contract values agrees with\n * one recomputed from introspected reloptions strings. The prefix, schema,\n * and table are excluded (they are orthogonal to index equivalence).\n *\n * The tuple order and encoding are a stability commitment with the same\n * status as the RLS tuple: any change re-suffixes every wire name.\n */\n/**\n * Canonicalizes one index option VALUE to the `on`/`off` boolean spelling:\n * JS booleans and the common catalog spellings (`pg_class.reloptions`\n * stores whatever spelling the DDL used, so a live index may carry\n * `'true'`/`'false'` or `'on'`/`'off'`) all map to one form; everything\n * else via `String()` (fully specified for numbers, so no platform\n * variance). Shared by the wire-name hash tuple, the node's option\n * equality, and the DDL renderer, so an authored `{ fastupdate: true }`\n * agrees with a live index created under any boolean spelling.\n */\nexport function normalizeIndexOptionValue(value: unknown): string {\n if (value === true || value === 'true' || value === 'on') return 'on';\n if (value === false || value === 'false' || value === 'off') return 'off';\n return String(value);\n}\n\nexport function computeIndexContentHash(parts: IndexContentHashParts): string {\n const sortedOptions = Object.entries(parts.options ?? {})\n .map(([key, value]): readonly [string, string] => [key, normalizeIndexOptionValue(value)])\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n\n const tuple = JSON.stringify([\n normalizeSqlBody(parts.expression ?? ''),\n normalizeSqlBody(parts.where ?? ''),\n parts.columns ?? [],\n parts.unique,\n parts.type ?? '',\n sortedOptions,\n ]);\n return createHash('sha256').update(tuple).digest('hex').slice(0, 8);\n}\n\n/**\n * Postgres identifiers cap at 63 characters and the wire name appends a\n * 9-character `_<8hex>` suffix, so an authored prefix is bounded at 54.\n */\nexport const WIRE_NAME_PREFIX_MAX_LENGTH = 54;\n\n/**\n * Rejects a wire-name prefix over {@link WIRE_NAME_PREFIX_MAX_LENGTH}.\n * `subject` opens the error message (e.g. `defineContract: policy prefix`).\n */\nexport function assertWireNamePrefixLength(prefix: string, subject: string): void {\n if (prefix.length > WIRE_NAME_PREFIX_MAX_LENGTH) {\n throw structuredError(\n 'CONTRACT.WIRE_NAME_PREFIX_TOO_LONG',\n `${subject} \"${prefix}\" exceeds the ${WIRE_NAME_PREFIX_MAX_LENGTH}-character maximum (Postgres identifiers cap at 63 characters and the wire name appends a 9-character hash suffix).`,\n { meta: { prefix, maxLength: WIRE_NAME_PREFIX_MAX_LENGTH } },\n );\n }\n}\n"],"mappings":";;;AAGA,SAAgB,iBAAiB,WAAmB,SAAoC;CACtF,OAAO,GAAG,UAAU,GAAG,QAAQ,KAAK,GAAG,EAAE;AAC3C;AASA,MAAM,oBAAoB;;;;;;;AAQ1B,SAAgB,eAAe,QAAgB,MAAsB;CACnE,OAAO,GAAG,OAAO,GAAG;AACtB;;;;;;;;AASA,SAAgB,cAAc,MAAoC;CAChE,MAAM,QAAQ,kBAAkB,KAAK,IAAI;CACzC,MAAM,SAAS,QAAQ;CACvB,MAAM,OAAO,QAAQ;CACrB,IAAI,WAAW,KAAA,KAAa,SAAS,KAAA,GAAW,OAAO,KAAA;CACvD,OAAO;EAAE;EAAQ;CAAK;AACxB;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,KAAqB;CACpD,OAAO,IAAI,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAAK;AACvC;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAgB,0BAA0B,OAAwB;CAChE,IAAI,UAAU,QAAQ,UAAU,UAAU,UAAU,MAAM,OAAO;CACjE,IAAI,UAAU,SAAS,UAAU,WAAW,UAAU,OAAO,OAAO;CACpE,OAAO,OAAO,KAAK;AACrB;AAEA,SAAgB,wBAAwB,OAAsC;CAC5E,MAAM,gBAAgB,OAAO,QAAQ,MAAM,WAAW,CAAC,CAAC,CAAC,CACtD,KAAK,CAAC,KAAK,WAAsC,CAAC,KAAK,0BAA0B,KAAK,CAAC,CAAC,CAAC,CACzF,MAAM,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;CAElD,MAAM,QAAQ,KAAK,UAAU;EAC3B,iBAAiB,MAAM,cAAc,EAAE;EACvC,iBAAiB,MAAM,SAAS,EAAE;EAClC,MAAM,WAAW,CAAC;EAClB,MAAM;EACN,MAAM,QAAQ;EACd;CACF,CAAC;CACD,OAAO,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC;AACpE;;;;;AAMA,MAAa,8BAA8B;;;;;AAM3C,SAAgB,2BAA2B,QAAgB,SAAuB;CAChF,IAAI,OAAO,SAAA,IACT,MAAM,gBACJ,sCACA,GAAG,QAAQ,IAAI,OAAO,sIACtB,EAAE,MAAM;EAAE;EAAQ,WAAA;CAAuC,EAAE,CAC7D;AAEJ"} |
| import { o as normalizeIndexOptionValue } from "./naming-BD7Y-Fq1.mjs"; | ||
| import { IRNodeBase, freezeNode } from "@prisma-next/framework-components/ir"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { canonicalStringify } from "@prisma-next/utils/canonical-stringify"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| import { isArrayEqual } from "@prisma-next/utils/array-equal"; | ||
| import { InternalError } from "@prisma-next/utils/internal-error"; | ||
| //#region src/ir/schema-node-kinds.ts | ||
| /** | ||
| * The `nodeKind` discriminant for each relational schema-diff leaf node. | ||
| * Each node carries a unique value; the differ pairs siblings by `id`, and | ||
| * these kinds distinguish the node types that appear as `PostgresTableSchemaNode` | ||
| * children (columns, primary key, foreign keys, uniques, indexes, checks) from | ||
| * each other and from the RLS policy/role kinds a target defines separately. | ||
| */ | ||
| const RelationalSchemaNodeKind = { | ||
| schema: "sql-schema", | ||
| table: "sql-table", | ||
| column: "sql-column", | ||
| columnDefault: "sql-column-default", | ||
| primaryKey: "sql-primary-key", | ||
| foreignKey: "sql-foreign-key", | ||
| unique: "sql-unique", | ||
| index: "sql-index", | ||
| check: "sql-check-constraint" | ||
| }; | ||
| /** | ||
| * The one real map from a relational `nodeKind` to the framework-neutral | ||
| * {@link DiffSubjectGranularity} its diff issues carry — the SQL family's | ||
| * post-diff filters (issue category, strict-mode gating) and the framework | ||
| * aggregate's unclaimed-elements sweep key on the granularity, never on the | ||
| * `nodeKind` spelling and never on anything stamped on the node. Resolution | ||
| * is by `nodeKind` equality against this map, not a suffix string match. | ||
| * Target-specific node kinds (Postgres namespace/table/policy/role) are | ||
| * outside this family layer's vocabulary and map their own kinds directly. | ||
| */ | ||
| const RELATIONAL_NODE_GRANULARITY = { | ||
| [RelationalSchemaNodeKind.schema]: "structural", | ||
| [RelationalSchemaNodeKind.table]: "entity", | ||
| [RelationalSchemaNodeKind.column]: "field", | ||
| [RelationalSchemaNodeKind.columnDefault]: "auxiliary", | ||
| [RelationalSchemaNodeKind.primaryKey]: "auxiliary", | ||
| [RelationalSchemaNodeKind.foreignKey]: "auxiliary", | ||
| [RelationalSchemaNodeKind.unique]: "auxiliary", | ||
| [RelationalSchemaNodeKind.index]: "auxiliary", | ||
| [RelationalSchemaNodeKind.check]: "auxiliary" | ||
| }; | ||
| function isRelationalSchemaNodeKind(nodeKind) { | ||
| return Object.hasOwn(RELATIONAL_NODE_GRANULARITY, nodeKind); | ||
| } | ||
| /** | ||
| * Looks up the subject granularity for a relational `nodeKind`. Throws for a | ||
| * `nodeKind` outside this map (a target-specific kind, e.g. Postgres's | ||
| * namespace/table/policy/role) — those map their own kinds directly rather | ||
| * than through this family-level map. | ||
| */ | ||
| function relationalNodeGranularity(nodeKind) { | ||
| if (!isRelationalSchemaNodeKind(nodeKind)) throw new Error(`relationalNodeGranularity: unrecognized relational node kind "${nodeKind}"`); | ||
| return RELATIONAL_NODE_GRANULARITY[nodeKind]; | ||
| } | ||
| /** | ||
| * The one real map from a relational `nodeKind` to its storage `entityKind` | ||
| * — the same vocabulary the contract storage's `entries` dictionary keys use | ||
| * (`elementCoordinates` walks it). Only the whole-table kind has an entity of | ||
| * its own; every other relational node (a column, an index, …) is nested | ||
| * under one and maps to nothing here. | ||
| */ | ||
| const RELATIONAL_NODE_ENTITY_KIND = { [RelationalSchemaNodeKind.table]: "table" }; | ||
| /** | ||
| * Looks up the storage `entityKind` for a relational `nodeKind` — sibling of | ||
| * {@link relationalNodeGranularity}, resolved by `nodeKind` equality against | ||
| * {@link RELATIONAL_NODE_ENTITY_KIND}. `undefined` for a target-specific kind | ||
| * (targets map their own kinds directly) or a relational kind with no entity | ||
| * of its own. | ||
| */ | ||
| function relationalNodeEntityKind(nodeKind) { | ||
| return isRelationalSchemaNodeKind(nodeKind) ? RELATIONAL_NODE_ENTITY_KIND[nodeKind] : void 0; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-schema-ir-node.ts | ||
| /** | ||
| * SQL Schema IR node base. Carries the family-level | ||
| * `kind = 'sql-schema-ir'` discriminator and inherits the framework's | ||
| * `freezeNode` affordance. | ||
| * | ||
| * SQL Schema IR represents the actual database state as discovered by | ||
| * introspection (the parallel to SQL Contract IR, which represents the | ||
| * desired state). | ||
| * | ||
| * The discriminator is installed as a non-enumerable own property, | ||
| * matching the SqlNode pattern. This keeps `JSON.stringify(node)` | ||
| * canonical (no `kind` field), keeps `toEqual({...})` test assertions | ||
| * against pre-lift flat shapes passing, and keeps `node.kind` readable | ||
| * for dispatch. | ||
| * | ||
| * Both `kind` and `nodeKind` are required: every concrete leaf is a node | ||
| * the generic differ can pair and compare, so every leaf must declare which | ||
| * node it is. `nodeKind` has no default here — every direct subclass sets its | ||
| * own literal value (the relational leaves via `RelationalSchemaNodeKind`, | ||
| * target concretions via their own vocabulary, e.g. `PostgresSchemaNodeKind`). | ||
| */ | ||
| var SqlSchemaIRNode = class extends IRNodeBase { | ||
| constructor() { | ||
| super(); | ||
| Object.defineProperty(this, "kind", { | ||
| value: "sql-schema-ir", | ||
| writable: false, | ||
| enumerable: false, | ||
| configurable: false | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Asserts `node` matches `predicate`, narrowing its type to `T`. The one | ||
| * shared implementation every node class's `static assert` and `isEqualTo` | ||
| * reach for, instead of each hand-writing its own throw: the message names | ||
| * the class the caller expected. | ||
| */ | ||
| function assertNode(node, className, predicate) { | ||
| if (node === void 0 || !predicate(node)) throw new Error(`Expected a ${className} but got nodeKind=${node?.nodeKind ?? "undefined"}`); | ||
| } | ||
| /** | ||
| * Defines a non-enumerable own property, the same treatment `kind` gets | ||
| * above: a derivation-time render-support field stays out of | ||
| * `JSON.stringify`, `toEqual({...})` structural assertions, and spreads, | ||
| * while remaining directly readable (`node.field`) for the one consumer | ||
| * that resolves it at plan time. A no-op when `value` is `undefined` — the | ||
| * property is simply absent, matching every other optional field on these | ||
| * nodes. | ||
| */ | ||
| function defineNonEnumerable(target, key, value) { | ||
| if (value === void 0) return; | ||
| Object.defineProperty(target, key, { | ||
| value, | ||
| enumerable: false, | ||
| writable: false, | ||
| configurable: false | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/ir/primary-key.ts | ||
| /** | ||
| * Primary-key Schema IR node. Mirrors the Contract IR `PrimaryKey` | ||
| * shape (same `columns` + optional `name`) so verification can compare | ||
| * intent and actual structurally. Defined here independently to avoid | ||
| * a sql-schema-ir -> sql-contract dependency. | ||
| * | ||
| * Implements `DiffableNode` so a primary key is directly a table's diff-tree | ||
| * child. `id` is a fixed sentinel — a table has at most one primary key, so | ||
| * there is never a sibling to disambiguate against. `isEqualTo` compares the | ||
| * column tuple; the PK's own `name` is a database-assigned label with no | ||
| * semantic weight, so it is not compared (mirrors the policy node's | ||
| * name-insensitivity to non-identifying detail). | ||
| */ | ||
| var PrimaryKey = class PrimaryKey extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.primaryKey; | ||
| columns; | ||
| constructor(input) { | ||
| super(); | ||
| this.columns = input.columns; | ||
| if (input.name !== void 0) this.name = input.name; | ||
| defineNonEnumerable(this, "dependsOn", input.dependsOn); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return "primary-key"; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.primaryKey; | ||
| } | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "PrimaryKey", PrimaryKey.is); | ||
| return this.columns.length === node.columns.length && this.columns.every((c, i) => c === node.columns[i]); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-check-constraint-ir.ts | ||
| /** | ||
| * Schema IR node for a table-level check constraint that restricts a | ||
| * column to a set of permitted values (an enum-style `IN (...)` check). | ||
| * | ||
| * Carries the **resolved values** rather than a raw SQL predicate so | ||
| * callers can compare value-sets without parsing SQL. | ||
| * | ||
| * Implements `DiffableNode` so a check constraint is directly a table's | ||
| * diff-tree child: `id` is the constraint name. `isEqualTo` compares | ||
| * `column` and the permitted-value set (order-insensitive — the database | ||
| * does not guarantee `IN (...)` ordering). | ||
| */ | ||
| var SqlCheckConstraintIR = class SqlCheckConstraintIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.check; | ||
| name; | ||
| column; | ||
| permittedValues; | ||
| constructor(input) { | ||
| super(); | ||
| this.name = input.name; | ||
| this.column = input.column; | ||
| this.permittedValues = Object.freeze([...input.permittedValues]); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return `check:${this.name}`; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.check; | ||
| } | ||
| /** | ||
| * Compares the permitted-value sets only (unordered), matching the | ||
| * relational walk's `verifyCheckConstraints`: two checks pairing by name | ||
| * compare their value sets — the `column` field is descriptive, not part | ||
| * of the comparison, so a name-paired check with equal values verifies | ||
| * regardless of which column carries it. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlCheckConstraintIR", SqlCheckConstraintIR.is); | ||
| const thisValues = new Set(this.permittedValues); | ||
| const otherValues = new Set(node.permittedValues); | ||
| if (thisValues.size !== otherValues.size) return false; | ||
| return [...thisValues].every((v) => otherValues.has(v)); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/resolved-default-equality.ts | ||
| /** | ||
| * Structural equality for two resolved column defaults, ported from the | ||
| * relational walk's `columnDefaultsEqual` normalized branch: kinds must | ||
| * match; literal values are normalized (Date and temporal-typed strings to | ||
| * ISO instants) then compared canonically (JSON objects match their | ||
| * canonical string form); function expressions compare case- and | ||
| * whitespace-insensitively. | ||
| * | ||
| * `nativeType` provides the temporal-normalization context (the actual | ||
| * side's resolved native type in a diff comparison). | ||
| */ | ||
| function resolvedDefaultsEqual(expected, actual, nativeType) { | ||
| if (expected.kind !== actual.kind) return false; | ||
| if (expected.kind === "literal" && actual.kind === "literal") return literalValuesEqual(normalizeLiteralValue(expected.value, nativeType), normalizeLiteralValue(actual.value, nativeType)); | ||
| if (expected.kind === "function" && actual.kind === "function") return normalizeFunctionExpression(expected.expression) === normalizeFunctionExpression(actual.expression); | ||
| return false; | ||
| } | ||
| function normalizeFunctionExpression(expression) { | ||
| return expression.toLowerCase().replace(/\s+/g, ""); | ||
| } | ||
| function isTemporalNativeType(nativeType) { | ||
| if (!nativeType) return false; | ||
| const normalized = nativeType.toLowerCase(); | ||
| return normalized.includes("timestamp") || normalized === "date"; | ||
| } | ||
| function normalizeLiteralValue(value, nativeType) { | ||
| if (value instanceof Date) return value.toISOString(); | ||
| if (typeof value === "string" && isTemporalNativeType(nativeType)) { | ||
| const parsed = new Date(value); | ||
| if (!Number.isNaN(parsed.getTime())) return parsed.toISOString(); | ||
| } | ||
| return value; | ||
| } | ||
| function literalValuesEqual(a, b) { | ||
| if (a === b) return true; | ||
| if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) return canonicalStringify(a) === canonicalStringify(b); | ||
| if (typeof a === "object" && a !== null && typeof b === "string") try { | ||
| return canonicalStringify(a) === canonicalStringify(JSON.parse(b)); | ||
| } catch { | ||
| return false; | ||
| } | ||
| if (typeof a === "string" && typeof b === "object" && b !== null) try { | ||
| return canonicalStringify(JSON.parse(a)) === canonicalStringify(b); | ||
| } catch { | ||
| return false; | ||
| } | ||
| return false; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-column-default-ir.ts | ||
| /** | ||
| * Schema-diff node for a column's default value. The default is the one | ||
| * column attribute with an extra/missing/drift lifecycle of its own, so it | ||
| * is a child node of the column rather than a compared attribute: an | ||
| * undeclared live default surfaces as `not-expected`, a declared default the | ||
| * database lacks as `not-found`, and a divergent value as `not-equal` — the | ||
| * three reasons cover the legacy `extra_default` / `default_missing` / | ||
| * `default_mismatch` vocabulary with no attribute inspection. | ||
| * | ||
| * `id` is a fixed sentinel — a column has at most one default. Built | ||
| * transiently by `SqlColumnIR.children()` from the column's own fields; | ||
| * never constructed by derivation or introspection directly. | ||
| */ | ||
| var SqlColumnDefaultIR = class SqlColumnDefaultIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.columnDefault; | ||
| constructor(input) { | ||
| super(); | ||
| if (input.resolved !== void 0) this.resolved = input.resolved; | ||
| if (input.raw !== void 0) this.raw = input.raw; | ||
| if (input.nativeTypeContext !== void 0) this.nativeTypeContext = input.nativeTypeContext; | ||
| defineNonEnumerable(this, "many", input.many); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return "default"; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.columnDefault; | ||
| } | ||
| /** | ||
| * Structured comparison with `this` as the expected side: both sides | ||
| * resolved compare per the relational walk's `columnDefaultsEqual` | ||
| * semantics; a declared expected default against an unparseable actual | ||
| * (raw present, no resolved parse) is a mismatch; two raw-only nodes fall | ||
| * back to raw string equality. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlColumnDefaultIR", SqlColumnDefaultIR.is); | ||
| if (this.resolved !== void 0 && node.resolved !== void 0) return resolvedDefaultsEqual(this.resolved, node.resolved, node.nativeTypeContext ?? this.nativeTypeContext); | ||
| if (this.resolved !== void 0 || node.resolved !== void 0) return false; | ||
| return this.raw === node.raw; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-column-ir.ts | ||
| /** | ||
| * Schema IR node for a single column on a table, as observed by | ||
| * introspection. Unlike the Contract IR `StorageColumn`, this carries | ||
| * the column's `name` (Schema IR columns are returned as arrays from | ||
| * introspection queries; the parent table re-keys them into a record | ||
| * for downstream consumers). | ||
| * | ||
| * Implements `DiffableNode` so a column is directly a table's diff-tree | ||
| * child: `id` is the column name (unique among a table's columns); `isEqualTo` | ||
| * compares this column's own attributes only — never children, since a | ||
| * column is a leaf. When both sides carry `resolvedNativeType` (stamped at | ||
| * derivation/introspection), the comparison uses the resolved values — | ||
| * resolved native type, nullability, and structured default equality per | ||
| * the relational walk's `columnDefaultsEqual` semantics, with `this` as the | ||
| * expected side. Otherwise it falls back to comparing raw fields. | ||
| */ | ||
| var SqlColumnIR = class SqlColumnIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.column; | ||
| name; | ||
| nativeType; | ||
| nullable; | ||
| constructor(input) { | ||
| super(); | ||
| this.name = input.name; | ||
| this.nativeType = input.nativeType; | ||
| this.nullable = input.nullable; | ||
| if (input.default !== void 0) this.default = input.default; | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| if (input.many !== void 0) this.many = input.many; | ||
| if (input.resolvedNativeType !== void 0) this.resolvedNativeType = input.resolvedNativeType; | ||
| if (input.resolvedDefault !== void 0) this.resolvedDefault = input.resolvedDefault; | ||
| defineNonEnumerable(this, "codecRef", input.codecRef); | ||
| defineNonEnumerable(this, "codecBaseNativeType", input.codecBaseNativeType); | ||
| defineNonEnumerable(this, "codecNamedType", input.codecNamedType); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return `column:${this.name}`; | ||
| } | ||
| /** | ||
| * The column's default, when declared/present, is the column's one child | ||
| * node — it has an extra/missing/drift lifecycle of its own, so the differ | ||
| * recurses to it rather than `isEqualTo` comparing it. Built transiently | ||
| * from this column's own fields. | ||
| */ | ||
| children() { | ||
| if (this.resolvedDefault === void 0 && this.default === void 0) return []; | ||
| return [new SqlColumnDefaultIR({ | ||
| ...ifDefined("resolved", this.resolvedDefault), | ||
| ...ifDefined("raw", this.default), | ||
| ...ifDefined("nativeTypeContext", this.resolvedNativeType), | ||
| ...ifDefined("many", this.many ?? this.codecRef?.many) | ||
| })]; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.column; | ||
| } | ||
| /** | ||
| * Compares the column's own attributes only — the default lives on the | ||
| * child node. When both sides carry `resolvedNativeType`, the resolved | ||
| * value governs (array-ness rides on its `[]` suffix); otherwise raw | ||
| * fields compare. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlColumnIR", SqlColumnIR.is); | ||
| if (this.resolvedNativeType !== void 0 && node.resolvedNativeType !== void 0) return this.resolvedNativeType === node.resolvedNativeType && this.nullable === node.nullable; | ||
| return this.nativeType === node.nativeType && this.nullable === node.nullable && Boolean(this.many) === Boolean(node.many); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-foreign-key-ir.ts | ||
| /** | ||
| * Schema IR node for a foreign-key constraint as observed by | ||
| * introspection. The `referencedTable` / `referencedColumns` field | ||
| * names match the introspection vocabulary (`pg_constraint.confkey`, | ||
| * etc.) and intentionally differ from the Contract IR's nested | ||
| * `references: { table, columns }` shape so that the verifier's | ||
| * structural comparison stays explicit about which side it's reading. | ||
| * | ||
| * Implements `DiffableNode` so a foreign key is directly a table's diff-tree | ||
| * child. Foreign keys are frequently unnamed (introspection may not carry a | ||
| * constraint name, and the contract side never invents one), so `id` is | ||
| * derived from the referencing/referenced coordinates rather than `name` — | ||
| * the same tuple that makes two FK constraints the same constraint. This | ||
| * also serves as the comparison key: two FKs with the same coordinates are | ||
| * paired by the differ, and `isEqualTo` then compares the remaining | ||
| * attribute — the referential actions. | ||
| */ | ||
| var SqlForeignKeyIR = class SqlForeignKeyIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.foreignKey; | ||
| columns; | ||
| referencedTable; | ||
| referencedColumns; | ||
| constructor(input) { | ||
| super(); | ||
| this.columns = input.columns; | ||
| this.referencedTable = input.referencedTable; | ||
| this.referencedColumns = input.referencedColumns; | ||
| if (input.referencedSchema !== void 0) this.referencedSchema = input.referencedSchema; | ||
| if (input.name !== void 0) this.name = input.name; | ||
| if (input.onDelete !== void 0) this.onDelete = input.onDelete; | ||
| if (input.onUpdate !== void 0) this.onUpdate = input.onUpdate; | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| const resolvedReferencedNamespace = input.resolvedReferencedNamespace ?? input.referencedSchema; | ||
| if (resolvedReferencedNamespace !== void 0) this.resolvedReferencedNamespace = resolvedReferencedNamespace; | ||
| defineNonEnumerable(this, "dependsOn", input.dependsOn); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| const referencedNamespace = this.resolvedReferencedNamespace ?? ""; | ||
| return `foreign-key:${this.columns.join(",")}->${referencedNamespace}.${this.referencedTable}(${this.referencedColumns.join(",")})`; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.foreignKey; | ||
| } | ||
| /** | ||
| * Referential-action comparison with `this` as the expected side, matching | ||
| * the relational walk's `getReferentialActionMismatches`: `noAction` is the | ||
| * database default and equivalent to an undeclared action, and drift is | ||
| * flagged only when the expected side declares a (normalized) action. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlForeignKeyIR", SqlForeignKeyIR.is); | ||
| return referentialActionMatches(this.onDelete, node.onDelete) && referentialActionMatches(this.onUpdate, node.onUpdate); | ||
| } | ||
| }; | ||
| function referentialActionMatches(expected, actual) { | ||
| const normalizedExpected = expected === "noAction" ? void 0 : expected; | ||
| if (normalizedExpected === void 0) return true; | ||
| return normalizedExpected === (actual === "noAction" ? void 0 : actual); | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-index-ir.ts | ||
| /** | ||
| * Schema IR node for a secondary index as observed by introspection. | ||
| * Unlike the Contract IR `Index`, the Schema IR carries an explicit | ||
| * `unique` field — introspection sees the underlying index regardless | ||
| * of whether the user expressed it as `@@index` or `@@unique`, and the | ||
| * verifier needs to distinguish them when comparing to the Contract. | ||
| * | ||
| * Implements `DiffableNode` so an index is directly a table's diff-tree | ||
| * child. Indexes are name-identified: every index — contract-derived or | ||
| * introspected — carries its full physical name, and `id` is that name. | ||
| * Names are catalog-unique per schema, so two indexes legitimately sharing | ||
| * one column tuple (a unique index beside a redundant plain index) are two | ||
| * distinct siblings, and expression indexes need no column tuple at all. | ||
| * | ||
| * `isEqualTo` is selected by the receiver (the differ always calls | ||
| * `expected.isEqualTo(actual)`) and delegates to {@link contentEquals} — | ||
| * the single node-owned content relation: both modes compare `unique` | ||
| * strict, `type` and option values through the named normalization seams, | ||
| * and `columns` ordered-strict when both sides carry them; an exact-named | ||
| * receiver (`prefix === undefined`) additionally byte-compares | ||
| * `expression`/`where` (both sides are reprints in the supported flow — | ||
| * normalizing would only mask real drift); a managed receiver never | ||
| * compares bodies (the wire-name hash already commits to them). | ||
| * | ||
| * `expression`, `where`, and `unique` are genuine SQL-family attributes — | ||
| * functional and partial indexes are standard SQL that any SQL target may | ||
| * introspect, so the family node must represent them; a target declining | ||
| * to author them is a capability decision, not target-specificity. | ||
| */ | ||
| var SqlIndexIR = class SqlIndexIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.index; | ||
| name; | ||
| unique; | ||
| constructor(input) { | ||
| super(); | ||
| if (input.columns === void 0 === (input.expression === void 0)) throw new InternalError(`SqlIndexIR "${input.name}": exactly one of columns or expression must be set.`); | ||
| this.name = input.name; | ||
| this.unique = input.unique; | ||
| if (input.prefix !== void 0) this.prefix = input.prefix; | ||
| if (input.columns !== void 0) this.columns = input.columns; | ||
| if (input.expression !== void 0) this.expression = input.expression; | ||
| if (input.where !== void 0) this.where = input.where; | ||
| if (input.type !== void 0) this.type = input.type; | ||
| if (input.options !== void 0) this.options = input.options; | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| defineNonEnumerable(this, "dependsOn", input.dependsOn); | ||
| defineNonEnumerable(this, "partial", input.partial); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return `index:${this.name}`; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.index; | ||
| } | ||
| /** | ||
| * Mode-selected structural equality — see the class doc. Delegates to the | ||
| * single node-owned relation: `columns` compare ordered-strict when both | ||
| * sides carry them; an exact receiver (`prefix === undefined`) | ||
| * byte-compares `expression ?? ''` and `where ?? ''`; a managed receiver | ||
| * never compares bodies (the wire-name hash already commits to them). | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlIndexIR", SqlIndexIR.is); | ||
| return this.contentEquals(node, { | ||
| columnPresence: "when-both-defined", | ||
| bodies: this.prefix !== void 0 ? "ignored" : "verbatim" | ||
| }); | ||
| } | ||
| /** | ||
| * The single index content-equality relation — every comparer (the differ | ||
| * via {@link isEqualTo}, the planner's rename content-pairing) calls this | ||
| * with its mode-appropriate strictness rather than growing a parallel | ||
| * relation: | ||
| * | ||
| * - `columnPresence: 'when-both-defined'` (the differ's rule) compares | ||
| * the tuples ordered-strict only when both sides carry them — a paired | ||
| * node's identity already agreed, so a column node meeting an | ||
| * expression node skips the tuple. | ||
| * - `columnPresence: 'matching'` (the rename-pairing rule) additionally | ||
| * requires presence to agree: a column index never pairs an expression | ||
| * index. | ||
| * - `bodies: 'verbatim'` byte-compares `expression ?? ''` / `where ?? ''` | ||
| * (absent ≡ empty, no normalization — both sides are reprints in the | ||
| * supported flow); `bodies: 'ignored'` skips them (managed identity — | ||
| * the wire-name hash commits to the content). | ||
| * | ||
| * `unique` compares strictly; `type` and option VALUES compare through | ||
| * the named normalization seams below. | ||
| */ | ||
| contentEquals(other, strictness) { | ||
| const columnsEqual = strictness.columnPresence === "matching" ? this.columns === void 0 === (other.columns === void 0) && (this.columns === void 0 || isArrayEqual(this.columns, other.columns ?? [])) : this.columns === void 0 || other.columns === void 0 || isArrayEqual(this.columns, other.columns); | ||
| if (!(this.unique === other.unique && normalizeIndexType(this.type) === normalizeIndexType(other.type) && indexOptionsEqual(this.options, other.options) && columnsEqual)) return false; | ||
| if (strictness.bodies === "ignored") return true; | ||
| return (this.expression ?? "") === (other.expression ?? "") && (this.where ?? "") === (other.where ?? ""); | ||
| } | ||
| }; | ||
| /** | ||
| * Comparison-side normalization seam: the default access method (`btree` in | ||
| * every supported SQL target) compares as absent, so an authored | ||
| * `type: "btree"` and a default-method introspected index (whose type the | ||
| * adapter or constructor normalized away) are equal. Applied by | ||
| * {@link SqlIndexIR.contentEquals} only — the wire-name hash keeps the | ||
| * authored spelling. | ||
| */ | ||
| function normalizeIndexType(type) { | ||
| return type === "btree" ? void 0 : type; | ||
| } | ||
| /** | ||
| * Option-bag equality: same key set, values compared through | ||
| * {@link normalizeIndexOptionValue} — Postgres introspection returns | ||
| * reloptions values as catalog-reprint strings (`'70'`, `'on'`) while | ||
| * contract option leaves are typed (number, boolean, string). | ||
| */ | ||
| function indexOptionsEqual(a, b) { | ||
| const aKeys = a ? Object.keys(a).sort() : []; | ||
| const bKeys = b ? Object.keys(b).sort() : []; | ||
| if (aKeys.length !== bKeys.length) return false; | ||
| for (let i = 0; i < aKeys.length; i += 1) if (aKeys[i] !== bKeys[i]) return false; | ||
| if (aKeys.length === 0) return true; | ||
| for (const key of aKeys) if (normalizeIndexOptionValue(a?.[key]) !== normalizeIndexOptionValue(b?.[key])) return false; | ||
| return true; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-unique-ir.ts | ||
| /** | ||
| * Schema IR node for a table-level unique constraint as observed by | ||
| * introspection. | ||
| * | ||
| * Implements `DiffableNode` so a unique constraint is directly a table's | ||
| * diff-tree child. Unique constraints are frequently unnamed, so `id` is | ||
| * derived from the column tuple rather than `name` — the column tuple is | ||
| * also what makes two unique constraints the same constraint, so it doubles | ||
| * as the pairing key. There are no further attributes to compare once | ||
| * columns are equal (the differ pairs on `id`), so `isEqualTo` is identity. | ||
| */ | ||
| var SqlUniqueIR = class SqlUniqueIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.unique; | ||
| columns; | ||
| constructor(input) { | ||
| super(); | ||
| this.columns = [...input.columns]; | ||
| if (input.name !== void 0) this.name = input.name; | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| defineNonEnumerable(this, "dependsOn", input.dependsOn); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return `unique:${this.columns.join(",")}`; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.unique; | ||
| } | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlUniqueIR", SqlUniqueIR.is); | ||
| return this.id === node.id; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-table-ir.ts | ||
| /** | ||
| * Schema IR node for a single table as observed by introspection. | ||
| * | ||
| * Unlike the Contract IR `StorageTable`, this carries the table's | ||
| * `name` — introspection queries return tables as arrays and the | ||
| * verifier keys them into `SqlSchemaIR.tables` afterwards, so the name | ||
| * stays on the table object for downstream call sites that walk | ||
| * `Object.values(schema.tables)`. | ||
| * | ||
| * The constructor normalises nested IR-class fields so downstream | ||
| * walks see a uniform AST regardless of whether the input was a | ||
| * plain-data literal (from introspection) or already-constructed | ||
| * class instances. | ||
| * | ||
| * Implements `DiffableNode` so a flat (single-schema) tree is directly | ||
| * diffable: `id` is the table name; `isEqualTo` is identity (the table's | ||
| * structural drift is entirely expressed by its children); `children()` | ||
| * yields every column, the primary key (when present), every foreign key, | ||
| * unique, index, and check constraint — the same composition and order as | ||
| * the Postgres table node, minus policies. | ||
| */ | ||
| var SqlTableIR = class extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.table; | ||
| name; | ||
| columns; | ||
| foreignKeys; | ||
| uniques; | ||
| indexes; | ||
| constructor(input) { | ||
| super(); | ||
| this.name = input.name; | ||
| this.columns = Object.freeze(Object.fromEntries(Object.entries(input.columns).map(([key, col]) => [key, col instanceof SqlColumnIR ? col : new SqlColumnIR(col)]))); | ||
| this.foreignKeys = Object.freeze(input.foreignKeys.map((fk) => fk instanceof SqlForeignKeyIR ? fk : new SqlForeignKeyIR(fk))); | ||
| this.uniques = Object.freeze(input.uniques.map((u) => u instanceof SqlUniqueIR ? u : new SqlUniqueIR(u))); | ||
| this.indexes = Object.freeze(input.indexes.map((i) => i instanceof SqlIndexIR ? i : new SqlIndexIR(i))); | ||
| if (input.primaryKey !== void 0) this.primaryKey = input.primaryKey instanceof PrimaryKey ? input.primaryKey : new PrimaryKey(input.primaryKey); | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| if (input.checks !== void 0 && input.checks.length > 0) this.checks = Object.freeze(input.checks.map((c) => c instanceof SqlCheckConstraintIR ? c : new SqlCheckConstraintIR(c))); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return this.name; | ||
| } | ||
| isEqualTo(other) { | ||
| return this.id === other.id; | ||
| } | ||
| children() { | ||
| return [ | ||
| ...Object.values(this.columns), | ||
| ...this.primaryKey ? [this.primaryKey] : [], | ||
| ...this.foreignKeys, | ||
| ...this.uniques, | ||
| ...this.indexes, | ||
| ...this.checks ?? [] | ||
| ]; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-schema-ir.ts | ||
| /** | ||
| * Root Schema IR node representing the complete database schema as | ||
| * observed by introspection. Target-agnostic; used by both verifiers | ||
| * (compare against intended Contract storage) and migration planners | ||
| * (derive operations needed to reconcile). | ||
| * | ||
| * The constructor normalises nested `SqlTableIR` instances so | ||
| * downstream walks see a uniform AST regardless of whether the input | ||
| * was a plain-data literal or already-constructed class instances. | ||
| * | ||
| * Implements `DiffableNode` as the root of a flat (single-schema) diff | ||
| * tree: `id` is the fixed sentinel `'database'` (roots have no siblings), | ||
| * `isEqualTo` is identity (a container has no own attributes), and | ||
| * `children()` yields the table nodes. | ||
| */ | ||
| var SqlSchemaIR = class extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.schema; | ||
| tables; | ||
| constructor(input) { | ||
| super(); | ||
| this.tables = Object.freeze(Object.fromEntries(Object.entries(input.tables).map(([key, t]) => [key, t instanceof SqlTableIR ? t : new SqlTableIR(t)]))); | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return "database"; | ||
| } | ||
| isEqualTo(other) { | ||
| return this.id === other.id; | ||
| } | ||
| children() { | ||
| return Object.values(this.tables); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { SqlForeignKeyIR as a, SqlCheckConstraintIR as c, assertNode as d, defineNonEnumerable as f, relationalNodeGranularity as h, SqlIndexIR as i, PrimaryKey as l, relationalNodeEntityKind as m, SqlTableIR as n, SqlColumnIR as o, RelationalSchemaNodeKind as p, SqlUniqueIR as r, SqlColumnDefaultIR as s, SqlSchemaIR as t, SqlSchemaIRNode as u }; | ||
| //# sourceMappingURL=types-Btj35AIt.mjs.map |
Sorry, the diff of this file is too big to display
| import { IRNodeBase } from "@prisma-next/framework-components/ir"; | ||
| import { DiffSubjectGranularity, DiffableNode, SchemaNodeRef } from "@prisma-next/framework-components/control"; | ||
| import { ColumnDefault } from "@prisma-next/contract/types"; | ||
| import { CodecRef } from "@prisma-next/framework-components/codec"; | ||
| //#region src/ir/sql-schema-ir-node.d.ts | ||
| /** | ||
| * SQL Schema IR node base. Carries the family-level | ||
| * `kind = 'sql-schema-ir'` discriminator and inherits the framework's | ||
| * `freezeNode` affordance. | ||
| * | ||
| * SQL Schema IR represents the actual database state as discovered by | ||
| * introspection (the parallel to SQL Contract IR, which represents the | ||
| * desired state). | ||
| * | ||
| * The discriminator is installed as a non-enumerable own property, | ||
| * matching the SqlNode pattern. This keeps `JSON.stringify(node)` | ||
| * canonical (no `kind` field), keeps `toEqual({...})` test assertions | ||
| * against pre-lift flat shapes passing, and keeps `node.kind` readable | ||
| * for dispatch. | ||
| * | ||
| * Both `kind` and `nodeKind` are required: every concrete leaf is a node | ||
| * the generic differ can pair and compare, so every leaf must declare which | ||
| * node it is. `nodeKind` has no default here — every direct subclass sets its | ||
| * own literal value (the relational leaves via `RelationalSchemaNodeKind`, | ||
| * target concretions via their own vocabulary, e.g. `PostgresSchemaNodeKind`). | ||
| */ | ||
| declare abstract class SqlSchemaIRNode extends IRNodeBase { | ||
| readonly kind: string; | ||
| /** | ||
| * Enumerable discriminant identifying which node this is (column / primary | ||
| * key / foreign key / unique / index / check / database / namespace / | ||
| * table / policy / role / …). Concretions set a unique value; the | ||
| * `.is`/`.assert` guards compare against it. Unlike `kind`, it is | ||
| * enumerable, so it survives a spread that flattens a node into a plain | ||
| * object. | ||
| */ | ||
| abstract readonly nodeKind: string; | ||
| constructor(); | ||
| } | ||
| /** | ||
| * Asserts `node` matches `predicate`, narrowing its type to `T`. The one | ||
| * shared implementation every node class's `static assert` and `isEqualTo` | ||
| * reach for, instead of each hand-writing its own throw: the message names | ||
| * the class the caller expected. | ||
| */ | ||
| declare function assertNode<T extends SqlSchemaIRNode>(node: SqlSchemaIRNode | undefined, className: string, predicate: (node: SqlSchemaIRNode) => node is T): asserts node is T; | ||
| /** | ||
| * Defines a non-enumerable own property, the same treatment `kind` gets | ||
| * above: a derivation-time render-support field stays out of | ||
| * `JSON.stringify`, `toEqual({...})` structural assertions, and spreads, | ||
| * while remaining directly readable (`node.field`) for the one consumer | ||
| * that resolves it at plan time. A no-op when `value` is `undefined` — the | ||
| * property is simply absent, matching every other optional field on these | ||
| * nodes. | ||
| */ | ||
| declare function defineNonEnumerable<T extends object>(target: T, key: string, value: unknown): void; | ||
| //#endregion | ||
| //#region src/ir/primary-key.d.ts | ||
| interface PrimaryKeyInput { | ||
| readonly columns: readonly string[]; | ||
| readonly name?: string; | ||
| /** | ||
| * The key's own column nodes, as root-anchored chains. The derivation | ||
| * stamps them so the primary key is dropped before the columns it is built | ||
| * on. Never compared by `isEqualTo`. | ||
| */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| } | ||
| /** | ||
| * Primary-key Schema IR node. Mirrors the Contract IR `PrimaryKey` | ||
| * shape (same `columns` + optional `name`) so verification can compare | ||
| * intent and actual structurally. Defined here independently to avoid | ||
| * a sql-schema-ir -> sql-contract dependency. | ||
| * | ||
| * Implements `DiffableNode` so a primary key is directly a table's diff-tree | ||
| * child. `id` is a fixed sentinel — a table has at most one primary key, so | ||
| * there is never a sibling to disambiguate against. `isEqualTo` compares the | ||
| * column tuple; the PK's own `name` is a database-assigned label with no | ||
| * semantic weight, so it is not compared (mirrors the policy node's | ||
| * name-insensitivity to non-identifying detail). | ||
| */ | ||
| declare class PrimaryKey extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-primary-key"; | ||
| readonly columns: readonly string[]; | ||
| readonly name?: string; | ||
| /** See {@link PrimaryKeyInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| constructor(input: PrimaryKeyInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is PrimaryKey; | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/schema-node-kinds.d.ts | ||
| /** | ||
| * The `nodeKind` discriminant for each relational schema-diff leaf node. | ||
| * Each node carries a unique value; the differ pairs siblings by `id`, and | ||
| * these kinds distinguish the node types that appear as `PostgresTableSchemaNode` | ||
| * children (columns, primary key, foreign keys, uniques, indexes, checks) from | ||
| * each other and from the RLS policy/role kinds a target defines separately. | ||
| */ | ||
| declare const RelationalSchemaNodeKind: { | ||
| readonly schema: "sql-schema"; | ||
| readonly table: "sql-table"; | ||
| readonly column: "sql-column"; | ||
| readonly columnDefault: "sql-column-default"; | ||
| readonly primaryKey: "sql-primary-key"; | ||
| readonly foreignKey: "sql-foreign-key"; | ||
| readonly unique: "sql-unique"; | ||
| readonly index: "sql-index"; | ||
| readonly check: "sql-check-constraint"; | ||
| }; | ||
| type RelationalSchemaNodeKind = (typeof RelationalSchemaNodeKind)[keyof typeof RelationalSchemaNodeKind]; | ||
| /** | ||
| * Looks up the subject granularity for a relational `nodeKind`. Throws for a | ||
| * `nodeKind` outside this map (a target-specific kind, e.g. Postgres's | ||
| * namespace/table/policy/role) — those map their own kinds directly rather | ||
| * than through this family-level map. | ||
| */ | ||
| declare function relationalNodeGranularity(nodeKind: string): DiffSubjectGranularity; | ||
| /** | ||
| * Looks up the storage `entityKind` for a relational `nodeKind` — sibling of | ||
| * {@link relationalNodeGranularity}, resolved by `nodeKind` equality against | ||
| * {@link RELATIONAL_NODE_ENTITY_KIND}. `undefined` for a target-specific kind | ||
| * (targets map their own kinds directly) or a relational kind with no entity | ||
| * of its own. | ||
| */ | ||
| declare function relationalNodeEntityKind(nodeKind: string): string | undefined; | ||
| //#endregion | ||
| //#region src/ir/sql-check-constraint-ir.d.ts | ||
| interface SqlCheckConstraintIRInput { | ||
| /** Constraint name as stored in the database catalog. */ | ||
| readonly name: string; | ||
| /** Column the check restricts. */ | ||
| readonly column: string; | ||
| /** Permitted values the column must be IN. */ | ||
| readonly permittedValues: readonly string[]; | ||
| } | ||
| /** | ||
| * Schema IR node for a table-level check constraint that restricts a | ||
| * column to a set of permitted values (an enum-style `IN (...)` check). | ||
| * | ||
| * Carries the **resolved values** rather than a raw SQL predicate so | ||
| * callers can compare value-sets without parsing SQL. | ||
| * | ||
| * Implements `DiffableNode` so a check constraint is directly a table's | ||
| * diff-tree child: `id` is the constraint name. `isEqualTo` compares | ||
| * `column` and the permitted-value set (order-insensitive — the database | ||
| * does not guarantee `IN (...)` ordering). | ||
| */ | ||
| declare class SqlCheckConstraintIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-check-constraint"; | ||
| readonly name: string; | ||
| readonly column: string; | ||
| readonly permittedValues: readonly string[]; | ||
| constructor(input: SqlCheckConstraintIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlCheckConstraintIR; | ||
| /** | ||
| * Compares the permitted-value sets only (unordered), matching the | ||
| * relational walk's `verifyCheckConstraints`: two checks pairing by name | ||
| * compare their value sets — the `column` field is descriptive, not part | ||
| * of the comparison, so a name-paired check with equal values verifies | ||
| * regardless of which column carries it. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-column-default-ir.d.ts | ||
| interface SqlColumnDefaultIRInput { | ||
| /** Structured resolved default (contract-declared or normalizer-parsed). */ | ||
| readonly resolved?: ColumnDefault; | ||
| /** Raw database default expression, when known (introspected side). */ | ||
| readonly raw?: string; | ||
| /** | ||
| * Native-type context for temporal literal normalization — the owning | ||
| * column's resolved native type. | ||
| */ | ||
| readonly nativeTypeContext?: string; | ||
| /** | ||
| * The owning column's array-ness, threaded through so the planner's | ||
| * set-default op-builder can render an array-literal default (e.g. | ||
| * `ARRAY[...]`) the same way the pre-`plan(start, end)` op-path did. See | ||
| * {@link import('./sql-column-ir').SqlColumnIRInput.many}. | ||
| */ | ||
| readonly many?: boolean; | ||
| } | ||
| /** | ||
| * Schema-diff node for a column's default value. The default is the one | ||
| * column attribute with an extra/missing/drift lifecycle of its own, so it | ||
| * is a child node of the column rather than a compared attribute: an | ||
| * undeclared live default surfaces as `not-expected`, a declared default the | ||
| * database lacks as `not-found`, and a divergent value as `not-equal` — the | ||
| * three reasons cover the legacy `extra_default` / `default_missing` / | ||
| * `default_mismatch` vocabulary with no attribute inspection. | ||
| * | ||
| * `id` is a fixed sentinel — a column has at most one default. Built | ||
| * transiently by `SqlColumnIR.children()` from the column's own fields; | ||
| * never constructed by derivation or introspection directly. | ||
| */ | ||
| declare class SqlColumnDefaultIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-column-default"; | ||
| readonly resolved?: ColumnDefault; | ||
| readonly raw?: string; | ||
| readonly nativeTypeContext?: string; | ||
| /** See {@link SqlColumnDefaultIRInput.many}. Non-enumerable so it stays out of JSON and structural equality. */ | ||
| readonly many?: boolean; | ||
| constructor(input: SqlColumnDefaultIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlColumnDefaultIR; | ||
| /** | ||
| * Structured comparison with `this` as the expected side: both sides | ||
| * resolved compare per the relational walk's `columnDefaultsEqual` | ||
| * semantics; a declared expected default against an unparseable actual | ||
| * (raw present, no resolved parse) is a mismatch; two raw-only nodes fall | ||
| * back to raw string equality. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-column-ir.d.ts | ||
| /** | ||
| * Namespaced annotations for extensibility. Each namespace | ||
| * (e.g. `pg`, `pgvector`) owns its annotations subtree. | ||
| */ | ||
| type SqlAnnotations = { | ||
| readonly [namespace: string]: unknown; | ||
| }; | ||
| interface SqlColumnIRInput { | ||
| readonly name: string; | ||
| readonly nativeType: string; | ||
| readonly nullable: boolean; | ||
| /** Raw database default expression (e.g. `'hello'::text`, `nextval('seq')`). */ | ||
| readonly default?: string; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** True when the column is a native array (e.g. `text[]`, `int4[]`). The `nativeType` carries the element type only (e.g. `text`, `int4`). */ | ||
| readonly many?: boolean; | ||
| /** | ||
| * Fully resolved native type, comparable across the two diff sides: | ||
| * the contract-derived side stamps the codec-expanded type (typeRef | ||
| * resolved, parameterized types expanded, `[]` appended for arrays); | ||
| * the introspected side stamps the target-normalized type with the same | ||
| * `[]` convention. Stamped at construction by derivation/introspection; | ||
| * absent on raw hand-built nodes. | ||
| */ | ||
| readonly resolvedNativeType?: string; | ||
| /** | ||
| * Structured default, comparable across the two diff sides: the | ||
| * contract-derived side stamps the contract's `ColumnDefault`; the | ||
| * introspected side stamps the target default-normalizer's parse of the | ||
| * raw expression. Absent when the column declares no default, or when | ||
| * the introspected raw default could not be parsed. | ||
| */ | ||
| readonly resolvedDefault?: ColumnDefault; | ||
| /** | ||
| * The column's resolved codec reference — the identity the migration | ||
| * planner's op-builders resolve DDL type rendering against at plan time | ||
| * (parameterized type expansion, e.g. `character` + `{ length: 36 }` → | ||
| * `character(36)`), calling the same codec hooks the pre-`plan(start, | ||
| * end)` op-path called. Carried the same way the query AST carries | ||
| * `CodecRef` (TML-2456) and the migration DDL renderer (TML-2918). | ||
| * Stamped on the EXPECTED (contract-derived) column at derivation, | ||
| * post-`typeRef` resolution (the referenced storage type's own | ||
| * codec/params, not the column's `typeRef` pointer). Absent on | ||
| * introspected/hand-built nodes — the actual side never renders DDL — | ||
| * and never compared by `isEqualTo`. | ||
| */ | ||
| readonly codecRef?: CodecRef; | ||
| /** | ||
| * The column's resolved BASE native type: pre-parameter-expansion, | ||
| * pre-array-suffix (e.g. `character`, not `character(36)` or | ||
| * `character[]`). Distinct from {@link resolvedNativeType} (the EXPANDED | ||
| * comparison value) — DDL rendering re-expands a parameterized type at | ||
| * plan time from this base, so it must not already carry the expansion. | ||
| * Stamped alongside {@link codecRef}. | ||
| */ | ||
| readonly codecBaseNativeType?: string; | ||
| /** | ||
| * True when the contract column declared its type via a named | ||
| * `storage.types` reference (`typeRef`) rather than inline fields — the | ||
| * migration planner quotes the base native type as an identifier in this | ||
| * case (e.g. a native enum's type name), matching the pre-`plan(start, | ||
| * end)` rendering exactly. | ||
| */ | ||
| readonly codecNamedType?: boolean; | ||
| } | ||
| /** | ||
| * Schema IR node for a single column on a table, as observed by | ||
| * introspection. Unlike the Contract IR `StorageColumn`, this carries | ||
| * the column's `name` (Schema IR columns are returned as arrays from | ||
| * introspection queries; the parent table re-keys them into a record | ||
| * for downstream consumers). | ||
| * | ||
| * Implements `DiffableNode` so a column is directly a table's diff-tree | ||
| * child: `id` is the column name (unique among a table's columns); `isEqualTo` | ||
| * compares this column's own attributes only — never children, since a | ||
| * column is a leaf. When both sides carry `resolvedNativeType` (stamped at | ||
| * derivation/introspection), the comparison uses the resolved values — | ||
| * resolved native type, nullability, and structured default equality per | ||
| * the relational walk's `columnDefaultsEqual` semantics, with `this` as the | ||
| * expected side. Otherwise it falls back to comparing raw fields. | ||
| */ | ||
| declare class SqlColumnIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-column"; | ||
| readonly name: string; | ||
| readonly nativeType: string; | ||
| readonly nullable: boolean; | ||
| readonly default?: string; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** True when the column is a native array (e.g. `text[]`, `int4[]`). The `nativeType` carries the element type only (e.g. `text`, `int4`). */ | ||
| readonly many?: boolean; | ||
| readonly resolvedNativeType?: string; | ||
| readonly resolvedDefault?: ColumnDefault; | ||
| /** See {@link SqlColumnIRInput.codecRef}. Non-enumerable so it stays out of JSON and structural equality. */ | ||
| readonly codecRef?: CodecRef; | ||
| /** See {@link SqlColumnIRInput.codecBaseNativeType}. Non-enumerable, same reason as {@link codecRef}. */ | ||
| readonly codecBaseNativeType?: string; | ||
| /** See {@link SqlColumnIRInput.codecNamedType}. Non-enumerable, same reason as {@link codecRef}. */ | ||
| readonly codecNamedType?: boolean; | ||
| constructor(input: SqlColumnIRInput); | ||
| get id(): string; | ||
| /** | ||
| * The column's default, when declared/present, is the column's one child | ||
| * node — it has an extra/missing/drift lifecycle of its own, so the differ | ||
| * recurses to it rather than `isEqualTo` comparing it. Built transiently | ||
| * from this column's own fields. | ||
| */ | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlColumnIR; | ||
| /** | ||
| * Compares the column's own attributes only — the default lives on the | ||
| * child node. When both sides carry `resolvedNativeType`, the resolved | ||
| * value governs (array-ness rides on its `[]` suffix); otherwise raw | ||
| * fields compare. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-foreign-key-ir.d.ts | ||
| type SqlReferentialAction = 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault'; | ||
| interface SqlForeignKeyIRInput { | ||
| readonly columns: readonly string[]; | ||
| readonly referencedTable: string; | ||
| readonly referencedColumns: readonly string[]; | ||
| /** Schema (namespace) of the referenced table — populated by adapters that introspect cross-schema FKs. */ | ||
| readonly referencedSchema?: string; | ||
| readonly name?: string; | ||
| readonly onDelete?: SqlReferentialAction; | ||
| readonly onUpdate?: SqlReferentialAction; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** | ||
| * The real live DDL namespace of the referenced table, comparable across | ||
| * the two diff sides. Contract-derived trees stamp it explicitly (resolving | ||
| * namespace ids — including the unbound sentinel — to the DDL namespace); | ||
| * introspected FKs default it to `referencedSchema`, whose value already | ||
| * is the live namespace. Folded into `id` in place of the raw value so an | ||
| * unbound-namespace contract FK pairs with its introspected counterpart. | ||
| */ | ||
| readonly resolvedReferencedNamespace?: string; | ||
| /** | ||
| * The referenced table's node, as the root-anchored chain the differ pairs | ||
| * siblings with. Stamped by the derivation, which holds the parent | ||
| * (database/namespace) context the chain's shape depends on. Never | ||
| * compared by `isEqualTo`. | ||
| */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| } | ||
| /** | ||
| * Schema IR node for a foreign-key constraint as observed by | ||
| * introspection. The `referencedTable` / `referencedColumns` field | ||
| * names match the introspection vocabulary (`pg_constraint.confkey`, | ||
| * etc.) and intentionally differ from the Contract IR's nested | ||
| * `references: { table, columns }` shape so that the verifier's | ||
| * structural comparison stays explicit about which side it's reading. | ||
| * | ||
| * Implements `DiffableNode` so a foreign key is directly a table's diff-tree | ||
| * child. Foreign keys are frequently unnamed (introspection may not carry a | ||
| * constraint name, and the contract side never invents one), so `id` is | ||
| * derived from the referencing/referenced coordinates rather than `name` — | ||
| * the same tuple that makes two FK constraints the same constraint. This | ||
| * also serves as the comparison key: two FKs with the same coordinates are | ||
| * paired by the differ, and `isEqualTo` then compares the remaining | ||
| * attribute — the referential actions. | ||
| */ | ||
| declare class SqlForeignKeyIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-foreign-key"; | ||
| readonly columns: readonly string[]; | ||
| readonly referencedTable: string; | ||
| readonly referencedColumns: readonly string[]; | ||
| readonly referencedSchema?: string; | ||
| readonly name?: string; | ||
| readonly onDelete?: SqlReferentialAction; | ||
| readonly onUpdate?: SqlReferentialAction; | ||
| readonly annotations?: SqlAnnotations; | ||
| readonly resolvedReferencedNamespace?: string; | ||
| /** See {@link SqlForeignKeyIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| constructor(input: SqlForeignKeyIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlForeignKeyIR; | ||
| /** | ||
| * Referential-action comparison with `this` as the expected side, matching | ||
| * the relational walk's `getReferentialActionMismatches`: `noAction` is the | ||
| * database default and equivalent to an undeclared action, and drift is | ||
| * flagged only when the expected side declares a (normalized) action. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-index-ir.d.ts | ||
| /** | ||
| * An index's element structure — exactly one of a column tuple or an opaque | ||
| * expression, unrepresentable-otherwise at the type level. No discriminant | ||
| * is stored (the node keeps flat readonly accessors); the constructor's xor | ||
| * throw stays as the backstop for introspection rows and JSON-derived | ||
| * inputs that bypass this union. | ||
| */ | ||
| type SqlIndexElements = { | ||
| /** Column-tuple elements. */ | ||
| readonly columns: readonly string[]; | ||
| readonly expression?: never; | ||
| } | { | ||
| readonly columns?: never; | ||
| /** | ||
| * Opaque SQL: the entire element list between the parens of CREATE | ||
| * INDEX — one string, never parsed. | ||
| */ | ||
| readonly expression: string; | ||
| }; | ||
| /** | ||
| * Every non-element field is a required key. Values that may legitimately | ||
| * be absent (an exact-named index's prefix, a default-method type) are | ||
| * typed `| undefined` instead of optional, so each construction site | ||
| * states the absence explicitly rather than omitting the key silently. | ||
| * Undefined values still produce an instance without the property. | ||
| */ | ||
| type SqlIndexIRInput = SqlIndexElements & { | ||
| /** Full physical name — the node's identity. */ | ||
| readonly name: string; | ||
| /** | ||
| * The managed-mode name prefix — its PRESENCE is the naming-mode | ||
| * discriminator (there is no stored enum). Present ⇔ managed: the | ||
| * toolchain owns the physical name and `name === formatWireName(prefix, | ||
| * <8hex content hash>)`. Absent ⇔ exact: `name` is an adopted verbatim | ||
| * physical name whose identity the author owns entirely. | ||
| */ | ||
| readonly prefix: string | undefined; | ||
| /** Opaque SQL: partial-index predicate (WHERE body, without the keyword). */ | ||
| readonly where: string | undefined; | ||
| readonly unique: boolean; | ||
| readonly type: string | undefined; | ||
| readonly options: Record<string, unknown> | undefined; | ||
| readonly annotations: SqlAnnotations | undefined; | ||
| /** | ||
| * The index's own column nodes, as root-anchored chains. The derivation | ||
| * stamps them so an index is dropped before the columns it is built on | ||
| * (Postgres auto-drops the index when a covered column goes). An expression | ||
| * index stamps chains to every column of its table — a deterministic | ||
| * over-approximation, since the opaque expression is never parsed. Never | ||
| * compared by `isEqualTo`. | ||
| */ | ||
| readonly dependsOn: readonly SchemaNodeRef[] | undefined; | ||
| /** | ||
| * Whether the index is partial (has a row predicate). Required: every | ||
| * producer must assert partiality explicitly, because a partial unique | ||
| * index does not guarantee at-most-one row per key and so cannot back a | ||
| * 1:1 relation — "unknown" must not silently default to "total". Never | ||
| * compared by `isEqualTo` and never serialized. | ||
| */ | ||
| readonly partial: boolean; | ||
| }; | ||
| /** | ||
| * Schema IR node for a secondary index as observed by introspection. | ||
| * Unlike the Contract IR `Index`, the Schema IR carries an explicit | ||
| * `unique` field — introspection sees the underlying index regardless | ||
| * of whether the user expressed it as `@@index` or `@@unique`, and the | ||
| * verifier needs to distinguish them when comparing to the Contract. | ||
| * | ||
| * Implements `DiffableNode` so an index is directly a table's diff-tree | ||
| * child. Indexes are name-identified: every index — contract-derived or | ||
| * introspected — carries its full physical name, and `id` is that name. | ||
| * Names are catalog-unique per schema, so two indexes legitimately sharing | ||
| * one column tuple (a unique index beside a redundant plain index) are two | ||
| * distinct siblings, and expression indexes need no column tuple at all. | ||
| * | ||
| * `isEqualTo` is selected by the receiver (the differ always calls | ||
| * `expected.isEqualTo(actual)`) and delegates to {@link contentEquals} — | ||
| * the single node-owned content relation: both modes compare `unique` | ||
| * strict, `type` and option values through the named normalization seams, | ||
| * and `columns` ordered-strict when both sides carry them; an exact-named | ||
| * receiver (`prefix === undefined`) additionally byte-compares | ||
| * `expression`/`where` (both sides are reprints in the supported flow — | ||
| * normalizing would only mask real drift); a managed receiver never | ||
| * compares bodies (the wire-name hash already commits to them). | ||
| * | ||
| * `expression`, `where`, and `unique` are genuine SQL-family attributes — | ||
| * functional and partial indexes are standard SQL that any SQL target may | ||
| * introspect, so the family node must represent them; a target declining | ||
| * to author them is a capability decision, not target-specificity. | ||
| */ | ||
| declare class SqlIndexIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-index"; | ||
| readonly name: string; | ||
| readonly unique: boolean; | ||
| readonly prefix?: string; | ||
| readonly columns?: readonly string[]; | ||
| readonly expression?: string; | ||
| readonly where?: string; | ||
| readonly type?: string; | ||
| readonly options?: Record<string, unknown>; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** See {@link SqlIndexIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| /** See {@link SqlIndexIRInput.partial}. Non-enumerable so it stays out of JSON and structural equality, matching `dependsOn`. */ | ||
| readonly partial: boolean; | ||
| constructor(input: SqlIndexIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlIndexIR; | ||
| /** | ||
| * Mode-selected structural equality — see the class doc. Delegates to the | ||
| * single node-owned relation: `columns` compare ordered-strict when both | ||
| * sides carry them; an exact receiver (`prefix === undefined`) | ||
| * byte-compares `expression ?? ''` and `where ?? ''`; a managed receiver | ||
| * never compares bodies (the wire-name hash already commits to them). | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| /** | ||
| * The single index content-equality relation — every comparer (the differ | ||
| * via {@link isEqualTo}, the planner's rename content-pairing) calls this | ||
| * with its mode-appropriate strictness rather than growing a parallel | ||
| * relation: | ||
| * | ||
| * - `columnPresence: 'when-both-defined'` (the differ's rule) compares | ||
| * the tuples ordered-strict only when both sides carry them — a paired | ||
| * node's identity already agreed, so a column node meeting an | ||
| * expression node skips the tuple. | ||
| * - `columnPresence: 'matching'` (the rename-pairing rule) additionally | ||
| * requires presence to agree: a column index never pairs an expression | ||
| * index. | ||
| * - `bodies: 'verbatim'` byte-compares `expression ?? ''` / `where ?? ''` | ||
| * (absent ≡ empty, no normalization — both sides are reprints in the | ||
| * supported flow); `bodies: 'ignored'` skips them (managed identity — | ||
| * the wire-name hash commits to the content). | ||
| * | ||
| * `unique` compares strictly; `type` and option VALUES compare through | ||
| * the named normalization seams below. | ||
| */ | ||
| contentEquals(other: SqlIndexIR, strictness: { | ||
| readonly columnPresence: 'when-both-defined' | 'matching'; | ||
| readonly bodies: 'verbatim' | 'ignored'; | ||
| }): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-unique-ir.d.ts | ||
| interface SqlUniqueIRInput { | ||
| readonly columns: readonly string[]; | ||
| readonly name?: string; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** | ||
| * The constraint's own column nodes, as root-anchored chains. The | ||
| * derivation stamps them so a unique constraint is dropped before the | ||
| * columns it is built on. Never compared by `isEqualTo`. | ||
| */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| } | ||
| /** | ||
| * Schema IR node for a table-level unique constraint as observed by | ||
| * introspection. | ||
| * | ||
| * Implements `DiffableNode` so a unique constraint is directly a table's | ||
| * diff-tree child. Unique constraints are frequently unnamed, so `id` is | ||
| * derived from the column tuple rather than `name` — the column tuple is | ||
| * also what makes two unique constraints the same constraint, so it doubles | ||
| * as the pairing key. There are no further attributes to compare once | ||
| * columns are equal (the differ pairs on `id`), so `isEqualTo` is identity. | ||
| */ | ||
| declare class SqlUniqueIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-unique"; | ||
| readonly columns: readonly string[]; | ||
| readonly name?: string; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** See {@link SqlUniqueIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| constructor(input: SqlUniqueIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlUniqueIR; | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-table-ir.d.ts | ||
| interface SqlTableIRInput { | ||
| readonly name: string; | ||
| readonly columns: Record<string, SqlColumnIR | SqlColumnIRInput>; | ||
| readonly foreignKeys: ReadonlyArray<SqlForeignKeyIR | SqlForeignKeyIRInput>; | ||
| readonly uniques: ReadonlyArray<SqlUniqueIR | SqlUniqueIRInput>; | ||
| readonly indexes: ReadonlyArray<SqlIndexIR | SqlIndexIRInput>; | ||
| readonly primaryKey?: PrimaryKey | PrimaryKeyInput; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** Optional check constraints for enum-restricted columns. Omitted when none present. */ | ||
| readonly checks?: ReadonlyArray<SqlCheckConstraintIR | SqlCheckConstraintIRInput>; | ||
| } | ||
| /** | ||
| * Schema IR node for a single table as observed by introspection. | ||
| * | ||
| * Unlike the Contract IR `StorageTable`, this carries the table's | ||
| * `name` — introspection queries return tables as arrays and the | ||
| * verifier keys them into `SqlSchemaIR.tables` afterwards, so the name | ||
| * stays on the table object for downstream call sites that walk | ||
| * `Object.values(schema.tables)`. | ||
| * | ||
| * The constructor normalises nested IR-class fields so downstream | ||
| * walks see a uniform AST regardless of whether the input was a | ||
| * plain-data literal (from introspection) or already-constructed | ||
| * class instances. | ||
| * | ||
| * Implements `DiffableNode` so a flat (single-schema) tree is directly | ||
| * diffable: `id` is the table name; `isEqualTo` is identity (the table's | ||
| * structural drift is entirely expressed by its children); `children()` | ||
| * yields every column, the primary key (when present), every foreign key, | ||
| * unique, index, and check constraint — the same composition and order as | ||
| * the Postgres table node, minus policies. | ||
| */ | ||
| declare class SqlTableIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-table"; | ||
| readonly name: string; | ||
| readonly columns: Readonly<Record<string, SqlColumnIR>>; | ||
| readonly foreignKeys: ReadonlyArray<SqlForeignKeyIR>; | ||
| readonly uniques: ReadonlyArray<SqlUniqueIR>; | ||
| readonly indexes: ReadonlyArray<SqlIndexIR>; | ||
| readonly primaryKey?: PrimaryKey; | ||
| readonly annotations?: SqlAnnotations; | ||
| readonly checks?: ReadonlyArray<SqlCheckConstraintIR>; | ||
| constructor(input: SqlTableIRInput); | ||
| get id(): string; | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| children(): readonly DiffableNode[]; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-schema-ir.d.ts | ||
| interface SqlSchemaIRInput { | ||
| readonly tables: Record<string, SqlTableIR | SqlTableIRInput>; | ||
| readonly annotations?: SqlAnnotations; | ||
| } | ||
| /** | ||
| * Root Schema IR node representing the complete database schema as | ||
| * observed by introspection. Target-agnostic; used by both verifiers | ||
| * (compare against intended Contract storage) and migration planners | ||
| * (derive operations needed to reconcile). | ||
| * | ||
| * The constructor normalises nested `SqlTableIR` instances so | ||
| * downstream walks see a uniform AST regardless of whether the input | ||
| * was a plain-data literal or already-constructed class instances. | ||
| * | ||
| * Implements `DiffableNode` as the root of a flat (single-schema) diff | ||
| * tree: `id` is the fixed sentinel `'database'` (roots have no siblings), | ||
| * `isEqualTo` is identity (a container has no own attributes), and | ||
| * `children()` yields the table nodes. | ||
| */ | ||
| declare class SqlSchemaIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-schema"; | ||
| readonly tables: Readonly<Record<string, SqlTableIR>>; | ||
| readonly annotations?: SqlAnnotations; | ||
| constructor(input: SqlSchemaIRInput); | ||
| get id(): string; | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| children(): readonly DiffableNode[]; | ||
| } | ||
| //#endregion | ||
| //#region src/types.d.ts | ||
| /** | ||
| * SQL type metadata for control-plane and execution-plane type | ||
| * availability and mapping. Read-only view of type information | ||
| * without encode/decode behavior. | ||
| */ | ||
| interface SqlTypeMetadata { | ||
| /** Namespaced type identifier, e.g. `pg/int4@1`, `pg/text@1`. */ | ||
| readonly typeId: string; | ||
| /** Contract scalar type IDs this type can handle. */ | ||
| readonly targetTypes: readonly string[]; | ||
| /** | ||
| * Native database type name (target-specific). Optional because | ||
| * not all types have a native database representation. | ||
| */ | ||
| readonly nativeType?: string; | ||
| } | ||
| /** | ||
| * Registry interface for SQL type metadata. Provides read-only | ||
| * iteration over type metadata entries. | ||
| */ | ||
| interface SqlTypeMetadataRegistry { | ||
| values(): IterableIterator<SqlTypeMetadata>; | ||
| } | ||
| //#endregion | ||
| export { relationalNodeGranularity as C, assertNode as D, SqlSchemaIRNode as E, defineNonEnumerable as O, relationalNodeEntityKind as S, PrimaryKeyInput as T, SqlColumnDefaultIR as _, SqlTableIR as a, SqlCheckConstraintIRInput as b, SqlUniqueIRInput as c, SqlForeignKeyIR as d, SqlForeignKeyIRInput as f, SqlColumnIRInput as g, SqlColumnIR as h, SqlSchemaIRInput as i, SqlIndexIR as l, SqlAnnotations as m, SqlTypeMetadataRegistry as n, SqlTableIRInput as o, SqlReferentialAction as p, SqlSchemaIR as r, SqlUniqueIR as s, SqlTypeMetadata as t, SqlIndexIRInput as u, SqlColumnDefaultIRInput as v, PrimaryKey as w, RelationalSchemaNodeKind as x, SqlCheckConstraintIR as y }; | ||
| //# sourceMappingURL=types-DzLk5eOU.d.mts.map |
| {"version":3,"file":"types-DzLk5eOU.d.mts","names":[],"sources":["../src/ir/sql-schema-ir-node.ts","../src/ir/primary-key.ts","../src/ir/schema-node-kinds.ts","../src/ir/sql-check-constraint-ir.ts","../src/ir/sql-column-default-ir.ts","../src/ir/sql-column-ir.ts","../src/ir/sql-foreign-key-ir.ts","../src/ir/sql-index-ir.ts","../src/ir/sql-unique-ir.ts","../src/ir/sql-table-ir.ts","../src/ir/sql-schema-ir.ts","../src/types.ts"],"mappings":"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"} |
+135
| import { createHash } from 'node:crypto'; | ||
| import { structuredError } from '@prisma-next/utils/structured-error'; | ||
| export function defaultIndexName(tableName: string, columns: readonly string[]): string { | ||
| return `${tableName}_${columns.join('_')}_idx`; | ||
| } | ||
| export interface WireName { | ||
| /** The user-supplied part before the `_<8hex>` suffix. */ | ||
| readonly prefix: string; | ||
| /** The 8-lowercase-hex content-hash suffix. */ | ||
| readonly hash: string; | ||
| } | ||
| const WIRE_NAME_PATTERN = /^(.+)_([0-9a-f]{8})$/; | ||
| /** | ||
| * Assembles a wire name from its user-supplied prefix and its 8-hex | ||
| * content-hash suffix. This module owns the `<prefix>_<hash>` format on both | ||
| * sides — construction here and parsing in {@link parseWireName} — so the two | ||
| * never drift. | ||
| */ | ||
| export function formatWireName(prefix: string, hash: string): string { | ||
| return `${prefix}_${hash}`; | ||
| } | ||
| /** | ||
| * Splits a wire name (`<prefix>_<8hex>`) into its prefix and content-hash | ||
| * suffix. Returns `undefined` when the name does not follow the wire-name | ||
| * shape (e.g. an object created outside the toolchain) — callers treat such | ||
| * names as all-prefix. Consumed by introspection (prefix extraction) and by | ||
| * rename pairing (same hash, different prefix). | ||
| */ | ||
| export function parseWireName(name: string): WireName | undefined { | ||
| const match = WIRE_NAME_PATTERN.exec(name); | ||
| const prefix = match?.[1]; | ||
| const hash = match?.[2]; | ||
| if (prefix === undefined || hash === undefined) return undefined; | ||
| return { prefix, hash }; | ||
| } | ||
| /** | ||
| * Stabilizes an authored SQL body (index expression, partial-index predicate, | ||
| * RLS policy predicate) for hashing: trim, and collapse runs of internal | ||
| * whitespace to a single space. | ||
| * | ||
| * This is deliberately minimal. The content hash is the equivalence relation | ||
| * for a wire-named object, and the wire name (prefix + hash) is the only | ||
| * thing ever compared — the hash is never recomputed from an introspected | ||
| * body, so there is no need to match the database's reprinted form. Minimal | ||
| * normalization also protects the no-collision property: aggressive rewriting | ||
| * (lowercasing, paren-stripping, cast-alias folding) risks collapsing two | ||
| * distinct bodies onto one hash. | ||
| * | ||
| * The normalizer is a stability commitment: any change re-suffixes all wire names. | ||
| */ | ||
| export function normalizeSqlBody(sql: string): string { | ||
| return sql.replace(/\s+/g, ' ').trim(); | ||
| } | ||
| export interface IndexContentHashParts { | ||
| readonly expression?: string; | ||
| readonly where?: string; | ||
| readonly columns?: readonly string[]; | ||
| readonly unique: boolean; | ||
| readonly type?: string; | ||
| readonly options?: Record<string, unknown>; | ||
| } | ||
| /** | ||
| * Returns the first 8 lowercase hex characters of the SHA-256 digest over the | ||
| * canonical content tuple for an index: | ||
| * | ||
| * [normalizeSqlBody(expression), normalizeSqlBody(where), columns, unique, type, sortedOptions] | ||
| * | ||
| * Columns hash in authored order — column order is semantic in an index. | ||
| * Option values are `String()`-coerced (matching the loose option equality | ||
| * used for diffing) so a hash computed from typed contract values agrees with | ||
| * one recomputed from introspected reloptions strings. The prefix, schema, | ||
| * and table are excluded (they are orthogonal to index equivalence). | ||
| * | ||
| * The tuple order and encoding are a stability commitment with the same | ||
| * status as the RLS tuple: any change re-suffixes every wire name. | ||
| */ | ||
| /** | ||
| * Canonicalizes one index option VALUE to the `on`/`off` boolean spelling: | ||
| * JS booleans and the common catalog spellings (`pg_class.reloptions` | ||
| * stores whatever spelling the DDL used, so a live index may carry | ||
| * `'true'`/`'false'` or `'on'`/`'off'`) all map to one form; everything | ||
| * else via `String()` (fully specified for numbers, so no platform | ||
| * variance). Shared by the wire-name hash tuple, the node's option | ||
| * equality, and the DDL renderer, so an authored `{ fastupdate: true }` | ||
| * agrees with a live index created under any boolean spelling. | ||
| */ | ||
| export function normalizeIndexOptionValue(value: unknown): string { | ||
| if (value === true || value === 'true' || value === 'on') return 'on'; | ||
| if (value === false || value === 'false' || value === 'off') return 'off'; | ||
| return String(value); | ||
| } | ||
| export function computeIndexContentHash(parts: IndexContentHashParts): string { | ||
| const sortedOptions = Object.entries(parts.options ?? {}) | ||
| .map(([key, value]): readonly [string, string] => [key, normalizeIndexOptionValue(value)]) | ||
| .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)); | ||
| const tuple = JSON.stringify([ | ||
| normalizeSqlBody(parts.expression ?? ''), | ||
| normalizeSqlBody(parts.where ?? ''), | ||
| parts.columns ?? [], | ||
| parts.unique, | ||
| parts.type ?? '', | ||
| sortedOptions, | ||
| ]); | ||
| return createHash('sha256').update(tuple).digest('hex').slice(0, 8); | ||
| } | ||
| /** | ||
| * Postgres identifiers cap at 63 characters and the wire name appends a | ||
| * 9-character `_<8hex>` suffix, so an authored prefix is bounded at 54. | ||
| */ | ||
| export const WIRE_NAME_PREFIX_MAX_LENGTH = 54; | ||
| /** | ||
| * Rejects a wire-name prefix over {@link WIRE_NAME_PREFIX_MAX_LENGTH}. | ||
| * `subject` opens the error message (e.g. `defineContract: policy prefix`). | ||
| */ | ||
| export function assertWireNamePrefixLength(prefix: string, subject: string): void { | ||
| if (prefix.length > WIRE_NAME_PREFIX_MAX_LENGTH) { | ||
| throw structuredError( | ||
| 'CONTRACT.WIRE_NAME_PREFIX_TOO_LONG', | ||
| `${subject} "${prefix}" exceeds the ${WIRE_NAME_PREFIX_MAX_LENGTH}-character maximum (Postgres identifiers cap at 63 characters and the wire name appends a 9-character hash suffix).`, | ||
| { meta: { prefix, maxLength: WIRE_NAME_PREFIX_MAX_LENGTH } }, | ||
| ); | ||
| } | ||
| } |
@@ -1,5 +0,87 @@ | ||
| //#region src/exports/naming.d.ts | ||
| //#region src/naming.d.ts | ||
| declare function defaultIndexName(tableName: string, columns: readonly string[]): string; | ||
| interface WireName { | ||
| /** The user-supplied part before the `_<8hex>` suffix. */ | ||
| readonly prefix: string; | ||
| /** The 8-lowercase-hex content-hash suffix. */ | ||
| readonly hash: string; | ||
| } | ||
| /** | ||
| * Assembles a wire name from its user-supplied prefix and its 8-hex | ||
| * content-hash suffix. This module owns the `<prefix>_<hash>` format on both | ||
| * sides — construction here and parsing in {@link parseWireName} — so the two | ||
| * never drift. | ||
| */ | ||
| declare function formatWireName(prefix: string, hash: string): string; | ||
| /** | ||
| * Splits a wire name (`<prefix>_<8hex>`) into its prefix and content-hash | ||
| * suffix. Returns `undefined` when the name does not follow the wire-name | ||
| * shape (e.g. an object created outside the toolchain) — callers treat such | ||
| * names as all-prefix. Consumed by introspection (prefix extraction) and by | ||
| * rename pairing (same hash, different prefix). | ||
| */ | ||
| declare function parseWireName(name: string): WireName | undefined; | ||
| /** | ||
| * Stabilizes an authored SQL body (index expression, partial-index predicate, | ||
| * RLS policy predicate) for hashing: trim, and collapse runs of internal | ||
| * whitespace to a single space. | ||
| * | ||
| * This is deliberately minimal. The content hash is the equivalence relation | ||
| * for a wire-named object, and the wire name (prefix + hash) is the only | ||
| * thing ever compared — the hash is never recomputed from an introspected | ||
| * body, so there is no need to match the database's reprinted form. Minimal | ||
| * normalization also protects the no-collision property: aggressive rewriting | ||
| * (lowercasing, paren-stripping, cast-alias folding) risks collapsing two | ||
| * distinct bodies onto one hash. | ||
| * | ||
| * The normalizer is a stability commitment: any change re-suffixes all wire names. | ||
| */ | ||
| declare function normalizeSqlBody(sql: string): string; | ||
| interface IndexContentHashParts { | ||
| readonly expression?: string; | ||
| readonly where?: string; | ||
| readonly columns?: readonly string[]; | ||
| readonly unique: boolean; | ||
| readonly type?: string; | ||
| readonly options?: Record<string, unknown>; | ||
| } | ||
| /** | ||
| * Returns the first 8 lowercase hex characters of the SHA-256 digest over the | ||
| * canonical content tuple for an index: | ||
| * | ||
| * [normalizeSqlBody(expression), normalizeSqlBody(where), columns, unique, type, sortedOptions] | ||
| * | ||
| * Columns hash in authored order — column order is semantic in an index. | ||
| * Option values are `String()`-coerced (matching the loose option equality | ||
| * used for diffing) so a hash computed from typed contract values agrees with | ||
| * one recomputed from introspected reloptions strings. The prefix, schema, | ||
| * and table are excluded (they are orthogonal to index equivalence). | ||
| * | ||
| * The tuple order and encoding are a stability commitment with the same | ||
| * status as the RLS tuple: any change re-suffixes every wire name. | ||
| */ | ||
| /** | ||
| * Canonicalizes one index option VALUE to the `on`/`off` boolean spelling: | ||
| * JS booleans and the common catalog spellings (`pg_class.reloptions` | ||
| * stores whatever spelling the DDL used, so a live index may carry | ||
| * `'true'`/`'false'` or `'on'`/`'off'`) all map to one form; everything | ||
| * else via `String()` (fully specified for numbers, so no platform | ||
| * variance). Shared by the wire-name hash tuple, the node's option | ||
| * equality, and the DDL renderer, so an authored `{ fastupdate: true }` | ||
| * agrees with a live index created under any boolean spelling. | ||
| */ | ||
| declare function normalizeIndexOptionValue(value: unknown): string; | ||
| declare function computeIndexContentHash(parts: IndexContentHashParts): string; | ||
| /** | ||
| * Postgres identifiers cap at 63 characters and the wire name appends a | ||
| * 9-character `_<8hex>` suffix, so an authored prefix is bounded at 54. | ||
| */ | ||
| declare const WIRE_NAME_PREFIX_MAX_LENGTH = 54; | ||
| /** | ||
| * Rejects a wire-name prefix over {@link WIRE_NAME_PREFIX_MAX_LENGTH}. | ||
| * `subject` opens the error message (e.g. `defineContract: policy prefix`). | ||
| */ | ||
| declare function assertWireNamePrefixLength(prefix: string, subject: string): void; | ||
| //#endregion | ||
| export { defaultIndexName }; | ||
| export { type IndexContentHashParts, WIRE_NAME_PREFIX_MAX_LENGTH, type WireName, assertWireNamePrefixLength, computeIndexContentHash, defaultIndexName, formatWireName, normalizeIndexOptionValue, normalizeSqlBody, parseWireName }; | ||
| //# sourceMappingURL=naming.d.mts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"naming.d.mts","names":[],"sources":["../../src/exports/naming.ts"],"mappings":";iBAAgB,iBAAiB,mBAAmB"} | ||
| {"version":3,"file":"naming.d.mts","names":[],"sources":["../../src/naming.ts"],"mappings":";iBAGgB,iBAAiB,mBAAmB;UAInC;;WAEN;;WAEA;;;;;;;;iBAWK,eAAe,gBAAgB;;;;;;;;iBAW/B,cAAc,eAAe;;;;;;;;;;;;;;;;iBAuB7B,iBAAiB;UAIhB;WACN;WACA;WACA;WACA;WACA;WACA,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4BL,0BAA0B;iBAM1B,wBAAwB,OAAO;;;;;cAoBlC;;;;;iBAMG,2BAA2B,gBAAgB"} |
@@ -1,8 +0,2 @@ | ||
| //#region src/exports/naming.ts | ||
| function defaultIndexName(tableName, columns) { | ||
| return `${tableName}_${columns.join("_")}_idx`; | ||
| } | ||
| //#endregion | ||
| export { defaultIndexName }; | ||
| //# sourceMappingURL=naming.mjs.map | ||
| import { a as formatWireName, c as parseWireName, i as defaultIndexName, n as assertWireNamePrefixLength, o as normalizeIndexOptionValue, r as computeIndexContentHash, s as normalizeSqlBody, t as WIRE_NAME_PREFIX_MAX_LENGTH } from "../naming-BD7Y-Fq1.mjs"; | ||
| export { WIRE_NAME_PREFIX_MAX_LENGTH, assertWireNamePrefixLength, computeIndexContentHash, defaultIndexName, formatWireName, normalizeIndexOptionValue, normalizeSqlBody, parseWireName }; |
@@ -1,2 +0,2 @@ | ||
| import { C as relationalNodeGranularity, D as assertNode, E as SqlSchemaIRNode, O as defineNonEnumerable, S as relationalNodeEntityKind, T as PrimaryKeyInput, _ as SqlColumnDefaultIR, a as SqlTableIR, b as SqlCheckConstraintIRInput, c as SqlUniqueIRInput, d as SqlForeignKeyIR, f as SqlForeignKeyIRInput, g as SqlColumnIRInput, h as SqlColumnIR, i as SqlSchemaIRInput, l as SqlIndexIR, m as SqlAnnotations, n as SqlTypeMetadataRegistry, o as SqlTableIRInput, p as SqlReferentialAction, r as SqlSchemaIR, s as SqlUniqueIR, t as SqlTypeMetadata, u as SqlIndexIRInput, v as SqlColumnDefaultIRInput, w as PrimaryKey, x as RelationalSchemaNodeKind, y as SqlCheckConstraintIR } from "../types-BwxXnNr-.mjs"; | ||
| import { C as relationalNodeGranularity, D as assertNode, E as SqlSchemaIRNode, O as defineNonEnumerable, S as relationalNodeEntityKind, T as PrimaryKeyInput, _ as SqlColumnDefaultIR, a as SqlTableIR, b as SqlCheckConstraintIRInput, c as SqlUniqueIRInput, d as SqlForeignKeyIR, f as SqlForeignKeyIRInput, g as SqlColumnIRInput, h as SqlColumnIR, i as SqlSchemaIRInput, l as SqlIndexIR, m as SqlAnnotations, n as SqlTypeMetadataRegistry, o as SqlTableIRInput, p as SqlReferentialAction, r as SqlSchemaIR, s as SqlUniqueIR, t as SqlTypeMetadata, u as SqlIndexIRInput, v as SqlColumnDefaultIRInput, w as PrimaryKey, x as RelationalSchemaNodeKind, y as SqlCheckConstraintIR } from "../types-DzLk5eOU.mjs"; | ||
| export { PrimaryKey, type PrimaryKeyInput, RelationalSchemaNodeKind, type SqlAnnotations, SqlCheckConstraintIR, type SqlCheckConstraintIRInput, SqlColumnDefaultIR, type SqlColumnDefaultIRInput, SqlColumnIR, type SqlColumnIRInput, SqlForeignKeyIR, type SqlForeignKeyIRInput, SqlIndexIR, type SqlIndexIRInput, type SqlReferentialAction, SqlSchemaIR, type SqlSchemaIRInput, SqlSchemaIRNode, SqlTableIR, type SqlTableIRInput, type SqlTypeMetadata, type SqlTypeMetadataRegistry, SqlUniqueIR, type SqlUniqueIRInput, assertNode, defineNonEnumerable, relationalNodeEntityKind, relationalNodeGranularity }; |
@@ -1,2 +0,2 @@ | ||
| import { a as SqlForeignKeyIR, c as SqlCheckConstraintIR, d as assertNode, f as defineNonEnumerable, h as relationalNodeGranularity, i as SqlIndexIR, l as PrimaryKey, m as relationalNodeEntityKind, n as SqlTableIR, o as SqlColumnIR, p as RelationalSchemaNodeKind, r as SqlUniqueIR, s as SqlColumnDefaultIR, t as SqlSchemaIR, u as SqlSchemaIRNode } from "../types-VVvdw9IR.mjs"; | ||
| import { a as SqlForeignKeyIR, c as SqlCheckConstraintIR, d as assertNode, f as defineNonEnumerable, h as relationalNodeGranularity, i as SqlIndexIR, l as PrimaryKey, m as relationalNodeEntityKind, n as SqlTableIR, o as SqlColumnIR, p as RelationalSchemaNodeKind, r as SqlUniqueIR, s as SqlColumnDefaultIR, t as SqlSchemaIR, u as SqlSchemaIRNode } from "../types-Btj35AIt.mjs"; | ||
| export { PrimaryKey, RelationalSchemaNodeKind, SqlCheckConstraintIR, SqlColumnDefaultIR, SqlColumnIR, SqlForeignKeyIR, SqlIndexIR, SqlSchemaIR, SqlSchemaIRNode, SqlTableIR, SqlUniqueIR, assertNode, defineNonEnumerable, relationalNodeEntityKind, relationalNodeGranularity }; |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import { C as relationalNodeGranularity, D as assertNode, E as SqlSchemaIRNode, O as defineNonEnumerable, S as relationalNodeEntityKind, T as PrimaryKeyInput, _ as SqlColumnDefaultIR, a as SqlTableIR, b as SqlCheckConstraintIRInput, c as SqlUniqueIRInput, d as SqlForeignKeyIR, f as SqlForeignKeyIRInput, g as SqlColumnIRInput, h as SqlColumnIR, i as SqlSchemaIRInput, l as SqlIndexIR, m as SqlAnnotations, n as SqlTypeMetadataRegistry, o as SqlTableIRInput, p as SqlReferentialAction, r as SqlSchemaIR, s as SqlUniqueIR, t as SqlTypeMetadata, u as SqlIndexIRInput, v as SqlColumnDefaultIRInput, w as PrimaryKey, x as RelationalSchemaNodeKind, y as SqlCheckConstraintIR } from "./types-BwxXnNr-.mjs"; | ||
| import { C as relationalNodeGranularity, D as assertNode, E as SqlSchemaIRNode, O as defineNonEnumerable, S as relationalNodeEntityKind, T as PrimaryKeyInput, _ as SqlColumnDefaultIR, a as SqlTableIR, b as SqlCheckConstraintIRInput, c as SqlUniqueIRInput, d as SqlForeignKeyIR, f as SqlForeignKeyIRInput, g as SqlColumnIRInput, h as SqlColumnIR, i as SqlSchemaIRInput, l as SqlIndexIR, m as SqlAnnotations, n as SqlTypeMetadataRegistry, o as SqlTableIRInput, p as SqlReferentialAction, r as SqlSchemaIR, s as SqlUniqueIR, t as SqlTypeMetadata, u as SqlIndexIRInput, v as SqlColumnDefaultIRInput, w as PrimaryKey, x as RelationalSchemaNodeKind, y as SqlCheckConstraintIR } from "./types-DzLk5eOU.mjs"; | ||
| export { PrimaryKey, type PrimaryKeyInput, RelationalSchemaNodeKind, type SqlAnnotations, SqlCheckConstraintIR, type SqlCheckConstraintIRInput, SqlColumnDefaultIR, type SqlColumnDefaultIRInput, SqlColumnIR, type SqlColumnIRInput, SqlForeignKeyIR, type SqlForeignKeyIRInput, SqlIndexIR, type SqlIndexIRInput, type SqlReferentialAction, SqlSchemaIR, type SqlSchemaIRInput, SqlSchemaIRNode, SqlTableIR, type SqlTableIRInput, type SqlTypeMetadata, type SqlTypeMetadataRegistry, SqlUniqueIR, type SqlUniqueIRInput, assertNode, defineNonEnumerable, relationalNodeEntityKind, relationalNodeGranularity }; |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import { a as SqlForeignKeyIR, c as SqlCheckConstraintIR, d as assertNode, f as defineNonEnumerable, h as relationalNodeGranularity, i as SqlIndexIR, l as PrimaryKey, m as relationalNodeEntityKind, n as SqlTableIR, o as SqlColumnIR, p as RelationalSchemaNodeKind, r as SqlUniqueIR, s as SqlColumnDefaultIR, t as SqlSchemaIR, u as SqlSchemaIRNode } from "./types-VVvdw9IR.mjs"; | ||
| import { a as SqlForeignKeyIR, c as SqlCheckConstraintIR, d as assertNode, f as defineNonEnumerable, h as relationalNodeGranularity, i as SqlIndexIR, l as PrimaryKey, m as relationalNodeEntityKind, n as SqlTableIR, o as SqlColumnIR, p as RelationalSchemaNodeKind, r as SqlUniqueIR, s as SqlColumnDefaultIR, t as SqlSchemaIR, u as SqlSchemaIRNode } from "./types-Btj35AIt.mjs"; | ||
| export { PrimaryKey, RelationalSchemaNodeKind, SqlCheckConstraintIR, SqlColumnDefaultIR, SqlColumnIR, SqlForeignKeyIR, SqlIndexIR, SqlSchemaIR, SqlSchemaIRNode, SqlTableIR, SqlUniqueIR, assertNode, defineNonEnumerable, relationalNodeEntityKind, relationalNodeGranularity }; |
+7
-7
| { | ||
| "name": "@prisma-next/sql-schema-ir", | ||
| "version": "0.16.0-dev.25", | ||
| "version": "0.16.0-dev.26", | ||
| "license": "Apache-2.0", | ||
@@ -9,10 +9,10 @@ "type": "module", | ||
| "dependencies": { | ||
| "@prisma-next/contract": "0.16.0-dev.25", | ||
| "@prisma-next/framework-components": "0.16.0-dev.25", | ||
| "@prisma-next/utils": "0.16.0-dev.25" | ||
| "@prisma-next/contract": "0.16.0-dev.26", | ||
| "@prisma-next/framework-components": "0.16.0-dev.26", | ||
| "@prisma-next/utils": "0.16.0-dev.26" | ||
| }, | ||
| "devDependencies": { | ||
| "@prisma-next/test-utils": "0.16.0-dev.25", | ||
| "@prisma-next/tsconfig": "0.16.0-dev.25", | ||
| "@prisma-next/tsdown": "0.16.0-dev.25", | ||
| "@prisma-next/test-utils": "0.16.0-dev.26", | ||
| "@prisma-next/tsconfig": "0.16.0-dev.26", | ||
| "@prisma-next/tsdown": "0.16.0-dev.26", | ||
| "tsdown": "0.22.8", | ||
@@ -19,0 +19,0 @@ "typescript": "5.9.3", |
+2
-0
@@ -57,2 +57,4 @@ # @prisma-next/sql-schema-ir | ||
| 4. **Name-identified indexes**: `SqlIndexIR` is identified by its full physical name (its diff-tree `id` is the name, so same-column-tuple siblings and expression indexes are representable). Managed indexes carry a `prefix` plus a content-hash wire name; the shared naming helpers (`formatWireName`, `parseWireName`, `normalizeSqlBody`, `computeIndexContentHash`) live in `@prisma-next/sql-schema-ir/naming`. | ||
| ## Usage | ||
@@ -59,0 +61,0 @@ |
@@ -1,3 +0,12 @@ | ||
| export function defaultIndexName(tableName: string, columns: readonly string[]): string { | ||
| return `${tableName}_${columns.join('_')}_idx`; | ||
| } | ||
| export { | ||
| assertWireNamePrefixLength, | ||
| computeIndexContentHash, | ||
| defaultIndexName, | ||
| formatWireName, | ||
| type IndexContentHashParts, | ||
| normalizeIndexOptionValue, | ||
| normalizeSqlBody, | ||
| parseWireName, | ||
| WIRE_NAME_PREFIX_MAX_LENGTH, | ||
| type WireName, | ||
| } from '../naming'; |
+156
-48
| import type { DiffableNode, SchemaNodeRef } from '@prisma-next/framework-components/control'; | ||
| import { freezeNode } from '@prisma-next/framework-components/ir'; | ||
| import { isArrayEqual } from '@prisma-next/utils/array-equal'; | ||
| import { blindCast } from '@prisma-next/utils/casts'; | ||
| import { InternalError } from '@prisma-next/utils/internal-error'; | ||
| import { normalizeIndexOptionValue } from '../naming'; | ||
| import { RelationalSchemaNodeKind } from './schema-node-kinds'; | ||
@@ -9,12 +12,44 @@ import type { SqlAnnotations } from './sql-column-ir'; | ||
| /** | ||
| * Every field is a required key. Values that may legitimately be absent | ||
| * (an unnamed `@@index`, the btree→undefined type normalization) are typed | ||
| * `| undefined` instead of optional, so each construction site states the | ||
| * absence explicitly rather than omitting the key silently. Undefined values | ||
| * still produce an instance without the property. | ||
| * An index's element structure — exactly one of a column tuple or an opaque | ||
| * expression, unrepresentable-otherwise at the type level. No discriminant | ||
| * is stored (the node keeps flat readonly accessors); the constructor's xor | ||
| * throw stays as the backstop for introspection rows and JSON-derived | ||
| * inputs that bypass this union. | ||
| */ | ||
| export interface SqlIndexIRInput { | ||
| readonly columns: readonly string[]; | ||
| export type SqlIndexElements = | ||
| | { | ||
| /** Column-tuple elements. */ | ||
| readonly columns: readonly string[]; | ||
| readonly expression?: never; | ||
| } | ||
| | { | ||
| readonly columns?: never; | ||
| /** | ||
| * Opaque SQL: the entire element list between the parens of CREATE | ||
| * INDEX — one string, never parsed. | ||
| */ | ||
| readonly expression: string; | ||
| }; | ||
| /** | ||
| * Every non-element field is a required key. Values that may legitimately | ||
| * be absent (an exact-named index's prefix, a default-method type) are | ||
| * typed `| undefined` instead of optional, so each construction site | ||
| * states the absence explicitly rather than omitting the key silently. | ||
| * Undefined values still produce an instance without the property. | ||
| */ | ||
| export type SqlIndexIRInput = SqlIndexElements & { | ||
| /** Full physical name — the node's identity. */ | ||
| readonly name: string; | ||
| /** | ||
| * The managed-mode name prefix — its PRESENCE is the naming-mode | ||
| * discriminator (there is no stored enum). Present ⇔ managed: the | ||
| * toolchain owns the physical name and `name === formatWireName(prefix, | ||
| * <8hex content hash>)`. Absent ⇔ exact: `name` is an adopted verbatim | ||
| * physical name whose identity the author owns entirely. | ||
| */ | ||
| readonly prefix: string | undefined; | ||
| /** Opaque SQL: partial-index predicate (WHERE body, without the keyword). */ | ||
| readonly where: string | undefined; | ||
| readonly unique: boolean; | ||
| readonly name: string | undefined; | ||
| readonly type: string | undefined; | ||
@@ -26,3 +61,5 @@ readonly options: Record<string, unknown> | undefined; | ||
| * stamps them so an index is dropped before the columns it is built on | ||
| * (Postgres auto-drops the index when a covered column goes). Never | ||
| * (Postgres auto-drops the index when a covered column goes). An expression | ||
| * index stamps chains to every column of its table — a deterministic | ||
| * over-approximation, since the opaque expression is never parsed. Never | ||
| * compared by `isEqualTo`. | ||
@@ -36,7 +73,6 @@ */ | ||
| * 1:1 relation — "unknown" must not silently default to "total". Never | ||
| * compared by `isEqualTo` and never serialized: differ/verify semantics | ||
| * are unchanged, and surfacing partial-index drift is out of scope. | ||
| * compared by `isEqualTo` and never serialized. | ||
| */ | ||
| readonly partial: boolean; | ||
| } | ||
| }; | ||
@@ -51,19 +87,22 @@ /** | ||
| * Implements `DiffableNode` so an index is directly a table's diff-tree | ||
| * child. Indexes are frequently unnamed, so `id` is derived from the column | ||
| * tuple — the same tuple that makes two indexes the same index, so it | ||
| * doubles as the pairing key. `isEqualTo` is symmetric structural equality | ||
| * on the remaining attributes: `unique`, `type`, and `options`. A unique | ||
| * index and a non-unique index on the same columns are different objects | ||
| * and are not equal — there is no "stronger satisfies weaker". | ||
| * child. Indexes are name-identified: every index — contract-derived or | ||
| * introspected — carries its full physical name, and `id` is that name. | ||
| * Names are catalog-unique per schema, so two indexes legitimately sharing | ||
| * one column tuple (a unique index beside a redundant plain index) are two | ||
| * distinct siblings, and expression indexes need no column tuple at all. | ||
| * | ||
| * Deliberately column-tuple-only (not `unique`): a contract `@@index` | ||
| * (non-unique) must still pair against a live unique index of the same | ||
| * columns so the differ can report the mismatch as an incompatible index | ||
| * change rather than a spurious missing+extra pair — see | ||
| * `planner.unique-index-structural.test.ts`. The corollary is that two | ||
| * indexes legitimately coexisting on one table with the *same* column tuple | ||
| * (e.g. a unique index and a redundant plain index Postgres has no problem | ||
| * hosting side by side) collide on this id; the postgres control adapter's | ||
| * introspection keeps only one such index per table+column-tuple rather | ||
| * than handing the differ two same-tree siblings it cannot represent. | ||
| * `isEqualTo` is selected by the receiver (the differ always calls | ||
| * `expected.isEqualTo(actual)`) and delegates to {@link contentEquals} — | ||
| * the single node-owned content relation: both modes compare `unique` | ||
| * strict, `type` and option values through the named normalization seams, | ||
| * and `columns` ordered-strict when both sides carry them; an exact-named | ||
| * receiver (`prefix === undefined`) additionally byte-compares | ||
| * `expression`/`where` (both sides are reprints in the supported flow — | ||
| * normalizing would only mask real drift); a managed receiver never | ||
| * compares bodies (the wire-name hash already commits to them). | ||
| * | ||
| * `expression`, `where`, and `unique` are genuine SQL-family attributes — | ||
| * functional and partial indexes are standard SQL that any SQL target may | ||
| * introspect, so the family node must represent them; a target declining | ||
| * to author them is a capability decision, not target-specificity. | ||
| */ | ||
@@ -73,5 +112,8 @@ export class SqlIndexIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly columns: readonly string[]; | ||
| readonly name: string; | ||
| readonly unique: boolean; | ||
| declare readonly name?: string; | ||
| declare readonly prefix?: string; | ||
| declare readonly columns?: readonly string[]; | ||
| declare readonly expression?: string; | ||
| declare readonly where?: string; | ||
| declare readonly type?: string; | ||
@@ -87,5 +129,13 @@ declare readonly options?: Record<string, unknown>; | ||
| super(); | ||
| this.columns = input.columns; | ||
| if ((input.columns === undefined) === (input.expression === undefined)) { | ||
| throw new InternalError( | ||
| `SqlIndexIR "${input.name}": exactly one of columns or expression must be set.`, | ||
| ); | ||
| } | ||
| this.name = input.name; | ||
| this.unique = input.unique; | ||
| if (input.name !== undefined) this.name = input.name; | ||
| if (input.prefix !== undefined) this.prefix = input.prefix; | ||
| if (input.columns !== undefined) this.columns = input.columns; | ||
| if (input.expression !== undefined) this.expression = input.expression; | ||
| if (input.where !== undefined) this.where = input.where; | ||
| if (input.type !== undefined) this.type = input.type; | ||
@@ -100,3 +150,3 @@ if (input.options !== undefined) this.options = input.options; | ||
| get id(): string { | ||
| return `index:${this.columns.join(',')}`; | ||
| return `index:${this.name}`; | ||
| } | ||
@@ -113,8 +163,7 @@ | ||
| /** | ||
| * Symmetric structural equality: two paired index nodes are equal iff their | ||
| * `unique` flag, `type`, and (loosely-compared) `options` all match. There | ||
| * is no satisfaction — a unique index does not equal a non-unique index. | ||
| * `options` compares loosely (introspection stringifies reloptions); `type` | ||
| * compares strictly after the introspection-side btree→undefined | ||
| * normalization done at construction. | ||
| * Mode-selected structural equality — see the class doc. Delegates to the | ||
| * single node-owned relation: `columns` compare ordered-strict when both | ||
| * sides carry them; an exact receiver (`prefix === undefined`) | ||
| * byte-compares `expression ?? ''` and `where ?? ''`; a managed receiver | ||
| * never compares bodies (the wire-name hash already commits to them). | ||
| */ | ||
@@ -127,6 +176,53 @@ isEqualTo(other: DiffableNode): boolean { | ||
| assertNode(node, 'SqlIndexIR', SqlIndexIR.is); | ||
| return this.contentEquals(node, { | ||
| columnPresence: 'when-both-defined', | ||
| bodies: this.prefix !== undefined ? 'ignored' : 'verbatim', | ||
| }); | ||
| } | ||
| /** | ||
| * The single index content-equality relation — every comparer (the differ | ||
| * via {@link isEqualTo}, the planner's rename content-pairing) calls this | ||
| * with its mode-appropriate strictness rather than growing a parallel | ||
| * relation: | ||
| * | ||
| * - `columnPresence: 'when-both-defined'` (the differ's rule) compares | ||
| * the tuples ordered-strict only when both sides carry them — a paired | ||
| * node's identity already agreed, so a column node meeting an | ||
| * expression node skips the tuple. | ||
| * - `columnPresence: 'matching'` (the rename-pairing rule) additionally | ||
| * requires presence to agree: a column index never pairs an expression | ||
| * index. | ||
| * - `bodies: 'verbatim'` byte-compares `expression ?? ''` / `where ?? ''` | ||
| * (absent ≡ empty, no normalization — both sides are reprints in the | ||
| * supported flow); `bodies: 'ignored'` skips them (managed identity — | ||
| * the wire-name hash commits to the content). | ||
| * | ||
| * `unique` compares strictly; `type` and option VALUES compare through | ||
| * the named normalization seams below. | ||
| */ | ||
| contentEquals( | ||
| other: SqlIndexIR, | ||
| strictness: { | ||
| readonly columnPresence: 'when-both-defined' | 'matching'; | ||
| readonly bodies: 'verbatim' | 'ignored'; | ||
| }, | ||
| ): boolean { | ||
| const columnsEqual = | ||
| strictness.columnPresence === 'matching' | ||
| ? (this.columns === undefined) === (other.columns === undefined) && | ||
| (this.columns === undefined || isArrayEqual(this.columns, other.columns ?? [])) | ||
| : this.columns === undefined || | ||
| other.columns === undefined || | ||
| isArrayEqual(this.columns, other.columns); | ||
| const structurallyEqual = | ||
| this.unique === other.unique && | ||
| normalizeIndexType(this.type) === normalizeIndexType(other.type) && | ||
| indexOptionsEqual(this.options, other.options) && | ||
| columnsEqual; | ||
| if (!structurallyEqual) return false; | ||
| if (strictness.bodies === 'ignored') return true; | ||
| return ( | ||
| this.unique === node.unique && | ||
| this.type === node.type && | ||
| indexOptionsLooselyEqual(this.options, node.options) | ||
| (this.expression ?? '') === (other.expression ?? '') && | ||
| (this.where ?? '') === (other.where ?? '') | ||
| ); | ||
@@ -137,8 +233,20 @@ } | ||
| /** | ||
| * Option-bag equality ported from the relational walk: same key set, values | ||
| * compared via `String()` coercion — Postgres introspection returns | ||
| * reloptions values as raw strings (`'70'`, `'false'`) while contract option | ||
| * leaves are typed (number, boolean, string). | ||
| * Comparison-side normalization seam: the default access method (`btree` in | ||
| * every supported SQL target) compares as absent, so an authored | ||
| * `type: "btree"` and a default-method introspected index (whose type the | ||
| * adapter or constructor normalized away) are equal. Applied by | ||
| * {@link SqlIndexIR.contentEquals} only — the wire-name hash keeps the | ||
| * authored spelling. | ||
| */ | ||
| function indexOptionsLooselyEqual( | ||
| function normalizeIndexType(type: string | undefined): string | undefined { | ||
| return type === 'btree' ? undefined : type; | ||
| } | ||
| /** | ||
| * Option-bag equality: same key set, values compared through | ||
| * {@link normalizeIndexOptionValue} — Postgres introspection returns | ||
| * reloptions values as catalog-reprint strings (`'70'`, `'on'`) while | ||
| * contract option leaves are typed (number, boolean, string). | ||
| */ | ||
| function indexOptionsEqual( | ||
| a: Record<string, unknown> | undefined, | ||
@@ -155,3 +263,3 @@ b: Record<string, unknown> | undefined, | ||
| for (const key of aKeys) { | ||
| if (String(a?.[key]) !== String(b?.[key])) { | ||
| if (normalizeIndexOptionValue(a?.[key]) !== normalizeIndexOptionValue(b?.[key])) { | ||
| return false; | ||
@@ -158,0 +266,0 @@ } |
+4
-1
@@ -36,3 +36,6 @@ /** | ||
| } from './ir/sql-foreign-key-ir'; | ||
| export { SqlIndexIR, type SqlIndexIRInput } from './ir/sql-index-ir'; | ||
| export { | ||
| SqlIndexIR, | ||
| type SqlIndexIRInput, | ||
| } from './ir/sql-index-ir'; | ||
| export { SqlSchemaIR, type SqlSchemaIRInput } from './ir/sql-schema-ir'; | ||
@@ -39,0 +42,0 @@ export { assertNode, defineNonEnumerable, SqlSchemaIRNode } from './ir/sql-schema-ir-node'; |
| {"version":3,"file":"naming.mjs","names":[],"sources":["../../src/exports/naming.ts"],"sourcesContent":["export function defaultIndexName(tableName: string, columns: readonly string[]): string {\n return `${tableName}_${columns.join('_')}_idx`;\n}\n"],"mappings":";AAAA,SAAgB,iBAAiB,WAAmB,SAAoC;CACtF,OAAO,GAAG,UAAU,GAAG,QAAQ,KAAK,GAAG,EAAE;AAC3C"} |
| import { IRNodeBase } from "@prisma-next/framework-components/ir"; | ||
| import { DiffSubjectGranularity, DiffableNode, SchemaNodeRef } from "@prisma-next/framework-components/control"; | ||
| import { ColumnDefault } from "@prisma-next/contract/types"; | ||
| import { CodecRef } from "@prisma-next/framework-components/codec"; | ||
| //#region src/ir/sql-schema-ir-node.d.ts | ||
| /** | ||
| * SQL Schema IR node base. Carries the family-level | ||
| * `kind = 'sql-schema-ir'` discriminator and inherits the framework's | ||
| * `freezeNode` affordance. | ||
| * | ||
| * SQL Schema IR represents the actual database state as discovered by | ||
| * introspection (the parallel to SQL Contract IR, which represents the | ||
| * desired state). | ||
| * | ||
| * The discriminator is installed as a non-enumerable own property, | ||
| * matching the SqlNode pattern. This keeps `JSON.stringify(node)` | ||
| * canonical (no `kind` field), keeps `toEqual({...})` test assertions | ||
| * against pre-lift flat shapes passing, and keeps `node.kind` readable | ||
| * for dispatch. | ||
| * | ||
| * Both `kind` and `nodeKind` are required: every concrete leaf is a node | ||
| * the generic differ can pair and compare, so every leaf must declare which | ||
| * node it is. `nodeKind` has no default here — every direct subclass sets its | ||
| * own literal value (the relational leaves via `RelationalSchemaNodeKind`, | ||
| * target concretions via their own vocabulary, e.g. `PostgresSchemaNodeKind`). | ||
| */ | ||
| declare abstract class SqlSchemaIRNode extends IRNodeBase { | ||
| readonly kind: string; | ||
| /** | ||
| * Enumerable discriminant identifying which node this is (column / primary | ||
| * key / foreign key / unique / index / check / database / namespace / | ||
| * table / policy / role / …). Concretions set a unique value; the | ||
| * `.is`/`.assert` guards compare against it. Unlike `kind`, it is | ||
| * enumerable, so it survives a spread that flattens a node into a plain | ||
| * object. | ||
| */ | ||
| abstract readonly nodeKind: string; | ||
| constructor(); | ||
| } | ||
| /** | ||
| * Asserts `node` matches `predicate`, narrowing its type to `T`. The one | ||
| * shared implementation every node class's `static assert` and `isEqualTo` | ||
| * reach for, instead of each hand-writing its own throw: the message names | ||
| * the class the caller expected. | ||
| */ | ||
| declare function assertNode<T extends SqlSchemaIRNode>(node: SqlSchemaIRNode | undefined, className: string, predicate: (node: SqlSchemaIRNode) => node is T): asserts node is T; | ||
| /** | ||
| * Defines a non-enumerable own property, the same treatment `kind` gets | ||
| * above: a derivation-time render-support field stays out of | ||
| * `JSON.stringify`, `toEqual({...})` structural assertions, and spreads, | ||
| * while remaining directly readable (`node.field`) for the one consumer | ||
| * that resolves it at plan time. A no-op when `value` is `undefined` — the | ||
| * property is simply absent, matching every other optional field on these | ||
| * nodes. | ||
| */ | ||
| declare function defineNonEnumerable<T extends object>(target: T, key: string, value: unknown): void; | ||
| //#endregion | ||
| //#region src/ir/primary-key.d.ts | ||
| interface PrimaryKeyInput { | ||
| readonly columns: readonly string[]; | ||
| readonly name?: string; | ||
| /** | ||
| * The key's own column nodes, as root-anchored chains. The derivation | ||
| * stamps them so the primary key is dropped before the columns it is built | ||
| * on. Never compared by `isEqualTo`. | ||
| */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| } | ||
| /** | ||
| * Primary-key Schema IR node. Mirrors the Contract IR `PrimaryKey` | ||
| * shape (same `columns` + optional `name`) so verification can compare | ||
| * intent and actual structurally. Defined here independently to avoid | ||
| * a sql-schema-ir -> sql-contract dependency. | ||
| * | ||
| * Implements `DiffableNode` so a primary key is directly a table's diff-tree | ||
| * child. `id` is a fixed sentinel — a table has at most one primary key, so | ||
| * there is never a sibling to disambiguate against. `isEqualTo` compares the | ||
| * column tuple; the PK's own `name` is a database-assigned label with no | ||
| * semantic weight, so it is not compared (mirrors the policy node's | ||
| * name-insensitivity to non-identifying detail). | ||
| */ | ||
| declare class PrimaryKey extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-primary-key"; | ||
| readonly columns: readonly string[]; | ||
| readonly name?: string; | ||
| /** See {@link PrimaryKeyInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| constructor(input: PrimaryKeyInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is PrimaryKey; | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/schema-node-kinds.d.ts | ||
| /** | ||
| * The `nodeKind` discriminant for each relational schema-diff leaf node. | ||
| * Each node carries a unique value; the differ pairs siblings by `id`, and | ||
| * these kinds distinguish the node types that appear as `PostgresTableSchemaNode` | ||
| * children (columns, primary key, foreign keys, uniques, indexes, checks) from | ||
| * each other and from the RLS policy/role kinds a target defines separately. | ||
| */ | ||
| declare const RelationalSchemaNodeKind: { | ||
| readonly schema: "sql-schema"; | ||
| readonly table: "sql-table"; | ||
| readonly column: "sql-column"; | ||
| readonly columnDefault: "sql-column-default"; | ||
| readonly primaryKey: "sql-primary-key"; | ||
| readonly foreignKey: "sql-foreign-key"; | ||
| readonly unique: "sql-unique"; | ||
| readonly index: "sql-index"; | ||
| readonly check: "sql-check-constraint"; | ||
| }; | ||
| type RelationalSchemaNodeKind = (typeof RelationalSchemaNodeKind)[keyof typeof RelationalSchemaNodeKind]; | ||
| /** | ||
| * Looks up the subject granularity for a relational `nodeKind`. Throws for a | ||
| * `nodeKind` outside this map (a target-specific kind, e.g. Postgres's | ||
| * namespace/table/policy/role) — those map their own kinds directly rather | ||
| * than through this family-level map. | ||
| */ | ||
| declare function relationalNodeGranularity(nodeKind: string): DiffSubjectGranularity; | ||
| /** | ||
| * Looks up the storage `entityKind` for a relational `nodeKind` — sibling of | ||
| * {@link relationalNodeGranularity}, resolved by `nodeKind` equality against | ||
| * {@link RELATIONAL_NODE_ENTITY_KIND}. `undefined` for a target-specific kind | ||
| * (targets map their own kinds directly) or a relational kind with no entity | ||
| * of its own. | ||
| */ | ||
| declare function relationalNodeEntityKind(nodeKind: string): string | undefined; | ||
| //#endregion | ||
| //#region src/ir/sql-check-constraint-ir.d.ts | ||
| interface SqlCheckConstraintIRInput { | ||
| /** Constraint name as stored in the database catalog. */ | ||
| readonly name: string; | ||
| /** Column the check restricts. */ | ||
| readonly column: string; | ||
| /** Permitted values the column must be IN. */ | ||
| readonly permittedValues: readonly string[]; | ||
| } | ||
| /** | ||
| * Schema IR node for a table-level check constraint that restricts a | ||
| * column to a set of permitted values (an enum-style `IN (...)` check). | ||
| * | ||
| * Carries the **resolved values** rather than a raw SQL predicate so | ||
| * callers can compare value-sets without parsing SQL. | ||
| * | ||
| * Implements `DiffableNode` so a check constraint is directly a table's | ||
| * diff-tree child: `id` is the constraint name. `isEqualTo` compares | ||
| * `column` and the permitted-value set (order-insensitive — the database | ||
| * does not guarantee `IN (...)` ordering). | ||
| */ | ||
| declare class SqlCheckConstraintIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-check-constraint"; | ||
| readonly name: string; | ||
| readonly column: string; | ||
| readonly permittedValues: readonly string[]; | ||
| constructor(input: SqlCheckConstraintIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlCheckConstraintIR; | ||
| /** | ||
| * Compares the permitted-value sets only (unordered), matching the | ||
| * relational walk's `verifyCheckConstraints`: two checks pairing by name | ||
| * compare their value sets — the `column` field is descriptive, not part | ||
| * of the comparison, so a name-paired check with equal values verifies | ||
| * regardless of which column carries it. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-column-default-ir.d.ts | ||
| interface SqlColumnDefaultIRInput { | ||
| /** Structured resolved default (contract-declared or normalizer-parsed). */ | ||
| readonly resolved?: ColumnDefault; | ||
| /** Raw database default expression, when known (introspected side). */ | ||
| readonly raw?: string; | ||
| /** | ||
| * Native-type context for temporal literal normalization — the owning | ||
| * column's resolved native type. | ||
| */ | ||
| readonly nativeTypeContext?: string; | ||
| /** | ||
| * The owning column's array-ness, threaded through so the planner's | ||
| * set-default op-builder can render an array-literal default (e.g. | ||
| * `ARRAY[...]`) the same way the pre-`plan(start, end)` op-path did. See | ||
| * {@link import('./sql-column-ir').SqlColumnIRInput.many}. | ||
| */ | ||
| readonly many?: boolean; | ||
| } | ||
| /** | ||
| * Schema-diff node for a column's default value. The default is the one | ||
| * column attribute with an extra/missing/drift lifecycle of its own, so it | ||
| * is a child node of the column rather than a compared attribute: an | ||
| * undeclared live default surfaces as `not-expected`, a declared default the | ||
| * database lacks as `not-found`, and a divergent value as `not-equal` — the | ||
| * three reasons cover the legacy `extra_default` / `default_missing` / | ||
| * `default_mismatch` vocabulary with no attribute inspection. | ||
| * | ||
| * `id` is a fixed sentinel — a column has at most one default. Built | ||
| * transiently by `SqlColumnIR.children()` from the column's own fields; | ||
| * never constructed by derivation or introspection directly. | ||
| */ | ||
| declare class SqlColumnDefaultIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-column-default"; | ||
| readonly resolved?: ColumnDefault; | ||
| readonly raw?: string; | ||
| readonly nativeTypeContext?: string; | ||
| /** See {@link SqlColumnDefaultIRInput.many}. Non-enumerable so it stays out of JSON and structural equality. */ | ||
| readonly many?: boolean; | ||
| constructor(input: SqlColumnDefaultIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlColumnDefaultIR; | ||
| /** | ||
| * Structured comparison with `this` as the expected side: both sides | ||
| * resolved compare per the relational walk's `columnDefaultsEqual` | ||
| * semantics; a declared expected default against an unparseable actual | ||
| * (raw present, no resolved parse) is a mismatch; two raw-only nodes fall | ||
| * back to raw string equality. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-column-ir.d.ts | ||
| /** | ||
| * Namespaced annotations for extensibility. Each namespace | ||
| * (e.g. `pg`, `pgvector`) owns its annotations subtree. | ||
| */ | ||
| type SqlAnnotations = { | ||
| readonly [namespace: string]: unknown; | ||
| }; | ||
| interface SqlColumnIRInput { | ||
| readonly name: string; | ||
| readonly nativeType: string; | ||
| readonly nullable: boolean; | ||
| /** Raw database default expression (e.g. `'hello'::text`, `nextval('seq')`). */ | ||
| readonly default?: string; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** True when the column is a native array (e.g. `text[]`, `int4[]`). The `nativeType` carries the element type only (e.g. `text`, `int4`). */ | ||
| readonly many?: boolean; | ||
| /** | ||
| * Fully resolved native type, comparable across the two diff sides: | ||
| * the contract-derived side stamps the codec-expanded type (typeRef | ||
| * resolved, parameterized types expanded, `[]` appended for arrays); | ||
| * the introspected side stamps the target-normalized type with the same | ||
| * `[]` convention. Stamped at construction by derivation/introspection; | ||
| * absent on raw hand-built nodes. | ||
| */ | ||
| readonly resolvedNativeType?: string; | ||
| /** | ||
| * Structured default, comparable across the two diff sides: the | ||
| * contract-derived side stamps the contract's `ColumnDefault`; the | ||
| * introspected side stamps the target default-normalizer's parse of the | ||
| * raw expression. Absent when the column declares no default, or when | ||
| * the introspected raw default could not be parsed. | ||
| */ | ||
| readonly resolvedDefault?: ColumnDefault; | ||
| /** | ||
| * The column's resolved codec reference — the identity the migration | ||
| * planner's op-builders resolve DDL type rendering against at plan time | ||
| * (parameterized type expansion, e.g. `character` + `{ length: 36 }` → | ||
| * `character(36)`), calling the same codec hooks the pre-`plan(start, | ||
| * end)` op-path called. Carried the same way the query AST carries | ||
| * `CodecRef` (TML-2456) and the migration DDL renderer (TML-2918). | ||
| * Stamped on the EXPECTED (contract-derived) column at derivation, | ||
| * post-`typeRef` resolution (the referenced storage type's own | ||
| * codec/params, not the column's `typeRef` pointer). Absent on | ||
| * introspected/hand-built nodes — the actual side never renders DDL — | ||
| * and never compared by `isEqualTo`. | ||
| */ | ||
| readonly codecRef?: CodecRef; | ||
| /** | ||
| * The column's resolved BASE native type: pre-parameter-expansion, | ||
| * pre-array-suffix (e.g. `character`, not `character(36)` or | ||
| * `character[]`). Distinct from {@link resolvedNativeType} (the EXPANDED | ||
| * comparison value) — DDL rendering re-expands a parameterized type at | ||
| * plan time from this base, so it must not already carry the expansion. | ||
| * Stamped alongside {@link codecRef}. | ||
| */ | ||
| readonly codecBaseNativeType?: string; | ||
| /** | ||
| * True when the contract column declared its type via a named | ||
| * `storage.types` reference (`typeRef`) rather than inline fields — the | ||
| * migration planner quotes the base native type as an identifier in this | ||
| * case (e.g. a native enum's type name), matching the pre-`plan(start, | ||
| * end)` rendering exactly. | ||
| */ | ||
| readonly codecNamedType?: boolean; | ||
| } | ||
| /** | ||
| * Schema IR node for a single column on a table, as observed by | ||
| * introspection. Unlike the Contract IR `StorageColumn`, this carries | ||
| * the column's `name` (Schema IR columns are returned as arrays from | ||
| * introspection queries; the parent table re-keys them into a record | ||
| * for downstream consumers). | ||
| * | ||
| * Implements `DiffableNode` so a column is directly a table's diff-tree | ||
| * child: `id` is the column name (unique among a table's columns); `isEqualTo` | ||
| * compares this column's own attributes only — never children, since a | ||
| * column is a leaf. When both sides carry `resolvedNativeType` (stamped at | ||
| * derivation/introspection), the comparison uses the resolved values — | ||
| * resolved native type, nullability, and structured default equality per | ||
| * the relational walk's `columnDefaultsEqual` semantics, with `this` as the | ||
| * expected side. Otherwise it falls back to comparing raw fields. | ||
| */ | ||
| declare class SqlColumnIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-column"; | ||
| readonly name: string; | ||
| readonly nativeType: string; | ||
| readonly nullable: boolean; | ||
| readonly default?: string; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** True when the column is a native array (e.g. `text[]`, `int4[]`). The `nativeType` carries the element type only (e.g. `text`, `int4`). */ | ||
| readonly many?: boolean; | ||
| readonly resolvedNativeType?: string; | ||
| readonly resolvedDefault?: ColumnDefault; | ||
| /** See {@link SqlColumnIRInput.codecRef}. Non-enumerable so it stays out of JSON and structural equality. */ | ||
| readonly codecRef?: CodecRef; | ||
| /** See {@link SqlColumnIRInput.codecBaseNativeType}. Non-enumerable, same reason as {@link codecRef}. */ | ||
| readonly codecBaseNativeType?: string; | ||
| /** See {@link SqlColumnIRInput.codecNamedType}. Non-enumerable, same reason as {@link codecRef}. */ | ||
| readonly codecNamedType?: boolean; | ||
| constructor(input: SqlColumnIRInput); | ||
| get id(): string; | ||
| /** | ||
| * The column's default, when declared/present, is the column's one child | ||
| * node — it has an extra/missing/drift lifecycle of its own, so the differ | ||
| * recurses to it rather than `isEqualTo` comparing it. Built transiently | ||
| * from this column's own fields. | ||
| */ | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlColumnIR; | ||
| /** | ||
| * Compares the column's own attributes only — the default lives on the | ||
| * child node. When both sides carry `resolvedNativeType`, the resolved | ||
| * value governs (array-ness rides on its `[]` suffix); otherwise raw | ||
| * fields compare. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-foreign-key-ir.d.ts | ||
| type SqlReferentialAction = 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault'; | ||
| interface SqlForeignKeyIRInput { | ||
| readonly columns: readonly string[]; | ||
| readonly referencedTable: string; | ||
| readonly referencedColumns: readonly string[]; | ||
| /** Schema (namespace) of the referenced table — populated by adapters that introspect cross-schema FKs. */ | ||
| readonly referencedSchema?: string; | ||
| readonly name?: string; | ||
| readonly onDelete?: SqlReferentialAction; | ||
| readonly onUpdate?: SqlReferentialAction; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** | ||
| * The real live DDL namespace of the referenced table, comparable across | ||
| * the two diff sides. Contract-derived trees stamp it explicitly (resolving | ||
| * namespace ids — including the unbound sentinel — to the DDL namespace); | ||
| * introspected FKs default it to `referencedSchema`, whose value already | ||
| * is the live namespace. Folded into `id` in place of the raw value so an | ||
| * unbound-namespace contract FK pairs with its introspected counterpart. | ||
| */ | ||
| readonly resolvedReferencedNamespace?: string; | ||
| /** | ||
| * The referenced table's node, as the root-anchored chain the differ pairs | ||
| * siblings with. Stamped by the derivation, which holds the parent | ||
| * (database/namespace) context the chain's shape depends on. Never | ||
| * compared by `isEqualTo`. | ||
| */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| } | ||
| /** | ||
| * Schema IR node for a foreign-key constraint as observed by | ||
| * introspection. The `referencedTable` / `referencedColumns` field | ||
| * names match the introspection vocabulary (`pg_constraint.confkey`, | ||
| * etc.) and intentionally differ from the Contract IR's nested | ||
| * `references: { table, columns }` shape so that the verifier's | ||
| * structural comparison stays explicit about which side it's reading. | ||
| * | ||
| * Implements `DiffableNode` so a foreign key is directly a table's diff-tree | ||
| * child. Foreign keys are frequently unnamed (introspection may not carry a | ||
| * constraint name, and the contract side never invents one), so `id` is | ||
| * derived from the referencing/referenced coordinates rather than `name` — | ||
| * the same tuple that makes two FK constraints the same constraint. This | ||
| * also serves as the comparison key: two FKs with the same coordinates are | ||
| * paired by the differ, and `isEqualTo` then compares the remaining | ||
| * attribute — the referential actions. | ||
| */ | ||
| declare class SqlForeignKeyIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-foreign-key"; | ||
| readonly columns: readonly string[]; | ||
| readonly referencedTable: string; | ||
| readonly referencedColumns: readonly string[]; | ||
| readonly referencedSchema?: string; | ||
| readonly name?: string; | ||
| readonly onDelete?: SqlReferentialAction; | ||
| readonly onUpdate?: SqlReferentialAction; | ||
| readonly annotations?: SqlAnnotations; | ||
| readonly resolvedReferencedNamespace?: string; | ||
| /** See {@link SqlForeignKeyIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| constructor(input: SqlForeignKeyIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlForeignKeyIR; | ||
| /** | ||
| * Referential-action comparison with `this` as the expected side, matching | ||
| * the relational walk's `getReferentialActionMismatches`: `noAction` is the | ||
| * database default and equivalent to an undeclared action, and drift is | ||
| * flagged only when the expected side declares a (normalized) action. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-index-ir.d.ts | ||
| /** | ||
| * Every field is a required key. Values that may legitimately be absent | ||
| * (an unnamed `@@index`, the btree→undefined type normalization) are typed | ||
| * `| undefined` instead of optional, so each construction site states the | ||
| * absence explicitly rather than omitting the key silently. Undefined values | ||
| * still produce an instance without the property. | ||
| */ | ||
| interface SqlIndexIRInput { | ||
| readonly columns: readonly string[]; | ||
| readonly unique: boolean; | ||
| readonly name: string | undefined; | ||
| readonly type: string | undefined; | ||
| readonly options: Record<string, unknown> | undefined; | ||
| readonly annotations: SqlAnnotations | undefined; | ||
| /** | ||
| * The index's own column nodes, as root-anchored chains. The derivation | ||
| * stamps them so an index is dropped before the columns it is built on | ||
| * (Postgres auto-drops the index when a covered column goes). Never | ||
| * compared by `isEqualTo`. | ||
| */ | ||
| readonly dependsOn: readonly SchemaNodeRef[] | undefined; | ||
| /** | ||
| * Whether the index is partial (has a row predicate). Required: every | ||
| * producer must assert partiality explicitly, because a partial unique | ||
| * index does not guarantee at-most-one row per key and so cannot back a | ||
| * 1:1 relation — "unknown" must not silently default to "total". Never | ||
| * compared by `isEqualTo` and never serialized: differ/verify semantics | ||
| * are unchanged, and surfacing partial-index drift is out of scope. | ||
| */ | ||
| readonly partial: boolean; | ||
| } | ||
| /** | ||
| * Schema IR node for a secondary index as observed by introspection. | ||
| * Unlike the Contract IR `Index`, the Schema IR carries an explicit | ||
| * `unique` field — introspection sees the underlying index regardless | ||
| * of whether the user expressed it as `@@index` or `@@unique`, and the | ||
| * verifier needs to distinguish them when comparing to the Contract. | ||
| * | ||
| * Implements `DiffableNode` so an index is directly a table's diff-tree | ||
| * child. Indexes are frequently unnamed, so `id` is derived from the column | ||
| * tuple — the same tuple that makes two indexes the same index, so it | ||
| * doubles as the pairing key. `isEqualTo` is symmetric structural equality | ||
| * on the remaining attributes: `unique`, `type`, and `options`. A unique | ||
| * index and a non-unique index on the same columns are different objects | ||
| * and are not equal — there is no "stronger satisfies weaker". | ||
| * | ||
| * Deliberately column-tuple-only (not `unique`): a contract `@@index` | ||
| * (non-unique) must still pair against a live unique index of the same | ||
| * columns so the differ can report the mismatch as an incompatible index | ||
| * change rather than a spurious missing+extra pair — see | ||
| * `planner.unique-index-structural.test.ts`. The corollary is that two | ||
| * indexes legitimately coexisting on one table with the *same* column tuple | ||
| * (e.g. a unique index and a redundant plain index Postgres has no problem | ||
| * hosting side by side) collide on this id; the postgres control adapter's | ||
| * introspection keeps only one such index per table+column-tuple rather | ||
| * than handing the differ two same-tree siblings it cannot represent. | ||
| */ | ||
| declare class SqlIndexIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-index"; | ||
| readonly columns: readonly string[]; | ||
| readonly unique: boolean; | ||
| readonly name?: string; | ||
| readonly type?: string; | ||
| readonly options?: Record<string, unknown>; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** See {@link SqlIndexIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| /** See {@link SqlIndexIRInput.partial}. Non-enumerable so it stays out of JSON and structural equality, matching `dependsOn`. */ | ||
| readonly partial: boolean; | ||
| constructor(input: SqlIndexIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlIndexIR; | ||
| /** | ||
| * Symmetric structural equality: two paired index nodes are equal iff their | ||
| * `unique` flag, `type`, and (loosely-compared) `options` all match. There | ||
| * is no satisfaction — a unique index does not equal a non-unique index. | ||
| * `options` compares loosely (introspection stringifies reloptions); `type` | ||
| * compares strictly after the introspection-side btree→undefined | ||
| * normalization done at construction. | ||
| */ | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-unique-ir.d.ts | ||
| interface SqlUniqueIRInput { | ||
| readonly columns: readonly string[]; | ||
| readonly name?: string; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** | ||
| * The constraint's own column nodes, as root-anchored chains. The | ||
| * derivation stamps them so a unique constraint is dropped before the | ||
| * columns it is built on. Never compared by `isEqualTo`. | ||
| */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| } | ||
| /** | ||
| * Schema IR node for a table-level unique constraint as observed by | ||
| * introspection. | ||
| * | ||
| * Implements `DiffableNode` so a unique constraint is directly a table's | ||
| * diff-tree child. Unique constraints are frequently unnamed, so `id` is | ||
| * derived from the column tuple rather than `name` — the column tuple is | ||
| * also what makes two unique constraints the same constraint, so it doubles | ||
| * as the pairing key. There are no further attributes to compare once | ||
| * columns are equal (the differ pairs on `id`), so `isEqualTo` is identity. | ||
| */ | ||
| declare class SqlUniqueIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-unique"; | ||
| readonly columns: readonly string[]; | ||
| readonly name?: string; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** See {@link SqlUniqueIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */ | ||
| readonly dependsOn?: readonly SchemaNodeRef[]; | ||
| constructor(input: SqlUniqueIRInput); | ||
| get id(): string; | ||
| children(): readonly DiffableNode[]; | ||
| static is(node: SqlSchemaIRNode): node is SqlUniqueIR; | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-table-ir.d.ts | ||
| interface SqlTableIRInput { | ||
| readonly name: string; | ||
| readonly columns: Record<string, SqlColumnIR | SqlColumnIRInput>; | ||
| readonly foreignKeys: ReadonlyArray<SqlForeignKeyIR | SqlForeignKeyIRInput>; | ||
| readonly uniques: ReadonlyArray<SqlUniqueIR | SqlUniqueIRInput>; | ||
| readonly indexes: ReadonlyArray<SqlIndexIR | SqlIndexIRInput>; | ||
| readonly primaryKey?: PrimaryKey | PrimaryKeyInput; | ||
| readonly annotations?: SqlAnnotations; | ||
| /** Optional check constraints for enum-restricted columns. Omitted when none present. */ | ||
| readonly checks?: ReadonlyArray<SqlCheckConstraintIR | SqlCheckConstraintIRInput>; | ||
| } | ||
| /** | ||
| * Schema IR node for a single table as observed by introspection. | ||
| * | ||
| * Unlike the Contract IR `StorageTable`, this carries the table's | ||
| * `name` — introspection queries return tables as arrays and the | ||
| * verifier keys them into `SqlSchemaIR.tables` afterwards, so the name | ||
| * stays on the table object for downstream call sites that walk | ||
| * `Object.values(schema.tables)`. | ||
| * | ||
| * The constructor normalises nested IR-class fields so downstream | ||
| * walks see a uniform AST regardless of whether the input was a | ||
| * plain-data literal (from introspection) or already-constructed | ||
| * class instances. | ||
| * | ||
| * Implements `DiffableNode` so a flat (single-schema) tree is directly | ||
| * diffable: `id` is the table name; `isEqualTo` is identity (the table's | ||
| * structural drift is entirely expressed by its children); `children()` | ||
| * yields every column, the primary key (when present), every foreign key, | ||
| * unique, index, and check constraint — the same composition and order as | ||
| * the Postgres table node, minus policies. | ||
| */ | ||
| declare class SqlTableIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-table"; | ||
| readonly name: string; | ||
| readonly columns: Readonly<Record<string, SqlColumnIR>>; | ||
| readonly foreignKeys: ReadonlyArray<SqlForeignKeyIR>; | ||
| readonly uniques: ReadonlyArray<SqlUniqueIR>; | ||
| readonly indexes: ReadonlyArray<SqlIndexIR>; | ||
| readonly primaryKey?: PrimaryKey; | ||
| readonly annotations?: SqlAnnotations; | ||
| readonly checks?: ReadonlyArray<SqlCheckConstraintIR>; | ||
| constructor(input: SqlTableIRInput); | ||
| get id(): string; | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| children(): readonly DiffableNode[]; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-schema-ir.d.ts | ||
| interface SqlSchemaIRInput { | ||
| readonly tables: Record<string, SqlTableIR | SqlTableIRInput>; | ||
| readonly annotations?: SqlAnnotations; | ||
| } | ||
| /** | ||
| * Root Schema IR node representing the complete database schema as | ||
| * observed by introspection. Target-agnostic; used by both verifiers | ||
| * (compare against intended Contract storage) and migration planners | ||
| * (derive operations needed to reconcile). | ||
| * | ||
| * The constructor normalises nested `SqlTableIR` instances so | ||
| * downstream walks see a uniform AST regardless of whether the input | ||
| * was a plain-data literal or already-constructed class instances. | ||
| * | ||
| * Implements `DiffableNode` as the root of a flat (single-schema) diff | ||
| * tree: `id` is the fixed sentinel `'database'` (roots have no siblings), | ||
| * `isEqualTo` is identity (a container has no own attributes), and | ||
| * `children()` yields the table nodes. | ||
| */ | ||
| declare class SqlSchemaIR extends SqlSchemaIRNode implements DiffableNode { | ||
| readonly nodeKind: "sql-schema"; | ||
| readonly tables: Readonly<Record<string, SqlTableIR>>; | ||
| readonly annotations?: SqlAnnotations; | ||
| constructor(input: SqlSchemaIRInput); | ||
| get id(): string; | ||
| isEqualTo(other: DiffableNode): boolean; | ||
| children(): readonly DiffableNode[]; | ||
| } | ||
| //#endregion | ||
| //#region src/types.d.ts | ||
| /** | ||
| * SQL type metadata for control-plane and execution-plane type | ||
| * availability and mapping. Read-only view of type information | ||
| * without encode/decode behavior. | ||
| */ | ||
| interface SqlTypeMetadata { | ||
| /** Namespaced type identifier, e.g. `pg/int4@1`, `pg/text@1`. */ | ||
| readonly typeId: string; | ||
| /** Contract scalar type IDs this type can handle. */ | ||
| readonly targetTypes: readonly string[]; | ||
| /** | ||
| * Native database type name (target-specific). Optional because | ||
| * not all types have a native database representation. | ||
| */ | ||
| readonly nativeType?: string; | ||
| } | ||
| /** | ||
| * Registry interface for SQL type metadata. Provides read-only | ||
| * iteration over type metadata entries. | ||
| */ | ||
| interface SqlTypeMetadataRegistry { | ||
| values(): IterableIterator<SqlTypeMetadata>; | ||
| } | ||
| //#endregion | ||
| export { relationalNodeGranularity as C, assertNode as D, SqlSchemaIRNode as E, defineNonEnumerable as O, relationalNodeEntityKind as S, PrimaryKeyInput as T, SqlColumnDefaultIR as _, SqlTableIR as a, SqlCheckConstraintIRInput as b, SqlUniqueIRInput as c, SqlForeignKeyIR as d, SqlForeignKeyIRInput as f, SqlColumnIRInput as g, SqlColumnIR as h, SqlSchemaIRInput as i, SqlIndexIR as l, SqlAnnotations as m, SqlTypeMetadataRegistry as n, SqlTableIRInput as o, SqlReferentialAction as p, SqlSchemaIR as r, SqlUniqueIR as s, SqlTypeMetadata as t, SqlIndexIRInput as u, SqlColumnDefaultIRInput as v, PrimaryKey as w, RelationalSchemaNodeKind as x, SqlCheckConstraintIR as y }; | ||
| //# sourceMappingURL=types-BwxXnNr-.d.mts.map |
| {"version":3,"file":"types-BwxXnNr-.d.mts","names":[],"sources":["../src/ir/sql-schema-ir-node.ts","../src/ir/primary-key.ts","../src/ir/schema-node-kinds.ts","../src/ir/sql-check-constraint-ir.ts","../src/ir/sql-column-default-ir.ts","../src/ir/sql-column-ir.ts","../src/ir/sql-foreign-key-ir.ts","../src/ir/sql-index-ir.ts","../src/ir/sql-unique-ir.ts","../src/ir/sql-table-ir.ts","../src/ir/sql-schema-ir.ts","../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;uBAuBsB,wBAAwB;WAC3B;;;;;;;;;oBAUC;;;;;;;;;iBAmBJ,WAAW,UAAU,iBACnC,MAAM,6BACN,mBACA,YAAY,MAAM,oBAAoB,QAAQ,YACrC,QAAQ;;;;;;;;;;iBAeH,oBAAoB,kBAClC,QAAQ,GACR,aACA;;;UCrEe;WACN;WACA;;;;;;WAMA,qBAAqB;;;;;;;;;;;;;;;cAgBnB,mBAAmB,2BAA2B;WACvC;WAET;WACQ;;WAEA,qBAAqB;cAE1B,OAAO;MAQf;EAIJ,qBAAqB;SAId,GAAG,MAAM,kBAAkB,QAAQ;EAI1C,UAAU,OAAO;;;;;;;;;;;cCjDN;;;;;;;;;;;KAYD,mCACF,uCAAuC;;;;;;;iBAoCjC,0BAA0B,mBAAmB;;;;;;;;iBAyB7C,yBAAyB;;;UC7ExB;;WAEN;;WAEA;;WAEA;;;;;;;;;;;;;;cAeE,6BAA6B,2BAA2B;WACjD;WAET;WACA;WACA;cAEG,OAAO;MAQf;EAIJ,qBAAqB;SAId,GAAG,MAAM,kBAAkB,QAAQ;;;;;;;;EAW1C,UAAU,OAAO;;;;UCrDF;;WAEN,WAAW;;WAEX;;;;;WAKA;;;;;;;WAOA;;;;;;;;;;;;;;;cAgBE,2BAA2B,2BAA2B;WAC/C;WAED,WAAW;WACX;WACA;;WAEA;cAEL,OAAO;MASf;EAIJ,qBAAqB;SAId,GAAG,MAAM,kBAAkB,QAAQ;;;;;;;;EAW1C,UAAU,OAAO;;;;;;;;KC/DP;YACA;;UAGK;WACN;WACA;WACA;;WAEA;WACA,cAAc;;WAEd;;;;;;;;;WASA;;;;;;;;WAQA,kBAAkB;;;;;;;;;;;;;;WAclB,WAAW;;;;;;;;;WASX;;;;;;;;WAQA;;;;;;;;;;;;;;;;;;cAmBE,oBAAoB,2BAA2B;WACxC;WAET;WACA;WACA;WACQ;WACA,cAAc;;WAEd;WACA;WACA,kBAAkB;;WAElB,WAAW;;WAEX;;WAEA;cAEL,OAAO;MAgBf;;;;;;;EAUJ,qBAAqB;SAoBd,GAAG,MAAM,kBAAkB,QAAQ;;;;;;;EAU1C,UAAU,OAAO;;;;KCjKP;UAEK;WACN;WACA;WACA;;WAEA;WACA;WACA,WAAW;WACX,WAAW;WACX,cAAc;;;;;;;;;WASd;;;;;;;WAOA,qBAAqB;;;;;;;;;;;;;;;;;;;cAoBnB,wBAAwB,2BAA2B;WAC5C;WAET;WACA;WACA;WACQ;WACA;WACA,WAAW;WACX,WAAW;WACX,cAAc;WACd;;WAEA,qBAAqB;cAE1B,OAAO;MAkBf;EAKJ,qBAAqB;SAId,GAAG,MAAM,kBAAkB,QAAQ;;;;;;;EAU1C,UAAU,OAAO;;;;;;;;;;;UC5FF;WACN;WACA;WACA;WACA;WACA,SAAS;WACT,aAAa;;;;;;;WAOb,oBAAoB;;;;;;;;;WASpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6BE,mBAAmB,2BAA2B;WACvC;WAET;WACA;WACQ;WACA;WACA,UAAU;WACV,cAAc;;WAEd,qBAAqB;;WAErB;cAEL,OAAO;MAaf;EAIJ,qBAAqB;SAId,GAAG,MAAM,kBAAkB,QAAQ;;;;;;;;;EAY1C,UAAU,OAAO;;;;UCzGF;WACN;WACA;WACA,cAAc;;;;;;WAMd,qBAAqB;;;;;;;;;;;;;cAcnB,oBAAoB,2BAA2B;WACxC;WAET;WACQ;WACA,cAAc;;WAEd,qBAAqB;cAE1B,OAAO;MASf;EAIJ,qBAAqB;SAId,GAAG,MAAM,kBAAkB,QAAQ;EAI1C,UAAU,OAAO;;;;UCjDF;WACN;WACA,SAAS,eAAe,cAAc;WACtC,aAAa,cAAc,kBAAkB;WAC7C,SAAS,cAAc,cAAc;WACrC,SAAS,cAAc,aAAa;WACpC,aAAa,aAAa;WAC1B,cAAc;;WAEd,SAAS,cAAc,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;cAwB5C,mBAAmB,2BAA2B;WACvC;WAET;WACA,SAAS,SAAS,eAAe;WACjC,aAAa,cAAc;WAC3B,SAAS,cAAc;WACvB,SAAS,cAAc;WACf,aAAa;WACb,cAAc;WACd,SAAS,cAAc;cAE5B,OAAO;MAqCf;EAIJ,UAAU,OAAO;EAIjB,qBAAqB;;;;UC9FN;WACN,QAAQ,eAAe,aAAa;WACpC,cAAc;;;;;;;;;;;;;;;;;cAkBZ,oBAAoB,2BAA2B;WACxC;WAET,QAAQ,SAAS,eAAe;WACxB,cAAc;cAEnB,OAAO;MAcf;EAIJ,UAAU,OAAO;EAIjB,qBAAqB;;;;;;;;;UCTN;;WAEN;;WAGA;;;;;WAMA;;;;;;UAOM;EACf,UAAU,iBAAiB"} |
| import { IRNodeBase, freezeNode } from "@prisma-next/framework-components/ir"; | ||
| import { blindCast } from "@prisma-next/utils/casts"; | ||
| import { canonicalStringify } from "@prisma-next/utils/canonical-stringify"; | ||
| import { ifDefined } from "@prisma-next/utils/defined"; | ||
| //#region src/ir/schema-node-kinds.ts | ||
| /** | ||
| * The `nodeKind` discriminant for each relational schema-diff leaf node. | ||
| * Each node carries a unique value; the differ pairs siblings by `id`, and | ||
| * these kinds distinguish the node types that appear as `PostgresTableSchemaNode` | ||
| * children (columns, primary key, foreign keys, uniques, indexes, checks) from | ||
| * each other and from the RLS policy/role kinds a target defines separately. | ||
| */ | ||
| const RelationalSchemaNodeKind = { | ||
| schema: "sql-schema", | ||
| table: "sql-table", | ||
| column: "sql-column", | ||
| columnDefault: "sql-column-default", | ||
| primaryKey: "sql-primary-key", | ||
| foreignKey: "sql-foreign-key", | ||
| unique: "sql-unique", | ||
| index: "sql-index", | ||
| check: "sql-check-constraint" | ||
| }; | ||
| /** | ||
| * The one real map from a relational `nodeKind` to the framework-neutral | ||
| * {@link DiffSubjectGranularity} its diff issues carry — the SQL family's | ||
| * post-diff filters (issue category, strict-mode gating) and the framework | ||
| * aggregate's unclaimed-elements sweep key on the granularity, never on the | ||
| * `nodeKind` spelling and never on anything stamped on the node. Resolution | ||
| * is by `nodeKind` equality against this map, not a suffix string match. | ||
| * Target-specific node kinds (Postgres namespace/table/policy/role) are | ||
| * outside this family layer's vocabulary and map their own kinds directly. | ||
| */ | ||
| const RELATIONAL_NODE_GRANULARITY = { | ||
| [RelationalSchemaNodeKind.schema]: "structural", | ||
| [RelationalSchemaNodeKind.table]: "entity", | ||
| [RelationalSchemaNodeKind.column]: "field", | ||
| [RelationalSchemaNodeKind.columnDefault]: "auxiliary", | ||
| [RelationalSchemaNodeKind.primaryKey]: "auxiliary", | ||
| [RelationalSchemaNodeKind.foreignKey]: "auxiliary", | ||
| [RelationalSchemaNodeKind.unique]: "auxiliary", | ||
| [RelationalSchemaNodeKind.index]: "auxiliary", | ||
| [RelationalSchemaNodeKind.check]: "auxiliary" | ||
| }; | ||
| function isRelationalSchemaNodeKind(nodeKind) { | ||
| return Object.hasOwn(RELATIONAL_NODE_GRANULARITY, nodeKind); | ||
| } | ||
| /** | ||
| * Looks up the subject granularity for a relational `nodeKind`. Throws for a | ||
| * `nodeKind` outside this map (a target-specific kind, e.g. Postgres's | ||
| * namespace/table/policy/role) — those map their own kinds directly rather | ||
| * than through this family-level map. | ||
| */ | ||
| function relationalNodeGranularity(nodeKind) { | ||
| if (!isRelationalSchemaNodeKind(nodeKind)) throw new Error(`relationalNodeGranularity: unrecognized relational node kind "${nodeKind}"`); | ||
| return RELATIONAL_NODE_GRANULARITY[nodeKind]; | ||
| } | ||
| /** | ||
| * The one real map from a relational `nodeKind` to its storage `entityKind` | ||
| * — the same vocabulary the contract storage's `entries` dictionary keys use | ||
| * (`elementCoordinates` walks it). Only the whole-table kind has an entity of | ||
| * its own; every other relational node (a column, an index, …) is nested | ||
| * under one and maps to nothing here. | ||
| */ | ||
| const RELATIONAL_NODE_ENTITY_KIND = { [RelationalSchemaNodeKind.table]: "table" }; | ||
| /** | ||
| * Looks up the storage `entityKind` for a relational `nodeKind` — sibling of | ||
| * {@link relationalNodeGranularity}, resolved by `nodeKind` equality against | ||
| * {@link RELATIONAL_NODE_ENTITY_KIND}. `undefined` for a target-specific kind | ||
| * (targets map their own kinds directly) or a relational kind with no entity | ||
| * of its own. | ||
| */ | ||
| function relationalNodeEntityKind(nodeKind) { | ||
| return isRelationalSchemaNodeKind(nodeKind) ? RELATIONAL_NODE_ENTITY_KIND[nodeKind] : void 0; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-schema-ir-node.ts | ||
| /** | ||
| * SQL Schema IR node base. Carries the family-level | ||
| * `kind = 'sql-schema-ir'` discriminator and inherits the framework's | ||
| * `freezeNode` affordance. | ||
| * | ||
| * SQL Schema IR represents the actual database state as discovered by | ||
| * introspection (the parallel to SQL Contract IR, which represents the | ||
| * desired state). | ||
| * | ||
| * The discriminator is installed as a non-enumerable own property, | ||
| * matching the SqlNode pattern. This keeps `JSON.stringify(node)` | ||
| * canonical (no `kind` field), keeps `toEqual({...})` test assertions | ||
| * against pre-lift flat shapes passing, and keeps `node.kind` readable | ||
| * for dispatch. | ||
| * | ||
| * Both `kind` and `nodeKind` are required: every concrete leaf is a node | ||
| * the generic differ can pair and compare, so every leaf must declare which | ||
| * node it is. `nodeKind` has no default here — every direct subclass sets its | ||
| * own literal value (the relational leaves via `RelationalSchemaNodeKind`, | ||
| * target concretions via their own vocabulary, e.g. `PostgresSchemaNodeKind`). | ||
| */ | ||
| var SqlSchemaIRNode = class extends IRNodeBase { | ||
| constructor() { | ||
| super(); | ||
| Object.defineProperty(this, "kind", { | ||
| value: "sql-schema-ir", | ||
| writable: false, | ||
| enumerable: false, | ||
| configurable: false | ||
| }); | ||
| } | ||
| }; | ||
| /** | ||
| * Asserts `node` matches `predicate`, narrowing its type to `T`. The one | ||
| * shared implementation every node class's `static assert` and `isEqualTo` | ||
| * reach for, instead of each hand-writing its own throw: the message names | ||
| * the class the caller expected. | ||
| */ | ||
| function assertNode(node, className, predicate) { | ||
| if (node === void 0 || !predicate(node)) throw new Error(`Expected a ${className} but got nodeKind=${node?.nodeKind ?? "undefined"}`); | ||
| } | ||
| /** | ||
| * Defines a non-enumerable own property, the same treatment `kind` gets | ||
| * above: a derivation-time render-support field stays out of | ||
| * `JSON.stringify`, `toEqual({...})` structural assertions, and spreads, | ||
| * while remaining directly readable (`node.field`) for the one consumer | ||
| * that resolves it at plan time. A no-op when `value` is `undefined` — the | ||
| * property is simply absent, matching every other optional field on these | ||
| * nodes. | ||
| */ | ||
| function defineNonEnumerable(target, key, value) { | ||
| if (value === void 0) return; | ||
| Object.defineProperty(target, key, { | ||
| value, | ||
| enumerable: false, | ||
| writable: false, | ||
| configurable: false | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/ir/primary-key.ts | ||
| /** | ||
| * Primary-key Schema IR node. Mirrors the Contract IR `PrimaryKey` | ||
| * shape (same `columns` + optional `name`) so verification can compare | ||
| * intent and actual structurally. Defined here independently to avoid | ||
| * a sql-schema-ir -> sql-contract dependency. | ||
| * | ||
| * Implements `DiffableNode` so a primary key is directly a table's diff-tree | ||
| * child. `id` is a fixed sentinel — a table has at most one primary key, so | ||
| * there is never a sibling to disambiguate against. `isEqualTo` compares the | ||
| * column tuple; the PK's own `name` is a database-assigned label with no | ||
| * semantic weight, so it is not compared (mirrors the policy node's | ||
| * name-insensitivity to non-identifying detail). | ||
| */ | ||
| var PrimaryKey = class PrimaryKey extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.primaryKey; | ||
| columns; | ||
| constructor(input) { | ||
| super(); | ||
| this.columns = input.columns; | ||
| if (input.name !== void 0) this.name = input.name; | ||
| defineNonEnumerable(this, "dependsOn", input.dependsOn); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return "primary-key"; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.primaryKey; | ||
| } | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "PrimaryKey", PrimaryKey.is); | ||
| return this.columns.length === node.columns.length && this.columns.every((c, i) => c === node.columns[i]); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-check-constraint-ir.ts | ||
| /** | ||
| * Schema IR node for a table-level check constraint that restricts a | ||
| * column to a set of permitted values (an enum-style `IN (...)` check). | ||
| * | ||
| * Carries the **resolved values** rather than a raw SQL predicate so | ||
| * callers can compare value-sets without parsing SQL. | ||
| * | ||
| * Implements `DiffableNode` so a check constraint is directly a table's | ||
| * diff-tree child: `id` is the constraint name. `isEqualTo` compares | ||
| * `column` and the permitted-value set (order-insensitive — the database | ||
| * does not guarantee `IN (...)` ordering). | ||
| */ | ||
| var SqlCheckConstraintIR = class SqlCheckConstraintIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.check; | ||
| name; | ||
| column; | ||
| permittedValues; | ||
| constructor(input) { | ||
| super(); | ||
| this.name = input.name; | ||
| this.column = input.column; | ||
| this.permittedValues = Object.freeze([...input.permittedValues]); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return `check:${this.name}`; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.check; | ||
| } | ||
| /** | ||
| * Compares the permitted-value sets only (unordered), matching the | ||
| * relational walk's `verifyCheckConstraints`: two checks pairing by name | ||
| * compare their value sets — the `column` field is descriptive, not part | ||
| * of the comparison, so a name-paired check with equal values verifies | ||
| * regardless of which column carries it. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlCheckConstraintIR", SqlCheckConstraintIR.is); | ||
| const thisValues = new Set(this.permittedValues); | ||
| const otherValues = new Set(node.permittedValues); | ||
| if (thisValues.size !== otherValues.size) return false; | ||
| return [...thisValues].every((v) => otherValues.has(v)); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/resolved-default-equality.ts | ||
| /** | ||
| * Structural equality for two resolved column defaults, ported from the | ||
| * relational walk's `columnDefaultsEqual` normalized branch: kinds must | ||
| * match; literal values are normalized (Date and temporal-typed strings to | ||
| * ISO instants) then compared canonically (JSON objects match their | ||
| * canonical string form); function expressions compare case- and | ||
| * whitespace-insensitively. | ||
| * | ||
| * `nativeType` provides the temporal-normalization context (the actual | ||
| * side's resolved native type in a diff comparison). | ||
| */ | ||
| function resolvedDefaultsEqual(expected, actual, nativeType) { | ||
| if (expected.kind !== actual.kind) return false; | ||
| if (expected.kind === "literal" && actual.kind === "literal") return literalValuesEqual(normalizeLiteralValue(expected.value, nativeType), normalizeLiteralValue(actual.value, nativeType)); | ||
| if (expected.kind === "function" && actual.kind === "function") return normalizeFunctionExpression(expected.expression) === normalizeFunctionExpression(actual.expression); | ||
| return false; | ||
| } | ||
| function normalizeFunctionExpression(expression) { | ||
| return expression.toLowerCase().replace(/\s+/g, ""); | ||
| } | ||
| function isTemporalNativeType(nativeType) { | ||
| if (!nativeType) return false; | ||
| const normalized = nativeType.toLowerCase(); | ||
| return normalized.includes("timestamp") || normalized === "date"; | ||
| } | ||
| function normalizeLiteralValue(value, nativeType) { | ||
| if (value instanceof Date) return value.toISOString(); | ||
| if (typeof value === "string" && isTemporalNativeType(nativeType)) { | ||
| const parsed = new Date(value); | ||
| if (!Number.isNaN(parsed.getTime())) return parsed.toISOString(); | ||
| } | ||
| return value; | ||
| } | ||
| function literalValuesEqual(a, b) { | ||
| if (a === b) return true; | ||
| if (typeof a === "object" && a !== null && typeof b === "object" && b !== null) return canonicalStringify(a) === canonicalStringify(b); | ||
| if (typeof a === "object" && a !== null && typeof b === "string") try { | ||
| return canonicalStringify(a) === canonicalStringify(JSON.parse(b)); | ||
| } catch { | ||
| return false; | ||
| } | ||
| if (typeof a === "string" && typeof b === "object" && b !== null) try { | ||
| return canonicalStringify(JSON.parse(a)) === canonicalStringify(b); | ||
| } catch { | ||
| return false; | ||
| } | ||
| return false; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-column-default-ir.ts | ||
| /** | ||
| * Schema-diff node for a column's default value. The default is the one | ||
| * column attribute with an extra/missing/drift lifecycle of its own, so it | ||
| * is a child node of the column rather than a compared attribute: an | ||
| * undeclared live default surfaces as `not-expected`, a declared default the | ||
| * database lacks as `not-found`, and a divergent value as `not-equal` — the | ||
| * three reasons cover the legacy `extra_default` / `default_missing` / | ||
| * `default_mismatch` vocabulary with no attribute inspection. | ||
| * | ||
| * `id` is a fixed sentinel — a column has at most one default. Built | ||
| * transiently by `SqlColumnIR.children()` from the column's own fields; | ||
| * never constructed by derivation or introspection directly. | ||
| */ | ||
| var SqlColumnDefaultIR = class SqlColumnDefaultIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.columnDefault; | ||
| constructor(input) { | ||
| super(); | ||
| if (input.resolved !== void 0) this.resolved = input.resolved; | ||
| if (input.raw !== void 0) this.raw = input.raw; | ||
| if (input.nativeTypeContext !== void 0) this.nativeTypeContext = input.nativeTypeContext; | ||
| defineNonEnumerable(this, "many", input.many); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return "default"; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.columnDefault; | ||
| } | ||
| /** | ||
| * Structured comparison with `this` as the expected side: both sides | ||
| * resolved compare per the relational walk's `columnDefaultsEqual` | ||
| * semantics; a declared expected default against an unparseable actual | ||
| * (raw present, no resolved parse) is a mismatch; two raw-only nodes fall | ||
| * back to raw string equality. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlColumnDefaultIR", SqlColumnDefaultIR.is); | ||
| if (this.resolved !== void 0 && node.resolved !== void 0) return resolvedDefaultsEqual(this.resolved, node.resolved, node.nativeTypeContext ?? this.nativeTypeContext); | ||
| if (this.resolved !== void 0 || node.resolved !== void 0) return false; | ||
| return this.raw === node.raw; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-column-ir.ts | ||
| /** | ||
| * Schema IR node for a single column on a table, as observed by | ||
| * introspection. Unlike the Contract IR `StorageColumn`, this carries | ||
| * the column's `name` (Schema IR columns are returned as arrays from | ||
| * introspection queries; the parent table re-keys them into a record | ||
| * for downstream consumers). | ||
| * | ||
| * Implements `DiffableNode` so a column is directly a table's diff-tree | ||
| * child: `id` is the column name (unique among a table's columns); `isEqualTo` | ||
| * compares this column's own attributes only — never children, since a | ||
| * column is a leaf. When both sides carry `resolvedNativeType` (stamped at | ||
| * derivation/introspection), the comparison uses the resolved values — | ||
| * resolved native type, nullability, and structured default equality per | ||
| * the relational walk's `columnDefaultsEqual` semantics, with `this` as the | ||
| * expected side. Otherwise it falls back to comparing raw fields. | ||
| */ | ||
| var SqlColumnIR = class SqlColumnIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.column; | ||
| name; | ||
| nativeType; | ||
| nullable; | ||
| constructor(input) { | ||
| super(); | ||
| this.name = input.name; | ||
| this.nativeType = input.nativeType; | ||
| this.nullable = input.nullable; | ||
| if (input.default !== void 0) this.default = input.default; | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| if (input.many !== void 0) this.many = input.many; | ||
| if (input.resolvedNativeType !== void 0) this.resolvedNativeType = input.resolvedNativeType; | ||
| if (input.resolvedDefault !== void 0) this.resolvedDefault = input.resolvedDefault; | ||
| defineNonEnumerable(this, "codecRef", input.codecRef); | ||
| defineNonEnumerable(this, "codecBaseNativeType", input.codecBaseNativeType); | ||
| defineNonEnumerable(this, "codecNamedType", input.codecNamedType); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return `column:${this.name}`; | ||
| } | ||
| /** | ||
| * The column's default, when declared/present, is the column's one child | ||
| * node — it has an extra/missing/drift lifecycle of its own, so the differ | ||
| * recurses to it rather than `isEqualTo` comparing it. Built transiently | ||
| * from this column's own fields. | ||
| */ | ||
| children() { | ||
| if (this.resolvedDefault === void 0 && this.default === void 0) return []; | ||
| return [new SqlColumnDefaultIR({ | ||
| ...ifDefined("resolved", this.resolvedDefault), | ||
| ...ifDefined("raw", this.default), | ||
| ...ifDefined("nativeTypeContext", this.resolvedNativeType), | ||
| ...ifDefined("many", this.many ?? this.codecRef?.many) | ||
| })]; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.column; | ||
| } | ||
| /** | ||
| * Compares the column's own attributes only — the default lives on the | ||
| * child node. When both sides carry `resolvedNativeType`, the resolved | ||
| * value governs (array-ness rides on its `[]` suffix); otherwise raw | ||
| * fields compare. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlColumnIR", SqlColumnIR.is); | ||
| if (this.resolvedNativeType !== void 0 && node.resolvedNativeType !== void 0) return this.resolvedNativeType === node.resolvedNativeType && this.nullable === node.nullable; | ||
| return this.nativeType === node.nativeType && this.nullable === node.nullable && Boolean(this.many) === Boolean(node.many); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-foreign-key-ir.ts | ||
| /** | ||
| * Schema IR node for a foreign-key constraint as observed by | ||
| * introspection. The `referencedTable` / `referencedColumns` field | ||
| * names match the introspection vocabulary (`pg_constraint.confkey`, | ||
| * etc.) and intentionally differ from the Contract IR's nested | ||
| * `references: { table, columns }` shape so that the verifier's | ||
| * structural comparison stays explicit about which side it's reading. | ||
| * | ||
| * Implements `DiffableNode` so a foreign key is directly a table's diff-tree | ||
| * child. Foreign keys are frequently unnamed (introspection may not carry a | ||
| * constraint name, and the contract side never invents one), so `id` is | ||
| * derived from the referencing/referenced coordinates rather than `name` — | ||
| * the same tuple that makes two FK constraints the same constraint. This | ||
| * also serves as the comparison key: two FKs with the same coordinates are | ||
| * paired by the differ, and `isEqualTo` then compares the remaining | ||
| * attribute — the referential actions. | ||
| */ | ||
| var SqlForeignKeyIR = class SqlForeignKeyIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.foreignKey; | ||
| columns; | ||
| referencedTable; | ||
| referencedColumns; | ||
| constructor(input) { | ||
| super(); | ||
| this.columns = input.columns; | ||
| this.referencedTable = input.referencedTable; | ||
| this.referencedColumns = input.referencedColumns; | ||
| if (input.referencedSchema !== void 0) this.referencedSchema = input.referencedSchema; | ||
| if (input.name !== void 0) this.name = input.name; | ||
| if (input.onDelete !== void 0) this.onDelete = input.onDelete; | ||
| if (input.onUpdate !== void 0) this.onUpdate = input.onUpdate; | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| const resolvedReferencedNamespace = input.resolvedReferencedNamespace ?? input.referencedSchema; | ||
| if (resolvedReferencedNamespace !== void 0) this.resolvedReferencedNamespace = resolvedReferencedNamespace; | ||
| defineNonEnumerable(this, "dependsOn", input.dependsOn); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| const referencedNamespace = this.resolvedReferencedNamespace ?? ""; | ||
| return `foreign-key:${this.columns.join(",")}->${referencedNamespace}.${this.referencedTable}(${this.referencedColumns.join(",")})`; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.foreignKey; | ||
| } | ||
| /** | ||
| * Referential-action comparison with `this` as the expected side, matching | ||
| * the relational walk's `getReferentialActionMismatches`: `noAction` is the | ||
| * database default and equivalent to an undeclared action, and drift is | ||
| * flagged only when the expected side declares a (normalized) action. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlForeignKeyIR", SqlForeignKeyIR.is); | ||
| return referentialActionMatches(this.onDelete, node.onDelete) && referentialActionMatches(this.onUpdate, node.onUpdate); | ||
| } | ||
| }; | ||
| function referentialActionMatches(expected, actual) { | ||
| const normalizedExpected = expected === "noAction" ? void 0 : expected; | ||
| if (normalizedExpected === void 0) return true; | ||
| return normalizedExpected === (actual === "noAction" ? void 0 : actual); | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-index-ir.ts | ||
| /** | ||
| * Schema IR node for a secondary index as observed by introspection. | ||
| * Unlike the Contract IR `Index`, the Schema IR carries an explicit | ||
| * `unique` field — introspection sees the underlying index regardless | ||
| * of whether the user expressed it as `@@index` or `@@unique`, and the | ||
| * verifier needs to distinguish them when comparing to the Contract. | ||
| * | ||
| * Implements `DiffableNode` so an index is directly a table's diff-tree | ||
| * child. Indexes are frequently unnamed, so `id` is derived from the column | ||
| * tuple — the same tuple that makes two indexes the same index, so it | ||
| * doubles as the pairing key. `isEqualTo` is symmetric structural equality | ||
| * on the remaining attributes: `unique`, `type`, and `options`. A unique | ||
| * index and a non-unique index on the same columns are different objects | ||
| * and are not equal — there is no "stronger satisfies weaker". | ||
| * | ||
| * Deliberately column-tuple-only (not `unique`): a contract `@@index` | ||
| * (non-unique) must still pair against a live unique index of the same | ||
| * columns so the differ can report the mismatch as an incompatible index | ||
| * change rather than a spurious missing+extra pair — see | ||
| * `planner.unique-index-structural.test.ts`. The corollary is that two | ||
| * indexes legitimately coexisting on one table with the *same* column tuple | ||
| * (e.g. a unique index and a redundant plain index Postgres has no problem | ||
| * hosting side by side) collide on this id; the postgres control adapter's | ||
| * introspection keeps only one such index per table+column-tuple rather | ||
| * than handing the differ two same-tree siblings it cannot represent. | ||
| */ | ||
| var SqlIndexIR = class SqlIndexIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.index; | ||
| columns; | ||
| unique; | ||
| constructor(input) { | ||
| super(); | ||
| this.columns = input.columns; | ||
| this.unique = input.unique; | ||
| if (input.name !== void 0) this.name = input.name; | ||
| if (input.type !== void 0) this.type = input.type; | ||
| if (input.options !== void 0) this.options = input.options; | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| defineNonEnumerable(this, "dependsOn", input.dependsOn); | ||
| defineNonEnumerable(this, "partial", input.partial); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return `index:${this.columns.join(",")}`; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.index; | ||
| } | ||
| /** | ||
| * Symmetric structural equality: two paired index nodes are equal iff their | ||
| * `unique` flag, `type`, and (loosely-compared) `options` all match. There | ||
| * is no satisfaction — a unique index does not equal a non-unique index. | ||
| * `options` compares loosely (introspection stringifies reloptions); `type` | ||
| * compares strictly after the introspection-side btree→undefined | ||
| * normalization done at construction. | ||
| */ | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlIndexIR", SqlIndexIR.is); | ||
| return this.unique === node.unique && this.type === node.type && indexOptionsLooselyEqual(this.options, node.options); | ||
| } | ||
| }; | ||
| /** | ||
| * Option-bag equality ported from the relational walk: same key set, values | ||
| * compared via `String()` coercion — Postgres introspection returns | ||
| * reloptions values as raw strings (`'70'`, `'false'`) while contract option | ||
| * leaves are typed (number, boolean, string). | ||
| */ | ||
| function indexOptionsLooselyEqual(a, b) { | ||
| const aKeys = a ? Object.keys(a).sort() : []; | ||
| const bKeys = b ? Object.keys(b).sort() : []; | ||
| if (aKeys.length !== bKeys.length) return false; | ||
| for (let i = 0; i < aKeys.length; i += 1) if (aKeys[i] !== bKeys[i]) return false; | ||
| if (aKeys.length === 0) return true; | ||
| for (const key of aKeys) if (String(a?.[key]) !== String(b?.[key])) return false; | ||
| return true; | ||
| } | ||
| //#endregion | ||
| //#region src/ir/sql-unique-ir.ts | ||
| /** | ||
| * Schema IR node for a table-level unique constraint as observed by | ||
| * introspection. | ||
| * | ||
| * Implements `DiffableNode` so a unique constraint is directly a table's | ||
| * diff-tree child. Unique constraints are frequently unnamed, so `id` is | ||
| * derived from the column tuple rather than `name` — the column tuple is | ||
| * also what makes two unique constraints the same constraint, so it doubles | ||
| * as the pairing key. There are no further attributes to compare once | ||
| * columns are equal (the differ pairs on `id`), so `isEqualTo` is identity. | ||
| */ | ||
| var SqlUniqueIR = class SqlUniqueIR extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.unique; | ||
| columns; | ||
| constructor(input) { | ||
| super(); | ||
| this.columns = [...input.columns]; | ||
| if (input.name !== void 0) this.name = input.name; | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| defineNonEnumerable(this, "dependsOn", input.dependsOn); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return `unique:${this.columns.join(",")}`; | ||
| } | ||
| children() { | ||
| return []; | ||
| } | ||
| static is(node) { | ||
| return node.nodeKind === RelationalSchemaNodeKind.unique; | ||
| } | ||
| isEqualTo(other) { | ||
| const node = blindCast(other); | ||
| assertNode(node, "SqlUniqueIR", SqlUniqueIR.is); | ||
| return this.id === node.id; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-table-ir.ts | ||
| /** | ||
| * Schema IR node for a single table as observed by introspection. | ||
| * | ||
| * Unlike the Contract IR `StorageTable`, this carries the table's | ||
| * `name` — introspection queries return tables as arrays and the | ||
| * verifier keys them into `SqlSchemaIR.tables` afterwards, so the name | ||
| * stays on the table object for downstream call sites that walk | ||
| * `Object.values(schema.tables)`. | ||
| * | ||
| * The constructor normalises nested IR-class fields so downstream | ||
| * walks see a uniform AST regardless of whether the input was a | ||
| * plain-data literal (from introspection) or already-constructed | ||
| * class instances. | ||
| * | ||
| * Implements `DiffableNode` so a flat (single-schema) tree is directly | ||
| * diffable: `id` is the table name; `isEqualTo` is identity (the table's | ||
| * structural drift is entirely expressed by its children); `children()` | ||
| * yields every column, the primary key (when present), every foreign key, | ||
| * unique, index, and check constraint — the same composition and order as | ||
| * the Postgres table node, minus policies. | ||
| */ | ||
| var SqlTableIR = class extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.table; | ||
| name; | ||
| columns; | ||
| foreignKeys; | ||
| uniques; | ||
| indexes; | ||
| constructor(input) { | ||
| super(); | ||
| this.name = input.name; | ||
| this.columns = Object.freeze(Object.fromEntries(Object.entries(input.columns).map(([key, col]) => [key, col instanceof SqlColumnIR ? col : new SqlColumnIR(col)]))); | ||
| this.foreignKeys = Object.freeze(input.foreignKeys.map((fk) => fk instanceof SqlForeignKeyIR ? fk : new SqlForeignKeyIR(fk))); | ||
| this.uniques = Object.freeze(input.uniques.map((u) => u instanceof SqlUniqueIR ? u : new SqlUniqueIR(u))); | ||
| this.indexes = Object.freeze(input.indexes.map((i) => i instanceof SqlIndexIR ? i : new SqlIndexIR(i))); | ||
| if (input.primaryKey !== void 0) this.primaryKey = input.primaryKey instanceof PrimaryKey ? input.primaryKey : new PrimaryKey(input.primaryKey); | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| if (input.checks !== void 0 && input.checks.length > 0) this.checks = Object.freeze(input.checks.map((c) => c instanceof SqlCheckConstraintIR ? c : new SqlCheckConstraintIR(c))); | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return this.name; | ||
| } | ||
| isEqualTo(other) { | ||
| return this.id === other.id; | ||
| } | ||
| children() { | ||
| return [ | ||
| ...Object.values(this.columns), | ||
| ...this.primaryKey ? [this.primaryKey] : [], | ||
| ...this.foreignKeys, | ||
| ...this.uniques, | ||
| ...this.indexes, | ||
| ...this.checks ?? [] | ||
| ]; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/ir/sql-schema-ir.ts | ||
| /** | ||
| * Root Schema IR node representing the complete database schema as | ||
| * observed by introspection. Target-agnostic; used by both verifiers | ||
| * (compare against intended Contract storage) and migration planners | ||
| * (derive operations needed to reconcile). | ||
| * | ||
| * The constructor normalises nested `SqlTableIR` instances so | ||
| * downstream walks see a uniform AST regardless of whether the input | ||
| * was a plain-data literal or already-constructed class instances. | ||
| * | ||
| * Implements `DiffableNode` as the root of a flat (single-schema) diff | ||
| * tree: `id` is the fixed sentinel `'database'` (roots have no siblings), | ||
| * `isEqualTo` is identity (a container has no own attributes), and | ||
| * `children()` yields the table nodes. | ||
| */ | ||
| var SqlSchemaIR = class extends SqlSchemaIRNode { | ||
| nodeKind = RelationalSchemaNodeKind.schema; | ||
| tables; | ||
| constructor(input) { | ||
| super(); | ||
| this.tables = Object.freeze(Object.fromEntries(Object.entries(input.tables).map(([key, t]) => [key, t instanceof SqlTableIR ? t : new SqlTableIR(t)]))); | ||
| if (input.annotations !== void 0) this.annotations = input.annotations; | ||
| freezeNode(this); | ||
| } | ||
| get id() { | ||
| return "database"; | ||
| } | ||
| isEqualTo(other) { | ||
| return this.id === other.id; | ||
| } | ||
| children() { | ||
| return Object.values(this.tables); | ||
| } | ||
| }; | ||
| //#endregion | ||
| export { SqlForeignKeyIR as a, SqlCheckConstraintIR as c, assertNode as d, defineNonEnumerable as f, relationalNodeGranularity as h, SqlIndexIR as i, PrimaryKey as l, relationalNodeEntityKind as m, SqlTableIR as n, SqlColumnIR as o, RelationalSchemaNodeKind as p, SqlUniqueIR as r, SqlColumnDefaultIR as s, SqlSchemaIR as t, SqlSchemaIRNode as u }; | ||
| //# sourceMappingURL=types-VVvdw9IR.mjs.map |
| {"version":3,"file":"types-VVvdw9IR.mjs","names":[],"sources":["../src/ir/schema-node-kinds.ts","../src/ir/sql-schema-ir-node.ts","../src/ir/primary-key.ts","../src/ir/sql-check-constraint-ir.ts","../src/ir/resolved-default-equality.ts","../src/ir/sql-column-default-ir.ts","../src/ir/sql-column-ir.ts","../src/ir/sql-foreign-key-ir.ts","../src/ir/sql-index-ir.ts","../src/ir/sql-unique-ir.ts","../src/ir/sql-table-ir.ts","../src/ir/sql-schema-ir.ts"],"sourcesContent":["import type { DiffSubjectGranularity } from '@prisma-next/framework-components/control';\n\n/**\n * The `nodeKind` discriminant for each relational schema-diff leaf node.\n * Each node carries a unique value; the differ pairs siblings by `id`, and\n * these kinds distinguish the node types that appear as `PostgresTableSchemaNode`\n * children (columns, primary key, foreign keys, uniques, indexes, checks) from\n * each other and from the RLS policy/role kinds a target defines separately.\n */\nexport const RelationalSchemaNodeKind = {\n schema: 'sql-schema',\n table: 'sql-table',\n column: 'sql-column',\n columnDefault: 'sql-column-default',\n primaryKey: 'sql-primary-key',\n foreignKey: 'sql-foreign-key',\n unique: 'sql-unique',\n index: 'sql-index',\n check: 'sql-check-constraint',\n} as const;\n\nexport type RelationalSchemaNodeKind =\n (typeof RelationalSchemaNodeKind)[keyof typeof RelationalSchemaNodeKind];\n\n/**\n * The one real map from a relational `nodeKind` to the framework-neutral\n * {@link DiffSubjectGranularity} its diff issues carry — the SQL family's\n * post-diff filters (issue category, strict-mode gating) and the framework\n * aggregate's unclaimed-elements sweep key on the granularity, never on the\n * `nodeKind` spelling and never on anything stamped on the node. Resolution\n * is by `nodeKind` equality against this map, not a suffix string match.\n * Target-specific node kinds (Postgres namespace/table/policy/role) are\n * outside this family layer's vocabulary and map their own kinds directly.\n */\nconst RELATIONAL_NODE_GRANULARITY: Readonly<\n Record<RelationalSchemaNodeKind, DiffSubjectGranularity>\n> = {\n [RelationalSchemaNodeKind.schema]: 'structural',\n [RelationalSchemaNodeKind.table]: 'entity',\n [RelationalSchemaNodeKind.column]: 'field',\n [RelationalSchemaNodeKind.columnDefault]: 'auxiliary',\n [RelationalSchemaNodeKind.primaryKey]: 'auxiliary',\n [RelationalSchemaNodeKind.foreignKey]: 'auxiliary',\n [RelationalSchemaNodeKind.unique]: 'auxiliary',\n [RelationalSchemaNodeKind.index]: 'auxiliary',\n [RelationalSchemaNodeKind.check]: 'auxiliary',\n};\n\nfunction isRelationalSchemaNodeKind(nodeKind: string): nodeKind is RelationalSchemaNodeKind {\n return Object.hasOwn(RELATIONAL_NODE_GRANULARITY, nodeKind);\n}\n\n/**\n * Looks up the subject granularity for a relational `nodeKind`. Throws for a\n * `nodeKind` outside this map (a target-specific kind, e.g. Postgres's\n * namespace/table/policy/role) — those map their own kinds directly rather\n * than through this family-level map.\n */\nexport function relationalNodeGranularity(nodeKind: string): DiffSubjectGranularity {\n if (!isRelationalSchemaNodeKind(nodeKind)) {\n throw new Error(`relationalNodeGranularity: unrecognized relational node kind \"${nodeKind}\"`);\n }\n return RELATIONAL_NODE_GRANULARITY[nodeKind];\n}\n\n/**\n * The one real map from a relational `nodeKind` to its storage `entityKind`\n * — the same vocabulary the contract storage's `entries` dictionary keys use\n * (`elementCoordinates` walks it). Only the whole-table kind has an entity of\n * its own; every other relational node (a column, an index, …) is nested\n * under one and maps to nothing here.\n */\nconst RELATIONAL_NODE_ENTITY_KIND: Partial<Readonly<Record<RelationalSchemaNodeKind, string>>> = {\n [RelationalSchemaNodeKind.table]: 'table',\n};\n\n/**\n * Looks up the storage `entityKind` for a relational `nodeKind` — sibling of\n * {@link relationalNodeGranularity}, resolved by `nodeKind` equality against\n * {@link RELATIONAL_NODE_ENTITY_KIND}. `undefined` for a target-specific kind\n * (targets map their own kinds directly) or a relational kind with no entity\n * of its own.\n */\nexport function relationalNodeEntityKind(nodeKind: string): string | undefined {\n return isRelationalSchemaNodeKind(nodeKind) ? RELATIONAL_NODE_ENTITY_KIND[nodeKind] : undefined;\n}\n","import { IRNodeBase } from '@prisma-next/framework-components/ir';\n\n/**\n * SQL Schema IR node base. Carries the family-level\n * `kind = 'sql-schema-ir'` discriminator and inherits the framework's\n * `freezeNode` affordance.\n *\n * SQL Schema IR represents the actual database state as discovered by\n * introspection (the parallel to SQL Contract IR, which represents the\n * desired state).\n *\n * The discriminator is installed as a non-enumerable own property,\n * matching the SqlNode pattern. This keeps `JSON.stringify(node)`\n * canonical (no `kind` field), keeps `toEqual({...})` test assertions\n * against pre-lift flat shapes passing, and keeps `node.kind` readable\n * for dispatch.\n *\n * Both `kind` and `nodeKind` are required: every concrete leaf is a node\n * the generic differ can pair and compare, so every leaf must declare which\n * node it is. `nodeKind` has no default here — every direct subclass sets its\n * own literal value (the relational leaves via `RelationalSchemaNodeKind`,\n * target concretions via their own vocabulary, e.g. `PostgresSchemaNodeKind`).\n */\nexport abstract class SqlSchemaIRNode extends IRNodeBase {\n declare readonly kind: string;\n\n /**\n * Enumerable discriminant identifying which node this is (column / primary\n * key / foreign key / unique / index / check / database / namespace /\n * table / policy / role / …). Concretions set a unique value; the\n * `.is`/`.assert` guards compare against it. Unlike `kind`, it is\n * enumerable, so it survives a spread that flattens a node into a plain\n * object.\n */\n abstract readonly nodeKind: string;\n\n constructor() {\n super();\n Object.defineProperty(this, 'kind', {\n value: 'sql-schema-ir',\n writable: false,\n enumerable: false,\n configurable: false,\n });\n }\n}\n\n/**\n * Asserts `node` matches `predicate`, narrowing its type to `T`. The one\n * shared implementation every node class's `static assert` and `isEqualTo`\n * reach for, instead of each hand-writing its own throw: the message names\n * the class the caller expected.\n */\nexport function assertNode<T extends SqlSchemaIRNode>(\n node: SqlSchemaIRNode | undefined,\n className: string,\n predicate: (node: SqlSchemaIRNode) => node is T,\n): asserts node is T {\n if (node === undefined || !predicate(node)) {\n throw new Error(`Expected a ${className} but got nodeKind=${node?.nodeKind ?? 'undefined'}`);\n }\n}\n\n/**\n * Defines a non-enumerable own property, the same treatment `kind` gets\n * above: a derivation-time render-support field stays out of\n * `JSON.stringify`, `toEqual({...})` structural assertions, and spreads,\n * while remaining directly readable (`node.field`) for the one consumer\n * that resolves it at plan time. A no-op when `value` is `undefined` — the\n * property is simply absent, matching every other optional field on these\n * nodes.\n */\nexport function defineNonEnumerable<T extends object>(\n target: T,\n key: string,\n value: unknown,\n): void {\n if (value === undefined) return;\n Object.defineProperty(target, key, {\n value,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n}\n","import type { DiffableNode, SchemaNodeRef } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport { assertNode, defineNonEnumerable, SqlSchemaIRNode } from './sql-schema-ir-node';\n\nexport interface PrimaryKeyInput {\n readonly columns: readonly string[];\n readonly name?: string;\n /**\n * The key's own column nodes, as root-anchored chains. The derivation\n * stamps them so the primary key is dropped before the columns it is built\n * on. Never compared by `isEqualTo`.\n */\n readonly dependsOn?: readonly SchemaNodeRef[];\n}\n\n/**\n * Primary-key Schema IR node. Mirrors the Contract IR `PrimaryKey`\n * shape (same `columns` + optional `name`) so verification can compare\n * intent and actual structurally. Defined here independently to avoid\n * a sql-schema-ir -> sql-contract dependency.\n *\n * Implements `DiffableNode` so a primary key is directly a table's diff-tree\n * child. `id` is a fixed sentinel — a table has at most one primary key, so\n * there is never a sibling to disambiguate against. `isEqualTo` compares the\n * column tuple; the PK's own `name` is a database-assigned label with no\n * semantic weight, so it is not compared (mirrors the policy node's\n * name-insensitivity to non-identifying detail).\n */\nexport class PrimaryKey extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.primaryKey;\n\n readonly columns: readonly string[];\n declare readonly name?: string;\n /** See {@link PrimaryKeyInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */\n declare readonly dependsOn?: readonly SchemaNodeRef[];\n\n constructor(input: PrimaryKeyInput) {\n super();\n this.columns = input.columns;\n if (input.name !== undefined) this.name = input.name;\n defineNonEnumerable(this, 'dependsOn', input.dependsOn);\n freezeNode(this);\n }\n\n get id(): string {\n return 'primary-key';\n }\n\n children(): readonly DiffableNode[] {\n return [];\n }\n\n static is(node: SqlSchemaIRNode): node is PrimaryKey {\n return node.nodeKind === RelationalSchemaNodeKind.primaryKey;\n }\n\n isEqualTo(other: DiffableNode): boolean {\n const node = blindCast<\n SqlSchemaIRNode,\n 'every diff-tree node the differ pairs is a SqlSchemaIRNode'\n >(other);\n assertNode(node, 'PrimaryKey', PrimaryKey.is);\n return (\n this.columns.length === node.columns.length &&\n this.columns.every((c, i) => c === node.columns[i])\n );\n }\n}\n","import type { DiffableNode } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport { assertNode, SqlSchemaIRNode } from './sql-schema-ir-node';\n\nexport interface SqlCheckConstraintIRInput {\n /** Constraint name as stored in the database catalog. */\n readonly name: string;\n /** Column the check restricts. */\n readonly column: string;\n /** Permitted values the column must be IN. */\n readonly permittedValues: readonly string[];\n}\n\n/**\n * Schema IR node for a table-level check constraint that restricts a\n * column to a set of permitted values (an enum-style `IN (...)` check).\n *\n * Carries the **resolved values** rather than a raw SQL predicate so\n * callers can compare value-sets without parsing SQL.\n *\n * Implements `DiffableNode` so a check constraint is directly a table's\n * diff-tree child: `id` is the constraint name. `isEqualTo` compares\n * `column` and the permitted-value set (order-insensitive — the database\n * does not guarantee `IN (...)` ordering).\n */\nexport class SqlCheckConstraintIR extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.check;\n\n readonly name: string;\n readonly column: string;\n readonly permittedValues: readonly string[];\n\n constructor(input: SqlCheckConstraintIRInput) {\n super();\n this.name = input.name;\n this.column = input.column;\n this.permittedValues = Object.freeze([...input.permittedValues]);\n freezeNode(this);\n }\n\n get id(): string {\n return `check:${this.name}`;\n }\n\n children(): readonly DiffableNode[] {\n return [];\n }\n\n static is(node: SqlSchemaIRNode): node is SqlCheckConstraintIR {\n return node.nodeKind === RelationalSchemaNodeKind.check;\n }\n\n /**\n * Compares the permitted-value sets only (unordered), matching the\n * relational walk's `verifyCheckConstraints`: two checks pairing by name\n * compare their value sets — the `column` field is descriptive, not part\n * of the comparison, so a name-paired check with equal values verifies\n * regardless of which column carries it.\n */\n isEqualTo(other: DiffableNode): boolean {\n const node = blindCast<\n SqlSchemaIRNode,\n 'every diff-tree node the differ pairs is a SqlSchemaIRNode'\n >(other);\n assertNode(node, 'SqlCheckConstraintIR', SqlCheckConstraintIR.is);\n const thisValues = new Set(this.permittedValues);\n const otherValues = new Set(node.permittedValues);\n if (thisValues.size !== otherValues.size) return false;\n return [...thisValues].every((v) => otherValues.has(v));\n }\n}\n","import type { ColumnDefault } from '@prisma-next/contract/types';\nimport { canonicalStringify } from '@prisma-next/utils/canonical-stringify';\n\n/**\n * Structural equality for two resolved column defaults, ported from the\n * relational walk's `columnDefaultsEqual` normalized branch: kinds must\n * match; literal values are normalized (Date and temporal-typed strings to\n * ISO instants) then compared canonically (JSON objects match their\n * canonical string form); function expressions compare case- and\n * whitespace-insensitively.\n *\n * `nativeType` provides the temporal-normalization context (the actual\n * side's resolved native type in a diff comparison).\n */\nexport function resolvedDefaultsEqual(\n expected: ColumnDefault,\n actual: ColumnDefault,\n nativeType?: string,\n): boolean {\n if (expected.kind !== actual.kind) return false;\n if (expected.kind === 'literal' && actual.kind === 'literal') {\n return literalValuesEqual(\n normalizeLiteralValue(expected.value, nativeType),\n normalizeLiteralValue(actual.value, nativeType),\n );\n }\n if (expected.kind === 'function' && actual.kind === 'function') {\n return (\n normalizeFunctionExpression(expected.expression) ===\n normalizeFunctionExpression(actual.expression)\n );\n }\n return false;\n}\n\nfunction normalizeFunctionExpression(expression: string): string {\n return expression.toLowerCase().replace(/\\s+/g, '');\n}\n\nfunction isTemporalNativeType(nativeType?: string): boolean {\n if (!nativeType) return false;\n const normalized = nativeType.toLowerCase();\n return normalized.includes('timestamp') || normalized === 'date';\n}\n\nfunction normalizeLiteralValue(value: unknown, nativeType?: string): unknown {\n if (value instanceof Date) {\n return value.toISOString();\n }\n if (typeof value === 'string' && isTemporalNativeType(nativeType)) {\n const parsed = new Date(value);\n if (!Number.isNaN(parsed.getTime())) {\n return parsed.toISOString();\n }\n }\n return value;\n}\n\nfunction literalValuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) {\n return canonicalStringify(a) === canonicalStringify(b);\n }\n if (typeof a === 'object' && a !== null && typeof b === 'string') {\n try {\n return canonicalStringify(a) === canonicalStringify(JSON.parse(b));\n } catch {\n return false;\n }\n }\n if (typeof a === 'string' && typeof b === 'object' && b !== null) {\n try {\n return canonicalStringify(JSON.parse(a)) === canonicalStringify(b);\n } catch {\n return false;\n }\n }\n return false;\n}\n","import type { ColumnDefault } from '@prisma-next/contract/types';\nimport type { DiffableNode } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { resolvedDefaultsEqual } from './resolved-default-equality';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport { assertNode, defineNonEnumerable, SqlSchemaIRNode } from './sql-schema-ir-node';\n\nexport interface SqlColumnDefaultIRInput {\n /** Structured resolved default (contract-declared or normalizer-parsed). */\n readonly resolved?: ColumnDefault;\n /** Raw database default expression, when known (introspected side). */\n readonly raw?: string;\n /**\n * Native-type context for temporal literal normalization — the owning\n * column's resolved native type.\n */\n readonly nativeTypeContext?: string;\n /**\n * The owning column's array-ness, threaded through so the planner's\n * set-default op-builder can render an array-literal default (e.g.\n * `ARRAY[...]`) the same way the pre-`plan(start, end)` op-path did. See\n * {@link import('./sql-column-ir').SqlColumnIRInput.many}.\n */\n readonly many?: boolean;\n}\n\n/**\n * Schema-diff node for a column's default value. The default is the one\n * column attribute with an extra/missing/drift lifecycle of its own, so it\n * is a child node of the column rather than a compared attribute: an\n * undeclared live default surfaces as `not-expected`, a declared default the\n * database lacks as `not-found`, and a divergent value as `not-equal` — the\n * three reasons cover the legacy `extra_default` / `default_missing` /\n * `default_mismatch` vocabulary with no attribute inspection.\n *\n * `id` is a fixed sentinel — a column has at most one default. Built\n * transiently by `SqlColumnIR.children()` from the column's own fields;\n * never constructed by derivation or introspection directly.\n */\nexport class SqlColumnDefaultIR extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.columnDefault;\n\n declare readonly resolved?: ColumnDefault;\n declare readonly raw?: string;\n declare readonly nativeTypeContext?: string;\n /** See {@link SqlColumnDefaultIRInput.many}. Non-enumerable so it stays out of JSON and structural equality. */\n declare readonly many?: boolean;\n\n constructor(input: SqlColumnDefaultIRInput) {\n super();\n if (input.resolved !== undefined) this.resolved = input.resolved;\n if (input.raw !== undefined) this.raw = input.raw;\n if (input.nativeTypeContext !== undefined) this.nativeTypeContext = input.nativeTypeContext;\n defineNonEnumerable(this, 'many', input.many);\n freezeNode(this);\n }\n\n get id(): string {\n return 'default';\n }\n\n children(): readonly DiffableNode[] {\n return [];\n }\n\n static is(node: SqlSchemaIRNode): node is SqlColumnDefaultIR {\n return node.nodeKind === RelationalSchemaNodeKind.columnDefault;\n }\n\n /**\n * Structured comparison with `this` as the expected side: both sides\n * resolved compare per the relational walk's `columnDefaultsEqual`\n * semantics; a declared expected default against an unparseable actual\n * (raw present, no resolved parse) is a mismatch; two raw-only nodes fall\n * back to raw string equality.\n */\n isEqualTo(other: DiffableNode): boolean {\n const node = blindCast<\n SqlSchemaIRNode,\n 'every diff-tree node the differ pairs is a SqlSchemaIRNode'\n >(other);\n assertNode(node, 'SqlColumnDefaultIR', SqlColumnDefaultIR.is);\n if (this.resolved !== undefined && node.resolved !== undefined) {\n return resolvedDefaultsEqual(\n this.resolved,\n node.resolved,\n node.nativeTypeContext ?? this.nativeTypeContext,\n );\n }\n if (this.resolved !== undefined || node.resolved !== undefined) {\n return false;\n }\n return this.raw === node.raw;\n }\n}\n","import type { ColumnDefault } from '@prisma-next/contract/types';\nimport type { CodecRef } from '@prisma-next/framework-components/codec';\nimport type { DiffableNode } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport { SqlColumnDefaultIR } from './sql-column-default-ir';\nimport { assertNode, defineNonEnumerable, SqlSchemaIRNode } from './sql-schema-ir-node';\n\n/**\n * Namespaced annotations for extensibility. Each namespace\n * (e.g. `pg`, `pgvector`) owns its annotations subtree.\n */\nexport type SqlAnnotations = {\n readonly [namespace: string]: unknown;\n};\n\nexport interface SqlColumnIRInput {\n readonly name: string;\n readonly nativeType: string;\n readonly nullable: boolean;\n /** Raw database default expression (e.g. `'hello'::text`, `nextval('seq')`). */\n readonly default?: string;\n readonly annotations?: SqlAnnotations;\n /** True when the column is a native array (e.g. `text[]`, `int4[]`). The `nativeType` carries the element type only (e.g. `text`, `int4`). */\n readonly many?: boolean;\n /**\n * Fully resolved native type, comparable across the two diff sides:\n * the contract-derived side stamps the codec-expanded type (typeRef\n * resolved, parameterized types expanded, `[]` appended for arrays);\n * the introspected side stamps the target-normalized type with the same\n * `[]` convention. Stamped at construction by derivation/introspection;\n * absent on raw hand-built nodes.\n */\n readonly resolvedNativeType?: string;\n /**\n * Structured default, comparable across the two diff sides: the\n * contract-derived side stamps the contract's `ColumnDefault`; the\n * introspected side stamps the target default-normalizer's parse of the\n * raw expression. Absent when the column declares no default, or when\n * the introspected raw default could not be parsed.\n */\n readonly resolvedDefault?: ColumnDefault;\n /**\n * The column's resolved codec reference — the identity the migration\n * planner's op-builders resolve DDL type rendering against at plan time\n * (parameterized type expansion, e.g. `character` + `{ length: 36 }` →\n * `character(36)`), calling the same codec hooks the pre-`plan(start,\n * end)` op-path called. Carried the same way the query AST carries\n * `CodecRef` (TML-2456) and the migration DDL renderer (TML-2918).\n * Stamped on the EXPECTED (contract-derived) column at derivation,\n * post-`typeRef` resolution (the referenced storage type's own\n * codec/params, not the column's `typeRef` pointer). Absent on\n * introspected/hand-built nodes — the actual side never renders DDL —\n * and never compared by `isEqualTo`.\n */\n readonly codecRef?: CodecRef;\n /**\n * The column's resolved BASE native type: pre-parameter-expansion,\n * pre-array-suffix (e.g. `character`, not `character(36)` or\n * `character[]`). Distinct from {@link resolvedNativeType} (the EXPANDED\n * comparison value) — DDL rendering re-expands a parameterized type at\n * plan time from this base, so it must not already carry the expansion.\n * Stamped alongside {@link codecRef}.\n */\n readonly codecBaseNativeType?: string;\n /**\n * True when the contract column declared its type via a named\n * `storage.types` reference (`typeRef`) rather than inline fields — the\n * migration planner quotes the base native type as an identifier in this\n * case (e.g. a native enum's type name), matching the pre-`plan(start,\n * end)` rendering exactly.\n */\n readonly codecNamedType?: boolean;\n}\n\n/**\n * Schema IR node for a single column on a table, as observed by\n * introspection. Unlike the Contract IR `StorageColumn`, this carries\n * the column's `name` (Schema IR columns are returned as arrays from\n * introspection queries; the parent table re-keys them into a record\n * for downstream consumers).\n *\n * Implements `DiffableNode` so a column is directly a table's diff-tree\n * child: `id` is the column name (unique among a table's columns); `isEqualTo`\n * compares this column's own attributes only — never children, since a\n * column is a leaf. When both sides carry `resolvedNativeType` (stamped at\n * derivation/introspection), the comparison uses the resolved values —\n * resolved native type, nullability, and structured default equality per\n * the relational walk's `columnDefaultsEqual` semantics, with `this` as the\n * expected side. Otherwise it falls back to comparing raw fields.\n */\nexport class SqlColumnIR extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.column;\n\n readonly name: string;\n readonly nativeType: string;\n readonly nullable: boolean;\n declare readonly default?: string;\n declare readonly annotations?: SqlAnnotations;\n /** True when the column is a native array (e.g. `text[]`, `int4[]`). The `nativeType` carries the element type only (e.g. `text`, `int4`). */\n declare readonly many?: boolean;\n declare readonly resolvedNativeType?: string;\n declare readonly resolvedDefault?: ColumnDefault;\n /** See {@link SqlColumnIRInput.codecRef}. Non-enumerable so it stays out of JSON and structural equality. */\n declare readonly codecRef?: CodecRef;\n /** See {@link SqlColumnIRInput.codecBaseNativeType}. Non-enumerable, same reason as {@link codecRef}. */\n declare readonly codecBaseNativeType?: string;\n /** See {@link SqlColumnIRInput.codecNamedType}. Non-enumerable, same reason as {@link codecRef}. */\n declare readonly codecNamedType?: boolean;\n\n constructor(input: SqlColumnIRInput) {\n super();\n this.name = input.name;\n this.nativeType = input.nativeType;\n this.nullable = input.nullable;\n if (input.default !== undefined) this.default = input.default;\n if (input.annotations !== undefined) this.annotations = input.annotations;\n if (input.many !== undefined) this.many = input.many;\n if (input.resolvedNativeType !== undefined) this.resolvedNativeType = input.resolvedNativeType;\n if (input.resolvedDefault !== undefined) this.resolvedDefault = input.resolvedDefault;\n defineNonEnumerable(this, 'codecRef', input.codecRef);\n defineNonEnumerable(this, 'codecBaseNativeType', input.codecBaseNativeType);\n defineNonEnumerable(this, 'codecNamedType', input.codecNamedType);\n freezeNode(this);\n }\n\n get id(): string {\n return `column:${this.name}`;\n }\n\n /**\n * The column's default, when declared/present, is the column's one child\n * node — it has an extra/missing/drift lifecycle of its own, so the differ\n * recurses to it rather than `isEqualTo` comparing it. Built transiently\n * from this column's own fields.\n */\n children(): readonly DiffableNode[] {\n if (this.resolvedDefault === undefined && this.default === undefined) {\n return [];\n }\n return [\n new SqlColumnDefaultIR({\n ...ifDefined('resolved', this.resolvedDefault),\n ...ifDefined('raw', this.default),\n ...ifDefined('nativeTypeContext', this.resolvedNativeType),\n // Contract-derived and introspected columns both set `this.many`\n // directly (with `nativeType` as the bare element type; array-ness\n // never rides on a `[]` suffix). `codecRef.many` is a fallback for\n // hand-built nodes that carry a codec ref without the column-level\n // flag. Either source works for the default node's array-literal\n // rendering.\n ...ifDefined('many', this.many ?? this.codecRef?.many),\n }),\n ];\n }\n\n static is(node: SqlSchemaIRNode): node is SqlColumnIR {\n return node.nodeKind === RelationalSchemaNodeKind.column;\n }\n\n /**\n * Compares the column's own attributes only — the default lives on the\n * child node. When both sides carry `resolvedNativeType`, the resolved\n * value governs (array-ness rides on its `[]` suffix); otherwise raw\n * fields compare.\n */\n isEqualTo(other: DiffableNode): boolean {\n const node = blindCast<\n SqlSchemaIRNode,\n 'every diff-tree node the differ pairs is a SqlSchemaIRNode'\n >(other);\n assertNode(node, 'SqlColumnIR', SqlColumnIR.is);\n if (this.resolvedNativeType !== undefined && node.resolvedNativeType !== undefined) {\n return this.resolvedNativeType === node.resolvedNativeType && this.nullable === node.nullable;\n }\n return (\n this.nativeType === node.nativeType &&\n this.nullable === node.nullable &&\n Boolean(this.many) === Boolean(node.many)\n );\n }\n}\n","import type { DiffableNode, SchemaNodeRef } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport type { SqlAnnotations } from './sql-column-ir';\nimport { assertNode, defineNonEnumerable, SqlSchemaIRNode } from './sql-schema-ir-node';\n\nexport type SqlReferentialAction = 'noAction' | 'restrict' | 'cascade' | 'setNull' | 'setDefault';\n\nexport interface SqlForeignKeyIRInput {\n readonly columns: readonly string[];\n readonly referencedTable: string;\n readonly referencedColumns: readonly string[];\n /** Schema (namespace) of the referenced table — populated by adapters that introspect cross-schema FKs. */\n readonly referencedSchema?: string;\n readonly name?: string;\n readonly onDelete?: SqlReferentialAction;\n readonly onUpdate?: SqlReferentialAction;\n readonly annotations?: SqlAnnotations;\n /**\n * The real live DDL namespace of the referenced table, comparable across\n * the two diff sides. Contract-derived trees stamp it explicitly (resolving\n * namespace ids — including the unbound sentinel — to the DDL namespace);\n * introspected FKs default it to `referencedSchema`, whose value already\n * is the live namespace. Folded into `id` in place of the raw value so an\n * unbound-namespace contract FK pairs with its introspected counterpart.\n */\n readonly resolvedReferencedNamespace?: string;\n /**\n * The referenced table's node, as the root-anchored chain the differ pairs\n * siblings with. Stamped by the derivation, which holds the parent\n * (database/namespace) context the chain's shape depends on. Never\n * compared by `isEqualTo`.\n */\n readonly dependsOn?: readonly SchemaNodeRef[];\n}\n\n/**\n * Schema IR node for a foreign-key constraint as observed by\n * introspection. The `referencedTable` / `referencedColumns` field\n * names match the introspection vocabulary (`pg_constraint.confkey`,\n * etc.) and intentionally differ from the Contract IR's nested\n * `references: { table, columns }` shape so that the verifier's\n * structural comparison stays explicit about which side it's reading.\n *\n * Implements `DiffableNode` so a foreign key is directly a table's diff-tree\n * child. Foreign keys are frequently unnamed (introspection may not carry a\n * constraint name, and the contract side never invents one), so `id` is\n * derived from the referencing/referenced coordinates rather than `name` —\n * the same tuple that makes two FK constraints the same constraint. This\n * also serves as the comparison key: two FKs with the same coordinates are\n * paired by the differ, and `isEqualTo` then compares the remaining\n * attribute — the referential actions.\n */\nexport class SqlForeignKeyIR extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.foreignKey;\n\n readonly columns: readonly string[];\n readonly referencedTable: string;\n readonly referencedColumns: readonly string[];\n declare readonly referencedSchema?: string;\n declare readonly name?: string;\n declare readonly onDelete?: SqlReferentialAction;\n declare readonly onUpdate?: SqlReferentialAction;\n declare readonly annotations?: SqlAnnotations;\n declare readonly resolvedReferencedNamespace?: string;\n /** See {@link SqlForeignKeyIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */\n declare readonly dependsOn?: readonly SchemaNodeRef[];\n\n constructor(input: SqlForeignKeyIRInput) {\n super();\n this.columns = input.columns;\n this.referencedTable = input.referencedTable;\n this.referencedColumns = input.referencedColumns;\n if (input.referencedSchema !== undefined) this.referencedSchema = input.referencedSchema;\n if (input.name !== undefined) this.name = input.name;\n if (input.onDelete !== undefined) this.onDelete = input.onDelete;\n if (input.onUpdate !== undefined) this.onUpdate = input.onUpdate;\n if (input.annotations !== undefined) this.annotations = input.annotations;\n const resolvedReferencedNamespace = input.resolvedReferencedNamespace ?? input.referencedSchema;\n if (resolvedReferencedNamespace !== undefined) {\n this.resolvedReferencedNamespace = resolvedReferencedNamespace;\n }\n defineNonEnumerable(this, 'dependsOn', input.dependsOn);\n freezeNode(this);\n }\n\n get id(): string {\n const referencedNamespace = this.resolvedReferencedNamespace ?? '';\n return `foreign-key:${this.columns.join(',')}->${referencedNamespace}.${this.referencedTable}(${this.referencedColumns.join(',')})`;\n }\n\n children(): readonly DiffableNode[] {\n return [];\n }\n\n static is(node: SqlSchemaIRNode): node is SqlForeignKeyIR {\n return node.nodeKind === RelationalSchemaNodeKind.foreignKey;\n }\n\n /**\n * Referential-action comparison with `this` as the expected side, matching\n * the relational walk's `getReferentialActionMismatches`: `noAction` is the\n * database default and equivalent to an undeclared action, and drift is\n * flagged only when the expected side declares a (normalized) action.\n */\n isEqualTo(other: DiffableNode): boolean {\n const node = blindCast<\n SqlSchemaIRNode,\n 'every diff-tree node the differ pairs is a SqlSchemaIRNode'\n >(other);\n assertNode(node, 'SqlForeignKeyIR', SqlForeignKeyIR.is);\n return (\n referentialActionMatches(this.onDelete, node.onDelete) &&\n referentialActionMatches(this.onUpdate, node.onUpdate)\n );\n }\n}\n\nfunction referentialActionMatches(\n expected: SqlReferentialAction | undefined,\n actual: SqlReferentialAction | undefined,\n): boolean {\n const normalizedExpected = expected === 'noAction' ? undefined : expected;\n if (normalizedExpected === undefined) return true;\n const normalizedActual = actual === 'noAction' ? undefined : actual;\n return normalizedExpected === normalizedActual;\n}\n","import type { DiffableNode, SchemaNodeRef } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport type { SqlAnnotations } from './sql-column-ir';\nimport { assertNode, defineNonEnumerable, SqlSchemaIRNode } from './sql-schema-ir-node';\n\n/**\n * Every field is a required key. Values that may legitimately be absent\n * (an unnamed `@@index`, the btree→undefined type normalization) are typed\n * `| undefined` instead of optional, so each construction site states the\n * absence explicitly rather than omitting the key silently. Undefined values\n * still produce an instance without the property.\n */\nexport interface SqlIndexIRInput {\n readonly columns: readonly string[];\n readonly unique: boolean;\n readonly name: string | undefined;\n readonly type: string | undefined;\n readonly options: Record<string, unknown> | undefined;\n readonly annotations: SqlAnnotations | undefined;\n /**\n * The index's own column nodes, as root-anchored chains. The derivation\n * stamps them so an index is dropped before the columns it is built on\n * (Postgres auto-drops the index when a covered column goes). Never\n * compared by `isEqualTo`.\n */\n readonly dependsOn: readonly SchemaNodeRef[] | undefined;\n /**\n * Whether the index is partial (has a row predicate). Required: every\n * producer must assert partiality explicitly, because a partial unique\n * index does not guarantee at-most-one row per key and so cannot back a\n * 1:1 relation — \"unknown\" must not silently default to \"total\". Never\n * compared by `isEqualTo` and never serialized: differ/verify semantics\n * are unchanged, and surfacing partial-index drift is out of scope.\n */\n readonly partial: boolean;\n}\n\n/**\n * Schema IR node for a secondary index as observed by introspection.\n * Unlike the Contract IR `Index`, the Schema IR carries an explicit\n * `unique` field — introspection sees the underlying index regardless\n * of whether the user expressed it as `@@index` or `@@unique`, and the\n * verifier needs to distinguish them when comparing to the Contract.\n *\n * Implements `DiffableNode` so an index is directly a table's diff-tree\n * child. Indexes are frequently unnamed, so `id` is derived from the column\n * tuple — the same tuple that makes two indexes the same index, so it\n * doubles as the pairing key. `isEqualTo` is symmetric structural equality\n * on the remaining attributes: `unique`, `type`, and `options`. A unique\n * index and a non-unique index on the same columns are different objects\n * and are not equal — there is no \"stronger satisfies weaker\".\n *\n * Deliberately column-tuple-only (not `unique`): a contract `@@index`\n * (non-unique) must still pair against a live unique index of the same\n * columns so the differ can report the mismatch as an incompatible index\n * change rather than a spurious missing+extra pair — see\n * `planner.unique-index-structural.test.ts`. The corollary is that two\n * indexes legitimately coexisting on one table with the *same* column tuple\n * (e.g. a unique index and a redundant plain index Postgres has no problem\n * hosting side by side) collide on this id; the postgres control adapter's\n * introspection keeps only one such index per table+column-tuple rather\n * than handing the differ two same-tree siblings it cannot represent.\n */\nexport class SqlIndexIR extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.index;\n\n readonly columns: readonly string[];\n readonly unique: boolean;\n declare readonly name?: string;\n declare readonly type?: string;\n declare readonly options?: Record<string, unknown>;\n declare readonly annotations?: SqlAnnotations;\n /** See {@link SqlIndexIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */\n declare readonly dependsOn?: readonly SchemaNodeRef[];\n /** See {@link SqlIndexIRInput.partial}. Non-enumerable so it stays out of JSON and structural equality, matching `dependsOn`. */\n declare readonly partial: boolean;\n\n constructor(input: SqlIndexIRInput) {\n super();\n this.columns = input.columns;\n this.unique = input.unique;\n if (input.name !== undefined) this.name = input.name;\n if (input.type !== undefined) this.type = input.type;\n if (input.options !== undefined) this.options = input.options;\n if (input.annotations !== undefined) this.annotations = input.annotations;\n defineNonEnumerable(this, 'dependsOn', input.dependsOn);\n defineNonEnumerable(this, 'partial', input.partial);\n freezeNode(this);\n }\n\n get id(): string {\n return `index:${this.columns.join(',')}`;\n }\n\n children(): readonly DiffableNode[] {\n return [];\n }\n\n static is(node: SqlSchemaIRNode): node is SqlIndexIR {\n return node.nodeKind === RelationalSchemaNodeKind.index;\n }\n\n /**\n * Symmetric structural equality: two paired index nodes are equal iff their\n * `unique` flag, `type`, and (loosely-compared) `options` all match. There\n * is no satisfaction — a unique index does not equal a non-unique index.\n * `options` compares loosely (introspection stringifies reloptions); `type`\n * compares strictly after the introspection-side btree→undefined\n * normalization done at construction.\n */\n isEqualTo(other: DiffableNode): boolean {\n const node = blindCast<\n SqlSchemaIRNode,\n 'every diff-tree node the differ pairs is a SqlSchemaIRNode'\n >(other);\n assertNode(node, 'SqlIndexIR', SqlIndexIR.is);\n return (\n this.unique === node.unique &&\n this.type === node.type &&\n indexOptionsLooselyEqual(this.options, node.options)\n );\n }\n}\n\n/**\n * Option-bag equality ported from the relational walk: same key set, values\n * compared via `String()` coercion — Postgres introspection returns\n * reloptions values as raw strings (`'70'`, `'false'`) while contract option\n * leaves are typed (number, boolean, string).\n */\nfunction indexOptionsLooselyEqual(\n a: Record<string, unknown> | undefined,\n b: Record<string, unknown> | undefined,\n): boolean {\n const aKeys = a ? Object.keys(a).sort() : [];\n const bKeys = b ? Object.keys(b).sort() : [];\n if (aKeys.length !== bKeys.length) return false;\n for (let i = 0; i < aKeys.length; i += 1) {\n if (aKeys[i] !== bKeys[i]) return false;\n }\n if (aKeys.length === 0) return true;\n for (const key of aKeys) {\n if (String(a?.[key]) !== String(b?.[key])) {\n return false;\n }\n }\n return true;\n}\n","import type { DiffableNode, SchemaNodeRef } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport type { SqlAnnotations } from './sql-column-ir';\nimport { assertNode, defineNonEnumerable, SqlSchemaIRNode } from './sql-schema-ir-node';\n\nexport interface SqlUniqueIRInput {\n readonly columns: readonly string[];\n readonly name?: string;\n readonly annotations?: SqlAnnotations;\n /**\n * The constraint's own column nodes, as root-anchored chains. The\n * derivation stamps them so a unique constraint is dropped before the\n * columns it is built on. Never compared by `isEqualTo`.\n */\n readonly dependsOn?: readonly SchemaNodeRef[];\n}\n\n/**\n * Schema IR node for a table-level unique constraint as observed by\n * introspection.\n *\n * Implements `DiffableNode` so a unique constraint is directly a table's\n * diff-tree child. Unique constraints are frequently unnamed, so `id` is\n * derived from the column tuple rather than `name` — the column tuple is\n * also what makes two unique constraints the same constraint, so it doubles\n * as the pairing key. There are no further attributes to compare once\n * columns are equal (the differ pairs on `id`), so `isEqualTo` is identity.\n */\nexport class SqlUniqueIR extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.unique;\n\n readonly columns: readonly string[];\n declare readonly name?: string;\n declare readonly annotations?: SqlAnnotations;\n /** See {@link SqlUniqueIRInput.dependsOn}. Non-enumerable so it stays out of JSON and structural equality, matching `SqlColumnIR.codecRef`. */\n declare readonly dependsOn?: readonly SchemaNodeRef[];\n\n constructor(input: SqlUniqueIRInput) {\n super();\n this.columns = [...input.columns];\n if (input.name !== undefined) this.name = input.name;\n if (input.annotations !== undefined) this.annotations = input.annotations;\n defineNonEnumerable(this, 'dependsOn', input.dependsOn);\n freezeNode(this);\n }\n\n get id(): string {\n return `unique:${this.columns.join(',')}`;\n }\n\n children(): readonly DiffableNode[] {\n return [];\n }\n\n static is(node: SqlSchemaIRNode): node is SqlUniqueIR {\n return node.nodeKind === RelationalSchemaNodeKind.unique;\n }\n\n isEqualTo(other: DiffableNode): boolean {\n const node = blindCast<\n SqlSchemaIRNode,\n 'every diff-tree node the differ pairs is a SqlSchemaIRNode'\n >(other);\n assertNode(node, 'SqlUniqueIR', SqlUniqueIR.is);\n return this.id === node.id;\n }\n}\n","import type { DiffableNode } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { PrimaryKey, type PrimaryKeyInput } from './primary-key';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport { SqlCheckConstraintIR, type SqlCheckConstraintIRInput } from './sql-check-constraint-ir';\nimport { type SqlAnnotations, SqlColumnIR, type SqlColumnIRInput } from './sql-column-ir';\nimport { SqlForeignKeyIR, type SqlForeignKeyIRInput } from './sql-foreign-key-ir';\nimport { SqlIndexIR, type SqlIndexIRInput } from './sql-index-ir';\nimport { SqlSchemaIRNode } from './sql-schema-ir-node';\nimport { SqlUniqueIR, type SqlUniqueIRInput } from './sql-unique-ir';\n\nexport interface SqlTableIRInput {\n readonly name: string;\n readonly columns: Record<string, SqlColumnIR | SqlColumnIRInput>;\n readonly foreignKeys: ReadonlyArray<SqlForeignKeyIR | SqlForeignKeyIRInput>;\n readonly uniques: ReadonlyArray<SqlUniqueIR | SqlUniqueIRInput>;\n readonly indexes: ReadonlyArray<SqlIndexIR | SqlIndexIRInput>;\n readonly primaryKey?: PrimaryKey | PrimaryKeyInput;\n readonly annotations?: SqlAnnotations;\n /** Optional check constraints for enum-restricted columns. Omitted when none present. */\n readonly checks?: ReadonlyArray<SqlCheckConstraintIR | SqlCheckConstraintIRInput>;\n}\n\n/**\n * Schema IR node for a single table as observed by introspection.\n *\n * Unlike the Contract IR `StorageTable`, this carries the table's\n * `name` — introspection queries return tables as arrays and the\n * verifier keys them into `SqlSchemaIR.tables` afterwards, so the name\n * stays on the table object for downstream call sites that walk\n * `Object.values(schema.tables)`.\n *\n * The constructor normalises nested IR-class fields so downstream\n * walks see a uniform AST regardless of whether the input was a\n * plain-data literal (from introspection) or already-constructed\n * class instances.\n *\n * Implements `DiffableNode` so a flat (single-schema) tree is directly\n * diffable: `id` is the table name; `isEqualTo` is identity (the table's\n * structural drift is entirely expressed by its children); `children()`\n * yields every column, the primary key (when present), every foreign key,\n * unique, index, and check constraint — the same composition and order as\n * the Postgres table node, minus policies.\n */\nexport class SqlTableIR extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.table;\n\n readonly name: string;\n readonly columns: Readonly<Record<string, SqlColumnIR>>;\n readonly foreignKeys: ReadonlyArray<SqlForeignKeyIR>;\n readonly uniques: ReadonlyArray<SqlUniqueIR>;\n readonly indexes: ReadonlyArray<SqlIndexIR>;\n declare readonly primaryKey?: PrimaryKey;\n declare readonly annotations?: SqlAnnotations;\n declare readonly checks?: ReadonlyArray<SqlCheckConstraintIR>;\n\n constructor(input: SqlTableIRInput) {\n super();\n this.name = input.name;\n this.columns = Object.freeze(\n Object.fromEntries(\n Object.entries(input.columns).map(([key, col]) => [\n key,\n col instanceof SqlColumnIR ? col : new SqlColumnIR(col),\n ]),\n ),\n );\n this.foreignKeys = Object.freeze(\n input.foreignKeys.map((fk) => (fk instanceof SqlForeignKeyIR ? fk : new SqlForeignKeyIR(fk))),\n );\n this.uniques = Object.freeze(\n input.uniques.map((u) => (u instanceof SqlUniqueIR ? u : new SqlUniqueIR(u))),\n );\n this.indexes = Object.freeze(\n input.indexes.map((i) => (i instanceof SqlIndexIR ? i : new SqlIndexIR(i))),\n );\n if (input.primaryKey !== undefined) {\n this.primaryKey =\n input.primaryKey instanceof PrimaryKey\n ? input.primaryKey\n : new PrimaryKey(input.primaryKey);\n }\n if (input.annotations !== undefined) this.annotations = input.annotations;\n if (input.checks !== undefined && input.checks.length > 0) {\n this.checks = Object.freeze(\n input.checks.map((c) =>\n c instanceof SqlCheckConstraintIR ? c : new SqlCheckConstraintIR(c),\n ),\n );\n }\n freezeNode(this);\n }\n\n get id(): string {\n return this.name;\n }\n\n isEqualTo(other: DiffableNode): boolean {\n return this.id === other.id;\n }\n\n children(): readonly DiffableNode[] {\n return [\n ...Object.values(this.columns),\n ...(this.primaryKey ? [this.primaryKey] : []),\n ...this.foreignKeys,\n ...this.uniques,\n ...this.indexes,\n ...(this.checks ?? []),\n ];\n }\n}\n","import type { DiffableNode } from '@prisma-next/framework-components/control';\nimport { freezeNode } from '@prisma-next/framework-components/ir';\nimport { RelationalSchemaNodeKind } from './schema-node-kinds';\nimport type { SqlAnnotations } from './sql-column-ir';\nimport { SqlSchemaIRNode } from './sql-schema-ir-node';\nimport { SqlTableIR, type SqlTableIRInput } from './sql-table-ir';\n\nexport interface SqlSchemaIRInput {\n readonly tables: Record<string, SqlTableIR | SqlTableIRInput>;\n readonly annotations?: SqlAnnotations;\n}\n\n/**\n * Root Schema IR node representing the complete database schema as\n * observed by introspection. Target-agnostic; used by both verifiers\n * (compare against intended Contract storage) and migration planners\n * (derive operations needed to reconcile).\n *\n * The constructor normalises nested `SqlTableIR` instances so\n * downstream walks see a uniform AST regardless of whether the input\n * was a plain-data literal or already-constructed class instances.\n *\n * Implements `DiffableNode` as the root of a flat (single-schema) diff\n * tree: `id` is the fixed sentinel `'database'` (roots have no siblings),\n * `isEqualTo` is identity (a container has no own attributes), and\n * `children()` yields the table nodes.\n */\nexport class SqlSchemaIR extends SqlSchemaIRNode implements DiffableNode {\n override readonly nodeKind = RelationalSchemaNodeKind.schema;\n\n readonly tables: Readonly<Record<string, SqlTableIR>>;\n declare readonly annotations?: SqlAnnotations;\n\n constructor(input: SqlSchemaIRInput) {\n super();\n this.tables = Object.freeze(\n Object.fromEntries(\n Object.entries(input.tables).map(([key, t]) => [\n key,\n t instanceof SqlTableIR ? t : new SqlTableIR(t),\n ]),\n ),\n );\n if (input.annotations !== undefined) this.annotations = input.annotations;\n freezeNode(this);\n }\n\n get id(): string {\n return 'database';\n }\n\n isEqualTo(other: DiffableNode): boolean {\n return this.id === other.id;\n }\n\n children(): readonly DiffableNode[] {\n return Object.values(this.tables);\n }\n}\n"],"mappings":"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"} |
238304
19.32%33
6.45%2290
20.97%155
1.31%+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed