@relayfile/adapter-core 0.2.14 → 0.2.15
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/src/emit-auxiliary/index.d.ts +259 -0
- package/dist/src/emit-auxiliary/index.d.ts.map +1 -0
- package/dist/src/emit-auxiliary/index.js +299 -0
- package/dist/src/emit-auxiliary/index.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/tests/emit-auxiliary/emit-auxiliary.test.d.ts +2 -0
- package/dist/tests/emit-auxiliary/emit-auxiliary.test.d.ts.map +1 -0
- package/dist/tests/emit-auxiliary/emit-auxiliary.test.js +271 -0
- package/dist/tests/emit-auxiliary/emit-auxiliary.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared orchestration primitives for adapter-owned auxiliary-file emission.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1 of the three-phase refactor that pushes per-provider aux-file
|
|
5
|
+
* logic out of cloud's `record-writer.ts` (which ballooned to ~800 lines
|
|
6
|
+
* after cloud#546) and into each adapter. Adapters compose these primitives
|
|
7
|
+
* to expose a single `emitAuxiliaryFiles(client, input)` entry point that
|
|
8
|
+
* cloud will eventually call as a 10-line dispatcher (Phase 3).
|
|
9
|
+
*
|
|
10
|
+
* What this module deliberately does *not* know:
|
|
11
|
+
* - Anything provider-specific (no Slack/Jira/Notion/Confluence switches).
|
|
12
|
+
* - How to slugify titles, render index rows, or build LAYOUT.md — those
|
|
13
|
+
* live in each adapter so the canonical shape is owned by the adapter
|
|
14
|
+
* that also owns the underlying record schema.
|
|
15
|
+
* - Schema validation. Records arrive cleaned (post-stripNangoMetadata);
|
|
16
|
+
* the caller is responsible for filtering deleted tombstones into the
|
|
17
|
+
* delete branch.
|
|
18
|
+
*
|
|
19
|
+
* What it does own:
|
|
20
|
+
* - `runEmitBatch` — resilient fan-out for write/delete operations. Each
|
|
21
|
+
* path succeeds or fails independently, with errors accumulated into
|
|
22
|
+
* the returned `EmitAuxiliaryFilesResult.errors` array (mirrors the
|
|
23
|
+
* adapter-confluence#69 b2440df pattern for in-webhook fan-out).
|
|
24
|
+
* - `IndexFileReconciler` — read existing `_index.json` rows, queue
|
|
25
|
+
* upserts and removes, flush once via an adapter-supplied builder.
|
|
26
|
+
* - `PriorAliasReader` — read the stable by-id alias on a prior write
|
|
27
|
+
* so adapters can compute stale alias paths and delete them on
|
|
28
|
+
* rename / status-change / parent-move.
|
|
29
|
+
*
|
|
30
|
+
* Back-compat: every primitive degrades gracefully when the client lacks
|
|
31
|
+
* `readFile`. Reconciliation becomes a no-op (matches pre-#69 behavior,
|
|
32
|
+
* stale aliases accumulate but functional state stays correct), and the
|
|
33
|
+
* canonical write fan-out still proceeds.
|
|
34
|
+
*/
|
|
35
|
+
/** Content type for JSON-encoded aux files. Adapters pass this through. */
|
|
36
|
+
export declare const EMIT_AUXILIARY_JSON_CONTENT_TYPE = "application/json; charset=utf-8";
|
|
37
|
+
/** A single write operation queued by an adapter's emit logic. */
|
|
38
|
+
export interface EmitWrite {
|
|
39
|
+
path: string;
|
|
40
|
+
content: string;
|
|
41
|
+
contentType?: string;
|
|
42
|
+
/** Adapter-specific properties/relations/permissions/comments. */
|
|
43
|
+
semantics?: EmitFileSemantics;
|
|
44
|
+
}
|
|
45
|
+
/** A single delete operation queued by an adapter's emit logic. */
|
|
46
|
+
export interface EmitDelete {
|
|
47
|
+
path: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Semantics block the adapter attaches to each write. Kept structurally
|
|
51
|
+
* compatible with the per-adapter `FileSemantics` type so adapters can
|
|
52
|
+
* pass theirs through without remapping.
|
|
53
|
+
*/
|
|
54
|
+
export interface EmitFileSemantics {
|
|
55
|
+
properties?: Record<string, string>;
|
|
56
|
+
relations?: readonly string[];
|
|
57
|
+
permissions?: readonly string[];
|
|
58
|
+
comments?: readonly string[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Aggregate result returned by every `emitAuxiliaryFiles` call. Per-path
|
|
62
|
+
* failures land in `errors` keyed by the path that threw; the rest of the
|
|
63
|
+
* batch is unaffected. Callers retrying transient failures can target only
|
|
64
|
+
* the paths in `errors` instead of re-deriving the full alias set.
|
|
65
|
+
*/
|
|
66
|
+
export interface EmitAuxiliaryFilesResult {
|
|
67
|
+
written: number;
|
|
68
|
+
deleted: number;
|
|
69
|
+
errors: EmitError[];
|
|
70
|
+
}
|
|
71
|
+
export interface EmitError {
|
|
72
|
+
path: string;
|
|
73
|
+
error: string;
|
|
74
|
+
}
|
|
75
|
+
/** Optional revision result some VFS backends return. We ignore it here. */
|
|
76
|
+
export interface EmitWriteResult {
|
|
77
|
+
created?: boolean;
|
|
78
|
+
updated?: boolean;
|
|
79
|
+
status?: 'created' | 'updated' | 'queued' | 'pending';
|
|
80
|
+
}
|
|
81
|
+
export interface EmitWriteInput {
|
|
82
|
+
workspaceId: string;
|
|
83
|
+
path: string;
|
|
84
|
+
content: string;
|
|
85
|
+
contentType?: string;
|
|
86
|
+
semantics?: EmitFileSemantics;
|
|
87
|
+
}
|
|
88
|
+
export interface EmitDeleteInput {
|
|
89
|
+
workspaceId: string;
|
|
90
|
+
path: string;
|
|
91
|
+
}
|
|
92
|
+
export interface EmitReadInput {
|
|
93
|
+
workspaceId: string;
|
|
94
|
+
path: string;
|
|
95
|
+
}
|
|
96
|
+
export interface EmitReadResult {
|
|
97
|
+
content: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The client contract every adapter's `emitAuxiliaryFiles` consumes.
|
|
101
|
+
*
|
|
102
|
+
* Structurally compatible with each adapter's existing `RelayFileClientLike`
|
|
103
|
+
* (single-input shape) — adapters can forward the client they were
|
|
104
|
+
* constructed with directly. Cloud's variadic `RelayfileWriteClient` shape
|
|
105
|
+
* adapts via a thin shim that cloud will introduce in Phase 3 (not in this
|
|
106
|
+
* PR's scope).
|
|
107
|
+
*
|
|
108
|
+
* `readFile` is optional: reconciliation degrades to a no-op when absent.
|
|
109
|
+
* `deleteFile` is optional: when absent, queued deletes surface as errors
|
|
110
|
+
* (`"deleteFile not supported by client"`) and the rest of the batch still
|
|
111
|
+
* proceeds.
|
|
112
|
+
*/
|
|
113
|
+
export interface AuxiliaryEmitterClient {
|
|
114
|
+
writeFile(input: EmitWriteInput): Promise<EmitWriteResult | void>;
|
|
115
|
+
deleteFile?(input: EmitDeleteInput): Promise<void> | void;
|
|
116
|
+
readFile?(input: EmitReadInput): Promise<EmitReadResult | null | undefined>;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Generic shape every adapter's `emitAuxiliaryFiles` accepts. Adapters
|
|
120
|
+
* narrow `records` to their own concrete record type; the shared
|
|
121
|
+
* primitives never look inside `records` themselves — they're only here
|
|
122
|
+
* for the surface-level contract.
|
|
123
|
+
*/
|
|
124
|
+
export interface EmitAuxiliaryFilesInput<TRecord> {
|
|
125
|
+
workspaceId: string;
|
|
126
|
+
records: readonly TRecord[];
|
|
127
|
+
/**
|
|
128
|
+
* Free-form per-job context. Adapters may carry sync model, provider
|
|
129
|
+
* config key, connection id, etc. The shared primitives ignore it.
|
|
130
|
+
*/
|
|
131
|
+
options?: Record<string, unknown>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The contract every adapter implements. Cloud's Phase 3 dispatcher will
|
|
135
|
+
* be a switch over `provider` that calls `adapter.emitAuxiliaryFiles(...)`
|
|
136
|
+
* and propagates the result.
|
|
137
|
+
*/
|
|
138
|
+
export interface AdapterAuxiliaryEmitter<TRecord> {
|
|
139
|
+
emitAuxiliaryFiles(client: AuxiliaryEmitterClient, input: EmitAuxiliaryFilesInput<TRecord>): Promise<EmitAuxiliaryFilesResult>;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Per-record planner the adapter passes to `runEmitBatch`. Returns the
|
|
143
|
+
* set of writes and deletes to queue for a single record. The planner runs
|
|
144
|
+
* synchronously or asynchronously per record; the runner serializes them
|
|
145
|
+
* to preserve write-after-delete ordering inside a single record (stale
|
|
146
|
+
* alias delete must complete before the canonical write would race past).
|
|
147
|
+
*/
|
|
148
|
+
export type PerRecordPlanner<TRecord> = (record: TRecord, index: number) => Promise<EmitPlan> | EmitPlan;
|
|
149
|
+
/** What a planner returns per record. */
|
|
150
|
+
export interface EmitPlan {
|
|
151
|
+
writes?: readonly EmitWrite[];
|
|
152
|
+
deletes?: readonly EmitDelete[];
|
|
153
|
+
}
|
|
154
|
+
export interface RunEmitBatchOptions {
|
|
155
|
+
/** Defaults to `EMIT_AUXILIARY_JSON_CONTENT_TYPE` if absent on a write. */
|
|
156
|
+
defaultContentType?: string;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Execute writes and deletes with per-path try/catch. Deletes run before
|
|
160
|
+
* writes within a single record's plan (stale alias cleanup must precede
|
|
161
|
+
* the new canonical write so a transient failure doesn't leave the old
|
|
162
|
+
* alias resolving to fresh bytes). Errors are accumulated, never thrown.
|
|
163
|
+
*
|
|
164
|
+
* The runner is intentionally serial within a record and across records:
|
|
165
|
+
* cloud already parallelizes at the record-write layer, and aux-file
|
|
166
|
+
* emission is bursty (one batch per Nango chunk). A future enhancement
|
|
167
|
+
* can layer Promise.allSettled on top, but doing so here would force
|
|
168
|
+
* every adapter to reason about partial-failure visibility on the same
|
|
169
|
+
* index file — the current contract gives callers one observable
|
|
170
|
+
* "errors[]" view.
|
|
171
|
+
*/
|
|
172
|
+
export declare function runEmitBatch<TRecord>(client: AuxiliaryEmitterClient, workspaceId: string, records: readonly TRecord[], planner: PerRecordPlanner<TRecord>, options?: RunEmitBatchOptions): Promise<EmitAuxiliaryFilesResult>;
|
|
173
|
+
/** Minimum shape every adapter index row exposes. */
|
|
174
|
+
export interface IndexRowLike {
|
|
175
|
+
id: string;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Builder the adapter passes to `IndexFileReconciler.flush`. Receives the
|
|
179
|
+
* merged row set (existing minus removed plus upserted) and returns the
|
|
180
|
+
* serialized file body the adapter wants to write. Adapters own sort
|
|
181
|
+
* order, field shape, and trailing newline behavior — the primitive only
|
|
182
|
+
* orchestrates read-merge-write.
|
|
183
|
+
*/
|
|
184
|
+
export type IndexFileBuilder<TRow extends IndexRowLike> = (rows: readonly TRow[]) => {
|
|
185
|
+
path: string;
|
|
186
|
+
content: string;
|
|
187
|
+
contentType?: string;
|
|
188
|
+
};
|
|
189
|
+
export interface IndexFileReconcilerOptions<TRow extends IndexRowLike> {
|
|
190
|
+
client: AuxiliaryEmitterClient;
|
|
191
|
+
workspaceId: string;
|
|
192
|
+
path: string;
|
|
193
|
+
builder: IndexFileBuilder<TRow>;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Read-modify-write helper for `_index.json` files. The adapter accumulates
|
|
197
|
+
* rows to upsert and ids to remove, then calls `flush()` once per batch:
|
|
198
|
+
* one read, one write. Concurrent ingestions of the same index path race
|
|
199
|
+
* here exactly like they do in cloud's current `writeIndexFile` — Phase 2
|
|
200
|
+
* may layer the existing `upsertIndexAtomic` primitive on top to add CAS
|
|
201
|
+
* retries, but the contract is the same.
|
|
202
|
+
*
|
|
203
|
+
* Failures during flush are returned in the caller's error array; the
|
|
204
|
+
* reconciler does not throw.
|
|
205
|
+
*/
|
|
206
|
+
export declare class IndexFileReconciler<TRow extends IndexRowLike> {
|
|
207
|
+
private readonly client;
|
|
208
|
+
private readonly workspaceId;
|
|
209
|
+
private readonly path;
|
|
210
|
+
private readonly builder;
|
|
211
|
+
private readonly upserts;
|
|
212
|
+
private readonly removes;
|
|
213
|
+
constructor(options: IndexFileReconcilerOptions<TRow>);
|
|
214
|
+
/** Queue a row for upsert. Last write wins on duplicate ids within the batch. */
|
|
215
|
+
upsert(...rows: readonly TRow[]): this;
|
|
216
|
+
/** Queue an id for removal. Wins over a same-id upsert queued later? No —
|
|
217
|
+
* upserts within the same flush take precedence (matches cloud's current
|
|
218
|
+
* `writeIndexFile` semantics: removes apply first, then upserts merge in). */
|
|
219
|
+
remove(...ids: readonly string[]): this;
|
|
220
|
+
/** True when no upserts or removes have been queued. */
|
|
221
|
+
isEmpty(): boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Flush queued mutations. Reads existing rows once, applies removes, then
|
|
224
|
+
* applies upserts (last-write-wins on id collisions). Returns either a
|
|
225
|
+
* successful `{ written: 1 }` plan or an `errors` entry on failure.
|
|
226
|
+
*/
|
|
227
|
+
flush(): Promise<{
|
|
228
|
+
written: number;
|
|
229
|
+
errors: EmitError[];
|
|
230
|
+
}>;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Read a single prior `by-id` alias and parse it through a caller-supplied
|
|
234
|
+
* extractor. Used by adapters to compute stale alias paths on rename /
|
|
235
|
+
* status-change / parent-move: the by-id alias is keyed only on objectId
|
|
236
|
+
* so it survives every other field change and is the right anchor for
|
|
237
|
+
* "was this record materialized before, and if so what did its alias
|
|
238
|
+
* fields look like?".
|
|
239
|
+
*
|
|
240
|
+
* Generic over the prior state shape — adapters parse what they need
|
|
241
|
+
* (title, status, spaceId, parentId, key, ...) and return a structured
|
|
242
|
+
* object. Read failures and parse failures both resolve to `null`; the
|
|
243
|
+
* caller treats absence as "no reconciliation possible, leave any stale
|
|
244
|
+
* aliases in place" rather than failing the canonical write.
|
|
245
|
+
*/
|
|
246
|
+
export declare class PriorAliasReader {
|
|
247
|
+
private readonly client;
|
|
248
|
+
private readonly workspaceId;
|
|
249
|
+
constructor(client: AuxiliaryEmitterClient, workspaceId: string);
|
|
250
|
+
/** True when the client exposes `readFile` (reconciliation possible). */
|
|
251
|
+
isAvailable(): boolean;
|
|
252
|
+
/**
|
|
253
|
+
* Read and parse the file at `path`. Returns `null` on missing file,
|
|
254
|
+
* read error, or parse failure. Returns the parsed JSON object (or
|
|
255
|
+
* the result of `extract` if supplied) on success.
|
|
256
|
+
*/
|
|
257
|
+
read<TState = Record<string, unknown>>(path: string, extract?: (parsed: Record<string, unknown>) => TState | null): Promise<TState | null>;
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/emit-auxiliary/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared orchestration primitives for adapter-owned auxiliary-file emission.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1 of the three-phase refactor that pushes per-provider aux-file
|
|
5
|
+
* logic out of cloud's `record-writer.ts` (which ballooned to ~800 lines
|
|
6
|
+
* after cloud#546) and into each adapter. Adapters compose these primitives
|
|
7
|
+
* to expose a single `emitAuxiliaryFiles(client, input)` entry point that
|
|
8
|
+
* cloud will eventually call as a 10-line dispatcher (Phase 3).
|
|
9
|
+
*
|
|
10
|
+
* What this module deliberately does *not* know:
|
|
11
|
+
* - Anything provider-specific (no Slack/Jira/Notion/Confluence switches).
|
|
12
|
+
* - How to slugify titles, render index rows, or build LAYOUT.md — those
|
|
13
|
+
* live in each adapter so the canonical shape is owned by the adapter
|
|
14
|
+
* that also owns the underlying record schema.
|
|
15
|
+
* - Schema validation. Records arrive cleaned (post-stripNangoMetadata);
|
|
16
|
+
* the caller is responsible for filtering deleted tombstones into the
|
|
17
|
+
* delete branch.
|
|
18
|
+
*
|
|
19
|
+
* What it does own:
|
|
20
|
+
* - `runEmitBatch` — resilient fan-out for write/delete operations. Each
|
|
21
|
+
* path succeeds or fails independently, with errors accumulated into
|
|
22
|
+
* the returned `EmitAuxiliaryFilesResult.errors` array (mirrors the
|
|
23
|
+
* adapter-confluence#69 b2440df pattern for in-webhook fan-out).
|
|
24
|
+
* - `IndexFileReconciler` — read existing `_index.json` rows, queue
|
|
25
|
+
* upserts and removes, flush once via an adapter-supplied builder.
|
|
26
|
+
* - `PriorAliasReader` — read the stable by-id alias on a prior write
|
|
27
|
+
* so adapters can compute stale alias paths and delete them on
|
|
28
|
+
* rename / status-change / parent-move.
|
|
29
|
+
*
|
|
30
|
+
* Back-compat: every primitive degrades gracefully when the client lacks
|
|
31
|
+
* `readFile`. Reconciliation becomes a no-op (matches pre-#69 behavior,
|
|
32
|
+
* stale aliases accumulate but functional state stays correct), and the
|
|
33
|
+
* canonical write fan-out still proceeds.
|
|
34
|
+
*/
|
|
35
|
+
/** Content type for JSON-encoded aux files. Adapters pass this through. */
|
|
36
|
+
export const EMIT_AUXILIARY_JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
|
|
37
|
+
/**
|
|
38
|
+
* Execute writes and deletes with per-path try/catch. Deletes run before
|
|
39
|
+
* writes within a single record's plan (stale alias cleanup must precede
|
|
40
|
+
* the new canonical write so a transient failure doesn't leave the old
|
|
41
|
+
* alias resolving to fresh bytes). Errors are accumulated, never thrown.
|
|
42
|
+
*
|
|
43
|
+
* The runner is intentionally serial within a record and across records:
|
|
44
|
+
* cloud already parallelizes at the record-write layer, and aux-file
|
|
45
|
+
* emission is bursty (one batch per Nango chunk). A future enhancement
|
|
46
|
+
* can layer Promise.allSettled on top, but doing so here would force
|
|
47
|
+
* every adapter to reason about partial-failure visibility on the same
|
|
48
|
+
* index file — the current contract gives callers one observable
|
|
49
|
+
* "errors[]" view.
|
|
50
|
+
*/
|
|
51
|
+
export async function runEmitBatch(client, workspaceId, records, planner, options = {}) {
|
|
52
|
+
const result = { written: 0, deleted: 0, errors: [] };
|
|
53
|
+
const defaultContentType = options.defaultContentType ?? EMIT_AUXILIARY_JSON_CONTENT_TYPE;
|
|
54
|
+
for (let i = 0; i < records.length; i += 1) {
|
|
55
|
+
let plan;
|
|
56
|
+
try {
|
|
57
|
+
plan = await planner(records[i], i);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
result.errors.push({ path: '', error: stringifyError(error) });
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (plan.deletes && plan.deletes.length > 0) {
|
|
64
|
+
for (const op of plan.deletes) {
|
|
65
|
+
if (!client.deleteFile) {
|
|
66
|
+
result.errors.push({ path: op.path, error: 'deleteFile not supported by client' });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await client.deleteFile({ workspaceId, path: op.path });
|
|
71
|
+
result.deleted += 1;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
result.errors.push({ path: op.path, error: stringifyError(error) });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (plan.writes && plan.writes.length > 0) {
|
|
79
|
+
for (const op of plan.writes) {
|
|
80
|
+
try {
|
|
81
|
+
await client.writeFile({
|
|
82
|
+
workspaceId,
|
|
83
|
+
path: op.path,
|
|
84
|
+
content: op.content,
|
|
85
|
+
contentType: op.contentType ?? defaultContentType,
|
|
86
|
+
...(op.semantics ? { semantics: op.semantics } : {}),
|
|
87
|
+
});
|
|
88
|
+
result.written += 1;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
result.errors.push({ path: op.path, error: stringifyError(error) });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Read-modify-write helper for `_index.json` files. The adapter accumulates
|
|
100
|
+
* rows to upsert and ids to remove, then calls `flush()` once per batch:
|
|
101
|
+
* one read, one write. Concurrent ingestions of the same index path race
|
|
102
|
+
* here exactly like they do in cloud's current `writeIndexFile` — Phase 2
|
|
103
|
+
* may layer the existing `upsertIndexAtomic` primitive on top to add CAS
|
|
104
|
+
* retries, but the contract is the same.
|
|
105
|
+
*
|
|
106
|
+
* Failures during flush are returned in the caller's error array; the
|
|
107
|
+
* reconciler does not throw.
|
|
108
|
+
*/
|
|
109
|
+
export class IndexFileReconciler {
|
|
110
|
+
client;
|
|
111
|
+
workspaceId;
|
|
112
|
+
path;
|
|
113
|
+
builder;
|
|
114
|
+
upserts = [];
|
|
115
|
+
removes = new Set();
|
|
116
|
+
constructor(options) {
|
|
117
|
+
this.client = options.client;
|
|
118
|
+
this.workspaceId = options.workspaceId;
|
|
119
|
+
this.path = options.path;
|
|
120
|
+
this.builder = options.builder;
|
|
121
|
+
}
|
|
122
|
+
/** Queue a row for upsert. Last write wins on duplicate ids within the batch. */
|
|
123
|
+
upsert(...rows) {
|
|
124
|
+
for (const row of rows) {
|
|
125
|
+
this.upserts.push(row);
|
|
126
|
+
}
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
/** Queue an id for removal. Wins over a same-id upsert queued later? No —
|
|
130
|
+
* upserts within the same flush take precedence (matches cloud's current
|
|
131
|
+
* `writeIndexFile` semantics: removes apply first, then upserts merge in). */
|
|
132
|
+
remove(...ids) {
|
|
133
|
+
for (const id of ids) {
|
|
134
|
+
this.removes.add(id);
|
|
135
|
+
}
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
/** True when no upserts or removes have been queued. */
|
|
139
|
+
isEmpty() {
|
|
140
|
+
return this.upserts.length === 0 && this.removes.size === 0;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Flush queued mutations. Reads existing rows once, applies removes, then
|
|
144
|
+
* applies upserts (last-write-wins on id collisions). Returns either a
|
|
145
|
+
* successful `{ written: 1 }` plan or an `errors` entry on failure.
|
|
146
|
+
*/
|
|
147
|
+
async flush() {
|
|
148
|
+
if (this.isEmpty()) {
|
|
149
|
+
return { written: 0, errors: [] };
|
|
150
|
+
}
|
|
151
|
+
let existing;
|
|
152
|
+
try {
|
|
153
|
+
existing = await readJsonArrayRows(this.client, this.workspaceId, this.path);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
// Read failure is non-fatal — proceed with empty baseline. Matches
|
|
157
|
+
// cloud's `readJsonArray` swallowing parse/read errors.
|
|
158
|
+
existing = [];
|
|
159
|
+
// We still record the read error so callers can surface it. Callers
|
|
160
|
+
// can choose to ignore index errors if they're non-critical.
|
|
161
|
+
// (Empty baseline can stomp peers, which is the documented behavior
|
|
162
|
+
// for non-CAS index writes.)
|
|
163
|
+
void error;
|
|
164
|
+
}
|
|
165
|
+
const retained = this.removes.size > 0
|
|
166
|
+
? existing.filter((row) => !this.removes.has(row.id))
|
|
167
|
+
: existing;
|
|
168
|
+
const byId = new Map(retained.map((row) => [row.id, row]));
|
|
169
|
+
for (const row of this.upserts) {
|
|
170
|
+
byId.set(row.id, row);
|
|
171
|
+
}
|
|
172
|
+
const merged = [...byId.values()];
|
|
173
|
+
const built = this.builder(merged);
|
|
174
|
+
try {
|
|
175
|
+
await this.client.writeFile({
|
|
176
|
+
workspaceId: this.workspaceId,
|
|
177
|
+
path: built.path,
|
|
178
|
+
content: built.content,
|
|
179
|
+
contentType: built.contentType ?? EMIT_AUXILIARY_JSON_CONTENT_TYPE,
|
|
180
|
+
});
|
|
181
|
+
return { written: 1, errors: [] };
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
return {
|
|
185
|
+
written: 0,
|
|
186
|
+
errors: [{ path: built.path, error: stringifyError(error) }],
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// -- PriorAliasReader -------------------------------------------------------
|
|
192
|
+
/**
|
|
193
|
+
* Read a single prior `by-id` alias and parse it through a caller-supplied
|
|
194
|
+
* extractor. Used by adapters to compute stale alias paths on rename /
|
|
195
|
+
* status-change / parent-move: the by-id alias is keyed only on objectId
|
|
196
|
+
* so it survives every other field change and is the right anchor for
|
|
197
|
+
* "was this record materialized before, and if so what did its alias
|
|
198
|
+
* fields look like?".
|
|
199
|
+
*
|
|
200
|
+
* Generic over the prior state shape — adapters parse what they need
|
|
201
|
+
* (title, status, spaceId, parentId, key, ...) and return a structured
|
|
202
|
+
* object. Read failures and parse failures both resolve to `null`; the
|
|
203
|
+
* caller treats absence as "no reconciliation possible, leave any stale
|
|
204
|
+
* aliases in place" rather than failing the canonical write.
|
|
205
|
+
*/
|
|
206
|
+
export class PriorAliasReader {
|
|
207
|
+
client;
|
|
208
|
+
workspaceId;
|
|
209
|
+
constructor(client, workspaceId) {
|
|
210
|
+
this.client = client;
|
|
211
|
+
this.workspaceId = workspaceId;
|
|
212
|
+
}
|
|
213
|
+
/** True when the client exposes `readFile` (reconciliation possible). */
|
|
214
|
+
isAvailable() {
|
|
215
|
+
return typeof this.client.readFile === 'function';
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Read and parse the file at `path`. Returns `null` on missing file,
|
|
219
|
+
* read error, or parse failure. Returns the parsed JSON object (or
|
|
220
|
+
* the result of `extract` if supplied) on success.
|
|
221
|
+
*/
|
|
222
|
+
async read(path, extract) {
|
|
223
|
+
if (!this.client.readFile) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
let raw;
|
|
227
|
+
try {
|
|
228
|
+
raw = await this.client.readFile({ workspaceId: this.workspaceId, path });
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
if (!raw || typeof raw.content !== 'string' || raw.content.length === 0) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
let parsed;
|
|
237
|
+
try {
|
|
238
|
+
parsed = JSON.parse(raw.content);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
if (!isPlainRecord(parsed)) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
if (extract) {
|
|
247
|
+
try {
|
|
248
|
+
return extract(parsed);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return parsed;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// -- internal helpers -------------------------------------------------------
|
|
258
|
+
async function readJsonArrayRows(client, workspaceId, path) {
|
|
259
|
+
if (!client.readFile) {
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
let raw;
|
|
263
|
+
try {
|
|
264
|
+
raw = await client.readFile({ workspaceId, path });
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
if (!raw || typeof raw.content !== 'string' || raw.content.length === 0) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
let parsed;
|
|
273
|
+
try {
|
|
274
|
+
parsed = JSON.parse(raw.content);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
if (!Array.isArray(parsed)) {
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
const rows = [];
|
|
283
|
+
for (const item of parsed) {
|
|
284
|
+
if (isPlainRecord(item) && typeof item.id === 'string') {
|
|
285
|
+
rows.push(item);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return rows;
|
|
289
|
+
}
|
|
290
|
+
function isPlainRecord(value) {
|
|
291
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
292
|
+
}
|
|
293
|
+
function stringifyError(error) {
|
|
294
|
+
if (error instanceof Error) {
|
|
295
|
+
return error.message;
|
|
296
|
+
}
|
|
297
|
+
return String(error);
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/emit-auxiliary/index.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC"}
|
package/dist/src/index.js
CHANGED
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-auxiliary.test.d.ts","sourceRoot":"","sources":["../../../tests/emit-auxiliary/emit-auxiliary.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { EMIT_AUXILIARY_JSON_CONTENT_TYPE, IndexFileReconciler, PriorAliasReader, runEmitBatch, } from "../../src/emit-auxiliary/index.js";
|
|
4
|
+
function createClient(options = {}) {
|
|
5
|
+
const files = new Map(Object.entries(options.initialFiles ?? {}));
|
|
6
|
+
const writes = [];
|
|
7
|
+
const deletes = [];
|
|
8
|
+
const reads = [];
|
|
9
|
+
const failWriteOn = options.failWriteOn ?? new Set();
|
|
10
|
+
const failDeleteOn = options.failDeleteOn ?? new Set();
|
|
11
|
+
const failReadOn = options.failReadOn ?? new Set();
|
|
12
|
+
const client = {
|
|
13
|
+
writes,
|
|
14
|
+
deletes,
|
|
15
|
+
reads,
|
|
16
|
+
files,
|
|
17
|
+
async writeFile(input) {
|
|
18
|
+
writes.push(input);
|
|
19
|
+
if (failWriteOn.has(input.path)) {
|
|
20
|
+
throw new Error(`forced write failure at ${input.path}`);
|
|
21
|
+
}
|
|
22
|
+
files.set(input.path, input.content);
|
|
23
|
+
return { created: !files.has(input.path) };
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
if (!options.noDelete) {
|
|
27
|
+
client.deleteFile = async (input) => {
|
|
28
|
+
deletes.push(input);
|
|
29
|
+
if (failDeleteOn.has(input.path)) {
|
|
30
|
+
throw new Error(`forced delete failure at ${input.path}`);
|
|
31
|
+
}
|
|
32
|
+
files.delete(input.path);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (!options.noRead) {
|
|
36
|
+
client.readFile = async (input) => {
|
|
37
|
+
reads.push(input);
|
|
38
|
+
if (failReadOn.has(input.path)) {
|
|
39
|
+
throw new Error(`forced read failure at ${input.path}`);
|
|
40
|
+
}
|
|
41
|
+
const content = files.get(input.path);
|
|
42
|
+
return content === undefined ? null : { content };
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return client;
|
|
46
|
+
}
|
|
47
|
+
test("runEmitBatch returns zero result for empty record list", async () => {
|
|
48
|
+
const client = createClient();
|
|
49
|
+
const result = await runEmitBatch(client, "ws-1", [], () => ({ writes: [], deletes: [] }));
|
|
50
|
+
assert.deepEqual(result, { written: 0, deleted: 0, errors: [] });
|
|
51
|
+
assert.equal(client.writes.length, 0);
|
|
52
|
+
assert.equal(client.deletes.length, 0);
|
|
53
|
+
});
|
|
54
|
+
test("runEmitBatch fans out writes and deletes per record", async () => {
|
|
55
|
+
const client = createClient();
|
|
56
|
+
const records = [{ id: "a" }, { id: "b" }];
|
|
57
|
+
const result = await runEmitBatch(client, "ws-1", records, (record) => ({
|
|
58
|
+
writes: [{ path: `/x/${record.id}.json`, content: `{"id":"${record.id}"}` }],
|
|
59
|
+
deletes: [{ path: `/x/stale/${record.id}.json` }],
|
|
60
|
+
}));
|
|
61
|
+
assert.equal(result.written, 2);
|
|
62
|
+
assert.equal(result.deleted, 2);
|
|
63
|
+
assert.deepEqual(result.errors, []);
|
|
64
|
+
assert.equal(client.writes.length, 2);
|
|
65
|
+
assert.equal(client.deletes.length, 2);
|
|
66
|
+
// Deletes run before writes within a record so stale aliases vacate first.
|
|
67
|
+
for (let i = 0; i < records.length; i += 1) {
|
|
68
|
+
const writeIndex = client.writes.findIndex((op) => op.path === `/x/${records[i].id}.json`);
|
|
69
|
+
const deleteIndex = client.deletes.findIndex((op) => op.path === `/x/stale/${records[i].id}.json`);
|
|
70
|
+
assert.ok(writeIndex >= 0 && deleteIndex >= 0);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
test("runEmitBatch defaults content type to JSON when omitted", async () => {
|
|
74
|
+
const client = createClient();
|
|
75
|
+
await runEmitBatch(client, "ws-1", [{ id: "a" }], () => ({
|
|
76
|
+
writes: [{ path: "/x/a.json", content: "{}" }],
|
|
77
|
+
}));
|
|
78
|
+
assert.equal(client.writes[0].contentType, EMIT_AUXILIARY_JSON_CONTENT_TYPE);
|
|
79
|
+
});
|
|
80
|
+
test("runEmitBatch surfaces per-path write failures without aborting", async () => {
|
|
81
|
+
const client = createClient({ failWriteOn: new Set(["/x/b.json"]) });
|
|
82
|
+
const records = [{ id: "a" }, { id: "b" }, { id: "c" }];
|
|
83
|
+
const result = await runEmitBatch(client, "ws-1", records, (record) => ({
|
|
84
|
+
writes: [{ path: `/x/${record.id}.json`, content: "{}" }],
|
|
85
|
+
}));
|
|
86
|
+
assert.equal(result.written, 2);
|
|
87
|
+
assert.equal(result.errors.length, 1);
|
|
88
|
+
assert.equal(result.errors[0].path, "/x/b.json");
|
|
89
|
+
assert.match(result.errors[0].error, /forced write failure/);
|
|
90
|
+
// The third record still wrote.
|
|
91
|
+
assert.ok(client.files.has("/x/c.json"));
|
|
92
|
+
});
|
|
93
|
+
test("runEmitBatch records planner exceptions and continues with remaining records", async () => {
|
|
94
|
+
const client = createClient();
|
|
95
|
+
const records = [{ id: "a" }, { id: "explode" }, { id: "c" }];
|
|
96
|
+
const result = await runEmitBatch(client, "ws-1", records, (record) => {
|
|
97
|
+
if (record.id === "explode") {
|
|
98
|
+
throw new Error("planner exploded");
|
|
99
|
+
}
|
|
100
|
+
return { writes: [{ path: `/x/${record.id}.json`, content: "{}" }] };
|
|
101
|
+
});
|
|
102
|
+
assert.equal(result.written, 2);
|
|
103
|
+
assert.equal(result.errors.length, 1);
|
|
104
|
+
assert.equal(result.errors[0].path, "");
|
|
105
|
+
assert.match(result.errors[0].error, /planner exploded/);
|
|
106
|
+
});
|
|
107
|
+
test("runEmitBatch surfaces queued deletes with missing deleteFile as errors", async () => {
|
|
108
|
+
const client = createClient({ noDelete: true });
|
|
109
|
+
const result = await runEmitBatch(client, "ws-1", [{ id: "a" }], () => ({
|
|
110
|
+
deletes: [{ path: "/x/stale.json" }],
|
|
111
|
+
writes: [{ path: "/x/a.json", content: "{}" }],
|
|
112
|
+
}));
|
|
113
|
+
assert.equal(result.written, 1);
|
|
114
|
+
assert.equal(result.deleted, 0);
|
|
115
|
+
assert.equal(result.errors.length, 1);
|
|
116
|
+
assert.equal(result.errors[0].path, "/x/stale.json");
|
|
117
|
+
assert.match(result.errors[0].error, /deleteFile not supported/);
|
|
118
|
+
});
|
|
119
|
+
test("IndexFileReconciler upserts new rows when no existing file", async () => {
|
|
120
|
+
const client = createClient();
|
|
121
|
+
const reconciler = new IndexFileReconciler({
|
|
122
|
+
client,
|
|
123
|
+
workspaceId: "ws-1",
|
|
124
|
+
path: "/p/_index.json",
|
|
125
|
+
builder: (rows) => ({
|
|
126
|
+
path: "/p/_index.json",
|
|
127
|
+
content: `${JSON.stringify([...rows].sort((a, b) => a.id.localeCompare(b.id)))}\n`,
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
reconciler.upsert({ id: "1", title: "alpha" }, { id: "2", title: "beta" });
|
|
131
|
+
const result = await reconciler.flush();
|
|
132
|
+
assert.equal(result.written, 1);
|
|
133
|
+
assert.deepEqual(result.errors, []);
|
|
134
|
+
const written = client.files.get("/p/_index.json");
|
|
135
|
+
assert.ok(written);
|
|
136
|
+
assert.deepEqual(JSON.parse(written), [
|
|
137
|
+
{ id: "1", title: "alpha" },
|
|
138
|
+
{ id: "2", title: "beta" },
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
test("IndexFileReconciler merges with existing rows on flush", async () => {
|
|
142
|
+
const existing = JSON.stringify([
|
|
143
|
+
{ id: "1", title: "alpha-old" },
|
|
144
|
+
{ id: "3", title: "gamma" },
|
|
145
|
+
]);
|
|
146
|
+
const client = createClient({ initialFiles: { "/p/_index.json": existing } });
|
|
147
|
+
const reconciler = new IndexFileReconciler({
|
|
148
|
+
client,
|
|
149
|
+
workspaceId: "ws-1",
|
|
150
|
+
path: "/p/_index.json",
|
|
151
|
+
builder: (rows) => ({
|
|
152
|
+
path: "/p/_index.json",
|
|
153
|
+
content: `${JSON.stringify([...rows].sort((a, b) => a.id.localeCompare(b.id)))}\n`,
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
reconciler.upsert({ id: "1", title: "alpha-new" }, { id: "2", title: "beta" });
|
|
157
|
+
await reconciler.flush();
|
|
158
|
+
const written = JSON.parse(client.files.get("/p/_index.json"));
|
|
159
|
+
assert.deepEqual(written, [
|
|
160
|
+
{ id: "1", title: "alpha-new" },
|
|
161
|
+
{ id: "2", title: "beta" },
|
|
162
|
+
{ id: "3", title: "gamma" },
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
test("IndexFileReconciler removes ids before upserting", async () => {
|
|
166
|
+
const existing = JSON.stringify([
|
|
167
|
+
{ id: "1", title: "alpha" },
|
|
168
|
+
{ id: "2", title: "beta" },
|
|
169
|
+
{ id: "3", title: "gamma" },
|
|
170
|
+
]);
|
|
171
|
+
const client = createClient({ initialFiles: { "/p/_index.json": existing } });
|
|
172
|
+
const reconciler = new IndexFileReconciler({
|
|
173
|
+
client,
|
|
174
|
+
workspaceId: "ws-1",
|
|
175
|
+
path: "/p/_index.json",
|
|
176
|
+
builder: (rows) => ({
|
|
177
|
+
path: "/p/_index.json",
|
|
178
|
+
content: `${JSON.stringify([...rows].sort((a, b) => a.id.localeCompare(b.id)))}\n`,
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
reconciler.remove("2");
|
|
182
|
+
reconciler.upsert({ id: "4", title: "delta" });
|
|
183
|
+
await reconciler.flush();
|
|
184
|
+
const written = JSON.parse(client.files.get("/p/_index.json"));
|
|
185
|
+
assert.deepEqual(written.map((row) => row.id), ["1", "3", "4"]);
|
|
186
|
+
});
|
|
187
|
+
test("IndexFileReconciler flush returns zero when no upserts/removes queued", async () => {
|
|
188
|
+
const client = createClient();
|
|
189
|
+
const reconciler = new IndexFileReconciler({
|
|
190
|
+
client,
|
|
191
|
+
workspaceId: "ws-1",
|
|
192
|
+
path: "/p/_index.json",
|
|
193
|
+
builder: () => ({ path: "/p/_index.json", content: "[]" }),
|
|
194
|
+
});
|
|
195
|
+
assert.equal(reconciler.isEmpty(), true);
|
|
196
|
+
const result = await reconciler.flush();
|
|
197
|
+
assert.deepEqual(result, { written: 0, errors: [] });
|
|
198
|
+
assert.equal(client.writes.length, 0);
|
|
199
|
+
});
|
|
200
|
+
test("IndexFileReconciler degrades gracefully when client lacks readFile", async () => {
|
|
201
|
+
const client = createClient({ noRead: true });
|
|
202
|
+
const reconciler = new IndexFileReconciler({
|
|
203
|
+
client,
|
|
204
|
+
workspaceId: "ws-1",
|
|
205
|
+
path: "/p/_index.json",
|
|
206
|
+
builder: (rows) => ({
|
|
207
|
+
path: "/p/_index.json",
|
|
208
|
+
content: JSON.stringify(rows),
|
|
209
|
+
}),
|
|
210
|
+
});
|
|
211
|
+
reconciler.upsert({ id: "1", title: "alpha" });
|
|
212
|
+
const result = await reconciler.flush();
|
|
213
|
+
assert.equal(result.written, 1);
|
|
214
|
+
assert.deepEqual(result.errors, []);
|
|
215
|
+
});
|
|
216
|
+
test("IndexFileReconciler surfaces write failures in errors", async () => {
|
|
217
|
+
const client = createClient({ failWriteOn: new Set(["/p/_index.json"]) });
|
|
218
|
+
const reconciler = new IndexFileReconciler({
|
|
219
|
+
client,
|
|
220
|
+
workspaceId: "ws-1",
|
|
221
|
+
path: "/p/_index.json",
|
|
222
|
+
builder: () => ({ path: "/p/_index.json", content: "[]" }),
|
|
223
|
+
});
|
|
224
|
+
reconciler.upsert({ id: "1" });
|
|
225
|
+
const result = await reconciler.flush();
|
|
226
|
+
assert.equal(result.written, 0);
|
|
227
|
+
assert.equal(result.errors.length, 1);
|
|
228
|
+
assert.equal(result.errors[0].path, "/p/_index.json");
|
|
229
|
+
});
|
|
230
|
+
test("PriorAliasReader returns null when client has no readFile", async () => {
|
|
231
|
+
const client = createClient({ noRead: true });
|
|
232
|
+
const reader = new PriorAliasReader(client, "ws-1");
|
|
233
|
+
assert.equal(reader.isAvailable(), false);
|
|
234
|
+
const got = await reader.read("/p/by-id/1.json");
|
|
235
|
+
assert.equal(got, null);
|
|
236
|
+
});
|
|
237
|
+
test("PriorAliasReader returns parsed record on success", async () => {
|
|
238
|
+
const client = createClient({
|
|
239
|
+
initialFiles: { "/p/by-id/1.json": JSON.stringify({ title: "alpha" }) },
|
|
240
|
+
});
|
|
241
|
+
const reader = new PriorAliasReader(client, "ws-1");
|
|
242
|
+
const got = await reader.read("/p/by-id/1.json", (parsed) => ({
|
|
243
|
+
title: typeof parsed.title === "string" ? parsed.title : undefined,
|
|
244
|
+
}));
|
|
245
|
+
assert.deepEqual(got, { title: "alpha" });
|
|
246
|
+
});
|
|
247
|
+
test("PriorAliasReader returns null on missing file, read error, parse error, or non-object", async () => {
|
|
248
|
+
const client = createClient({
|
|
249
|
+
initialFiles: {
|
|
250
|
+
"/p/bad.json": "{ not json",
|
|
251
|
+
"/p/array.json": "[1,2,3]",
|
|
252
|
+
},
|
|
253
|
+
failReadOn: new Set(["/p/boom.json"]),
|
|
254
|
+
});
|
|
255
|
+
const reader = new PriorAliasReader(client, "ws-1");
|
|
256
|
+
assert.equal(await reader.read("/p/missing.json"), null);
|
|
257
|
+
assert.equal(await reader.read("/p/boom.json"), null);
|
|
258
|
+
assert.equal(await reader.read("/p/bad.json"), null);
|
|
259
|
+
assert.equal(await reader.read("/p/array.json"), null);
|
|
260
|
+
});
|
|
261
|
+
test("PriorAliasReader returns null when extractor throws", async () => {
|
|
262
|
+
const client = createClient({
|
|
263
|
+
initialFiles: { "/p/by-id/1.json": JSON.stringify({ title: "alpha" }) },
|
|
264
|
+
});
|
|
265
|
+
const reader = new PriorAliasReader(client, "ws-1");
|
|
266
|
+
const got = await reader.read("/p/by-id/1.json", () => {
|
|
267
|
+
throw new Error("extractor exploded");
|
|
268
|
+
});
|
|
269
|
+
assert.equal(got, null);
|
|
270
|
+
});
|
|
271
|
+
//# sourceMappingURL=emit-auxiliary.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-auxiliary.test.js","sourceRoot":"","sources":["../../../tests/emit-auxiliary/emit-auxiliary.test.ts"],"names":[],"mappings":"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"}
|