@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
package/dist/db/CLAUDE.md
CHANGED
|
@@ -80,10 +80,10 @@ Content-addressed byte store. Cells reference facts by blake3 hash
|
|
|
80
80
|
(`cell.refs`, auto-extracted from `data`); the fact layer stores the bytes.
|
|
81
81
|
Optional — minimal consumers never migrate it.
|
|
82
82
|
|
|
83
|
-
- **`fact_ddl.ts`** — `
|
|
84
|
-
`external_url`, CHECK enforces exactly one) + `
|
|
83
|
+
- **`fact_ddl.ts`** — `fact` (content-addressed bytes: embedded `bytes` xor
|
|
84
|
+
`external_url`, CHECK enforces exactly one) + `fact_ref` (declared
|
|
85
85
|
dependency edges; `target_hash` deliberately not an FK, for federation) +
|
|
86
|
-
`
|
|
86
|
+
`memo` (`(fn_id, input_hash) → output_hash`). `FACT_MIGRATION_NS`, namespace
|
|
87
87
|
`fuz_facts`.
|
|
88
88
|
- **`fact_queries.ts`** — mechanical `query_put_fact` (idempotent `ON CONFLICT
|
|
89
89
|
DO NOTHING`), `_put_fact_refs`, `_get_fact` / `_get_fact_meta` / `_has_fact`
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* downstream code can target a stable schema. The table ships
|
|
9
9
|
* present-but-unwritten.
|
|
10
10
|
*
|
|
11
|
-
* `fact_hash` is intentionally **not** a foreign key to `
|
|
11
|
+
* `fact_hash` is intentionally **not** a foreign key to `fact(hash)` —
|
|
12
12
|
* snapshots may be evicted by GC policy while history rows remain as audit
|
|
13
13
|
* traces, and federation may target facts on another instance.
|
|
14
14
|
*
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* downstream code can target a stable schema. The table ships
|
|
9
9
|
* present-but-unwritten.
|
|
10
10
|
*
|
|
11
|
-
* `fact_hash` is intentionally **not** a foreign key to `
|
|
11
|
+
* `fact_hash` is intentionally **not** a foreign key to `fact(hash)` —
|
|
12
12
|
* snapshots may be evicted by GC policy while history rows remain as audit
|
|
13
13
|
* traces, and federation may target facts on another instance.
|
|
14
14
|
*
|
package/dist/db/fact_ddl.d.ts
CHANGED
|
@@ -3,32 +3,32 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Three tables:
|
|
5
5
|
*
|
|
6
|
-
* - `
|
|
6
|
+
* - `fact` — content-addressed bytes. `hash = 'blake3:<hex64>'`. Either
|
|
7
7
|
* embedded (`bytes`) or referenced (`external_url`); the CHECK constraint
|
|
8
8
|
* enforces exactly one populated. Idempotent: same bytes always produce
|
|
9
9
|
* the same hash, so `INSERT … ON CONFLICT DO NOTHING` is the put primitive.
|
|
10
|
-
* - `
|
|
10
|
+
* - `fact_ref` — declared dependency edges (source fact → target fact).
|
|
11
11
|
* `target_hash` is intentionally **not** a foreign key: in federation a
|
|
12
12
|
* reference may target a fact stored on another instance.
|
|
13
|
-
* - `
|
|
13
|
+
* - `memo` — `(fn_id, input_hash) → output_hash` for memoized computations.
|
|
14
14
|
*
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
17
|
import type { Migration, MigrationNamespace } from './migrate.js';
|
|
18
|
-
/** `
|
|
19
|
-
export declare const FACTS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS
|
|
18
|
+
/** `fact` table — content-addressed byte store. */
|
|
19
|
+
export declare const FACTS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS fact (\n\thash TEXT PRIMARY KEY,\n\tbytes BYTEA,\n\texternal_url TEXT,\n\tcontent_type TEXT,\n\tsize BIGINT NOT NULL,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tCONSTRAINT fact_storage_present CHECK (bytes IS NOT NULL OR external_url IS NOT NULL)\n)";
|
|
20
20
|
/**
|
|
21
|
-
* `
|
|
21
|
+
* `fact_ref` table — declared dependency edges between facts.
|
|
22
22
|
*
|
|
23
23
|
* `target_hash` is not a foreign key (federation: target may live remotely).
|
|
24
24
|
*/
|
|
25
|
-
export declare const FACT_REFS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS
|
|
25
|
+
export declare const FACT_REFS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS fact_ref (\n\tsource_hash TEXT NOT NULL REFERENCES fact(hash) ON DELETE CASCADE,\n\ttarget_hash TEXT NOT NULL,\n\tPRIMARY KEY (source_hash, target_hash)\n)";
|
|
26
26
|
/** Reverse lookup: which facts reference a given target? */
|
|
27
|
-
export declare const FACT_REFS_TARGET_INDEX = "\nCREATE INDEX IF NOT EXISTS
|
|
28
|
-
/** `
|
|
29
|
-
export declare const MEMOS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS
|
|
27
|
+
export declare const FACT_REFS_TARGET_INDEX = "\nCREATE INDEX IF NOT EXISTS idx_fact_ref_target ON fact_ref(target_hash)";
|
|
28
|
+
/** `memo` table — `(fn_id, input_hash) → output_hash` for memoized computations. */
|
|
29
|
+
export declare const MEMOS_SCHEMA = "\nCREATE TABLE IF NOT EXISTS memo (\n\tfn_id TEXT NOT NULL,\n\tinput_hash TEXT NOT NULL,\n\toutput_hash TEXT NOT NULL,\n\tcreated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n\tPRIMARY KEY (fn_id, input_hash)\n)";
|
|
30
30
|
/** Tables created by `FACT_MIGRATION_NS`, in drop order (children first). */
|
|
31
|
-
export declare const FACT_DROP_TABLES: readonly ["
|
|
31
|
+
export declare const FACT_DROP_TABLES: readonly ["memo", "fact_ref", "fact"];
|
|
32
32
|
/** Fact + memo migrations. */
|
|
33
33
|
export declare const FACT_MIGRATIONS: Array<Migration>;
|
|
34
34
|
/** Namespace identifier for fact + memo migrations. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAEhE,
|
|
1
|
+
{"version":3,"file":"fact_ddl.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_ddl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAC,SAAS,EAAE,kBAAkB,EAAC,MAAM,cAAc,CAAC;AAEhE,mDAAmD;AACnD,eAAO,MAAM,YAAY,qSASvB,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6LAK3B,CAAC;AAEH,4DAA4D;AAC5D,eAAO,MAAM,sBAAsB,8EACqC,CAAC;AAEzE,oFAAoF;AACpF,eAAO,MAAM,YAAY,mNAOvB,CAAC;AAEH,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,uCAAwC,CAAC;AAEtE,8BAA8B;AAC9B,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,SAAS,CAU5C,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,wBAAwB,cAAc,CAAC;AAEpD,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,EAAE,kBAG/B,CAAC"}
|
package/dist/db/fact_ddl.js
CHANGED
|
@@ -3,45 +3,45 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Three tables:
|
|
5
5
|
*
|
|
6
|
-
* - `
|
|
6
|
+
* - `fact` — content-addressed bytes. `hash = 'blake3:<hex64>'`. Either
|
|
7
7
|
* embedded (`bytes`) or referenced (`external_url`); the CHECK constraint
|
|
8
8
|
* enforces exactly one populated. Idempotent: same bytes always produce
|
|
9
9
|
* the same hash, so `INSERT … ON CONFLICT DO NOTHING` is the put primitive.
|
|
10
|
-
* - `
|
|
10
|
+
* - `fact_ref` — declared dependency edges (source fact → target fact).
|
|
11
11
|
* `target_hash` is intentionally **not** a foreign key: in federation a
|
|
12
12
|
* reference may target a fact stored on another instance.
|
|
13
|
-
* - `
|
|
13
|
+
* - `memo` — `(fn_id, input_hash) → output_hash` for memoized computations.
|
|
14
14
|
*
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
|
-
/** `
|
|
17
|
+
/** `fact` table — content-addressed byte store. */
|
|
18
18
|
export const FACTS_SCHEMA = `
|
|
19
|
-
CREATE TABLE IF NOT EXISTS
|
|
19
|
+
CREATE TABLE IF NOT EXISTS fact (
|
|
20
20
|
hash TEXT PRIMARY KEY,
|
|
21
21
|
bytes BYTEA,
|
|
22
22
|
external_url TEXT,
|
|
23
23
|
content_type TEXT,
|
|
24
24
|
size BIGINT NOT NULL,
|
|
25
25
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
26
|
-
CONSTRAINT
|
|
26
|
+
CONSTRAINT fact_storage_present CHECK (bytes IS NOT NULL OR external_url IS NOT NULL)
|
|
27
27
|
)`;
|
|
28
28
|
/**
|
|
29
|
-
* `
|
|
29
|
+
* `fact_ref` table — declared dependency edges between facts.
|
|
30
30
|
*
|
|
31
31
|
* `target_hash` is not a foreign key (federation: target may live remotely).
|
|
32
32
|
*/
|
|
33
33
|
export const FACT_REFS_SCHEMA = `
|
|
34
|
-
CREATE TABLE IF NOT EXISTS
|
|
35
|
-
source_hash TEXT NOT NULL REFERENCES
|
|
34
|
+
CREATE TABLE IF NOT EXISTS fact_ref (
|
|
35
|
+
source_hash TEXT NOT NULL REFERENCES fact(hash) ON DELETE CASCADE,
|
|
36
36
|
target_hash TEXT NOT NULL,
|
|
37
37
|
PRIMARY KEY (source_hash, target_hash)
|
|
38
38
|
)`;
|
|
39
39
|
/** Reverse lookup: which facts reference a given target? */
|
|
40
40
|
export const FACT_REFS_TARGET_INDEX = `
|
|
41
|
-
CREATE INDEX IF NOT EXISTS
|
|
42
|
-
/** `
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_fact_ref_target ON fact_ref(target_hash)`;
|
|
42
|
+
/** `memo` table — `(fn_id, input_hash) → output_hash` for memoized computations. */
|
|
43
43
|
export const MEMOS_SCHEMA = `
|
|
44
|
-
CREATE TABLE IF NOT EXISTS
|
|
44
|
+
CREATE TABLE IF NOT EXISTS memo (
|
|
45
45
|
fn_id TEXT NOT NULL,
|
|
46
46
|
input_hash TEXT NOT NULL,
|
|
47
47
|
output_hash TEXT NOT NULL,
|
|
@@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS memos (
|
|
|
49
49
|
PRIMARY KEY (fn_id, input_hash)
|
|
50
50
|
)`;
|
|
51
51
|
/** Tables created by `FACT_MIGRATION_NS`, in drop order (children first). */
|
|
52
|
-
export const FACT_DROP_TABLES = ['
|
|
52
|
+
export const FACT_DROP_TABLES = ['memo', 'fact_ref', 'fact'];
|
|
53
53
|
/** Fact + memo migrations. */
|
|
54
54
|
export const FACT_MIGRATIONS = [
|
|
55
55
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Raw queries against the `
|
|
2
|
+
* Raw queries against the `fact` and `fact_ref` tables.
|
|
3
3
|
*
|
|
4
4
|
* Convention: `deps: QueryDeps` first, no audit side effects, mutations are
|
|
5
5
|
* idempotent (`ON CONFLICT DO NOTHING`) so the same hash can be written by
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { QueryDeps } from './query_deps.js';
|
|
15
15
|
import type { FactHash } from '@fuzdev/fuz_util/fact_hash.js';
|
|
16
|
-
/** Row shape for `SELECT … FROM
|
|
16
|
+
/** Row shape for `SELECT … FROM fact`. */
|
|
17
17
|
export interface FactRow {
|
|
18
18
|
hash: FactHash;
|
|
19
19
|
bytes: Uint8Array | null;
|
|
@@ -33,11 +33,11 @@ export interface FactMetaRow {
|
|
|
33
33
|
/**
|
|
34
34
|
* Idempotently insert a fact row.
|
|
35
35
|
*
|
|
36
|
-
* `bytes` xor `external_url` per the `
|
|
36
|
+
* `bytes` xor `external_url` per the `fact_storage_present` CHECK
|
|
37
37
|
* constraint; the caller is responsible for satisfying it (the queries
|
|
38
38
|
* layer does not second-guess). Returns `true` when a new row was
|
|
39
39
|
* inserted, `false` when a row already existed (caller can use this to
|
|
40
|
-
* decide whether to also write `
|
|
40
|
+
* decide whether to also write `fact_ref`).
|
|
41
41
|
*/
|
|
42
42
|
export declare const query_put_fact: (deps: QueryDeps, input: {
|
|
43
43
|
hash: FactHash;
|
|
@@ -62,7 +62,7 @@ export declare const query_get_fact: (deps: QueryDeps, hash: FactHash) => Promis
|
|
|
62
62
|
*/
|
|
63
63
|
export declare const query_get_fact_meta: (deps: QueryDeps, hash: FactHash) => Promise<FactMetaRow | null>;
|
|
64
64
|
/**
|
|
65
|
-
* Cheap existence check. Backed by the `
|
|
65
|
+
* Cheap existence check. Backed by the `fact` PK index.
|
|
66
66
|
*/
|
|
67
67
|
export declare const query_has_fact: (deps: QueryDeps, hash: FactHash) => Promise<boolean>;
|
|
68
68
|
/**
|
|
@@ -71,7 +71,7 @@ export declare const query_has_fact: (deps: QueryDeps, hash: FactHash) => Promis
|
|
|
71
71
|
*/
|
|
72
72
|
export declare const query_get_fact_refs: (deps: QueryDeps, source_hash: FactHash) => Promise<Array<FactHash>>;
|
|
73
73
|
/**
|
|
74
|
-
* Drop a fact row. Cascades `
|
|
74
|
+
* Drop a fact row. Cascades `fact_ref` rows via the `ON DELETE CASCADE`
|
|
75
75
|
* FK on `source_hash`. Returns the deleted row's `(size, external_url)`
|
|
76
76
|
* so the caller can unlink the disk file (if any) and tally freed bytes,
|
|
77
77
|
* or `null` when no row matched (idempotent: deleting an absent fact is
|
|
@@ -104,17 +104,17 @@ export interface OrphanFactsListResult {
|
|
|
104
104
|
}>;
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
-
* Compute the "orphan facts" set: rows in `
|
|
107
|
+
* Compute the "orphan facts" set: rows in `fact` where no active
|
|
108
108
|
* (non-tombstone) `cell.refs` array contains the hash.
|
|
109
109
|
*
|
|
110
|
-
* The `cell` join is deliberately app-coupled — `
|
|
110
|
+
* The `cell` join is deliberately app-coupled — `fact` lives in the
|
|
111
111
|
* `fuz_facts` namespace and `cell.refs` lives in `fuz_cell`, but the
|
|
112
112
|
* orphan predicate only makes sense in apps that route content through
|
|
113
113
|
* cells. When a non-cell fact consumer ever appears (signed memo
|
|
114
114
|
* outputs? external fact mirrors?) the predicate moves to a generic
|
|
115
115
|
* `fact_consumers` registry; today the cell layer is the only consumer.
|
|
116
116
|
*
|
|
117
|
-
* The `older_than` filter applies to `
|
|
117
|
+
* The `older_than` filter applies to `fact.created_at`. Pass `null`
|
|
118
118
|
* to skip the filter (used by the list-summary preview); the delete
|
|
119
119
|
* handler always passes a non-null cutoff (default 0, meaning "any
|
|
120
120
|
* orphan").
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fact_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE/C,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,+BAA+B,CAAC;AAE5D,
|
|
1
|
+
{"version":3,"file":"fact_queries.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/db/fact_queries.ts"],"names":[],"mappings":"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"}
|
package/dist/db/fact_queries.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Raw queries against the `
|
|
2
|
+
* Raw queries against the `fact` and `fact_ref` tables.
|
|
3
3
|
*
|
|
4
4
|
* Convention: `deps: QueryDeps` first, no audit side effects, mutations are
|
|
5
5
|
* idempotent (`ON CONFLICT DO NOTHING`) so the same hash can be written by
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
/**
|
|
15
15
|
* Idempotently insert a fact row.
|
|
16
16
|
*
|
|
17
|
-
* `bytes` xor `external_url` per the `
|
|
17
|
+
* `bytes` xor `external_url` per the `fact_storage_present` CHECK
|
|
18
18
|
* constraint; the caller is responsible for satisfying it (the queries
|
|
19
19
|
* layer does not second-guess). Returns `true` when a new row was
|
|
20
20
|
* inserted, `false` when a row already existed (caller can use this to
|
|
21
|
-
* decide whether to also write `
|
|
21
|
+
* decide whether to also write `fact_ref`).
|
|
22
22
|
*/
|
|
23
23
|
export const query_put_fact = async (deps, input) => {
|
|
24
|
-
const row = await deps.db.query_one(`INSERT INTO
|
|
24
|
+
const row = await deps.db.query_one(`INSERT INTO fact (hash, bytes, external_url, content_type, size)
|
|
25
25
|
VALUES ($1, $2, $3, $4, $5)
|
|
26
26
|
ON CONFLICT (hash) DO NOTHING
|
|
27
27
|
RETURNING hash`, [input.hash, input.bytes, input.external_url, input.content_type, input.size]);
|
|
@@ -35,7 +35,7 @@ export const query_put_fact = async (deps, input) => {
|
|
|
35
35
|
export const query_put_fact_refs = async (deps, source_hash, target_hashes) => {
|
|
36
36
|
if (target_hashes.length === 0)
|
|
37
37
|
return;
|
|
38
|
-
await deps.db.query(`INSERT INTO
|
|
38
|
+
await deps.db.query(`INSERT INTO fact_ref (source_hash, target_hash)
|
|
39
39
|
SELECT $1::text, unnest($2::text[])
|
|
40
40
|
ON CONFLICT (source_hash, target_hash) DO NOTHING`, [source_hash, target_hashes]);
|
|
41
41
|
};
|
|
@@ -45,7 +45,7 @@ export const query_put_fact_refs = async (deps, source_hash, target_hashes) => {
|
|
|
45
45
|
*/
|
|
46
46
|
export const query_get_fact = async (deps, hash) => {
|
|
47
47
|
const row = await deps.db.query_one(`SELECT hash, bytes, external_url, content_type, size, created_at
|
|
48
|
-
FROM
|
|
48
|
+
FROM fact WHERE hash = $1`, [hash]);
|
|
49
49
|
return row ?? null;
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
@@ -53,14 +53,14 @@ export const query_get_fact = async (deps, hash) => {
|
|
|
53
53
|
*/
|
|
54
54
|
export const query_get_fact_meta = async (deps, hash) => {
|
|
55
55
|
const row = await deps.db.query_one(`SELECT hash, external_url, content_type, size, created_at
|
|
56
|
-
FROM
|
|
56
|
+
FROM fact WHERE hash = $1`, [hash]);
|
|
57
57
|
return row ?? null;
|
|
58
58
|
};
|
|
59
59
|
/**
|
|
60
|
-
* Cheap existence check. Backed by the `
|
|
60
|
+
* Cheap existence check. Backed by the `fact` PK index.
|
|
61
61
|
*/
|
|
62
62
|
export const query_has_fact = async (deps, hash) => {
|
|
63
|
-
const row = await deps.db.query_one(`SELECT EXISTS(SELECT 1 FROM
|
|
63
|
+
const row = await deps.db.query_one(`SELECT EXISTS(SELECT 1 FROM fact WHERE hash = $1) AS exists`, [hash]);
|
|
64
64
|
return row?.exists ?? false;
|
|
65
65
|
};
|
|
66
66
|
/**
|
|
@@ -68,11 +68,11 @@ export const query_has_fact = async (deps, hash) => {
|
|
|
68
68
|
* that need stable ordering should sort.
|
|
69
69
|
*/
|
|
70
70
|
export const query_get_fact_refs = async (deps, source_hash) => {
|
|
71
|
-
const rows = await deps.db.query(`SELECT target_hash FROM
|
|
71
|
+
const rows = await deps.db.query(`SELECT target_hash FROM fact_ref WHERE source_hash = $1`, [source_hash]);
|
|
72
72
|
return rows.map((r) => r.target_hash);
|
|
73
73
|
};
|
|
74
74
|
/**
|
|
75
|
-
* Drop a fact row. Cascades `
|
|
75
|
+
* Drop a fact row. Cascades `fact_ref` rows via the `ON DELETE CASCADE`
|
|
76
76
|
* FK on `source_hash`. Returns the deleted row's `(size, external_url)`
|
|
77
77
|
* so the caller can unlink the disk file (if any) and tally freed bytes,
|
|
78
78
|
* or `null` when no row matched (idempotent: deleting an absent fact is
|
|
@@ -84,24 +84,24 @@ export const query_get_fact_refs = async (deps, source_hash) => {
|
|
|
84
84
|
* `PgFactStore.delete` handles the disk-file unlink.
|
|
85
85
|
*/
|
|
86
86
|
export const query_delete_fact = async (deps, hash) => {
|
|
87
|
-
const row = await deps.db.query_one(`DELETE FROM
|
|
87
|
+
const row = await deps.db.query_one(`DELETE FROM fact WHERE hash = $1
|
|
88
88
|
RETURNING size, external_url`, [hash]);
|
|
89
89
|
if (!row)
|
|
90
90
|
return null;
|
|
91
91
|
return { size: Number(row.size), external_url: row.external_url };
|
|
92
92
|
};
|
|
93
93
|
/**
|
|
94
|
-
* Compute the "orphan facts" set: rows in `
|
|
94
|
+
* Compute the "orphan facts" set: rows in `fact` where no active
|
|
95
95
|
* (non-tombstone) `cell.refs` array contains the hash.
|
|
96
96
|
*
|
|
97
|
-
* The `cell` join is deliberately app-coupled — `
|
|
97
|
+
* The `cell` join is deliberately app-coupled — `fact` lives in the
|
|
98
98
|
* `fuz_facts` namespace and `cell.refs` lives in `fuz_cell`, but the
|
|
99
99
|
* orphan predicate only makes sense in apps that route content through
|
|
100
100
|
* cells. When a non-cell fact consumer ever appears (signed memo
|
|
101
101
|
* outputs? external fact mirrors?) the predicate moves to a generic
|
|
102
102
|
* `fact_consumers` registry; today the cell layer is the only consumer.
|
|
103
103
|
*
|
|
104
|
-
* The `older_than` filter applies to `
|
|
104
|
+
* The `older_than` filter applies to `fact.created_at`. Pass `null`
|
|
105
105
|
* to skip the filter (used by the list-summary preview); the delete
|
|
106
106
|
* handler always passes a non-null cutoff (default 0, meaning "any
|
|
107
107
|
* orphan").
|
|
@@ -113,7 +113,7 @@ export const query_delete_fact = async (deps, hash) => {
|
|
|
113
113
|
*/
|
|
114
114
|
export const query_orphan_facts_list = async (deps, older_than, sample_limit) => {
|
|
115
115
|
const summary = await deps.db.query_one(`SELECT COUNT(*)::bigint AS count, COALESCE(SUM(size), 0)::bigint AS total
|
|
116
|
-
FROM
|
|
116
|
+
FROM fact f
|
|
117
117
|
WHERE NOT EXISTS (
|
|
118
118
|
SELECT 1 FROM cell c
|
|
119
119
|
WHERE c.refs @> ARRAY[f.hash]::text[]
|
|
@@ -121,7 +121,7 @@ export const query_orphan_facts_list = async (deps, older_than, sample_limit) =>
|
|
|
121
121
|
)
|
|
122
122
|
AND ($1::timestamptz IS NULL OR f.created_at < $1::timestamptz)`, [older_than]);
|
|
123
123
|
const sample_rows = await deps.db.query(`SELECT hash, size, created_at, external_url
|
|
124
|
-
FROM
|
|
124
|
+
FROM fact f
|
|
125
125
|
WHERE NOT EXISTS (
|
|
126
126
|
SELECT 1 FROM cell c
|
|
127
127
|
WHERE c.refs @> ARRAY[f.hash]::text[]
|
|
@@ -150,7 +150,7 @@ export const query_orphan_facts_list = async (deps, older_than, sample_limit) =>
|
|
|
150
150
|
*/
|
|
151
151
|
export const query_orphan_facts_select_for_delete = async (deps, older_than) => {
|
|
152
152
|
const rows = await deps.db.query(`SELECT hash, size, external_url
|
|
153
|
-
FROM
|
|
153
|
+
FROM fact f
|
|
154
154
|
WHERE NOT EXISTS (
|
|
155
155
|
SELECT 1 FROM cell c
|
|
156
156
|
WHERE c.refs @> ARRAY[f.hash]::text[]
|
package/dist/db/fact_store.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export declare const create_default_fetcher: () => FactExternalFetcher;
|
|
|
42
42
|
* Construction-time deps for `PgFactStore`.
|
|
43
43
|
*
|
|
44
44
|
* `embedded_threshold` (bytes) is the inline-vs-external cutoff: payloads
|
|
45
|
-
* at or under it store embedded in the `
|
|
45
|
+
* at or under it store embedded in the `fact` row, larger ones route to
|
|
46
46
|
* the external fetcher. Defaults to `FACT_EMBEDDED_THRESHOLD_DEFAULT`
|
|
47
47
|
* (1 MiB). Consumers tune it per workload — e.g. a much lower bound
|
|
48
48
|
* (~16 KiB) keeps only small JSON inline and routes image originals +
|
|
@@ -86,8 +86,8 @@ export declare class PgFactStore implements FactStore {
|
|
|
86
86
|
get_meta(hash: FactHash): Promise<FactMeta | null>;
|
|
87
87
|
get_refs(hash: FactHash): Promise<Array<FactHash>>;
|
|
88
88
|
/**
|
|
89
|
-
* Drop a fact row. `
|
|
90
|
-
* cascade via the FK; `
|
|
89
|
+
* Drop a fact row. `fact_ref` rows referencing this hash as a source
|
|
90
|
+
* cascade via the FK; `fact_ref` targeting this hash do **not** —
|
|
91
91
|
* they remain as dangling pointers, consistent with the federation
|
|
92
92
|
* model where `target_hash` is intentionally not a FK.
|
|
93
93
|
*
|
package/dist/db/fact_store.js
CHANGED
|
@@ -157,8 +157,8 @@ export class PgFactStore {
|
|
|
157
157
|
return query_get_fact_refs(this.#deps, hash);
|
|
158
158
|
}
|
|
159
159
|
/**
|
|
160
|
-
* Drop a fact row. `
|
|
161
|
-
* cascade via the FK; `
|
|
160
|
+
* Drop a fact row. `fact_ref` rows referencing this hash as a source
|
|
161
|
+
* cascade via the FK; `fact_ref` targeting this hash do **not** —
|
|
162
162
|
* they remain as dangling pointers, consistent with the federation
|
|
163
163
|
* model where `target_hash` is intentionally not a FK.
|
|
164
164
|
*
|
package/dist/testing/CLAUDE.md
CHANGED
|
@@ -209,25 +209,26 @@ test-only by construction.
|
|
|
209
209
|
|
|
210
210
|
### `schema_introspect.ts` — `query_schema_snapshot`
|
|
211
211
|
|
|
212
|
-
- `query_schema_snapshot(db, options?)` — introspects a live DB into a deterministic `SchemaSnapshot` via `pg_catalog` + `information_schema`. Captures tables, columns (with `udt_name` to distinguish int4/int8), indexes (`indexdef`), constraints (`pg_get_constraintdef`), sequences
|
|
213
|
-
- `SchemaSnapshot` —
|
|
212
|
+
- `query_schema_snapshot(db, options?)` — introspects a live DB into a deterministic `SchemaSnapshot` via `pg_catalog` + `information_schema`. Captures tables, columns (with `udt_name` to distinguish int4/int8), indexes (`indexdef`), constraints (`pg_get_constraintdef`), and sequences. The `schema_version` migration tracker is always excluded — it's framework bookkeeping, not domain schema, and impls organize migration namespaces differently.
|
|
213
|
+
- `SchemaSnapshot` — the Zod schema is canonical (co-located in `schema_introspect.ts`; the cross-impl `_testing_schema_snapshot` RPC action reuses it as its wire validator, and the type is `z.infer`'d from it). Fully JSON-serializable; every collection deterministically sorted on capture so structural equality is stable across runs.
|
|
214
214
|
|
|
215
215
|
### `schema_parity.ts` — `assert_schema_snapshots_equal`
|
|
216
216
|
|
|
217
217
|
- `diff_schema_snapshots(a, b)` — structured `Array<SchemaDiff>` between two snapshots; empty array means parity holds.
|
|
218
218
|
- `format_schema_diffs(diffs, labels?)` — human-readable multi-line rendering; labels name the impl on each side (e.g., `{a: 'deno', b: 'rust'}`).
|
|
219
219
|
- `assert_schema_snapshots_equal(a, b, labels?)` — throws on drift with a fully-formatted diff message.
|
|
220
|
-
- `SchemaDiff` — tagged-union per drift kind: `
|
|
220
|
+
- `SchemaDiff` — tagged-union per drift kind: `table_only_in`, `column_only_in`, `column_field_differs`, `index_only_in`, `index_definition_differs`, `constraint_only_in`, `constraint_differs`, `sequence_only_in`, `sequence_data_type_differs`.
|
|
221
221
|
|
|
222
|
-
**Cross-impl gate pattern** —
|
|
223
|
-
|
|
224
|
-
bootstrap each impl against an isolated
|
|
222
|
+
**Cross-impl gate pattern** — a dual-impl consumer running two backends
|
|
223
|
+
(a TS Hono server and a Rust spine server) against a shared schema, plus
|
|
224
|
+
fuz_app's own cross-backend suite, bootstrap each impl against an isolated
|
|
225
|
+
DB, snapshot, then compare:
|
|
225
226
|
|
|
226
227
|
```ts
|
|
227
|
-
await drop_recreate_db('
|
|
228
|
+
await drop_recreate_db('app_test');
|
|
228
229
|
await spawn_backend(deno_config);
|
|
229
230
|
const snapshot_deno = await query_schema_snapshot(db);
|
|
230
|
-
await drop_recreate_db('
|
|
231
|
+
await drop_recreate_db('app_test');
|
|
231
232
|
await spawn_backend(rust_config);
|
|
232
233
|
const snapshot_rust = await query_schema_snapshot(db);
|
|
233
234
|
assert_schema_snapshots_equal(snapshot_deno, snapshot_rust, {a: 'deno', b: 'rust'});
|
|
@@ -789,8 +790,8 @@ points:
|
|
|
789
790
|
The standard test suites take a unified
|
|
790
791
|
`{setup_test, surface_source, capabilities}` shape so the same suite bodies
|
|
791
792
|
run against an in-process Hono harness today and against a spawned backend
|
|
792
|
-
over real HTTP — either the Rust spine (`zzz_server`,
|
|
793
|
-
the non-domain `testing_spine_stub`) or a **TS** spine binary built on the
|
|
793
|
+
over real HTTP — either the Rust spine (`zzz_server`, another consumer's
|
|
794
|
+
spine server, or the non-domain `testing_spine_stub`) or a **TS** spine binary built on the
|
|
794
795
|
test-server core below (fuz_app's own domain-free `testing_spine_server`, run
|
|
795
796
|
on Node + Deno + Bun). In-process is the fast feedback path; cross-process is the
|
|
796
797
|
source of truth for wire-shape conformance.
|
|
@@ -1094,7 +1095,11 @@ in-process legs (plain `gro test`) are `src/test/auth/cell_crud_parity.db.test.t
|
|
|
1094
1095
|
same bootstrap-equivalent step (the only path for roles like
|
|
1095
1096
|
`ROLE_KEEPER` whose `grant_paths` is bootstrap-only), refreshes
|
|
1096
1097
|
`DaemonTokenState.keeper_account_id` to the new row, then fires the
|
|
1097
|
-
consumer-supplied `reset_state` callback for domain-state reset
|
|
1098
|
+
consumer-supplied `reset_state(db)` callback for domain-state reset —
|
|
1099
|
+
passed the **transactional** `Db` the auth wipe ran on, so DB-domain
|
|
1100
|
+
consumers (e.g. fuz_forge truncating its cell / fact / file tables) reset
|
|
1101
|
+
on the same connection rather than a separately-pooled one that would
|
|
1102
|
+
deadlock against this open transaction under PGlite. Auth
|
|
1098
1103
|
gates on `credential_types: ['daemon_token']` — effectively keeper-only
|
|
1099
1104
|
without forcing the `actor: 'required'` ⟺ `acting?: ActingActor`
|
|
1100
1105
|
biconditional. No free-form runtime grant action exists — see the
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generic vitest `globalSetup` factory for cross-backend integration suites.
|
|
4
|
+
*
|
|
5
|
+
* Pairs with `make_cross_backend_project`: each cross-backend vitest project
|
|
6
|
+
* sets its own `test.name`, and this factory derives the backend name from
|
|
7
|
+
* that project name (vitest 4 passes the `TestProject` to globalSetup), picks
|
|
8
|
+
* the matching `BackendConfig`, spawns + bootstraps it via `bootstrap_backend`,
|
|
9
|
+
* and `provide`s a serializable handle that `*.cross.test.ts` files
|
|
10
|
+
* `inject` and rebuild with `reconstruct_bootstrapped_handle`.
|
|
11
|
+
*
|
|
12
|
+
* A consumer's `global_setup.ts` collapses to:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import {create_cross_backend_global_setup} from
|
|
16
|
+
* '@fuzdev/fuz_app/testing/cross_backend/create_cross_backend_global_setup.js';
|
|
17
|
+
* import {deno_backend_config, rust_backend_config} from './my_backend_config.js';
|
|
18
|
+
* import './cross_test_types.js'; // augments inject('backend_handle')
|
|
19
|
+
*
|
|
20
|
+
* export default create_cross_backend_global_setup({
|
|
21
|
+
* configs: {deno: deno_backend_config, rust: rust_backend_config},
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* vitest 4's `provide` hard-rejects non-serializable values, so the live
|
|
26
|
+
* `child` / `teardown` / `keeper_transport` are stripped via
|
|
27
|
+
* `serialize_bootstrapped_handle`; the teardown closure stays in the
|
|
28
|
+
* globalSetup process and is returned for vitest to fire after the suite.
|
|
29
|
+
*
|
|
30
|
+
* @module
|
|
31
|
+
*/
|
|
32
|
+
import type { TestProject } from 'vitest/node';
|
|
33
|
+
import type { BackendConfig } from './backend_config.js';
|
|
34
|
+
export interface CrossBackendGlobalSetupOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Map of derived backend name → `BackendConfig` factory. The derived
|
|
37
|
+
* name (see `derive_name`) selects the factory; unknown names throw with
|
|
38
|
+
* the full supported list so a misnamed project surfaces clearly.
|
|
39
|
+
*/
|
|
40
|
+
readonly configs: Readonly<Record<string, () => BackendConfig>>;
|
|
41
|
+
/**
|
|
42
|
+
* Derive the backend name from the vitest project name. Default strips
|
|
43
|
+
* `cross_backend_(ts_)?`.
|
|
44
|
+
*/
|
|
45
|
+
readonly derive_name?: (project_name: string) => string;
|
|
46
|
+
/**
|
|
47
|
+
* Key passed to `project.provide` (and read by `inject` in test files).
|
|
48
|
+
* Default `'backend_handle'`. Augment vitest's `ProvidedContext` for it.
|
|
49
|
+
*/
|
|
50
|
+
readonly provide_key?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build a vitest `globalSetup` default export. Returns the
|
|
54
|
+
* `(project) => teardown` function vitest 4 expects.
|
|
55
|
+
*/
|
|
56
|
+
export declare const create_cross_backend_global_setup: ({ configs, derive_name, provide_key, }: CrossBackendGlobalSetupOptions) => ((project: TestProject) => Promise<() => Promise<void>>);
|
|
57
|
+
//# sourceMappingURL=create_cross_backend_global_setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create_cross_backend_global_setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/create_cross_backend_global_setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAYvD,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,aAAa,CAAC,CAAC,CAAC;IAChE;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,eAAO,MAAM,iCAAiC,GAAI,wCAI/C,8BAA8B,KAAG,CAAC,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAqB1F,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import { bootstrap_backend } from './bootstrap_backend.js';
|
|
3
|
+
import { serialize_bootstrapped_handle } from './setup.js';
|
|
4
|
+
/**
|
|
5
|
+
* Default project-name → backend-name reduction: strips the
|
|
6
|
+
* `cross_backend_` prefix plus an optional `ts_` discriminator (the TS
|
|
7
|
+
* canonical backends carry it to distinguish JS runtimes). So
|
|
8
|
+
* `cross_backend_ts_deno` → `deno` and `cross_backend_rust` → `rust`.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_PROJECT_NAME_PREFIX = /^cross_backend_(?:ts_)?/;
|
|
11
|
+
/**
|
|
12
|
+
* Build a vitest `globalSetup` default export. Returns the
|
|
13
|
+
* `(project) => teardown` function vitest 4 expects.
|
|
14
|
+
*/
|
|
15
|
+
export const create_cross_backend_global_setup = ({ configs, derive_name = (project_name) => project_name.replace(DEFAULT_PROJECT_NAME_PREFIX, ''), provide_key = 'backend_handle', }) => {
|
|
16
|
+
return async (project) => {
|
|
17
|
+
const name = derive_name(project.name);
|
|
18
|
+
const factory = configs[name];
|
|
19
|
+
if (!factory) {
|
|
20
|
+
throw new Error(`Could not derive backend config from vitest project '${project.name}' ` +
|
|
21
|
+
`(derived name '${name}') — expected one of: ${Object.keys(configs).join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
const bootstrapped = await bootstrap_backend(factory());
|
|
24
|
+
// vitest's `provide` is keyed on the consumer-augmented `ProvidedContext`;
|
|
25
|
+
// the generic key is a plain string, so cast past the keyof constraint.
|
|
26
|
+
project.provide(provide_key, serialize_bootstrapped_handle(bootstrapped));
|
|
27
|
+
return async () => {
|
|
28
|
+
await bootstrapped.teardown();
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Vitest `globalSetup` factory for cross-impl **schema-parity** projects.
|
|
4
|
+
*
|
|
5
|
+
* The per-backend `create_cross_backend_global_setup` spawns one backend
|
|
6
|
+
* (derived from the project name). A schema-parity gate instead needs
|
|
7
|
+
* *two* backends alive at once so the test process can capture each one's
|
|
8
|
+
* schema (via `capture_schema_snapshot`) and diff them with
|
|
9
|
+
* `assert_schema_snapshots_equal`. This factory spawns + bootstraps both
|
|
10
|
+
* configs and `provide`s both serialized handles.
|
|
11
|
+
*
|
|
12
|
+
* A consumer's parity `global_setup.ts` collapses to:
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* import {create_schema_parity_global_setup} from
|
|
16
|
+
* '@fuzdev/fuz_app/testing/cross_backend/create_schema_parity_global_setup.js';
|
|
17
|
+
* import {deno_backend_config, rust_backend_config} from './my_backend_config.js';
|
|
18
|
+
* import './cross_test_types.js'; // augments the two provide keys
|
|
19
|
+
*
|
|
20
|
+
* export default create_schema_parity_global_setup({
|
|
21
|
+
* configs: {a: deno_backend_config, b: rust_backend_config},
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* The parity `.cross.test.ts` `inject`s both keys, rebuilds each with
|
|
26
|
+
* `reconstruct_bootstrapped_handle`, and asserts. Run this project in a
|
|
27
|
+
* later `groupOrder` than the single-backend projects (or with distinct
|
|
28
|
+
* ports) — it reuses both configs' ports, so it must not run concurrently
|
|
29
|
+
* with the per-backend projects.
|
|
30
|
+
*
|
|
31
|
+
* @module
|
|
32
|
+
*/
|
|
33
|
+
import type { TestProject } from 'vitest/node';
|
|
34
|
+
import type { BackendConfig } from './backend_config.js';
|
|
35
|
+
export interface SchemaParityGlobalSetupOptions {
|
|
36
|
+
/**
|
|
37
|
+
* The two backend config factories to spawn. `a` is spawned first; on
|
|
38
|
+
* its success `b` is spawned (with `a` torn down if `b` throws). Label
|
|
39
|
+
* them in the parity test via `assert_schema_snapshots_equal`'s labels.
|
|
40
|
+
*/
|
|
41
|
+
readonly configs: {
|
|
42
|
+
readonly a: () => BackendConfig;
|
|
43
|
+
readonly b: () => BackendConfig;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* `project.provide` keys for the two serialized handles (read by
|
|
47
|
+
* `inject` in the parity test). Default `parity_handle_a` /
|
|
48
|
+
* `parity_handle_b`. Augment vitest's `ProvidedContext` for them.
|
|
49
|
+
*/
|
|
50
|
+
readonly provide_keys?: {
|
|
51
|
+
readonly a: string;
|
|
52
|
+
readonly b: string;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Build a vitest `globalSetup` default export that spawns both backends.
|
|
57
|
+
*/
|
|
58
|
+
export declare const create_schema_parity_global_setup: ({ configs, provide_keys, }: SchemaParityGlobalSetupOptions) => ((project: TestProject) => Promise<() => Promise<void>>);
|
|
59
|
+
//# sourceMappingURL=create_schema_parity_global_setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create_schema_parity_global_setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/create_schema_parity_global_setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIvD,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,aAAa,CAAC;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,aAAa,CAAA;KAAC,CAAC;IACrF;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CACjE;AAED;;GAEG;AACH,eAAO,MAAM,iCAAiC,GAAI,4BAG/C,8BAA8B,KAAG,CAAC,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAmB1F,CAAC"}
|