/** * Partition dial: project one deploy surface into a set of pgpm packages. * * The object graph (statements classified by `@pgsql/semantics`, grouped * into identity-keyed units) is the source of truth; which package a unit * lives in is a *derived projection* of a declarative partition config. * Units can be assigned by schema, by object kind, by explicit cherry-pick * (identity or derived path), or dragged in as the dependency closure of a * package's seeds. `requires` headers are a mechanical post-pass over the * statement graph: intra-package edges become `requires: `, * cross-package edges become `requires: :` — both rendered * through the naming spec, never authored. * * A partition is rejected (hard error) when packages require each other in * a cycle: that is the one unshippable configuration, because no package * install order can satisfy it. * * Like the other drivers in this package the emit shape is structurally * typed on the change seam — no dependency on `@pgpmjs/bundle` or * `@pgpmjs/core`. Callers materialize packages however they like (write * pgpm.plan + deploy trees, feed a bundle, ...). */ import { PathStyle } from '@pgpmjs/naming-spec'; import { StatementKind } from '@pgsql/semantics'; import { ObjectIdentity, ObjectIdentityKind } from '@pgsql/transform'; /** One deployable unit: an object (by identity) and the statements that build it. */ export interface PartitionUnit { /** The object's identity, or `null` for split riders and the residual unit. */ identity: ObjectIdentity | null; /** Statement kind of the unit's primary statement (`grant` for split grants, ...). */ primaryKind: StatementKind; /** Canonical change path derived from the identity via the naming spec. */ path: string; /** Indexes into the classified statement list, in source order. */ statements: number[]; /** The unit's deploy SQL (statement text joined in source order). */ sql: string; /** * A statement in this unit executes dynamic SQL: its edges are incomplete, * so assignments involving it cannot be proven safe by the graph alone. */ dynamicSql: boolean; } /** * Matches units for assignment. Every given field must match (AND); array * values match any element (OR). Omitted fields match everything, so * `{ schema: 'billing' }` selects a whole schema, `{ kind: 'policy' }` * selects every policy, and `{ path: 'schemas/app/tables/users/table' }` * cherry-picks one object. */ export interface UnitSelector { kind?: ObjectIdentityKind | ObjectIdentityKind[]; /** * Statement-level kind of the unit's primary statement — selects units * that have no object identity, like grants split out via * {@link PartitionConfig.splitRiders} (`statementKind: 'grant'`). */ statementKind?: StatementKind | StatementKind[]; schema?: string | string[]; name?: string | string[]; /** Owning table, for table-scoped kinds (trigger/policy/index/constraint/seed). */ table?: string | string[]; /** The derived naming-spec path — exact cherry-pick. */ path?: string | string[]; } /** Assigns matching units to a package. Rules are tried in order; first match wins. */ export interface PartitionRule { package: string; /** A unit matching ANY of these selectors is assigned to `package`. */ select: UnitSelector[]; /** * Also pull in the transitive dependency closure (hard + fk edges) of the * selected units. Only units that would otherwise land in the default * package are pulled — a unit another rule claimed stays where it is and * becomes an ordinary cross-package dependency. */ closure?: boolean; } export interface PartitionConfig { /** Assignment rules, in priority order. */ rules: PartitionRule[]; /** Package for units no rule matches. */ defaultPackage: string; /** Naming-spec rendering style for derived paths (default `directory`). */ style?: PathStyle; /** * Statement kinds to split out of their host unit into standalone units * (e.g. `['grant']` makes every GRANT its own selectable unit named * `/grants/`), so security surface can be partitioned * independently of the objects it attaches to. Split units depend on * their host, so ordering stays correct wherever they are assigned. */ splitRiders?: StatementKind[]; } /** One emitted change of a partitioned package. */ export interface PartitionedChange { /** Change path (naming spec). */ name: string; /** * Same-package dependencies as plain paths, cross-package dependencies as * `:` — the pgpm cross-package requires convention. */ dependencies: string[]; deploy: string; } export interface PartitionedPackage { name: string; /** Changes in a valid deploy order (statement-graph topological order). */ changes: PartitionedChange[]; /** Package names this package requires (from cross-package edges). */ requires: string[]; } export interface PartitionUnitsResult { packages: PartitionedPackage[]; /** unit path → package name, for every unit. */ assignments: Map; /** Units pulled in by closure rules: path → the rule's package. */ closureIncluded: Map; warnings: string[]; } /** A partition whose packages require each other in a cycle — unshippable. */ export declare class PartitionCycleError extends Error { /** The packages forming the cycle. */ readonly packages: string[]; /** One offending edge per hop: `fromPkg:fromPath -> toPkg:toPath`. */ readonly edges: string[]; constructor( /** The packages forming the cycle. */ packages: string[], /** One offending edge per hop: `fromPkg:fromPath -> toPkg:toPath`. */ edges: string[]); } /** Path of the residual unit for statements attributable to no object. */ export declare const RESIDUAL_UNIT_PATH = "misc/statements"; /** A change's deploy surface going into the partition (same seam as the granularity driver). */ export interface PartitionInputChange { name: string; dependencies: string[]; deploy: string; } /** * Partition a deploy surface into packages. * * Accepts either raw SQL or pgpm changes (whose deploy scripts are flattened * in the given order — run the granularity dial first if a different unit * shape is wanted). Requires `loadModule()` from `plpgsql-parser` first. * * Assignment: for each unit the first matching rule wins; unmatched units go * to `defaultPackage`. Closure rules then pull the transitive dependency * closure (hard + fk) of their units out of the default package. Finally the * requires post-pass renders every unit dependency through the naming spec — * same package as ``, cross-package as `:` — and the * package-level graph is checked for cycles ({@link PartitionCycleError}). */ export declare function partitionUnits(input: string | PartitionInputChange[], config: PartitionConfig): PartitionUnitsResult;