/** * Semantic diff driver: identity-keyed schema delta between two deploy * surfaces. * * Where `diffBundles` (in `@pgpmjs/bundle`) compares change names and content * digests — which breaks the moment a dial regroups or renames changes — this * driver re-keys the comparison on object identity (`identityOf`), so the * same schema authored at different granularities or partitions diffs as * equal. Objects are compared at the AST level (normalized parse trees, so * formatting never matters); tables are compared column-by-column and * constraint-by-constraint so an added column emits `ALTER TABLE ADD COLUMN`, * not a table rebuild. * * The emitted delta is a set of pgpm changes: DROPs for removed objects * (reverse topological order), then CREATE/ALTER changes for added and * modified objects routed through the granularity pipeline * (`restructureChanges`), so names come from the naming spec and `requires` * from the statement graph — the same derivation every other projection uses. * Every emitted change carries a full deploy/revert/verify triple: inverses * and existence checks are composed at the AST level via `@pgsql/scripts`' * node-level API (`invertStatement`/`existenceCheck`) — never hand-templated * and never round-tripped through deparsed text — deparsing only once at the * end. Statements the node layer cannot derive fall back to the text layer * (`revertFor`/`verifyFor`) solely to surface its not-derivable comment and * warning. * * Requires `loadModule()` from `plpgsql-parser` before use. */ import { PathStyle } from '@pgpmjs/naming-spec'; import type { ObjectIdentity } from '@pgsql/transform'; import { Granularity } from '@pgsql/transform'; import { ChangeGranularity, GranularityChange } from './granularity-driver'; /** How one object differs between the two sides. */ export type ObjectDelta = 'added' | 'removed' | 'modified'; export interface SemanticObjectDiff { identity: ObjectIdentity; /** Canonical naming-spec path for the object. */ path: string; delta: ObjectDelta; /** For modified tables: the column/constraint-level detail. */ columnsAdded?: string[]; columnsRemoved?: string[]; columnsModified?: string[]; } export interface SemanticDiffOptions { /** Granularity for the emitted CREATE/ALTER changes (default `object`). */ granularity?: Granularity; /** Change-level distribution for emitted changes (default `object`). */ changeGranularity?: ChangeGranularity; /** Naming-spec rendering style (default `directory`). */ style?: PathStyle; } /** One emitted delta change: a full deploy/revert/verify triple. */ export interface SemanticDeltaChange extends GranularityChange { revert: string; verify: string; } export interface SemanticDiffResult { /** True when both sides describe the same objects with the same shapes. */ identical: boolean; /** Per-object dispositions, in `to`-then-removed order. */ objects: SemanticObjectDiff[]; /** * The migration delta: DROP changes for removed objects first (reverse * topological order), then CREATE/ALTER changes named and ordered by the * granularity pipeline. */ changes: SemanticDeltaChange[]; warnings: string[]; } /** * Diff two deploy surfaces by object identity and emit the migration delta. * * `from`/`to` are whole deploy scripts (flatten changes in plan order to * diff bundles/modules). The result's `changes` deploy `to`'s shape on top * of `from`: reverse-topological DROPs, then CREATE/ALTERs named and * ordered by `restructureChanges`. */ export declare function diffSchemas(from: string, to: string, options?: SemanticDiffOptions): SemanticDiffResult; /** A change's deploy surface (same seam as the other drivers). */ export interface DiffInputChange { name: string; dependencies: string[]; deploy: string; } /** * Identity-keyed diff over pgpm changes: flattens each side's deploy scripts * in plan order and delegates to {@link diffSchemas}. Change names never * enter the comparison — regrouping, renaming, or repartitioning the same * schema yields an identical diff. */ export declare function diffChangeSets(from: DiffInputChange[], to: DiffInputChange[], options?: SemanticDiffOptions): SemanticDiffResult; /** How one object a change touches relates to the diff's delta. */ export type ChangeObjectDelta = ObjectDelta | 'unchanged'; /** * Whether a `to`-side change is already satisfied by the `from` side: * - `satisfied`: every object it touches is unchanged in the diff * - `unsatisfied`: every object it touches is added/modified in the diff * - `partial`: a mix of unchanged and delta objects * - `inert`: no identifiable objects (seed data, grants-only, dynamic SQL) */ export type ChangeSatisfaction = 'satisfied' | 'unsatisfied' | 'partial' | 'inert'; export interface ChangeObjectCoverage { /** Canonical naming-spec path for the object. */ path: string; delta: ChangeObjectDelta; } export interface ChangeCoverage { /** The input change's name (as given, e.g. `pkg:change`). */ name: string; status: ChangeSatisfaction; objects: ChangeObjectCoverage[]; } /** * Cover each `to`-side change against a semantic diff's per-object delta: * which of the objects the change touches are already present and identical * on the `from` side. A `satisfied` change deploys nothing new — its ledger * entry can be backfilled instead of executing it; an `unsatisfied` change is * the genuine delta. Identity keying is shared with the diff itself, so * regrouped/renamed/reordered authorship of the same objects covers the same. * * `diff` must come from diffing the same `from` side against these changes * (`diffChangeSets(fromChanges, toChanges)`). */ export declare function coverChanges(changes: DiffInputChange[], diff: SemanticDiffResult, options?: { style?: PathStyle; }): ChangeCoverage[];