import type { CliConfigValueOrigin } from "./config-document.ts"; import { type CliConfigWithRawPresence, type ConfigAbsencePolicy, type ProjectConfig } from "./project-config/project-config.ts"; /** * Config drift classification between the local project config and the * effective remote configuration reported by the Management API * (`GET /v2/projects/{ref}/config`). Pure and synchronous: fetching the * response, resolving the target, and rendering output are the caller's job * (`supabase config diff`, and `config pull` after it). See ADR 0022. * * Both operands are convergence projections from this package's normalizers * (ADR 0021): the local operand is derived here from the loaded `{config, * document}` pair via `fromConfigDocument` (raw-presence-masked, * canonicalized, secrets omitted), and the caller builds `remote` with * `fromApiProjectConfig(response)`. The comparable surface is the mapping * registry's — a path with no registry row is unmanaged by construction — * and the raw document's declared-key set drives `update` vs `remote_only`, * since a decoded config cannot distinguish "the file wrote the default" * from "the file is silent". * * Paths are segment arrays everywhere in this module's API (a record key — * an `auth.sms.test_otp` phone number, a `[remotes.*]` name — may itself * contain a `.`, so dotted strings are lossy); joining is display-only and * belongs to the renderer. */ export type ConfigChangeClass = "update" | "remote_only" | "local_only"; export interface ConfigChange { /** * Config path segments within the hosted subset, e.g. `["api", * "max_rows"]`. Join for display only — a segment may contain a `.`. */ readonly path: ReadonlyArray; /** * `update`: declared locally and reported remotely, values differ. * `remote_only`: reported remotely while the file does not declare it (or * push cannot communicate the declared state), and differing from the * unconfigured baseline. `local_only`: the local projection carries a * declared value the response did not account for. */ readonly class: ConfigChangeClass; /** * Local convergence-projected value; `undefined` when the projection is * silent. For an undeclared `remote_only` path this is the materialized * schema default — the value a `config push` would write over the remote — * so consumers can answer "what would push change?" without re-deriving it. */ readonly local: unknown; /** Remote value; `undefined` when the response did not report it. */ readonly remote: unknown; /** * Whether the raw document declares this path — distinguishes "the file * wrote this value" from "the local side is a schema-materialized default". */ readonly declared: boolean; /** Environment variables local `env()` references resolved from, if any. */ readonly envVariables?: ReadonlyArray | undefined; } export interface ConfigChangeCounts { readonly update: number; readonly remote_only: number; readonly local_only: number; readonly total: number; } export interface ConfigChangeSet { /** Reportable differences, ordered by path. */ readonly changes: ReadonlyArray; /** * Managed secret paths the file sets a value for (the registry's * `isSecret` rows). These were never compared — the platform reports HMAC * digests, and both normalizers omit secret leaves — so a clean `changes` * list is still only a partial claim; callers must surface this. */ readonly masked: ReadonlyArray>; /** * Comparable non-secret paths the file declares but the local projection * dropped — declared state no actor's write path can express, because the * owning feature is switched off (see ADR 0021's addendum for examples). The * reason isn't always self-contained: a path can be pruned because a * different, sibling path is undeclared. Never compared on the local side, * so — like `masked` — a clean `changes` list is only a partial claim; * callers must surface this. */ readonly unmanaged: ReadonlyArray>; readonly counts: ConfigChangeCounts; /** * Which {@link ConfigAbsencePolicy} `options.local` selected for this call — * `"absent-is-hands-off"` when `options.local.document` was supplied, * `"absent-is-default"` otherwise. See that type's docstring for the danger * matrix this protects against, and {@link DiffProjectConfigOptions.local} * for the classification cliff omitting `document` falls off. */ readonly absencePolicy: ConfigAbsencePolicy; } export interface DiffProjectConfigOptions { /** * The loaded local config: the `{config, document}` pair `fromConfigDocument` * accepts. Omitting `document` is a cliff, not gentle degradation: every * path's `declared` flag becomes `false`, so `local_only` never fires and * `unmanaged`/`masked` are always empty, and this selects * {@link ConfigAbsencePolicy} `"absent-is-default"` instead of * `"absent-is-hands-off"`. */ readonly local: CliConfigWithRawPresence & { readonly valueOrigins?: ReadonlyArray | undefined; }; /** The remote operand: `fromApiProjectConfig(response)`. */ readonly remote: ProjectConfig; } export type ConfigArrayEquality = "set" | "sequence"; /** * Type-aware value equality. Scalars tolerate string/number and * string/boolean representation skew. Arrays default to sequence semantics — * order matters unless the field's registry row opts into `"set"` (e.g. * `auth.additional_redirect_urls` is membership-only, while `api.schemas`' * order matters). Defaulting to sequence over-reports rather than * under-reports drift. */ export declare function isEqualConfigValue(a: unknown, b: unknown, arrayEquality?: ConfigArrayEquality): boolean; /** * Classifies every comparable path into the change set. Pure: no I/O, no * dependency on command flags or output formatting. Runs `fromConfigDocument` * over `options.local`, so a document the registry cannot canonicalize throws * `ProjectConfigParseError` — callers translate at their own boundary. */ export declare function diffProjectConfig(options: DiffProjectConfigOptions): ConfigChangeSet; //# sourceMappingURL=config-diff.d.ts.map