docs: codify the FK-safe stream ordering as a wire-format invariant
CI / lint (pull_request) Successful in 31s
CI / format (pull_request) Successful in 31s
CI / install-and-build (pull_request) Successful in 55s
CI / typecheck-tests (pull_request) Successful in 31s
CI / typecheck-source (pull_request) Successful in 31s
CI / test (pull_request) Successful in 42s
CI / lint (pull_request) Successful in 31s
CI / format (pull_request) Successful in 31s
CI / install-and-build (pull_request) Successful in 55s
CI / typecheck-tests (pull_request) Successful in 31s
CI / typecheck-source (pull_request) Successful in 31s
CI / test (pull_request) Successful in 42s
The postgres importer inserts records sequentially with FK enforcement live, so it depends on records arriving in dependency order. That requirement was implicit in each backend's export table list; make it explicit and enforced. - Add EXPORT_KIND_ORDER (user, abode, resident, apikey, note) as a documented single source of truth, with the FK dependency chain spelled out on its doc comment, and note the ordering guarantee on the ExportEnvelope wire-format doc. Derive the isExportKind set from it. - Both backends' export() now iterate EXPORT_KIND_ORDER via a loader map (postgres omits note by leaving it out of the map), so emission order is tied to the constant and can't drift. - Tests: a change-detector on EXPORT_KIND_ORDER, plus assertions that both the sqlite and postgres (fake) exports emit record kinds grouped in FK-safe order (kind rank non-decreasing down the stream, after the leading meta). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #13.
This commit is contained in:
@@ -48,13 +48,14 @@ import type {
|
||||
import type { WrappedDb } from "./impl/types.js";
|
||||
import { Readable } from "node:stream";
|
||||
import readline from "node:readline";
|
||||
import type {
|
||||
Exportable,
|
||||
ExportKind,
|
||||
ExportOptions,
|
||||
Importable,
|
||||
ImportOptions,
|
||||
ImportResult,
|
||||
import {
|
||||
EXPORT_KIND_ORDER,
|
||||
type Exportable,
|
||||
type ExportKind,
|
||||
type ExportOptions,
|
||||
type Importable,
|
||||
type ImportOptions,
|
||||
type ImportResult,
|
||||
} from "../types/ExportImport.js";
|
||||
import { isExportKind, kindAllowed, recordAllowed } from "../export/filter.js";
|
||||
|
||||
@@ -558,14 +559,16 @@ export class SqliteInterface
|
||||
// `load()` is exactly one `WrappedDb.all()`). Kept lazy so the first query
|
||||
// only fires once the destination starts pulling, and skipped entirely
|
||||
// once the signal is aborted — no further reads after the destination
|
||||
// goes away.
|
||||
const tables: [ExportKind, () => { uid?: string; aid?: string }[]][] = [
|
||||
["user", () => selectClientUsers(db)],
|
||||
["abode", () => selectAbodes(db)],
|
||||
["resident", () => selectResidents(db)],
|
||||
["apikey", () => selectClientApikeys(db)],
|
||||
["note", () => selectNotes(db)],
|
||||
];
|
||||
// goes away. Emission follows the FK-safe EXPORT_KIND_ORDER (part of the
|
||||
// wire contract; see ExportImport.ts).
|
||||
const loaders: Record<ExportKind, () => { uid?: string; aid?: string }[]> =
|
||||
{
|
||||
user: () => selectClientUsers(db),
|
||||
abode: () => selectAbodes(db),
|
||||
resident: () => selectResidents(db),
|
||||
apikey: () => selectClientApikeys(db),
|
||||
note: () => selectNotes(db),
|
||||
};
|
||||
|
||||
async function* generate(): AsyncGenerator<string> {
|
||||
if (signal?.aborted) return;
|
||||
@@ -579,10 +582,10 @@ export class SqliteInterface
|
||||
},
|
||||
}) + "\n";
|
||||
try {
|
||||
for (const [kind, load] of tables) {
|
||||
for (const kind of EXPORT_KIND_ORDER) {
|
||||
if (signal?.aborted) return;
|
||||
if (!kindAllowed(filter, kind)) continue;
|
||||
for (const row of load()) {
|
||||
for (const row of loaders[kind]()) {
|
||||
if (signal?.aborted) return;
|
||||
if (recordAllowed(filter, kind, row)) {
|
||||
yield JSON.stringify({ kind, data: row }) + "\n";
|
||||
|
||||
Reference in New Issue
Block a user