@catalyst-cloud/sdk 0.1.1 → 0.2.1
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/live-sync-client.d.ts +2 -0
- package/dist/live-sync-client.d.ts.map +1 -1
- package/dist/live-sync-client.js +9 -2
- package/dist/live-sync-client.js.map +1 -1
- package/dist/node.d.ts +8 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +22 -0
- package/dist/node.js.map +1 -0
- package/dist/replica/catalyst-replica.d.ts +159 -0
- package/dist/replica/catalyst-replica.d.ts.map +1 -0
- package/dist/replica/catalyst-replica.js +443 -0
- package/dist/replica/catalyst-replica.js.map +1 -0
- package/dist/replica/engine.d.ts +67 -0
- package/dist/replica/engine.d.ts.map +1 -0
- package/dist/replica/engine.js +216 -0
- package/dist/replica/engine.js.map +1 -0
- package/dist/replica/writer-lock.d.ts +34 -0
- package/dist/replica/writer-lock.d.ts.map +1 -0
- package/dist/replica/writer-lock.js +135 -0
- package/dist/replica/writer-lock.js.map +1 -0
- package/package.json +23 -1
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
// @catalyst-cloud/sdk/node — CatalystReplica: host-sync's writer + read seam behind ONE import.
|
|
2
|
+
//
|
|
3
|
+
// Composed from the published @catalyst-cloud/{schema,read-model,replicate} packages + the SDK's own
|
|
4
|
+
// LiveSyncClient. It is the drop-in replacement for the hand-rolled writer loop in apps/host-sync
|
|
5
|
+
// (apply.ts + sync-client.ts + live-client.ts + read-adapter.ts), now engine-generic:
|
|
6
|
+
//
|
|
7
|
+
// start() = open DB (injected engine, default bun:sqlite/node:sqlite auto-detect)
|
|
8
|
+
// → applyMigrations(MIRROR_MIGRATIONS) + sync_meta DDL
|
|
9
|
+
// → open LiveSyncClient: if cursor==null it STREAM-seeds /snapshot (the injected reseed),
|
|
10
|
+
// then replays {type:"sync", after:cursor}, applyDelta + cursor per frame; {type:"resync"}
|
|
11
|
+
// re-seeds. RESOLVES WHEN FIRST 'live' (caught-up + ready to read); background sync
|
|
12
|
+
// continues until close(). This is the deliberate fork from the raw transport's
|
|
13
|
+
// resolve-on-stop.
|
|
14
|
+
// reads = SYNCHRONOUS over the read-model SqlExecutor (node/bun sqlite is sync). The
|
|
15
|
+
// `buildIssuesView(replica.sql, …)` calls are LITERALLY unchanged.
|
|
16
|
+
//
|
|
17
|
+
// Reads, writes, migrations, cursor and wire semantics are byte-identical to host-sync because this
|
|
18
|
+
// runs the SAME @catalyst-cloud/read-model builders over the SAME @catalyst-cloud/replicate write path
|
|
19
|
+
// and the SAME @catalyst-cloud/schema MIRROR_MIGRATIONS.
|
|
20
|
+
import { applyMigrations, MIRROR_MIGRATIONS } from "@catalyst-cloud/schema";
|
|
21
|
+
import { applyDelta, truncateReplica, getCursor, setCursor, } from "@catalyst-cloud/replicate";
|
|
22
|
+
import { buildIssuesView, buildIssueDetail, buildPullsView, buildProjectsView, buildProjectDetail, buildInitiativesView, buildInitiativeDetail, } from "@catalyst-cloud/read-model";
|
|
23
|
+
import { LiveSyncClient, stripTrailingSlashes, } from "../live-sync-client.js";
|
|
24
|
+
import { autoDetectEngine, autoDetectReadonlyEngine, } from "./engine.js";
|
|
25
|
+
import { claimWriterLock, } from "./writer-lock.js";
|
|
26
|
+
/** Host-sync bookkeeping table (NOT part of the DO mirror schema, so not in MIRROR_MIGRATIONS): the
|
|
27
|
+
* change-feed cursor, so a restart resumes from the live `{type:"sync", after}` replay (or /changes)
|
|
28
|
+
* instead of a full /snapshot every boot. */
|
|
29
|
+
const SYNC_META_DDL = `CREATE TABLE IF NOT EXISTS sync_meta (
|
|
30
|
+
key TEXT PRIMARY KEY, value TEXT
|
|
31
|
+
);`;
|
|
32
|
+
/** Rows per streamed seed transaction — bounds memory + fsync so a large tenant never OOMs the node/bun
|
|
33
|
+
* process the way host-sync's buffered `await res.text()` + `split("\n")` snapshot would. */
|
|
34
|
+
const SEED_BATCH_ROWS = 1000;
|
|
35
|
+
export class CatalystReplica {
|
|
36
|
+
opts;
|
|
37
|
+
baseUrl;
|
|
38
|
+
fetchImpl;
|
|
39
|
+
log;
|
|
40
|
+
engine = null;
|
|
41
|
+
writeDb = null;
|
|
42
|
+
sqlExecutor = null;
|
|
43
|
+
client = null;
|
|
44
|
+
/** In-memory high-water of the last seq persisted, so each frame avoids re-reading the cursor row. */
|
|
45
|
+
highWater = 0;
|
|
46
|
+
_status = "connecting";
|
|
47
|
+
started = false;
|
|
48
|
+
closed = false;
|
|
49
|
+
/** READ-ONLY mode (opened via {@link CatalystReplica.openReadOnly}): no migrations, no seed, no
|
|
50
|
+
* socket; `start()` is a no-op and the write path is unreachable. */
|
|
51
|
+
readonlyMode = false;
|
|
52
|
+
/** The claimed single-writer lock (writers only; null for readers / `:memory:` / disabled). */
|
|
53
|
+
writerLock = null;
|
|
54
|
+
/** Resolved on the first 'live' status (start() = caught-up + ready to read); rejected on close /
|
|
55
|
+
* an initial seed failure. */
|
|
56
|
+
liveResolve = null;
|
|
57
|
+
liveReject = null;
|
|
58
|
+
constructor(opts) {
|
|
59
|
+
this.opts = opts;
|
|
60
|
+
this.baseUrl = stripTrailingSlashes(opts.baseUrl);
|
|
61
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
62
|
+
this.log =
|
|
63
|
+
opts.log ??
|
|
64
|
+
((lvl, msg, extra) => console[lvl === "error" ? "error" : "log"](`[catalyst-replica] ${msg}`, extra ?? ""));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Open a CatalystReplica as a READ-ONLY READER over a file another process owns as the WRITER
|
|
68
|
+
* (single-writer/many-reader, ADR-0008). The sqlite handle is opened read-only (`{ readonly: true }`
|
|
69
|
+
* + `busy_timeout`), so the reader runs NO migrations, pulls NO /snapshot, opens NO LiveSyncClient,
|
|
70
|
+
* and the write path is unreachable — it only serves the synchronous `build*View` reads, `.sql`,
|
|
71
|
+
* `.handle`, and `.cursor` off whatever the writer has already persisted. There is no live tailing:
|
|
72
|
+
* the reader sees the file's committed state at read time (a fresh `issues()` re-queries, so it
|
|
73
|
+
* reflects the writer's latest committed rows).
|
|
74
|
+
*
|
|
75
|
+
* Async because the default engine dynamic-imports its sqlite driver; once it resolves the replica
|
|
76
|
+
* is already open, so `start()` on the returned reader is a no-op.
|
|
77
|
+
*/
|
|
78
|
+
static async openReadOnly(opts) {
|
|
79
|
+
if (!opts.dbPath || opts.dbPath === ":memory:" || opts.dbPath.startsWith("file::memory:")) {
|
|
80
|
+
throw new Error("CatalystReplica.openReadOnly: a reader needs a FILE path to a writer-seeded replica; " +
|
|
81
|
+
"':memory:' is a fresh per-connection DB with nothing to read.");
|
|
82
|
+
}
|
|
83
|
+
// Reuse the writer constructor with placeholders for the connect-only fields (never touched in
|
|
84
|
+
// read-only mode), then open the read-only engine eagerly.
|
|
85
|
+
const replica = new CatalystReplica({
|
|
86
|
+
baseUrl: "readonly://local",
|
|
87
|
+
account: "",
|
|
88
|
+
auth: { kind: "cookie" },
|
|
89
|
+
dbPath: opts.dbPath,
|
|
90
|
+
engine: opts.engine,
|
|
91
|
+
log: opts.log,
|
|
92
|
+
});
|
|
93
|
+
await replica.openReadOnlyInternal(opts);
|
|
94
|
+
return replica;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Open + migrate the replica, then open the live socket. Resolves when the replica is caught-up and
|
|
98
|
+
* ready to read (first 'live'); background sync continues until close(). A cold tenant stream-seeds
|
|
99
|
+
* /snapshot first (via the injected reseed inside LiveSyncClient.start), so 'live' implies
|
|
100
|
+
* seed-complete. NOTE: there is no built-in timeout — a stalled /snapshot or unreachable host can
|
|
101
|
+
* delay 'live'; drive progress via onStatus.
|
|
102
|
+
*/
|
|
103
|
+
async start() {
|
|
104
|
+
// A reader is already open (openReadOnly opened the engine); start() is an inert no-op.
|
|
105
|
+
if (this.readonlyMode)
|
|
106
|
+
return;
|
|
107
|
+
if (this.closed)
|
|
108
|
+
throw new Error("CatalystReplica: start() after close()");
|
|
109
|
+
if (this.started)
|
|
110
|
+
throw new Error("CatalystReplica: start() already called");
|
|
111
|
+
this.started = true;
|
|
112
|
+
// Single-writer guard: claim sole ownership of the file BEFORE opening it, so a second concurrent
|
|
113
|
+
// writer rejects here instead of racing the cursor/seed. A no-op for ':memory:' or when disabled.
|
|
114
|
+
this.writerLock = claimWriterLock(this.opts.dbPath, this.opts.writerGuard ?? {}, this.log);
|
|
115
|
+
const engine = await this.resolveEngine();
|
|
116
|
+
this.engine = engine;
|
|
117
|
+
this.writeDb = {
|
|
118
|
+
run: (sql, ...bindings) => engine.run(sql, ...bindings),
|
|
119
|
+
get: (sql, ...bindings) => engine.get(sql, ...bindings),
|
|
120
|
+
};
|
|
121
|
+
this.sqlExecutor = {
|
|
122
|
+
exec: (query, ...bindings) => ({
|
|
123
|
+
toArray: () => engine.all(query, ...bindings.map(engine.toBindable)),
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
// Migrate via the ~3-line MigrationDb adapter, then add the host-only cursor table.
|
|
127
|
+
const migrationDb = {
|
|
128
|
+
exec: (sql) => engine.exec(sql),
|
|
129
|
+
query: (sql) => engine.all(sql),
|
|
130
|
+
};
|
|
131
|
+
applyMigrations(migrationDb, MIRROR_MIGRATIONS);
|
|
132
|
+
engine.exec(SYNC_META_DDL);
|
|
133
|
+
this.highWater = getCursor(this.writeDb) ?? 0;
|
|
134
|
+
this.client = new LiveSyncClient({
|
|
135
|
+
baseUrl: this.baseUrl,
|
|
136
|
+
accountId: this.opts.account,
|
|
137
|
+
connectPath: this.opts.connectPath,
|
|
138
|
+
auth: this.opts.auth,
|
|
139
|
+
reseed: () => this.seedFromSnapshot(),
|
|
140
|
+
getCursor: () => getCursor(this.writeDb),
|
|
141
|
+
onChange: (frame) => this.applyFrame(frame),
|
|
142
|
+
onStatus: (status) => this.handleStatus(status),
|
|
143
|
+
backoffMs: this.opts.backoffMs,
|
|
144
|
+
maxBackoffMs: this.opts.maxBackoffMs,
|
|
145
|
+
wsFactory: this.opts.wsFactory,
|
|
146
|
+
log: this.log,
|
|
147
|
+
});
|
|
148
|
+
const livePromise = new Promise((resolve, reject) => {
|
|
149
|
+
this.liveResolve = resolve;
|
|
150
|
+
this.liveReject = reject;
|
|
151
|
+
// The transport's start() resolves only on stop() ("runs forever"); we fork on first 'live'.
|
|
152
|
+
// Run it in the background and surface an initial seed/connect failure as a start() rejection.
|
|
153
|
+
void this.client.start().catch((err) => {
|
|
154
|
+
const rej = this.liveReject;
|
|
155
|
+
this.clearLiveDeferred();
|
|
156
|
+
rej?.(err);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
const timeoutMs = this.opts.startTimeoutMs;
|
|
160
|
+
if (timeoutMs == null)
|
|
161
|
+
return livePromise;
|
|
162
|
+
// Race the start sequence (seed + connect-to-live) against a deadline. On timeout, tear down the
|
|
163
|
+
// SAME way close() does (stop the socket, release the writer lock, close the engine) so nothing
|
|
164
|
+
// leaks, then reject — a supervisor fails fast instead of hanging on a wedged /snapshot. Reaching
|
|
165
|
+
// 'live' first cancels the (unref'd) timer, so there's no late rejection and the timer never holds
|
|
166
|
+
// the event loop open on its own.
|
|
167
|
+
return new Promise((resolve, reject) => {
|
|
168
|
+
let settled = false;
|
|
169
|
+
const timer = setTimeout(() => {
|
|
170
|
+
if (settled)
|
|
171
|
+
return;
|
|
172
|
+
settled = true;
|
|
173
|
+
void this.close().finally(() => {
|
|
174
|
+
reject(new Error(`CatalystReplica.start() did not reach 'live' within ${timeoutMs}ms ` +
|
|
175
|
+
`(account=${this.opts.account}, dbPath=${this.opts.dbPath})`));
|
|
176
|
+
});
|
|
177
|
+
}, timeoutMs);
|
|
178
|
+
timer.unref?.();
|
|
179
|
+
livePromise.then(() => {
|
|
180
|
+
if (settled)
|
|
181
|
+
return;
|
|
182
|
+
settled = true;
|
|
183
|
+
clearTimeout(timer);
|
|
184
|
+
resolve();
|
|
185
|
+
}, (err) => {
|
|
186
|
+
if (settled)
|
|
187
|
+
return;
|
|
188
|
+
settled = true;
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
reject(err);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Stop the socket, release the writer lock, close the DB. Idempotent. Rejects a still-pending
|
|
195
|
+
* start(). On a reader: no socket/lock to release — just closes the read-only handle. */
|
|
196
|
+
async close() {
|
|
197
|
+
if (this.closed)
|
|
198
|
+
return;
|
|
199
|
+
this.closed = true;
|
|
200
|
+
this.client?.stop();
|
|
201
|
+
const rej = this.liveReject;
|
|
202
|
+
this.clearLiveDeferred();
|
|
203
|
+
rej?.(new Error("CatalystReplica: closed before first 'live'"));
|
|
204
|
+
try {
|
|
205
|
+
this.writerLock?.release();
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
this.log("warn", "writer-lock release threw", err);
|
|
209
|
+
}
|
|
210
|
+
this.writerLock = null;
|
|
211
|
+
try {
|
|
212
|
+
this.engine?.close();
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
this.log("warn", "engine close threw", err);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// ── Reads ───────────────────────────────────────────────────────────────────────────────────────
|
|
219
|
+
/** The read-model SqlExecutor over the replica. `buildIssuesView(replica.sql, …)` is unchanged. */
|
|
220
|
+
get sql() {
|
|
221
|
+
if (!this.sqlExecutor)
|
|
222
|
+
throw new Error("CatalystReplica: call start() before reading");
|
|
223
|
+
return this.sqlExecutor;
|
|
224
|
+
}
|
|
225
|
+
issues(opts) {
|
|
226
|
+
return buildIssuesView(this.sql, opts?.limit, opts?.offset);
|
|
227
|
+
}
|
|
228
|
+
issue(identifier) {
|
|
229
|
+
return buildIssueDetail(this.sql, identifier);
|
|
230
|
+
}
|
|
231
|
+
pulls(opts) {
|
|
232
|
+
return buildPullsView(this.sql, opts?.limit, opts?.offset);
|
|
233
|
+
}
|
|
234
|
+
projects(opts) {
|
|
235
|
+
return buildProjectsView(this.sql, opts?.limit, opts?.offset);
|
|
236
|
+
}
|
|
237
|
+
project(id) {
|
|
238
|
+
return buildProjectDetail(this.sql, id);
|
|
239
|
+
}
|
|
240
|
+
initiatives(opts) {
|
|
241
|
+
return buildInitiativesView(this.sql, opts?.limit, opts?.offset);
|
|
242
|
+
}
|
|
243
|
+
initiative(id) {
|
|
244
|
+
return buildInitiativeDetail(this.sql, id);
|
|
245
|
+
}
|
|
246
|
+
/** The durable change-feed cursor (sync_meta), or null before the first seed. */
|
|
247
|
+
get cursor() {
|
|
248
|
+
return this.writeDb ? getCursor(this.writeDb) : null;
|
|
249
|
+
}
|
|
250
|
+
/** The connection lifecycle status. */
|
|
251
|
+
get status() {
|
|
252
|
+
return this._status;
|
|
253
|
+
}
|
|
254
|
+
/** The raw driver Database the SDK owns — for `drizzle(replica.handle, { schema: mirrorSchema })`. */
|
|
255
|
+
get handle() {
|
|
256
|
+
return this.engine?.handle;
|
|
257
|
+
}
|
|
258
|
+
// ── Internals ─────────────────────────────────────────────────────────────────────────────────
|
|
259
|
+
async resolveEngine() {
|
|
260
|
+
const e = this.opts.engine;
|
|
261
|
+
if (e === undefined)
|
|
262
|
+
return autoDetectEngine(this.opts.dbPath);
|
|
263
|
+
if (typeof e === "function")
|
|
264
|
+
return e(this.opts.dbPath);
|
|
265
|
+
return e;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Open the read-only engine and wire ONLY the read ports — no migrations, no `sync_meta` DDL, no
|
|
269
|
+
* snapshot, no LiveSyncClient. The write-facing `writeDb` is a read-capable shim whose `run` throws,
|
|
270
|
+
* so reads (`sql`/`build*View`/`cursor`) work but any accidental write path is unreachable AND the
|
|
271
|
+
* underlying sqlite handle is itself read-only.
|
|
272
|
+
*/
|
|
273
|
+
async openReadOnlyInternal(opts) {
|
|
274
|
+
this.readonlyMode = true;
|
|
275
|
+
this.started = true; // already open → start() is a no-op
|
|
276
|
+
const e = opts.engine;
|
|
277
|
+
const engine = e === undefined
|
|
278
|
+
? await autoDetectReadonlyEngine(opts.dbPath)
|
|
279
|
+
: typeof e === "function"
|
|
280
|
+
? await e(opts.dbPath)
|
|
281
|
+
: e;
|
|
282
|
+
this.engine = engine;
|
|
283
|
+
this.sqlExecutor = {
|
|
284
|
+
exec: (query, ...bindings) => ({
|
|
285
|
+
toArray: () => engine.all(query, ...bindings.map(engine.toBindable)),
|
|
286
|
+
}),
|
|
287
|
+
};
|
|
288
|
+
// Read-capable cursor access; `run` is poisoned so the write path can never fire on a reader.
|
|
289
|
+
this.writeDb = {
|
|
290
|
+
get: (sql, ...bindings) => engine.get(sql, ...bindings),
|
|
291
|
+
run: () => {
|
|
292
|
+
throw new Error("CatalystReplica: this replica is READ-ONLY (opened via openReadOnly)");
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
this._status = "live"; // a reader is "live" the moment it is open (it has no socket)
|
|
296
|
+
}
|
|
297
|
+
handleStatus(status) {
|
|
298
|
+
this._status = status;
|
|
299
|
+
if (status === "live" && this.liveResolve) {
|
|
300
|
+
const res = this.liveResolve;
|
|
301
|
+
this.clearLiveDeferred();
|
|
302
|
+
res();
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
this.opts.onStatus?.(status);
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
this.log("warn", "onStatus handler threw", err);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
clearLiveDeferred() {
|
|
312
|
+
this.liveResolve = null;
|
|
313
|
+
this.liveReject = null;
|
|
314
|
+
}
|
|
315
|
+
/** Land one delta + advance the durable cursor atomically (a crash can't skip a seq), then signal. */
|
|
316
|
+
applyFrame(frame) {
|
|
317
|
+
const engine = this.engine;
|
|
318
|
+
const writeDb = this.writeDb;
|
|
319
|
+
if (!engine || !writeDb)
|
|
320
|
+
return;
|
|
321
|
+
try {
|
|
322
|
+
engine.transaction(() => {
|
|
323
|
+
applyDelta(writeDb, { entity: frame.entity, op: frame.op, row: frame.row ?? {}, entityId: frame.entityId }, engine.toBindable);
|
|
324
|
+
// Advance to the seq we SAW (not just applied), so a stale-but-newer-seq delta still moves the
|
|
325
|
+
// cursor forward and a reconnect doesn't re-request it.
|
|
326
|
+
if (frame.seq > this.highWater)
|
|
327
|
+
setCursor(writeDb, frame.seq, engine.toBindable);
|
|
328
|
+
});
|
|
329
|
+
if (frame.seq > this.highWater)
|
|
330
|
+
this.highWater = frame.seq;
|
|
331
|
+
try {
|
|
332
|
+
this.opts.onChange?.();
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
this.log("warn", "onChange handler threw", err);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
this.log("error", `apply failed for ${frame.entity} seq=${frame.seq}`, err);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Stream-seed the replica from /snapshot and return the fresh cursor. Streams `response.body` as
|
|
344
|
+
* NDJSON (chunked, batched transactions) so a large tenant never materializes the whole snapshot in
|
|
345
|
+
* memory — the OOM fix vs host-sync's buffered seed. The cursor row is DELETED up front so an
|
|
346
|
+
* interrupted seed self-heals (getCursor → null → re-seed on the next start), preserving host-sync's
|
|
347
|
+
* atomic truncate+apply+setCursor safety without holding one giant transaction.
|
|
348
|
+
*/
|
|
349
|
+
async seedFromSnapshot() {
|
|
350
|
+
const engine = this.engine;
|
|
351
|
+
const writeDb = this.writeDb;
|
|
352
|
+
const url = `${this.baseUrl}/snapshot?account=${encodeURIComponent(this.opts.account)}`;
|
|
353
|
+
const res = await this.fetchImpl(url, { headers: this.feedHeaders() });
|
|
354
|
+
if (!res.ok)
|
|
355
|
+
throw new Error(`/snapshot ${res.status}`);
|
|
356
|
+
// Invalidate the cursor BEFORE truncating so a crash mid-seed re-seeds rather than going live over
|
|
357
|
+
// an empty replica from a stale cursor.
|
|
358
|
+
engine.run("DELETE FROM sync_meta WHERE key = 'cursor'");
|
|
359
|
+
engine.transaction(() => truncateReplica(writeDb));
|
|
360
|
+
let cursor = 0;
|
|
361
|
+
let batch = [];
|
|
362
|
+
const flush = () => {
|
|
363
|
+
if (batch.length === 0)
|
|
364
|
+
return;
|
|
365
|
+
const rows = batch;
|
|
366
|
+
batch = [];
|
|
367
|
+
engine.transaction(() => {
|
|
368
|
+
for (const rec of rows) {
|
|
369
|
+
if (rec.entity === undefined)
|
|
370
|
+
continue;
|
|
371
|
+
applyDelta(writeDb, { entity: rec.entity, op: rec.op ?? "upsert", row: rec.row ?? {} }, engine.toBindable);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
};
|
|
375
|
+
let rowCount = 0;
|
|
376
|
+
for await (const line of iterateNdjson(res)) {
|
|
377
|
+
const rec = JSON.parse(line);
|
|
378
|
+
if (typeof rec.cursor === "number") {
|
|
379
|
+
cursor = rec.cursor; // the FINAL line carries the cursor
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
batch.push(rec);
|
|
383
|
+
rowCount++;
|
|
384
|
+
if (batch.length >= SEED_BATCH_ROWS)
|
|
385
|
+
flush();
|
|
386
|
+
}
|
|
387
|
+
flush();
|
|
388
|
+
engine.transaction(() => setCursor(writeDb, cursor, engine.toBindable));
|
|
389
|
+
this.highWater = cursor;
|
|
390
|
+
this.log("info", `snapshot seeded (${rowCount} rows), cursor=${cursor}`);
|
|
391
|
+
try {
|
|
392
|
+
this.opts.onChange?.();
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
this.log("warn", "onChange handler threw", err);
|
|
396
|
+
}
|
|
397
|
+
return cursor;
|
|
398
|
+
}
|
|
399
|
+
feedHeaders() {
|
|
400
|
+
const h = { accept: "application/x-ndjson" };
|
|
401
|
+
if (this.opts.auth.kind === "token")
|
|
402
|
+
h["authorization"] = `Bearer ${this.opts.auth.token}`;
|
|
403
|
+
return h;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Iterate an NDJSON Response as non-empty lines. Streams `response.body` (chunked + partial-line
|
|
408
|
+
* buffered) when present — the production path that never buffers the whole snapshot; falls back to a
|
|
409
|
+
* buffered `await res.text()` when the body is absent (e.g. a test fetch stand-in).
|
|
410
|
+
*/
|
|
411
|
+
async function* iterateNdjson(res) {
|
|
412
|
+
const body = res.body;
|
|
413
|
+
if (body && typeof body.getReader === "function") {
|
|
414
|
+
const reader = body.getReader();
|
|
415
|
+
const decoder = new TextDecoder();
|
|
416
|
+
let buf = "";
|
|
417
|
+
for (;;) {
|
|
418
|
+
const { done, value } = await reader.read();
|
|
419
|
+
if (done)
|
|
420
|
+
break;
|
|
421
|
+
buf += decoder.decode(value, { stream: true });
|
|
422
|
+
let nl;
|
|
423
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
424
|
+
const line = buf.slice(0, nl);
|
|
425
|
+
buf = buf.slice(nl + 1);
|
|
426
|
+
if (line.length > 0)
|
|
427
|
+
yield line;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
buf += decoder.decode();
|
|
431
|
+
if (buf.length > 0) {
|
|
432
|
+
for (const line of buf.split("\n"))
|
|
433
|
+
if (line.length > 0)
|
|
434
|
+
yield line;
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const text = await res.text();
|
|
439
|
+
for (const line of text.split("\n"))
|
|
440
|
+
if (line.length > 0)
|
|
441
|
+
yield line;
|
|
442
|
+
}
|
|
443
|
+
//# sourceMappingURL=catalyst-replica.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalyst-replica.js","sourceRoot":"","sources":["../../src/replica/catalyst-replica.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** A bindable SQLite scalar on the node/bun side (booleans stored as 0/1 INTEGER; blobs as Uint8Array). */
|
|
2
|
+
export type EngineBindable = string | number | bigint | null | Uint8Array;
|
|
3
|
+
/**
|
|
4
|
+
* The portable sqlite engine the SDK owns and adapts. Generic over the bindable type `B` so a driver
|
|
5
|
+
* that binds binary/bigint differently can declare its own; the node/bun builtins use `EngineBindable`.
|
|
6
|
+
*/
|
|
7
|
+
export interface ReplicaEngine<B = unknown> {
|
|
8
|
+
/** Execute DDL / migration statement(s) for side effect (no rows). */
|
|
9
|
+
exec(sql: string): void;
|
|
10
|
+
/** Run a parameterized SELECT → rows as plain objects (reads + the migration ledger). */
|
|
11
|
+
all(sql: string, ...bindings: B[]): Record<string, B>[];
|
|
12
|
+
/** Run a parameterized mutation → sqlite3_changes() (rows written). */
|
|
13
|
+
run(sql: string, ...bindings: B[]): number;
|
|
14
|
+
/** Run a single-row query → the first row, or undefined. */
|
|
15
|
+
get(sql: string, ...bindings: B[]): Record<string, B> | undefined;
|
|
16
|
+
/** Run `fn` inside one atomic transaction (snapshot-seed batch / delta apply). */
|
|
17
|
+
transaction<T>(fn: () => T): T;
|
|
18
|
+
/** Coerce ONE wire JSON value to an engine-bindable scalar (bool → 0/1, blob/bigint per engine). */
|
|
19
|
+
toBindable: (value: unknown) => B;
|
|
20
|
+
/** Close the underlying database handle. */
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
23
|
+
/** A factory that opens a `ReplicaEngine` over `dbPath`. May be async (drivers are dynamic-imported). */
|
|
24
|
+
export type EngineFactory = (dbPath: string) => ReplicaEngine | Promise<ReplicaEngine>;
|
|
25
|
+
/** A `ReplicaEngine` plus the raw driver `handle` (exposed via CatalystReplica.handle for drizzle). */
|
|
26
|
+
export type ReplicaEngineWithHandle = ReplicaEngine<EngineBindable> & {
|
|
27
|
+
readonly handle: unknown;
|
|
28
|
+
};
|
|
29
|
+
export declare function bunSqliteEngine(dbPath: string): Promise<ReplicaEngineWithHandle>;
|
|
30
|
+
/** Read-only `bun:sqlite` engine (`{ readonly: true }` + `busy_timeout`). See {@link openBun}. */
|
|
31
|
+
export declare function bunSqliteReadonlyEngine(dbPath: string): Promise<ReplicaEngineWithHandle>;
|
|
32
|
+
export declare function nodeSqliteEngine(dbPath: string): Promise<ReplicaEngineWithHandle>;
|
|
33
|
+
/** Read-only `node:sqlite` engine (`{ readOnly: true }` + `busy_timeout`). See {@link openNode}. */
|
|
34
|
+
export declare function nodeSqliteReadonlyEngine(dbPath: string): Promise<ReplicaEngineWithHandle>;
|
|
35
|
+
/** A statement handle the `better-sqlite3` default export hands back (typed structurally — no import). */
|
|
36
|
+
interface BetterSqlite3Statement {
|
|
37
|
+
all(...bindings: unknown[]): unknown[];
|
|
38
|
+
get(...bindings: unknown[]): unknown;
|
|
39
|
+
run(...bindings: unknown[]): {
|
|
40
|
+
changes: number | bigint;
|
|
41
|
+
lastInsertRowid: number | bigint;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** The `better-sqlite3` Database surface this engine uses (typed structurally — no import / no peer pin). */
|
|
45
|
+
interface BetterSqlite3Database {
|
|
46
|
+
prepare(sql: string): BetterSqlite3Statement;
|
|
47
|
+
exec(sql: string): void;
|
|
48
|
+
pragma(source: string): unknown;
|
|
49
|
+
transaction<Args extends unknown[], R>(fn: (...args: Args) => R): (...args: Args) => R;
|
|
50
|
+
close(): void;
|
|
51
|
+
}
|
|
52
|
+
/** `import Database from "better-sqlite3"` is structurally assignable to this constructor type. */
|
|
53
|
+
export type BetterSqlite3Driver = new (path?: string, options?: unknown) => BetterSqlite3Database;
|
|
54
|
+
export declare function betterSqlite3Engine(driver: BetterSqlite3Driver, dbPath: string): ReplicaEngineWithHandle;
|
|
55
|
+
/** Read-only `better-sqlite3` engine (`{ readonly: true }` + `busy_timeout`). See {@link openBetter}. */
|
|
56
|
+
export declare function betterSqlite3ReadonlyEngine(driver: BetterSqlite3Driver, dbPath: string): ReplicaEngineWithHandle;
|
|
57
|
+
/**
|
|
58
|
+
* Auto-detect the default engine when `opts.engine` is omitted: Bun → `bunSqliteEngine`; else
|
|
59
|
+
* `node:sqlite` if available → `nodeSqliteEngine`; else throw a clear "pass opts.engine" error rather
|
|
60
|
+
* than pick an engine that resolves but breaks on first write.
|
|
61
|
+
*/
|
|
62
|
+
export declare function autoDetectEngine(dbPath: string): Promise<ReplicaEngineWithHandle>;
|
|
63
|
+
/** READ-ONLY counterpart of {@link autoDetectEngine}: Bun → `bunSqliteReadonlyEngine`; else
|
|
64
|
+
* `nodeSqliteReadonlyEngine`. Used by `CatalystReplica.openReadOnly` when no engine is injected. */
|
|
65
|
+
export declare function autoDetectReadonlyEngine(dbPath: string): Promise<ReplicaEngineWithHandle>;
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/replica/engine.ts"],"names":[],"mappings":"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"}
|