@fuzdev/fuz_app 0.69.0 → 0.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db/CLAUDE.md +3 -3
- package/dist/db/cell_history_ddl.d.ts +1 -1
- package/dist/db/cell_history_ddl.js +1 -1
- package/dist/db/fact_ddl.d.ts +11 -11
- package/dist/db/fact_ddl.d.ts.map +1 -1
- package/dist/db/fact_ddl.js +13 -13
- package/dist/db/fact_queries.d.ts +9 -9
- package/dist/db/fact_queries.d.ts.map +1 -1
- package/dist/db/fact_queries.js +18 -18
- package/dist/db/fact_store.d.ts +3 -3
- package/dist/db/fact_store.js +2 -2
- package/dist/testing/CLAUDE.md +16 -11
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts +57 -0
- package/dist/testing/cross_backend/create_cross_backend_global_setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/create_cross_backend_global_setup.js +31 -0
- package/dist/testing/cross_backend/create_schema_parity_global_setup.d.ts +59 -0
- package/dist/testing/cross_backend/create_schema_parity_global_setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/create_schema_parity_global_setup.js +27 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts +13 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +4 -4
- package/dist/testing/cross_backend/make_cross_backend_project.d.ts +72 -0
- package/dist/testing/cross_backend/make_cross_backend_project.d.ts.map +1 -0
- package/dist/testing/cross_backend/make_cross_backend_project.js +51 -0
- package/dist/testing/cross_backend/setup.d.ts +16 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +16 -0
- package/dist/testing/cross_backend/standard.d.ts +8 -0
- package/dist/testing/cross_backend/standard.d.ts.map +1 -1
- package/dist/testing/cross_backend/standard.js +1 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +71 -11
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +38 -5
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +20 -3
- package/dist/testing/schema_introspect.d.ts +74 -58
- package/dist/testing/schema_introspect.d.ts.map +1 -1
- package/dist/testing/schema_introspect.js +77 -15
- package/dist/testing/schema_parity.d.ts +7 -17
- package/dist/testing/schema_parity.d.ts.map +1 -1
- package/dist/testing/schema_parity.js +3 -37
- package/package.json +1 -1
|
@@ -5,8 +5,6 @@ import './assert_dev_env.js';
|
|
|
5
5
|
*
|
|
6
6
|
* The snapshot covers:
|
|
7
7
|
*
|
|
8
|
-
* - `schema_version` rows (`namespace`, `name`, `sequence`) — captures
|
|
9
|
-
* migration set state across impls
|
|
10
8
|
* - Tables with columns (data type, nullability, default, identity)
|
|
11
9
|
* - Indexes with canonical Postgres-rendered definitions
|
|
12
10
|
* - Constraints (CHECK, FOREIGN KEY, PRIMARY KEY, UNIQUE, EXCLUSION)
|
|
@@ -21,60 +19,80 @@ import './assert_dev_env.js';
|
|
|
21
19
|
*
|
|
22
20
|
* @module
|
|
23
21
|
*/
|
|
22
|
+
import { z } from 'zod';
|
|
24
23
|
import type { Db } from '../db/db.js';
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
24
|
+
/**
|
|
25
|
+
* Per-column structural metadata. The Zod schema is the canonical source
|
|
26
|
+
* for the column shape — `SchemaSnapshot` reuses it as the cross-impl
|
|
27
|
+
* `_testing_schema_snapshot` RPC action's wire validator, so the
|
|
28
|
+
* introspection type and the wire contract can't drift apart.
|
|
29
|
+
*/
|
|
30
|
+
export declare const ColumnSnapshot: z.ZodObject<{
|
|
31
|
+
data_type: z.ZodString;
|
|
32
|
+
udt_name: z.ZodString;
|
|
33
|
+
is_nullable: z.ZodBoolean;
|
|
34
|
+
column_default: z.ZodNullable<z.ZodString>;
|
|
35
|
+
is_identity: z.ZodBoolean;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
export type ColumnSnapshot = z.infer<typeof ColumnSnapshot>;
|
|
38
38
|
/** Per-table structural metadata. */
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
export declare const TableSnapshot: z.ZodObject<{
|
|
40
|
+
columns: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
41
|
+
data_type: z.ZodString;
|
|
42
|
+
udt_name: z.ZodString;
|
|
43
|
+
is_nullable: z.ZodBoolean;
|
|
44
|
+
column_default: z.ZodNullable<z.ZodString>;
|
|
45
|
+
is_identity: z.ZodBoolean;
|
|
46
|
+
}, z.core.$strip>>;
|
|
47
|
+
indexes: z.ZodArray<z.ZodObject<{
|
|
48
|
+
name: z.ZodString;
|
|
49
|
+
definition: z.ZodString;
|
|
50
|
+
}, z.core.$strip>>;
|
|
51
|
+
constraints: z.ZodArray<z.ZodObject<{
|
|
52
|
+
name: z.ZodString;
|
|
53
|
+
type: z.ZodString;
|
|
54
|
+
definition: z.ZodString;
|
|
55
|
+
}, z.core.$strip>>;
|
|
56
|
+
}, z.core.$strip>;
|
|
57
|
+
export type TableSnapshot = z.infer<typeof TableSnapshot>;
|
|
54
58
|
/** Sequence metadata — `data_type` is `bigint` (BIGSERIAL) or `integer` (SERIAL). */
|
|
55
|
-
export
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface SchemaVersionRow {
|
|
60
|
-
readonly namespace: string;
|
|
61
|
-
readonly name: string;
|
|
62
|
-
readonly sequence: number;
|
|
63
|
-
}
|
|
59
|
+
export declare const SequenceSnapshot: z.ZodObject<{
|
|
60
|
+
data_type: z.ZodString;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
export type SequenceSnapshot = z.infer<typeof SequenceSnapshot>;
|
|
64
63
|
/**
|
|
65
|
-
* Normalized database schema snapshot for parity comparison
|
|
64
|
+
* Normalized database schema snapshot for parity comparison — the single
|
|
65
|
+
* source of truth for the snapshot shape across the introspection query
|
|
66
|
+
* (`query_schema_snapshot`), the diff comparator (`schema_parity.ts`), and
|
|
67
|
+
* the cross-impl RPC action's wire validator (`testing_reset_actions.ts`).
|
|
66
68
|
*
|
|
67
69
|
* All fields are deterministically ordered on capture so structural equality
|
|
68
70
|
* via `JSON.stringify` or per-key comparison yields stable results.
|
|
69
71
|
*/
|
|
70
|
-
export
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
export declare const SchemaSnapshot: z.ZodObject<{
|
|
73
|
+
tables: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
74
|
+
columns: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
75
|
+
data_type: z.ZodString;
|
|
76
|
+
udt_name: z.ZodString;
|
|
77
|
+
is_nullable: z.ZodBoolean;
|
|
78
|
+
column_default: z.ZodNullable<z.ZodString>;
|
|
79
|
+
is_identity: z.ZodBoolean;
|
|
80
|
+
}, z.core.$strip>>;
|
|
81
|
+
indexes: z.ZodArray<z.ZodObject<{
|
|
82
|
+
name: z.ZodString;
|
|
83
|
+
definition: z.ZodString;
|
|
84
|
+
}, z.core.$strip>>;
|
|
85
|
+
constraints: z.ZodArray<z.ZodObject<{
|
|
86
|
+
name: z.ZodString;
|
|
87
|
+
type: z.ZodString;
|
|
88
|
+
definition: z.ZodString;
|
|
89
|
+
}, z.core.$strip>>;
|
|
90
|
+
}, z.core.$strip>>;
|
|
91
|
+
sequences: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
92
|
+
data_type: z.ZodString;
|
|
93
|
+
}, z.core.$strip>>;
|
|
94
|
+
}, z.core.$strip>;
|
|
95
|
+
export type SchemaSnapshot = z.infer<typeof SchemaSnapshot>;
|
|
78
96
|
/** Filter options for `query_schema_snapshot`. */
|
|
79
97
|
export interface QuerySchemaSnapshotOptions {
|
|
80
98
|
/**
|
|
@@ -83,8 +101,10 @@ export interface QuerySchemaSnapshotOptions {
|
|
|
83
101
|
*/
|
|
84
102
|
readonly schema?: string;
|
|
85
103
|
/**
|
|
86
|
-
* Tables to exclude from the snapshot. The `schema_version`
|
|
87
|
-
* is always excluded
|
|
104
|
+
* Tables to exclude from the snapshot. The `schema_version` migration
|
|
105
|
+
* tracker is always excluded — it's framework bookkeeping created by the
|
|
106
|
+
* migration runner, identical across impls, and not part of any
|
|
107
|
+
* consumer's domain schema.
|
|
88
108
|
*/
|
|
89
109
|
readonly exclude_tables?: ReadonlyArray<string>;
|
|
90
110
|
}
|
|
@@ -92,15 +112,11 @@ export interface QuerySchemaSnapshotOptions {
|
|
|
92
112
|
* Introspect a live database into a deterministic `SchemaSnapshot`.
|
|
93
113
|
*
|
|
94
114
|
* Reads `information_schema` and `pg_catalog` to capture tables, columns,
|
|
95
|
-
* indexes, constraints,
|
|
96
|
-
* rows. The `applied_at` timestamp is deliberately excluded — only the set
|
|
97
|
-
* of applied migrations matters for parity.
|
|
98
|
-
*
|
|
99
|
-
* The `schema_version` table itself never appears in the `tables` field;
|
|
100
|
-
* its structure is identical across consumers and would only add noise.
|
|
115
|
+
* indexes, constraints, and sequences.
|
|
101
116
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
117
|
+
* The `schema_version` migration tracker never appears in the `tables`
|
|
118
|
+
* field — it's framework bookkeeping created by the migration runner,
|
|
119
|
+
* identical across consumers, and would only add noise.
|
|
104
120
|
*/
|
|
105
121
|
export declare const query_schema_snapshot: (db: Db, options?: QuerySchemaSnapshotOptions) => Promise<SchemaSnapshot>;
|
|
106
122
|
//# sourceMappingURL=schema_introspect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema_introspect.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_introspect.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B
|
|
1
|
+
{"version":3,"file":"schema_introspect.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_introspect.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AAEpC;;;;;GAKG;AACH,eAAO,MAAM,cAAc;;;;;;iBAWzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,qCAAqC;AACrC,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;iBAOxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D,qFAAqF;AACrF,eAAO,MAAM,gBAAgB;;iBAE3B,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEhE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;iBAKzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,kDAAkD;AAClD,MAAM,WAAW,0BAA0B;IAC1C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChD;AAyDD;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GACjC,IAAI,EAAE,EACN,UAAS,0BAA+B,KACtC,OAAO,CAAC,cAAc,CAuGxB,CAAC"}
|
|
@@ -1,4 +1,71 @@
|
|
|
1
1
|
import './assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* PostgreSQL schema introspection — produces a normalized, JSON-serializable
|
|
4
|
+
* snapshot of a database's structure for cross-impl parity checks.
|
|
5
|
+
*
|
|
6
|
+
* The snapshot covers:
|
|
7
|
+
*
|
|
8
|
+
* - Tables with columns (data type, nullability, default, identity)
|
|
9
|
+
* - Indexes with canonical Postgres-rendered definitions
|
|
10
|
+
* - Constraints (CHECK, FOREIGN KEY, PRIMARY KEY, UNIQUE, EXCLUSION)
|
|
11
|
+
* - Sequences with data type — distinguishes `int4` (SERIAL) from `int8`
|
|
12
|
+
* (BIGSERIAL)
|
|
13
|
+
*
|
|
14
|
+
* Designed for `pg_catalog` introspection — works against both PostgreSQL
|
|
15
|
+
* and PGlite. The snapshot is fully deterministic: every collection sorts by
|
|
16
|
+
* a stable key and excludes time-varying fields like `applied_at`.
|
|
17
|
+
*
|
|
18
|
+
* Paired with `schema_parity.ts` for comparison + assertion helpers.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
/**
|
|
24
|
+
* Per-column structural metadata. The Zod schema is the canonical source
|
|
25
|
+
* for the column shape — `SchemaSnapshot` reuses it as the cross-impl
|
|
26
|
+
* `_testing_schema_snapshot` RPC action's wire validator, so the
|
|
27
|
+
* introspection type and the wire contract can't drift apart.
|
|
28
|
+
*/
|
|
29
|
+
export const ColumnSnapshot = z.object({
|
|
30
|
+
/** SQL standard type name from `information_schema.columns.data_type`. */
|
|
31
|
+
data_type: z.string(),
|
|
32
|
+
/** Postgres-native type name from `information_schema.columns.udt_name`. */
|
|
33
|
+
udt_name: z.string(),
|
|
34
|
+
/** `true` when the column accepts NULL. */
|
|
35
|
+
is_nullable: z.boolean(),
|
|
36
|
+
/** Default-value expression as Postgres reports it, or `null` if none. */
|
|
37
|
+
column_default: z.string().nullable(),
|
|
38
|
+
/** `true` when the column was declared GENERATED ... AS IDENTITY. */
|
|
39
|
+
is_identity: z.boolean(),
|
|
40
|
+
});
|
|
41
|
+
/** Per-table structural metadata. */
|
|
42
|
+
export const TableSnapshot = z.object({
|
|
43
|
+
/** Column metadata keyed by column name (sorted on serialization). */
|
|
44
|
+
columns: z.record(z.string(), ColumnSnapshot),
|
|
45
|
+
/** Index definitions as Postgres renders them via `pg_indexes.indexdef`. */
|
|
46
|
+
indexes: z.array(z.object({ name: z.string(), definition: z.string() })),
|
|
47
|
+
/** Constraint definitions as Postgres renders them via `pg_get_constraintdef`. */
|
|
48
|
+
constraints: z.array(z.object({ name: z.string(), type: z.string(), definition: z.string() })),
|
|
49
|
+
});
|
|
50
|
+
/** Sequence metadata — `data_type` is `bigint` (BIGSERIAL) or `integer` (SERIAL). */
|
|
51
|
+
export const SequenceSnapshot = z.object({
|
|
52
|
+
data_type: z.string(),
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Normalized database schema snapshot for parity comparison — the single
|
|
56
|
+
* source of truth for the snapshot shape across the introspection query
|
|
57
|
+
* (`query_schema_snapshot`), the diff comparator (`schema_parity.ts`), and
|
|
58
|
+
* the cross-impl RPC action's wire validator (`testing_reset_actions.ts`).
|
|
59
|
+
*
|
|
60
|
+
* All fields are deterministically ordered on capture so structural equality
|
|
61
|
+
* via `JSON.stringify` or per-key comparison yields stable results.
|
|
62
|
+
*/
|
|
63
|
+
export const SchemaSnapshot = z.object({
|
|
64
|
+
/** Tables keyed by name. */
|
|
65
|
+
tables: z.record(z.string(), TableSnapshot),
|
|
66
|
+
/** Sequences keyed by name. */
|
|
67
|
+
sequences: z.record(z.string(), SequenceSnapshot),
|
|
68
|
+
});
|
|
2
69
|
const sort_keys = (record) => {
|
|
3
70
|
const sorted = {};
|
|
4
71
|
for (const key of Object.keys(record).sort()) {
|
|
@@ -28,26 +95,16 @@ const contype_to_kind = (contype) => {
|
|
|
28
95
|
* Introspect a live database into a deterministic `SchemaSnapshot`.
|
|
29
96
|
*
|
|
30
97
|
* Reads `information_schema` and `pg_catalog` to capture tables, columns,
|
|
31
|
-
* indexes, constraints,
|
|
32
|
-
* rows. The `applied_at` timestamp is deliberately excluded — only the set
|
|
33
|
-
* of applied migrations matters for parity.
|
|
34
|
-
*
|
|
35
|
-
* The `schema_version` table itself never appears in the `tables` field;
|
|
36
|
-
* its structure is identical across consumers and would only add noise.
|
|
98
|
+
* indexes, constraints, and sequences.
|
|
37
99
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
100
|
+
* The `schema_version` migration tracker never appears in the `tables`
|
|
101
|
+
* field — it's framework bookkeeping created by the migration runner,
|
|
102
|
+
* identical across consumers, and would only add noise.
|
|
40
103
|
*/
|
|
41
104
|
export const query_schema_snapshot = async (db, options = {}) => {
|
|
42
105
|
const schema = options.schema ?? 'public';
|
|
43
106
|
const exclude_tables = new Set(options.exclude_tables ?? []);
|
|
44
107
|
exclude_tables.add('schema_version');
|
|
45
|
-
// schema_version rows — the migration tracker. Exclude `applied_at`
|
|
46
|
-
// because timestamps differ across bootstraps even when the migration
|
|
47
|
-
// set is identical.
|
|
48
|
-
const schema_version_rows = await db.query(`SELECT namespace, name, sequence
|
|
49
|
-
FROM schema_version
|
|
50
|
-
ORDER BY namespace ASC, sequence ASC`);
|
|
51
108
|
// All tables in the target schema, minus the excludes.
|
|
52
109
|
const table_rows = await db.query(`SELECT table_name
|
|
53
110
|
FROM information_schema.tables
|
|
@@ -68,6 +125,11 @@ export const query_schema_snapshot = async (db, options = {}) => {
|
|
|
68
125
|
WHERE schemaname = $1
|
|
69
126
|
ORDER BY tablename ASC, indexname ASC`, [schema]);
|
|
70
127
|
// Constraints — pg_get_constraintdef produces a canonical text rendering.
|
|
128
|
+
// Skip NOT NULL constraints (`contype = 'n'`): PG17+ catalogs them as
|
|
129
|
+
// named `pg_constraint` rows while PGlite / older PG don't, and
|
|
130
|
+
// nullability is already captured per-column by `is_nullable` — including
|
|
131
|
+
// them would report a pure engine-cataloging artifact as cross-backend
|
|
132
|
+
// drift between a PGlite and a real-Postgres backend.
|
|
71
133
|
const constraint_rows = await db.query(`SELECT c.conrelid::regclass::text AS table_name,
|
|
72
134
|
c.conname,
|
|
73
135
|
c.contype::text,
|
|
@@ -76,6 +138,7 @@ export const query_schema_snapshot = async (db, options = {}) => {
|
|
|
76
138
|
JOIN pg_namespace n ON n.oid = c.connamespace
|
|
77
139
|
WHERE n.nspname = $1
|
|
78
140
|
AND c.conrelid != 0
|
|
141
|
+
AND c.contype != 'n'
|
|
79
142
|
ORDER BY table_name ASC, conname ASC`, [schema]);
|
|
80
143
|
// Sequences — data_type distinguishes bigint (BIGSERIAL) from integer (SERIAL).
|
|
81
144
|
const sequence_rows = await db.query(`SELECT sequence_name, data_type
|
|
@@ -116,7 +179,6 @@ export const query_schema_snapshot = async (db, options = {}) => {
|
|
|
116
179
|
sequences[row.sequence_name] = { data_type: row.data_type };
|
|
117
180
|
}
|
|
118
181
|
return {
|
|
119
|
-
schema_version: schema_version_rows,
|
|
120
182
|
tables: sort_keys(tables),
|
|
121
183
|
sequences: sort_keys(sequences),
|
|
122
184
|
};
|
|
@@ -6,7 +6,7 @@ import './assert_dev_env.js';
|
|
|
6
6
|
* Two live impls (TS fuz_app vs Rust spine) are each other's parity
|
|
7
7
|
* reference. After both bootstrap, snapshot each, diff, fail loudly on
|
|
8
8
|
* drift. The diff entries name the specific divergence (column type,
|
|
9
|
-
* missing index,
|
|
9
|
+
* missing index, constraint absent on one side) so the error
|
|
10
10
|
* message points at the source.
|
|
11
11
|
*
|
|
12
12
|
* Consumer pattern (in zzz's integration runner or fuz_app's own
|
|
@@ -29,8 +29,8 @@ import './assert_dev_env.js';
|
|
|
29
29
|
* impls with the same columns in different declaration order compare
|
|
30
30
|
* equal (functional parity is preserved; `SELECT *` ordering is not)
|
|
31
31
|
* - `COMMENT ON ...`
|
|
32
|
-
* - the `schema_version`
|
|
33
|
-
*
|
|
32
|
+
* - the `schema_version` migration tracker (always excluded — framework
|
|
33
|
+
* bookkeeping, not domain schema)
|
|
34
34
|
* - permissions / `GRANT`s
|
|
35
35
|
*
|
|
36
36
|
* None of these are used by the current fuz_app auth schema. Extend
|
|
@@ -40,19 +40,9 @@ import './assert_dev_env.js';
|
|
|
40
40
|
*
|
|
41
41
|
* @module
|
|
42
42
|
*/
|
|
43
|
-
import type { ColumnSnapshot, SchemaSnapshot
|
|
43
|
+
import type { ColumnSnapshot, SchemaSnapshot } from './schema_introspect.js';
|
|
44
44
|
/** Structured drift entry. `where` is the named source impl ('a' or 'b'). */
|
|
45
45
|
export type SchemaDiff = {
|
|
46
|
-
readonly kind: 'schema_version_only_in';
|
|
47
|
-
readonly where: 'a' | 'b';
|
|
48
|
-
readonly row: SchemaVersionRow;
|
|
49
|
-
} | {
|
|
50
|
-
readonly kind: 'schema_version_sequence_differs';
|
|
51
|
-
readonly namespace: string;
|
|
52
|
-
readonly name: string;
|
|
53
|
-
readonly a: number;
|
|
54
|
-
readonly b: number;
|
|
55
|
-
} | {
|
|
56
46
|
readonly kind: 'table_only_in';
|
|
57
47
|
readonly where: 'a' | 'b';
|
|
58
48
|
readonly table: string;
|
|
@@ -109,9 +99,9 @@ export type SchemaDiff = {
|
|
|
109
99
|
/**
|
|
110
100
|
* Structural diff between two snapshots — empty array means parity holds.
|
|
111
101
|
*
|
|
112
|
-
* Order of diffs is deterministic:
|
|
113
|
-
*
|
|
114
|
-
*
|
|
102
|
+
* Order of diffs is deterministic: tables in sorted order (with
|
|
103
|
+
* column/index/constraint sub-diffs grouped per table), then sequences.
|
|
104
|
+
* Consumers can rely on this for stable diff output.
|
|
115
105
|
*/
|
|
116
106
|
export declare const diff_schema_snapshots: (a: SchemaSnapshot, b: SchemaSnapshot) => Array<SchemaDiff>;
|
|
117
107
|
/** Labels used in formatted output — defaults to `'a'` and `'b'`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema_parity.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_parity.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EACX,cAAc,EACd,cAAc,
|
|
1
|
+
{"version":3,"file":"schema_parity.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/schema_parity.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,13 +2,12 @@ import './assert_dev_env.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Structural diff between two snapshots — empty array means parity holds.
|
|
4
4
|
*
|
|
5
|
-
* Order of diffs is deterministic:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Order of diffs is deterministic: tables in sorted order (with
|
|
6
|
+
* column/index/constraint sub-diffs grouped per table), then sequences.
|
|
7
|
+
* Consumers can rely on this for stable diff output.
|
|
8
8
|
*/
|
|
9
9
|
export const diff_schema_snapshots = (a, b) => {
|
|
10
10
|
const diffs = [];
|
|
11
|
-
diff_schema_version(a.schema_version, b.schema_version, diffs);
|
|
12
11
|
const all_tables = new Set([...Object.keys(a.tables), ...Object.keys(b.tables)]);
|
|
13
12
|
for (const table of [...all_tables].sort()) {
|
|
14
13
|
const ta = a.tables[table];
|
|
@@ -39,33 +38,6 @@ export const diff_schema_snapshots = (a, b) => {
|
|
|
39
38
|
}
|
|
40
39
|
return diffs;
|
|
41
40
|
};
|
|
42
|
-
const diff_schema_version = (a, b, out) => {
|
|
43
|
-
const key = (r) => `${r.namespace}\x00${r.name}`;
|
|
44
|
-
const a_by_key = new Map(a.map((r) => [key(r), r]));
|
|
45
|
-
const b_by_key = new Map(b.map((r) => [key(r), r]));
|
|
46
|
-
const keys = new Set([...a_by_key.keys(), ...b_by_key.keys()]);
|
|
47
|
-
for (const k of [...keys].sort()) {
|
|
48
|
-
const row_a = a_by_key.get(k);
|
|
49
|
-
const row_b = b_by_key.get(k);
|
|
50
|
-
if (!row_a) {
|
|
51
|
-
out.push({ kind: 'schema_version_only_in', where: 'b', row: row_b });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (!row_b) {
|
|
55
|
-
out.push({ kind: 'schema_version_only_in', where: 'a', row: row_a });
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
if (row_a.sequence !== row_b.sequence) {
|
|
59
|
-
out.push({
|
|
60
|
-
kind: 'schema_version_sequence_differs',
|
|
61
|
-
namespace: row_a.namespace,
|
|
62
|
-
name: row_a.name,
|
|
63
|
-
a: row_a.sequence,
|
|
64
|
-
b: row_b.sequence,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
41
|
const COLUMN_FIELDS = [
|
|
70
42
|
'data_type',
|
|
71
43
|
'udt_name',
|
|
@@ -165,12 +137,6 @@ export const format_schema_diffs = (diffs, labels = {}) => {
|
|
|
165
137
|
const lines = [];
|
|
166
138
|
for (const d of diffs) {
|
|
167
139
|
switch (d.kind) {
|
|
168
|
-
case 'schema_version_only_in':
|
|
169
|
-
lines.push(` schema_version row only in ${where_label(d.where)}: ${d.row.namespace}/${d.row.name} (sequence ${d.row.sequence})`);
|
|
170
|
-
break;
|
|
171
|
-
case 'schema_version_sequence_differs':
|
|
172
|
-
lines.push(` schema_version sequence differs for ${d.namespace}/${d.name}: ${label_a}=${d.a}, ${label_b}=${d.b}`);
|
|
173
|
-
break;
|
|
174
140
|
case 'table_only_in':
|
|
175
141
|
lines.push(` table ${d.table} only in ${where_label(d.where)}`);
|
|
176
142
|
break;
|