testing/schema_parity.ts

Cross-impl schema parity — structural diff + assertion over two SchemaSnapshots captured via query_schema_snapshot.

Two live impls (TS fuz_app vs Rust spine) are each other's parity reference. After both bootstrap, snapshot each, diff, fail loudly on drift. The diff entries name the specific divergence (column type, missing index, constraint absent on one side) so the error message points at the source.

Consumer pattern (in zzz's integration runner or fuz_app's own cross-backend tests):

const snapshot_a = await query_schema_snapshot(db_after_deno_bootstrap); const snapshot_b = await query_schema_snapshot(db_after_rust_bootstrap); assert_schema_snapshots_equal(snapshot_a, snapshot_b, {a: 'deno', b: 'rust'});

Non-coverage — drift the gate does not detect:

  • regular triggers (pg_trigger); CONSTRAINT TRIGGER is captured via pg_constraint, but standalone CREATE TRIGGER is not
  • views, materialized views, functions, procedures
  • table storage parameters (fillfactor, tablespace, autovacuum settings)
  • column physical order — the snapshot keys columns by name, so two impls with the same columns in different declaration order compare equal (functional parity is preserved; SELECT * ordering is not)
  • COMMENT ON ...
  • the schema_version migration tracker (always excluded — framework bookkeeping, not domain schema)
  • permissions / GRANTs

None of these are used by the current fuz_app auth schema. Extend query_schema_snapshot + SchemaDiff if a consumer's schema reaches for them; omitting them today keeps the diff surface focused on what fuz_app actually emits.

view source

Declarations
#

9 declarations

assert_migration_trackers_equal
#

testing/schema_parity.ts view source

(a: { entries: { namespace: string; name: string; sequence: number; }[]; }, b: { entries: { namespace: string; name: string; sequence: number; }[]; }, labels?: SchemaDiffLabels): void import {assert_migration_trackers_equal} from '@fuzdev/fuz_app/testing/schema_parity.js';

Throw if the two spines' schema_version trackers disagree — the gate for the swap-freely invariant (any consumer can swap TS↔Rust over one DB without re-bootstrapping). This catches what assert_schema_snapshots_equal is blind to by design: the snapshot excludes the tracker, so a migration-name or partitioning divergence that yields an identical *schema* (e.g. cell_v0 vs full_cell_schema, or cell_history bundled vs isolated) passes schema parity but breaks the runner's positional name-prefix check at boot (name-divergence-at-N). The error names the impls and lists every diff.

a

type { entries: { namespace: string; name: string; sequence: number; }[]; }

b

type { entries: { namespace: string; name: string; sequence: number; }[]; }

labels

default {}

returns

void

assert_schema_snapshots_equal
#

testing/schema_parity.ts view source

(a: { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }, b: { ...; }, labels?: SchemaDiffLabels): void import {assert_schema_snapshots_equal} from '@fuzdev/fuz_app/testing/schema_parity.js';

Throw if the two snapshots disagree. The error message names the impls (via labels) and lists every diff, so the failure is self-diagnosing.

Consumers wire this after bootstrapping each impl against an isolated DB:

await drop_recreate_db('zzz_test'); await spawn_backend(deno_config); const snapshot_deno = await query_schema_snapshot(db, {}); await drop_recreate_db('zzz_test'); await spawn_backend(rust_config); const snapshot_rust = await query_schema_snapshot(db, {}); assert_schema_snapshots_equal(snapshot_deno, snapshot_rust, {a: 'deno', b: 'rust'});

a

type { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }

b

type { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }

labels

default {}

returns

void

diff_migration_trackers
#

testing/schema_parity.ts view source

(a: { entries: { namespace: string; name: string; sequence: number; }[]; }, b: { entries: { namespace: string; name: string; sequence: number; }[]; }): MigrationTrackerDiff[] import {diff_migration_trackers} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structural diff between two migration trackers — empty array means the two spines recorded byte-identical migration identity. Keyed on (namespace, name); sequence mismatches on a shared key are reported too. Deterministic order: shared/missing rows in sorted (namespace, name) order.

a

type { entries: { namespace: string; name: string; sequence: number; }[]; }

b

type { entries: { namespace: string; name: string; sequence: number; }[]; }

returns

MigrationTrackerDiff[]

diff_schema_snapshots
#

testing/schema_parity.ts view source

(a: { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }, b: { ...; }): SchemaDiff[] import {diff_schema_snapshots} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structural diff between two snapshots — empty array means parity holds.

Order of diffs is deterministic: tables in sorted order (with column/index/constraint sub-diffs grouped per table), then sequences. Consumers can rely on this for stable diff output.

a

type { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }

b

type { tables: Record<string, { columns: Record<string, { data_type: string; udt_name: string; is_nullable: boolean; column_default: string | null; is_identity: boolean; }>; indexes: { name: string; definition: string; }[]; constraints: { ...; }[]; }>; sequences: Record<...>; enums: Record<...>; }

returns

SchemaDiff[]

format_migration_tracker_diffs
#

testing/schema_parity.ts view source

(diffs: readonly MigrationTrackerDiff[], labels?: SchemaDiffLabels): string import {format_migration_tracker_diffs} from '@fuzdev/fuz_app/testing/schema_parity.js';

Render migration-tracker diffs as a human-readable multi-line string. Empty diffs produce an empty string.

diffs

type readonly MigrationTrackerDiff[]

labels

default {}

returns

string

format_schema_diffs
#

testing/schema_parity.ts view source

(diffs: readonly SchemaDiff[], labels?: SchemaDiffLabels): string import {format_schema_diffs} from '@fuzdev/fuz_app/testing/schema_parity.js';

Render a diff list as a human-readable multi-line string. Empty diffs produce an empty string.

diffs

type readonly SchemaDiff[]

labels

default {}

returns

string

MigrationTrackerDiff
#

testing/schema_parity.ts view source

MigrationTrackerDiff import type {MigrationTrackerDiff} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structured migration-identity drift entry. Keyed on (namespace, name) — the schema_version PK — so a name rename and a partitioning change both surface as tracker_row_only_in, and a re-order surfaces as tracker_sequence_differs.

SchemaDiff
#

testing/schema_parity.ts view source

SchemaDiff import type {SchemaDiff} from '@fuzdev/fuz_app/testing/schema_parity.js';

Structured drift entry. where is the named source impl ('a' or 'b').

SchemaDiffLabels
#

testing/schema_parity.ts view source

SchemaDiffLabels import type {SchemaDiffLabels} from '@fuzdev/fuz_app/testing/schema_parity.js';

Labels used in formatted output — defaults to 'a' and 'b'.

a?

type string

readonly

b?

type string

readonly

Depends on
#