@catalyst-cloud/sdk 0.1.1 → 0.2.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/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 +153 -0
- package/dist/replica/catalyst-replica.d.ts.map +1 -0
- package/dist/replica/catalyst-replica.js +409 -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 +27 -0
- package/dist/replica/writer-lock.d.ts.map +1 -0
- package/dist/replica/writer-lock.js +125 -0
- package/dist/replica/writer-lock.js.map +1 -0
- package/package.json +23 -1
|
@@ -92,6 +92,8 @@ export declare function toWsOrigin(baseUrl: string): string;
|
|
|
92
92
|
* is ever appended (the type system + this single construction point make a browser token leak
|
|
93
93
|
* impossible). Token is ordered FIRST so a truncated log line still reveals the account.
|
|
94
94
|
*/
|
|
95
|
+
/** Strip trailing "/" without a backtracking regex (ReDoS-safe vs `/\/+$/`, CodeQL js/polynomial-redos). */
|
|
96
|
+
export declare function stripTrailingSlashes(s: string): string;
|
|
95
97
|
export declare function buildConnectUrl(opts: {
|
|
96
98
|
baseUrl: string;
|
|
97
99
|
connectPath: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-sync-client.d.ts","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"live-sync-client.d.ts","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"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"}
|
package/dist/live-sync-client.js
CHANGED
|
@@ -40,8 +40,15 @@ export function toWsOrigin(baseUrl) {
|
|
|
40
40
|
* is ever appended (the type system + this single construction point make a browser token leak
|
|
41
41
|
* impossible). Token is ordered FIRST so a truncated log line still reveals the account.
|
|
42
42
|
*/
|
|
43
|
+
/** Strip trailing "/" without a backtracking regex (ReDoS-safe vs `/\/+$/`, CodeQL js/polynomial-redos). */
|
|
44
|
+
export function stripTrailingSlashes(s) {
|
|
45
|
+
let end = s.length;
|
|
46
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47 /* "/" */)
|
|
47
|
+
end--;
|
|
48
|
+
return end === s.length ? s : s.slice(0, end);
|
|
49
|
+
}
|
|
43
50
|
export function buildConnectUrl(opts) {
|
|
44
|
-
const origin = toWsOrigin(opts.baseUrl
|
|
51
|
+
const origin = toWsOrigin(stripTrailingSlashes(opts.baseUrl));
|
|
45
52
|
const params = new URLSearchParams();
|
|
46
53
|
if (opts.auth.kind === "token")
|
|
47
54
|
params.set("token", opts.auth.token);
|
|
@@ -69,7 +76,7 @@ export class LiveSyncClient {
|
|
|
69
76
|
reconnectTimer = null;
|
|
70
77
|
resolveDone = null;
|
|
71
78
|
constructor(opts) {
|
|
72
|
-
this.baseUrl = opts.baseUrl
|
|
79
|
+
this.baseUrl = stripTrailingSlashes(opts.baseUrl);
|
|
73
80
|
this.accountId = opts.accountId;
|
|
74
81
|
this.connectPath = opts.connectPath ?? "/connect";
|
|
75
82
|
this.auth = opts.auth;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-sync-client.js","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,+FAA+F;AAC/F,kGAAkG;AAClG,mGAAmG;AACnG,sGAAsG;AACtG,qGAAqG;AACrG,sCAAsC;AACtC,EAAE;AACF,oGAAoG;AACpG,mFAAmF;AACnF,EAAE;AACF,oGAAoG;AACpG,sGAAsG;AACtG,uGAAuG;AACvG,sGAAsG;AACtG,wFAAwF;AACxF,gGAAgG;AAChG,wEAAwE;AACxE,8FAA8F;AAC9F,kGAAkG;AAClG,8CAA8C;AAC9C,EAAE;AACF,oGAAoG;AACpG,wFAAwF;AA+FxF,gFAAgF;AAChF,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,IAAI,GAAI,UAA+D,CAAC,SAAS,CAAC;IACxF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CACb,sFAAsF,CACvF,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"live-sync-client.js","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"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"}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { CatalystReplica, type CatalystReplicaOptions, type CatalystReplicaReadOnlyOptions, } from "./replica/catalyst-replica.js";
|
|
2
|
+
export { bunSqliteEngine, bunSqliteReadonlyEngine, nodeSqliteEngine, nodeSqliteReadonlyEngine, betterSqlite3Engine, betterSqlite3ReadonlyEngine, autoDetectEngine, autoDetectReadonlyEngine, type ReplicaEngine, type EngineFactory, type EngineBindable, type BetterSqlite3Driver, } from "./replica/engine.js";
|
|
3
|
+
export { claimWriterLock, type WriterGuardOptions, type WriterLockHandle, } from "./replica/writer-lock.js";
|
|
4
|
+
export { LiveSyncClient, type AuthStrategy, type LiveSyncStatus, type LogLevel, type WebSocketLike, type WebSocketFactory, } from "./live-sync-client.js";
|
|
5
|
+
export { ENTITY_NAMES, CHANGE_OPS, type AccountId, type EntityName, type ChangeOp, type ChangeFrame, } from "./types.js";
|
|
6
|
+
export type { SqlExecutor, SqlValue, IssueView, IssueDetailView, PullView, ProjectView, ProjectDetailView, InitiativeView, InitiativeDetailView, } from "@catalyst-cloud/read-model";
|
|
7
|
+
export { mirrorSchema, type MirrorEntityName } from "@catalyst-cloud/schema";
|
|
8
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,GACpC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EACL,cAAc,EACd,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,YAAY,EACZ,UAAU,EACV,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,QAAQ,EACb,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,WAAW,EACX,QAAQ,EACR,SAAS,EACT,eAAe,EACf,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAMpC,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @catalyst-cloud/sdk/node — the managed replica for node/bun (CTC-113).
|
|
2
|
+
//
|
|
3
|
+
// `CatalystReplica` is host-sync's writer + read seam behind ONE import: open + migrate a local
|
|
4
|
+
// SQLite replica, stream-seed /snapshot, subscribe to the live change feed (the SDK's LiveSyncClient),
|
|
5
|
+
// and serve the @catalyst-cloud/read-model `build*View` queries SYNCHRONOUSLY over it. Composed from
|
|
6
|
+
// the published @catalyst-cloud/{schema,read-model,replicate} packages; the sqlite driver is INJECTED
|
|
7
|
+
// (default auto-detect: bun:sqlite, else node:sqlite) so the SDK never hard-deps one.
|
|
8
|
+
//
|
|
9
|
+
// The root entrypoint ("@catalyst-cloud/sdk") stays the transport-only, isomorphic surface; this
|
|
10
|
+
// subpath adds the replica. Import this only from a node/bun backend.
|
|
11
|
+
export { CatalystReplica, } from "./replica/catalyst-replica.js";
|
|
12
|
+
export { bunSqliteEngine, bunSqliteReadonlyEngine, nodeSqliteEngine, nodeSqliteReadonlyEngine, betterSqlite3Engine, betterSqlite3ReadonlyEngine, autoDetectEngine, autoDetectReadonlyEngine, } from "./replica/engine.js";
|
|
13
|
+
export { claimWriterLock, } from "./replica/writer-lock.js";
|
|
14
|
+
// Re-export the transport surface so a consumer can stay on one import.
|
|
15
|
+
export { LiveSyncClient, } from "./live-sync-client.js";
|
|
16
|
+
export { ENTITY_NAMES, CHANGE_OPS, } from "./types.js";
|
|
17
|
+
// Re-export the Drizzle table schema so the `.handle` ad-hoc escape hatch is a one-liner — the
|
|
18
|
+
// read-model `build*View` results stay the primary read surface; this is for custom typed queries:
|
|
19
|
+
// import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
20
|
+
// const db = drizzle(replica.handle as Database, { schema: mirrorSchema });
|
|
21
|
+
export { mirrorSchema } from "@catalyst-cloud/schema";
|
|
22
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,gGAAgG;AAChG,uGAAuG;AACvG,qGAAqG;AACrG,sGAAsG;AACtG,sFAAsF;AACtF,EAAE;AACF,iGAAiG;AACjG,sEAAsE;AAEtE,OAAO,EACL,eAAe,GAGhB,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,gBAAgB,EAChB,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,gBAAgB,EAChB,wBAAwB,GAKzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,eAAe,GAGhB,MAAM,0BAA0B,CAAC;AAElC,wEAAwE;AACxE,OAAO,EACL,cAAc,GAMf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,YAAY,EACZ,UAAU,GAKX,MAAM,YAAY,CAAC;AAepB,+FAA+F;AAC/F,mGAAmG;AACnG,sDAAsD;AACtD,8EAA8E;AAC9E,OAAO,EAAE,YAAY,EAAyB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { type SqlExecutor, type IssueView, type IssueDetailView, type PullView, type ProjectView, type ProjectDetailView, type InitiativeView, type InitiativeDetailView } from "@catalyst-cloud/read-model";
|
|
2
|
+
import { type AuthStrategy, type LiveSyncStatus, type LogLevel, type WebSocketFactory } from "../live-sync-client.js";
|
|
3
|
+
import { type EngineFactory, type ReplicaEngine } from "./engine.js";
|
|
4
|
+
import { type WriterGuardOptions } from "./writer-lock.js";
|
|
5
|
+
export interface CatalystReplicaOptions {
|
|
6
|
+
/** http(s) origin incl. any path prefix (…/api/v1); the scheme is swapped to ws(s) for /connect. */
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
/** Tenant id = mirror name → `?account=` on every feed request. */
|
|
9
|
+
account: string;
|
|
10
|
+
/** How to authorize: {kind:'token',token} (host bearer rides /connect as ?token= and /snapshot as
|
|
11
|
+
* Authorization) | {kind:'cookie'} (same-origin session cookie). */
|
|
12
|
+
auth: AuthStrategy;
|
|
13
|
+
/** File path or ':memory:'. */
|
|
14
|
+
dbPath: string;
|
|
15
|
+
/** INJECTED sqlite engine, or a factory over dbPath. Default = auto-detect (bun:sqlite, else node:sqlite). */
|
|
16
|
+
engine?: ReplicaEngine | EngineFactory;
|
|
17
|
+
/** The connect route. Default '/connect'. */
|
|
18
|
+
connectPath?: string;
|
|
19
|
+
/** Snapshot/changes fetch. Default global fetch. */
|
|
20
|
+
fetchImpl?: typeof fetch;
|
|
21
|
+
/** Fired after each applied delta (live frame OR a completed seed) — a refetch hook. */
|
|
22
|
+
onChange?: () => void;
|
|
23
|
+
/** Connection lifecycle, for UI/logging. */
|
|
24
|
+
onStatus?: (status: LiveSyncStatus) => void;
|
|
25
|
+
/** Base reconnect backoff in ms. Default 1000. */
|
|
26
|
+
backoffMs?: number;
|
|
27
|
+
/** Reconnect backoff ceiling in ms. Default 30_000. */
|
|
28
|
+
maxBackoffMs?: number;
|
|
29
|
+
/** Injectable WebSocket factory (tests). Defaults to the runtime global WebSocket. */
|
|
30
|
+
wsFactory?: WebSocketFactory;
|
|
31
|
+
/** Optional structured logger; defaults to console. */
|
|
32
|
+
log?: (level: LogLevel, msg: string, extra?: unknown) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Single-writer guard (a sidecar `dbPath + '.writer.lock'`). On `start()` the writer best-effort
|
|
35
|
+
* claims sole ownership of the file; a second LIVE writer on the same path makes `start()` throw a
|
|
36
|
+
* clear error (single-writer/many-reader, ADR-0008). Advisory, not a hard OS lock; a no-op for
|
|
37
|
+
* `:memory:`. Default: enabled. Set `{ disabled: true }` to skip, `{ override: true }` to steal.
|
|
38
|
+
*/
|
|
39
|
+
writerGuard?: WriterGuardOptions;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Options for a READ-ONLY reader ({@link CatalystReplica.openReadOnly}). A reader needs only the file
|
|
43
|
+
* path (+ optional injected engine / logger): no `baseUrl`, `account`, or `auth`, because it opens NO
|
|
44
|
+
* socket and pulls NO snapshot.
|
|
45
|
+
*/
|
|
46
|
+
export interface CatalystReplicaReadOnlyOptions {
|
|
47
|
+
/** File path to an EXISTING writer-seeded replica. `:memory:` makes no sense for a reader (a fresh,
|
|
48
|
+
* empty, per-connection DB) and is rejected. */
|
|
49
|
+
dbPath: string;
|
|
50
|
+
/** INJECTED read-only sqlite engine or a factory over dbPath. Default = auto-detect READ-ONLY
|
|
51
|
+
* (bun:sqlite/node:sqlite opened `{ readonly: true }`). Pass a writable engine at your own risk. */
|
|
52
|
+
engine?: ReplicaEngine | EngineFactory;
|
|
53
|
+
/** Optional structured logger; defaults to console. */
|
|
54
|
+
log?: (level: LogLevel, msg: string, extra?: unknown) => void;
|
|
55
|
+
}
|
|
56
|
+
export declare class CatalystReplica {
|
|
57
|
+
private readonly opts;
|
|
58
|
+
private readonly baseUrl;
|
|
59
|
+
private readonly fetchImpl;
|
|
60
|
+
private readonly log;
|
|
61
|
+
private engine;
|
|
62
|
+
private writeDb;
|
|
63
|
+
private sqlExecutor;
|
|
64
|
+
private client;
|
|
65
|
+
/** In-memory high-water of the last seq persisted, so each frame avoids re-reading the cursor row. */
|
|
66
|
+
private highWater;
|
|
67
|
+
private _status;
|
|
68
|
+
private started;
|
|
69
|
+
private closed;
|
|
70
|
+
/** READ-ONLY mode (opened via {@link CatalystReplica.openReadOnly}): no migrations, no seed, no
|
|
71
|
+
* socket; `start()` is a no-op and the write path is unreachable. */
|
|
72
|
+
private readonlyMode;
|
|
73
|
+
/** The claimed single-writer lock (writers only; null for readers / `:memory:` / disabled). */
|
|
74
|
+
private writerLock;
|
|
75
|
+
/** Resolved on the first 'live' status (start() = caught-up + ready to read); rejected on close /
|
|
76
|
+
* an initial seed failure. */
|
|
77
|
+
private liveResolve;
|
|
78
|
+
private liveReject;
|
|
79
|
+
constructor(opts: CatalystReplicaOptions);
|
|
80
|
+
/**
|
|
81
|
+
* Open a CatalystReplica as a READ-ONLY READER over a file another process owns as the WRITER
|
|
82
|
+
* (single-writer/many-reader, ADR-0008). The sqlite handle is opened read-only (`{ readonly: true }`
|
|
83
|
+
* + `busy_timeout`), so the reader runs NO migrations, pulls NO /snapshot, opens NO LiveSyncClient,
|
|
84
|
+
* and the write path is unreachable — it only serves the synchronous `build*View` reads, `.sql`,
|
|
85
|
+
* `.handle`, and `.cursor` off whatever the writer has already persisted. There is no live tailing:
|
|
86
|
+
* the reader sees the file's committed state at read time (a fresh `issues()` re-queries, so it
|
|
87
|
+
* reflects the writer's latest committed rows).
|
|
88
|
+
*
|
|
89
|
+
* Async because the default engine dynamic-imports its sqlite driver; once it resolves the replica
|
|
90
|
+
* is already open, so `start()` on the returned reader is a no-op.
|
|
91
|
+
*/
|
|
92
|
+
static openReadOnly(opts: CatalystReplicaReadOnlyOptions): Promise<CatalystReplica>;
|
|
93
|
+
/**
|
|
94
|
+
* Open + migrate the replica, then open the live socket. Resolves when the replica is caught-up and
|
|
95
|
+
* ready to read (first 'live'); background sync continues until close(). A cold tenant stream-seeds
|
|
96
|
+
* /snapshot first (via the injected reseed inside LiveSyncClient.start), so 'live' implies
|
|
97
|
+
* seed-complete. NOTE: there is no built-in timeout — a stalled /snapshot or unreachable host can
|
|
98
|
+
* delay 'live'; drive progress via onStatus.
|
|
99
|
+
*/
|
|
100
|
+
start(): Promise<void>;
|
|
101
|
+
/** Stop the socket, release the writer lock, close the DB. Idempotent. Rejects a still-pending
|
|
102
|
+
* start(). On a reader: no socket/lock to release — just closes the read-only handle. */
|
|
103
|
+
close(): Promise<void>;
|
|
104
|
+
/** The read-model SqlExecutor over the replica. `buildIssuesView(replica.sql, …)` is unchanged. */
|
|
105
|
+
get sql(): SqlExecutor;
|
|
106
|
+
issues(opts?: {
|
|
107
|
+
limit?: number;
|
|
108
|
+
offset?: number;
|
|
109
|
+
}): IssueView[];
|
|
110
|
+
issue(identifier: string): IssueDetailView | null;
|
|
111
|
+
pulls(opts?: {
|
|
112
|
+
limit?: number;
|
|
113
|
+
offset?: number;
|
|
114
|
+
}): PullView[];
|
|
115
|
+
projects(opts?: {
|
|
116
|
+
limit?: number;
|
|
117
|
+
offset?: number;
|
|
118
|
+
}): ProjectView[];
|
|
119
|
+
project(id: string): ProjectDetailView | null;
|
|
120
|
+
initiatives(opts?: {
|
|
121
|
+
limit?: number;
|
|
122
|
+
offset?: number;
|
|
123
|
+
}): InitiativeView[];
|
|
124
|
+
initiative(id: string): InitiativeDetailView | null;
|
|
125
|
+
/** The durable change-feed cursor (sync_meta), or null before the first seed. */
|
|
126
|
+
get cursor(): number | null;
|
|
127
|
+
/** The connection lifecycle status. */
|
|
128
|
+
get status(): LiveSyncStatus;
|
|
129
|
+
/** The raw driver Database the SDK owns — for `drizzle(replica.handle, { schema: mirrorSchema })`. */
|
|
130
|
+
get handle(): unknown;
|
|
131
|
+
private resolveEngine;
|
|
132
|
+
/**
|
|
133
|
+
* Open the read-only engine and wire ONLY the read ports — no migrations, no `sync_meta` DDL, no
|
|
134
|
+
* snapshot, no LiveSyncClient. The write-facing `writeDb` is a read-capable shim whose `run` throws,
|
|
135
|
+
* so reads (`sql`/`build*View`/`cursor`) work but any accidental write path is unreachable AND the
|
|
136
|
+
* underlying sqlite handle is itself read-only.
|
|
137
|
+
*/
|
|
138
|
+
private openReadOnlyInternal;
|
|
139
|
+
private handleStatus;
|
|
140
|
+
private clearLiveDeferred;
|
|
141
|
+
/** Land one delta + advance the durable cursor atomically (a crash can't skip a seq), then signal. */
|
|
142
|
+
private applyFrame;
|
|
143
|
+
/**
|
|
144
|
+
* Stream-seed the replica from /snapshot and return the fresh cursor. Streams `response.body` as
|
|
145
|
+
* NDJSON (chunked, batched transactions) so a large tenant never materializes the whole snapshot in
|
|
146
|
+
* memory — the OOM fix vs host-sync's buffered seed. The cursor row is DELETED up front so an
|
|
147
|
+
* interrupted seed self-heals (getCursor → null → re-seed on the next start), preserving host-sync's
|
|
148
|
+
* atomic truncate+apply+setCursor safety without holding one giant transaction.
|
|
149
|
+
*/
|
|
150
|
+
private seedFromSnapshot;
|
|
151
|
+
private feedHeaders;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=catalyst-replica.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalyst-replica.d.ts","sourceRoot":"","sources":["../../src/replica/catalyst-replica.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,409 @@
|
|
|
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
|
+
return 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
|
+
}
|
|
160
|
+
/** Stop the socket, release the writer lock, close the DB. Idempotent. Rejects a still-pending
|
|
161
|
+
* start(). On a reader: no socket/lock to release — just closes the read-only handle. */
|
|
162
|
+
async close() {
|
|
163
|
+
if (this.closed)
|
|
164
|
+
return;
|
|
165
|
+
this.closed = true;
|
|
166
|
+
this.client?.stop();
|
|
167
|
+
const rej = this.liveReject;
|
|
168
|
+
this.clearLiveDeferred();
|
|
169
|
+
rej?.(new Error("CatalystReplica: closed before first 'live'"));
|
|
170
|
+
try {
|
|
171
|
+
this.writerLock?.release();
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
this.log("warn", "writer-lock release threw", err);
|
|
175
|
+
}
|
|
176
|
+
this.writerLock = null;
|
|
177
|
+
try {
|
|
178
|
+
this.engine?.close();
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
this.log("warn", "engine close threw", err);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ── Reads ───────────────────────────────────────────────────────────────────────────────────────
|
|
185
|
+
/** The read-model SqlExecutor over the replica. `buildIssuesView(replica.sql, …)` is unchanged. */
|
|
186
|
+
get sql() {
|
|
187
|
+
if (!this.sqlExecutor)
|
|
188
|
+
throw new Error("CatalystReplica: call start() before reading");
|
|
189
|
+
return this.sqlExecutor;
|
|
190
|
+
}
|
|
191
|
+
issues(opts) {
|
|
192
|
+
return buildIssuesView(this.sql, opts?.limit, opts?.offset);
|
|
193
|
+
}
|
|
194
|
+
issue(identifier) {
|
|
195
|
+
return buildIssueDetail(this.sql, identifier);
|
|
196
|
+
}
|
|
197
|
+
pulls(opts) {
|
|
198
|
+
return buildPullsView(this.sql, opts?.limit, opts?.offset);
|
|
199
|
+
}
|
|
200
|
+
projects(opts) {
|
|
201
|
+
return buildProjectsView(this.sql, opts?.limit, opts?.offset);
|
|
202
|
+
}
|
|
203
|
+
project(id) {
|
|
204
|
+
return buildProjectDetail(this.sql, id);
|
|
205
|
+
}
|
|
206
|
+
initiatives(opts) {
|
|
207
|
+
return buildInitiativesView(this.sql, opts?.limit, opts?.offset);
|
|
208
|
+
}
|
|
209
|
+
initiative(id) {
|
|
210
|
+
return buildInitiativeDetail(this.sql, id);
|
|
211
|
+
}
|
|
212
|
+
/** The durable change-feed cursor (sync_meta), or null before the first seed. */
|
|
213
|
+
get cursor() {
|
|
214
|
+
return this.writeDb ? getCursor(this.writeDb) : null;
|
|
215
|
+
}
|
|
216
|
+
/** The connection lifecycle status. */
|
|
217
|
+
get status() {
|
|
218
|
+
return this._status;
|
|
219
|
+
}
|
|
220
|
+
/** The raw driver Database the SDK owns — for `drizzle(replica.handle, { schema: mirrorSchema })`. */
|
|
221
|
+
get handle() {
|
|
222
|
+
return this.engine?.handle;
|
|
223
|
+
}
|
|
224
|
+
// ── Internals ─────────────────────────────────────────────────────────────────────────────────
|
|
225
|
+
async resolveEngine() {
|
|
226
|
+
const e = this.opts.engine;
|
|
227
|
+
if (e === undefined)
|
|
228
|
+
return autoDetectEngine(this.opts.dbPath);
|
|
229
|
+
if (typeof e === "function")
|
|
230
|
+
return e(this.opts.dbPath);
|
|
231
|
+
return e;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Open the read-only engine and wire ONLY the read ports — no migrations, no `sync_meta` DDL, no
|
|
235
|
+
* snapshot, no LiveSyncClient. The write-facing `writeDb` is a read-capable shim whose `run` throws,
|
|
236
|
+
* so reads (`sql`/`build*View`/`cursor`) work but any accidental write path is unreachable AND the
|
|
237
|
+
* underlying sqlite handle is itself read-only.
|
|
238
|
+
*/
|
|
239
|
+
async openReadOnlyInternal(opts) {
|
|
240
|
+
this.readonlyMode = true;
|
|
241
|
+
this.started = true; // already open → start() is a no-op
|
|
242
|
+
const e = opts.engine;
|
|
243
|
+
const engine = e === undefined
|
|
244
|
+
? await autoDetectReadonlyEngine(opts.dbPath)
|
|
245
|
+
: typeof e === "function"
|
|
246
|
+
? await e(opts.dbPath)
|
|
247
|
+
: e;
|
|
248
|
+
this.engine = engine;
|
|
249
|
+
this.sqlExecutor = {
|
|
250
|
+
exec: (query, ...bindings) => ({
|
|
251
|
+
toArray: () => engine.all(query, ...bindings.map(engine.toBindable)),
|
|
252
|
+
}),
|
|
253
|
+
};
|
|
254
|
+
// Read-capable cursor access; `run` is poisoned so the write path can never fire on a reader.
|
|
255
|
+
this.writeDb = {
|
|
256
|
+
get: (sql, ...bindings) => engine.get(sql, ...bindings),
|
|
257
|
+
run: () => {
|
|
258
|
+
throw new Error("CatalystReplica: this replica is READ-ONLY (opened via openReadOnly)");
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
this._status = "live"; // a reader is "live" the moment it is open (it has no socket)
|
|
262
|
+
}
|
|
263
|
+
handleStatus(status) {
|
|
264
|
+
this._status = status;
|
|
265
|
+
if (status === "live" && this.liveResolve) {
|
|
266
|
+
const res = this.liveResolve;
|
|
267
|
+
this.clearLiveDeferred();
|
|
268
|
+
res();
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
this.opts.onStatus?.(status);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
this.log("warn", "onStatus handler threw", err);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
clearLiveDeferred() {
|
|
278
|
+
this.liveResolve = null;
|
|
279
|
+
this.liveReject = null;
|
|
280
|
+
}
|
|
281
|
+
/** Land one delta + advance the durable cursor atomically (a crash can't skip a seq), then signal. */
|
|
282
|
+
applyFrame(frame) {
|
|
283
|
+
const engine = this.engine;
|
|
284
|
+
const writeDb = this.writeDb;
|
|
285
|
+
if (!engine || !writeDb)
|
|
286
|
+
return;
|
|
287
|
+
try {
|
|
288
|
+
engine.transaction(() => {
|
|
289
|
+
applyDelta(writeDb, { entity: frame.entity, op: frame.op, row: frame.row ?? {}, entityId: frame.entityId }, engine.toBindable);
|
|
290
|
+
// Advance to the seq we SAW (not just applied), so a stale-but-newer-seq delta still moves the
|
|
291
|
+
// cursor forward and a reconnect doesn't re-request it.
|
|
292
|
+
if (frame.seq > this.highWater)
|
|
293
|
+
setCursor(writeDb, frame.seq, engine.toBindable);
|
|
294
|
+
});
|
|
295
|
+
if (frame.seq > this.highWater)
|
|
296
|
+
this.highWater = frame.seq;
|
|
297
|
+
try {
|
|
298
|
+
this.opts.onChange?.();
|
|
299
|
+
}
|
|
300
|
+
catch (err) {
|
|
301
|
+
this.log("warn", "onChange handler threw", err);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
this.log("error", `apply failed for ${frame.entity} seq=${frame.seq}`, err);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Stream-seed the replica from /snapshot and return the fresh cursor. Streams `response.body` as
|
|
310
|
+
* NDJSON (chunked, batched transactions) so a large tenant never materializes the whole snapshot in
|
|
311
|
+
* memory — the OOM fix vs host-sync's buffered seed. The cursor row is DELETED up front so an
|
|
312
|
+
* interrupted seed self-heals (getCursor → null → re-seed on the next start), preserving host-sync's
|
|
313
|
+
* atomic truncate+apply+setCursor safety without holding one giant transaction.
|
|
314
|
+
*/
|
|
315
|
+
async seedFromSnapshot() {
|
|
316
|
+
const engine = this.engine;
|
|
317
|
+
const writeDb = this.writeDb;
|
|
318
|
+
const url = `${this.baseUrl}/snapshot?account=${encodeURIComponent(this.opts.account)}`;
|
|
319
|
+
const res = await this.fetchImpl(url, { headers: this.feedHeaders() });
|
|
320
|
+
if (!res.ok)
|
|
321
|
+
throw new Error(`/snapshot ${res.status}`);
|
|
322
|
+
// Invalidate the cursor BEFORE truncating so a crash mid-seed re-seeds rather than going live over
|
|
323
|
+
// an empty replica from a stale cursor.
|
|
324
|
+
engine.run("DELETE FROM sync_meta WHERE key = 'cursor'");
|
|
325
|
+
engine.transaction(() => truncateReplica(writeDb));
|
|
326
|
+
let cursor = 0;
|
|
327
|
+
let batch = [];
|
|
328
|
+
const flush = () => {
|
|
329
|
+
if (batch.length === 0)
|
|
330
|
+
return;
|
|
331
|
+
const rows = batch;
|
|
332
|
+
batch = [];
|
|
333
|
+
engine.transaction(() => {
|
|
334
|
+
for (const rec of rows) {
|
|
335
|
+
if (rec.entity === undefined)
|
|
336
|
+
continue;
|
|
337
|
+
applyDelta(writeDb, { entity: rec.entity, op: rec.op ?? "upsert", row: rec.row ?? {} }, engine.toBindable);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
let rowCount = 0;
|
|
342
|
+
for await (const line of iterateNdjson(res)) {
|
|
343
|
+
const rec = JSON.parse(line);
|
|
344
|
+
if (typeof rec.cursor === "number") {
|
|
345
|
+
cursor = rec.cursor; // the FINAL line carries the cursor
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
batch.push(rec);
|
|
349
|
+
rowCount++;
|
|
350
|
+
if (batch.length >= SEED_BATCH_ROWS)
|
|
351
|
+
flush();
|
|
352
|
+
}
|
|
353
|
+
flush();
|
|
354
|
+
engine.transaction(() => setCursor(writeDb, cursor, engine.toBindable));
|
|
355
|
+
this.highWater = cursor;
|
|
356
|
+
this.log("info", `snapshot seeded (${rowCount} rows), cursor=${cursor}`);
|
|
357
|
+
try {
|
|
358
|
+
this.opts.onChange?.();
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
this.log("warn", "onChange handler threw", err);
|
|
362
|
+
}
|
|
363
|
+
return cursor;
|
|
364
|
+
}
|
|
365
|
+
feedHeaders() {
|
|
366
|
+
const h = { accept: "application/x-ndjson" };
|
|
367
|
+
if (this.opts.auth.kind === "token")
|
|
368
|
+
h["authorization"] = `Bearer ${this.opts.auth.token}`;
|
|
369
|
+
return h;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Iterate an NDJSON Response as non-empty lines. Streams `response.body` (chunked + partial-line
|
|
374
|
+
* buffered) when present — the production path that never buffers the whole snapshot; falls back to a
|
|
375
|
+
* buffered `await res.text()` when the body is absent (e.g. a test fetch stand-in).
|
|
376
|
+
*/
|
|
377
|
+
async function* iterateNdjson(res) {
|
|
378
|
+
const body = res.body;
|
|
379
|
+
if (body && typeof body.getReader === "function") {
|
|
380
|
+
const reader = body.getReader();
|
|
381
|
+
const decoder = new TextDecoder();
|
|
382
|
+
let buf = "";
|
|
383
|
+
for (;;) {
|
|
384
|
+
const { done, value } = await reader.read();
|
|
385
|
+
if (done)
|
|
386
|
+
break;
|
|
387
|
+
buf += decoder.decode(value, { stream: true });
|
|
388
|
+
let nl;
|
|
389
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
390
|
+
const line = buf.slice(0, nl);
|
|
391
|
+
buf = buf.slice(nl + 1);
|
|
392
|
+
if (line.length > 0)
|
|
393
|
+
yield line;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
buf += decoder.decode();
|
|
397
|
+
if (buf.length > 0) {
|
|
398
|
+
for (const line of buf.split("\n"))
|
|
399
|
+
if (line.length > 0)
|
|
400
|
+
yield line;
|
|
401
|
+
}
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const text = await res.text();
|
|
405
|
+
for (const line of text.split("\n"))
|
|
406
|
+
if (line.length > 0)
|
|
407
|
+
yield line;
|
|
408
|
+
}
|
|
409
|
+
//# 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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"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// @catalyst-cloud/sdk/node — the portable sqlite ENGINE seam + the injected driver factories.
|
|
2
|
+
//
|
|
3
|
+
// CatalystReplica never statically imports a sqlite driver: that is how it avoids hard-depping one.
|
|
4
|
+
// It defines one tiny portable `ReplicaEngine` — the union of replicate's `ReplicaWriteDb` (run/get),
|
|
5
|
+
// read-model's `SqlExecutor` (all), and schema's `MigrationDb` (exec/query) primitives, plus a
|
|
6
|
+
// per-engine `toBindable` (wire JSON → engine-bindable) and `transaction`/`close`. CatalystReplica
|
|
7
|
+
// adapts THIS one object into the three ports it needs.
|
|
8
|
+
//
|
|
9
|
+
// Drivers are INJECTED, not imported. The SDK ships three factory adapters, each dynamic-import()ing
|
|
10
|
+
// its driver only when called:
|
|
11
|
+
// • bunSqliteEngine(dbPath) — `await import("bun:sqlite")` (runtime builtin → never in package.json)
|
|
12
|
+
// • nodeSqliteEngine(dbPath) — `await import("node:sqlite")` { DatabaseSync } (Node >=22.5, builtin)
|
|
13
|
+
// • betterSqlite3Engine(driver, …) — the consumer passes `import Database from "better-sqlite3"`
|
|
14
|
+
// (an OPTIONAL peer, never statically imported here).
|
|
15
|
+
// Any other driver (D1, sql.js, …) is supported by passing a custom `ReplicaEngine`.
|
|
16
|
+
// Load RUNTIME-BUILTIN drivers (`bun:sqlite` / `node:sqlite`) via `createRequire`, NOT a literal
|
|
17
|
+
// `import("…")`. Vite/vitest and esbuild rewrite (and break) a literal dynamic import of a builtin —
|
|
18
|
+
// they strip the `node:`/`bun:` prefix and fail to resolve a bare `sqlite`; and a `new Function`
|
|
19
|
+
// import throws "dynamic import callback was not specified" under node ESM. `require` of a builtin is
|
|
20
|
+
// untouched by the bundler and resolves natively on both node and Bun. This keeps the drivers OUT of
|
|
21
|
+
// package.json (they are platform builtins) and loaded only when a factory is called.
|
|
22
|
+
import { createRequire } from "node:module";
|
|
23
|
+
const requireBuiltin = createRequire(import.meta.url);
|
|
24
|
+
/** Require a RUNTIME-BUILTIN driver module; the type is supplied by the caller via `typeof import(...)`. */
|
|
25
|
+
function importBuiltin(specifier) {
|
|
26
|
+
return Promise.resolve(requireBuiltin(specifier));
|
|
27
|
+
}
|
|
28
|
+
/** Coerce a wire JSON value to a node/bun-bindable scalar. Booleans → 0/1, blobs → Uint8Array, nested
|
|
29
|
+
* object/array → JSON text (DO rows are flat scalars; this just keeps one odd field from wedging sync). */
|
|
30
|
+
function toBindable(value) {
|
|
31
|
+
if (value === null || value === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
if (typeof value === "boolean")
|
|
34
|
+
return value ? 1 : 0;
|
|
35
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint")
|
|
36
|
+
return value;
|
|
37
|
+
if (value instanceof Uint8Array)
|
|
38
|
+
return value;
|
|
39
|
+
if (value instanceof ArrayBuffer)
|
|
40
|
+
return new Uint8Array(value);
|
|
41
|
+
return JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
/** Fold a driver into the portable engine. `all`/`run`/`get` re-prepare per call (bounded read path,
|
|
44
|
+
* and applyDelta's SQL varies per row anyway, so statement caching would not help). */
|
|
45
|
+
function makeEngine(driver) {
|
|
46
|
+
return {
|
|
47
|
+
handle: driver.handle,
|
|
48
|
+
exec: (sql) => driver.exec(sql),
|
|
49
|
+
all: (sql, ...bindings) => driver.prepare(sql).all(...bindings),
|
|
50
|
+
run: (sql, ...bindings) => driver.prepare(sql).run(...bindings),
|
|
51
|
+
get: (sql, ...bindings) => driver.prepare(sql).get(...bindings),
|
|
52
|
+
transaction: (fn) => driver.transaction(fn),
|
|
53
|
+
toBindable,
|
|
54
|
+
close: () => driver.close(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `bun:sqlite` engine. `db.query(sql)` prepares; `.run().changes` is the write count; WAL keeps
|
|
59
|
+
* concurrent readers non-blocking. `bun:sqlite` is a Bun runtime builtin → dynamic-imported, never a
|
|
60
|
+
* package.json dependency.
|
|
61
|
+
*
|
|
62
|
+
* The WRITER opens read-write + WAL. The READER (`readonly`) opens `{ readonly: true }` and sets
|
|
63
|
+
* `busy_timeout = 250` instead of WAL — exactly how CTL's replica-read.mjs opens the same file: under
|
|
64
|
+
* the single-writer/many-reader topology (ADR-0008) a checkpoint can briefly hold the lock, so a
|
|
65
|
+
* reader waits a beat rather than failing. A readonly handle runs NO migrations and rejects writes at
|
|
66
|
+
* the sqlite layer.
|
|
67
|
+
*/
|
|
68
|
+
async function openBun(dbPath, readonly) {
|
|
69
|
+
const { Database } = await importBuiltin("bun:sqlite");
|
|
70
|
+
const db = readonly ? new Database(dbPath, { readonly: true }) : new Database(dbPath);
|
|
71
|
+
try {
|
|
72
|
+
db.run(readonly ? "PRAGMA busy_timeout = 250" : "PRAGMA journal_mode = WAL");
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// :memory: rejects WAL on some builds; busy_timeout is harmless either way.
|
|
76
|
+
}
|
|
77
|
+
return makeEngine({
|
|
78
|
+
handle: db,
|
|
79
|
+
exec: (sql) => db.exec(sql),
|
|
80
|
+
prepare: (sql) => {
|
|
81
|
+
const st = db.query(sql);
|
|
82
|
+
return {
|
|
83
|
+
all: (...b) => st.all(...b),
|
|
84
|
+
get: (...b) => (st.get(...b) ?? undefined),
|
|
85
|
+
run: (...b) => st.run(...b).changes,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
transaction: (fn) => db.transaction(fn)(),
|
|
89
|
+
close: () => db.close(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export function bunSqliteEngine(dbPath) {
|
|
93
|
+
return openBun(dbPath, false);
|
|
94
|
+
}
|
|
95
|
+
/** Read-only `bun:sqlite` engine (`{ readonly: true }` + `busy_timeout`). See {@link openBun}. */
|
|
96
|
+
export function bunSqliteReadonlyEngine(dbPath) {
|
|
97
|
+
return openBun(dbPath, true);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* `node:sqlite` engine (Node >=22.5; may need `--experimental-sqlite` on some 22.x). `DatabaseSync`
|
|
101
|
+
* has no `.transaction()` helper, so we wrap BEGIN/COMMIT/ROLLBACK. Also a runtime builtin → never a
|
|
102
|
+
* package.json dependency. The `readonly` variant opens `{ readOnly: true }` (node:sqlite's spelling)
|
|
103
|
+
* + `busy_timeout`; see {@link openBun} for the read-only rationale.
|
|
104
|
+
*/
|
|
105
|
+
async function openNode(dbPath, readonly) {
|
|
106
|
+
const { DatabaseSync } = await importBuiltin("node:sqlite");
|
|
107
|
+
const db = readonly ? new DatabaseSync(dbPath, { readOnly: true }) : new DatabaseSync(dbPath);
|
|
108
|
+
try {
|
|
109
|
+
db.exec(readonly ? "PRAGMA busy_timeout = 250" : "PRAGMA journal_mode = WAL");
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// :memory: cannot WAL — harmless; busy_timeout is allowed on a readonly handle.
|
|
113
|
+
}
|
|
114
|
+
return makeEngine({
|
|
115
|
+
handle: db,
|
|
116
|
+
exec: (sql) => db.exec(sql),
|
|
117
|
+
prepare: (sql) => {
|
|
118
|
+
const st = db.prepare(sql);
|
|
119
|
+
return {
|
|
120
|
+
all: (...b) => st.all(...b),
|
|
121
|
+
get: (...b) => (st.get(...b) ?? undefined),
|
|
122
|
+
run: (...b) => Number(st.run(...b).changes),
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
transaction: (fn) => {
|
|
126
|
+
db.exec("BEGIN");
|
|
127
|
+
try {
|
|
128
|
+
const out = fn();
|
|
129
|
+
db.exec("COMMIT");
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
db.exec("ROLLBACK");
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
close: () => db.close(),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
export function nodeSqliteEngine(dbPath) {
|
|
141
|
+
return openNode(dbPath, false);
|
|
142
|
+
}
|
|
143
|
+
/** Read-only `node:sqlite` engine (`{ readOnly: true }` + `busy_timeout`). See {@link openNode}. */
|
|
144
|
+
export function nodeSqliteReadonlyEngine(dbPath) {
|
|
145
|
+
return openNode(dbPath, true);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `better-sqlite3` engine. The consumer passes the driver constructor (`import Database from
|
|
149
|
+
* "better-sqlite3"`) so the SDK never statically imports it — it stays an OPTIONAL peer the consumer's
|
|
150
|
+
* bundler resolves. Synchronous (the driver is already loaded), so no dynamic import here. The
|
|
151
|
+
* `readonly` variant opens `{ readonly: true }` + `busy_timeout`; see {@link openBun}.
|
|
152
|
+
*/
|
|
153
|
+
function openBetter(driver, dbPath, readonly) {
|
|
154
|
+
const db = readonly ? new driver(dbPath, { readonly: true }) : new driver(dbPath);
|
|
155
|
+
try {
|
|
156
|
+
db.pragma(readonly ? "busy_timeout = 250" : "journal_mode = WAL");
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// :memory: cannot WAL — harmless; busy_timeout is allowed on a readonly handle.
|
|
160
|
+
}
|
|
161
|
+
return makeEngine({
|
|
162
|
+
handle: db,
|
|
163
|
+
exec: (sql) => db.exec(sql),
|
|
164
|
+
prepare: (sql) => {
|
|
165
|
+
const st = db.prepare(sql);
|
|
166
|
+
return {
|
|
167
|
+
all: (...b) => st.all(...b),
|
|
168
|
+
get: (...b) => (st.get(...b) ?? undefined),
|
|
169
|
+
run: (...b) => Number(st.run(...b).changes),
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
transaction: (fn) => db.transaction(fn)(),
|
|
173
|
+
close: () => db.close(),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
export function betterSqlite3Engine(driver, dbPath) {
|
|
177
|
+
return openBetter(driver, dbPath, false);
|
|
178
|
+
}
|
|
179
|
+
/** Read-only `better-sqlite3` engine (`{ readonly: true }` + `busy_timeout`). See {@link openBetter}. */
|
|
180
|
+
export function betterSqlite3ReadonlyEngine(driver, dbPath) {
|
|
181
|
+
return openBetter(driver, dbPath, true);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Auto-detect the default engine when `opts.engine` is omitted: Bun → `bunSqliteEngine`; else
|
|
185
|
+
* `node:sqlite` if available → `nodeSqliteEngine`; else throw a clear "pass opts.engine" error rather
|
|
186
|
+
* than pick an engine that resolves but breaks on first write.
|
|
187
|
+
*/
|
|
188
|
+
export async function autoDetectEngine(dbPath) {
|
|
189
|
+
if (typeof globalThis.Bun !== "undefined") {
|
|
190
|
+
return bunSqliteEngine(dbPath);
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
return await nodeSqliteEngine(dbPath);
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
throw new Error("CatalystReplica: no sqlite engine available — pass opts.engine. " +
|
|
197
|
+
"Auto-detect needs Bun (bun:sqlite) or Node >=22.5 (node:sqlite). " +
|
|
198
|
+
`Underlying error: ${err instanceof Error ? err.message : String(err)}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/** READ-ONLY counterpart of {@link autoDetectEngine}: Bun → `bunSqliteReadonlyEngine`; else
|
|
202
|
+
* `nodeSqliteReadonlyEngine`. Used by `CatalystReplica.openReadOnly` when no engine is injected. */
|
|
203
|
+
export async function autoDetectReadonlyEngine(dbPath) {
|
|
204
|
+
if (typeof globalThis.Bun !== "undefined") {
|
|
205
|
+
return bunSqliteReadonlyEngine(dbPath);
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
return await nodeSqliteReadonlyEngine(dbPath);
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
throw new Error("CatalystReplica: no read-only sqlite engine available — pass opts.engine. " +
|
|
212
|
+
"Auto-detect needs Bun (bun:sqlite) or Node >=22.5 (node:sqlite). " +
|
|
213
|
+
`Underlying error: ${err instanceof Error ? err.message : String(err)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/replica/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { LogLevel } from "../live-sync-client.js";
|
|
2
|
+
/** Config for the single-writer guard. Passed as `CatalystReplicaOptions.writerGuard`. */
|
|
3
|
+
export interface WriterGuardOptions {
|
|
4
|
+
/** Disable the guard entirely (claim nothing). Default false — the guard is ON for file replicas. */
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
/** Steal an existing LIVE lock instead of throwing (the configurable override). Default false. */
|
|
7
|
+
override?: boolean;
|
|
8
|
+
/** A lock with no heartbeat newer than this many ms is treated as abandoned + reclaimed. Default 15000. */
|
|
9
|
+
staleMs?: number;
|
|
10
|
+
/** How often the owning writer rewrites its heartbeat. Default max(1000, staleMs/3). */
|
|
11
|
+
heartbeatMs?: number;
|
|
12
|
+
}
|
|
13
|
+
/** A claimed writer lock. `release()` stops the heartbeat and removes the file if still ours. Idempotent. */
|
|
14
|
+
export interface WriterLockHandle {
|
|
15
|
+
/** The sidecar lock file path (`dbPath + '.writer.lock'`). */
|
|
16
|
+
readonly path: string;
|
|
17
|
+
release(): void;
|
|
18
|
+
}
|
|
19
|
+
type Logger = (level: LogLevel, msg: string, extra?: unknown) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Best-effort claim sole ownership of `dbPath` for a writer. Returns a {@link WriterLockHandle} (or
|
|
22
|
+
* `null` when the guard is disabled or `dbPath` is in-memory). Throws a clear error when another LIVE
|
|
23
|
+
* writer already holds the lock and `override` is not set.
|
|
24
|
+
*/
|
|
25
|
+
export declare function claimWriterLock(dbPath: string, opts: WriterGuardOptions, log?: Logger): WriterLockHandle | null;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=writer-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer-lock.d.ts","sourceRoot":"","sources":["../../src/replica/writer-lock.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEvD,0FAA0F;AAC1F,MAAM,WAAW,kBAAkB;IACjC,qGAAqG;IACrG,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2GAA2G;IAC3G,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,6GAA6G;AAC7G,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAWD,KAAK,MAAM,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AA4BtE;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,kBAAkB,EACxB,GAAG,CAAC,EAAE,MAAM,GACX,gBAAgB,GAAG,IAAI,CA0EzB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// @catalyst-cloud/sdk/node — the single-writer guard for a CatalystReplica writer (CTC-113).
|
|
2
|
+
//
|
|
3
|
+
// The managed replica is single-writer/many-reader (ADR-0008): ONE process owns the SQLite file and
|
|
4
|
+
// applies the change feed; everyone else opens it READ-ONLY (`CatalystReplica.openReadOnly`). Two
|
|
5
|
+
// concurrent WRITERS on the same file would race the cursor + truncate-and-reseed and silently
|
|
6
|
+
// diverge. This guard makes that mistake LOUD: when a writer `start()`s it best-effort claims sole
|
|
7
|
+
// ownership of `dbPath` via a sidecar lock file `dbPath + '.writer.lock'` carrying `{pid, owner,
|
|
8
|
+
// heartbeat}`. A second live writer on the same path throws a clear error instead of corrupting the
|
|
9
|
+
// replica.
|
|
10
|
+
//
|
|
11
|
+
// LIMITS — this is deliberately ADVISORY, not a hard OS lock:
|
|
12
|
+
// • It is cooperative: it only stops OTHER CatalystReplica writers. A raw `new Database(path)` from
|
|
13
|
+
// unrelated code is not blocked (use OS file locking / WAL for that).
|
|
14
|
+
// • Liveness has two signals. The HEARTBEAT (a timestamp rewritten on an interval) is the portable,
|
|
15
|
+
// cross-host signal — a lock older than `staleMs` with no heartbeat is treated as abandoned and
|
|
16
|
+
// reclaimed. The PID probe (`process.kill(pid,0)`) only means anything SAME-HOST; cross-host or
|
|
17
|
+
// when it can't probe, it conservatively assumes the holder is alive and relies on the heartbeat.
|
|
18
|
+
// • A hard `kill -9` can leave a stale lock until `staleMs` elapses; the next writer reclaims it.
|
|
19
|
+
// • `:memory:` (and `file::memory:`) replicas are per-connection, never shared, so the guard is a
|
|
20
|
+
// no-op for them.
|
|
21
|
+
import * as fs from "node:fs";
|
|
22
|
+
const DEFAULT_STALE_MS = 15_000;
|
|
23
|
+
function readLock(lockPath) {
|
|
24
|
+
try {
|
|
25
|
+
const rec = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
26
|
+
if (rec && typeof rec.pid === "number" && typeof rec.heartbeat === "number")
|
|
27
|
+
return rec;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null; // absent, partial write, or garbage → treat as no lock
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** SAME-HOST liveness probe. Returns true when it cannot tell (assume the holder is alive — the
|
|
35
|
+
* heartbeat staleness check is the portable signal that ultimately reclaims a dead lock). */
|
|
36
|
+
function pidAlive(pid) {
|
|
37
|
+
const proc = globalThis.process;
|
|
38
|
+
if (!proc?.kill || !pid)
|
|
39
|
+
return true;
|
|
40
|
+
try {
|
|
41
|
+
proc.kill(pid, 0); // signal 0 = existence check, never delivered
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (err.code === "ESRCH")
|
|
46
|
+
return false; // no such process
|
|
47
|
+
return true; // EPERM: exists but owned by another user → alive
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Best-effort claim sole ownership of `dbPath` for a writer. Returns a {@link WriterLockHandle} (or
|
|
52
|
+
* `null` when the guard is disabled or `dbPath` is in-memory). Throws a clear error when another LIVE
|
|
53
|
+
* writer already holds the lock and `override` is not set.
|
|
54
|
+
*/
|
|
55
|
+
export function claimWriterLock(dbPath, opts, log) {
|
|
56
|
+
if (opts.disabled)
|
|
57
|
+
return null;
|
|
58
|
+
if (!dbPath || dbPath === ":memory:" || dbPath.startsWith("file::memory:"))
|
|
59
|
+
return null;
|
|
60
|
+
const lockPath = `${dbPath}.writer.lock`;
|
|
61
|
+
const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;
|
|
62
|
+
const heartbeatMs = opts.heartbeatMs ?? Math.max(1000, Math.floor(staleMs / 3));
|
|
63
|
+
const pid = globalThis.process?.pid ?? 0;
|
|
64
|
+
const owner = `${pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
65
|
+
const serialize = () => JSON.stringify({ pid, owner, heartbeat: Date.now() });
|
|
66
|
+
const writeFresh = () => fs.writeFileSync(lockPath, serialize());
|
|
67
|
+
// Atomic exclusive create: wins the claim outright if no file exists.
|
|
68
|
+
let created = false;
|
|
69
|
+
try {
|
|
70
|
+
const fd = fs.openSync(lockPath, "wx");
|
|
71
|
+
fs.writeSync(fd, serialize());
|
|
72
|
+
fs.closeSync(fd);
|
|
73
|
+
created = true;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err.code !== "EEXIST")
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
if (!created) {
|
|
80
|
+
const existing = readLock(lockPath);
|
|
81
|
+
const held = existing != null && Date.now() - existing.heartbeat < staleMs && pidAlive(existing.pid);
|
|
82
|
+
if (held && !opts.override) {
|
|
83
|
+
throw new Error(`CatalystReplica: another writer owns this replica at ${dbPath} ` +
|
|
84
|
+
`(pid=${existing.pid}, last heartbeat ${Date.now() - existing.heartbeat}ms ago). ` +
|
|
85
|
+
`Only ONE writer may hold a replica file (ADR-0008 single-writer/many-reader); open it ` +
|
|
86
|
+
`read-only with CatalystReplica.openReadOnly() instead. Pass writerGuard:{override:true} ` +
|
|
87
|
+
`to take it over, or writerGuard:{disabled:true} to skip the check. NOTE: this guard is ` +
|
|
88
|
+
`advisory (a sidecar ${lockPath}), not a hard OS lock.`);
|
|
89
|
+
}
|
|
90
|
+
log?.("warn", held
|
|
91
|
+
? `writer-lock: overriding a live writer at ${lockPath} (writerGuard.override)`
|
|
92
|
+
: `writer-lock: reclaiming a stale/abandoned lock at ${lockPath}`);
|
|
93
|
+
writeFresh();
|
|
94
|
+
}
|
|
95
|
+
// Keep the lock fresh so peers see it's alive; unref so it never holds the event loop open.
|
|
96
|
+
const timer = setInterval(() => {
|
|
97
|
+
try {
|
|
98
|
+
writeFresh();
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
log?.("warn", "writer-lock heartbeat failed", err);
|
|
102
|
+
}
|
|
103
|
+
}, heartbeatMs);
|
|
104
|
+
timer.unref?.();
|
|
105
|
+
let released = false;
|
|
106
|
+
return {
|
|
107
|
+
path: lockPath,
|
|
108
|
+
release() {
|
|
109
|
+
if (released)
|
|
110
|
+
return;
|
|
111
|
+
released = true;
|
|
112
|
+
clearInterval(timer);
|
|
113
|
+
const cur = readLock(lockPath);
|
|
114
|
+
if (cur && cur.owner === owner) {
|
|
115
|
+
try {
|
|
116
|
+
fs.unlinkSync(lockPath);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// already gone — fine
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=writer-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer-lock.js","sourceRoot":"","sources":["../../src/replica/writer-lock.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Keep a live local copy of your Linear and GitHub project data — pushed in real time, without polling rate limits or webhook tunnels.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Coalesce Labs",
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
25
|
"import": "./dist/index.js",
|
|
26
26
|
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./node": {
|
|
29
|
+
"types": "./dist/node.d.ts",
|
|
30
|
+
"import": "./dist/node.js",
|
|
31
|
+
"default": "./dist/node.js"
|
|
27
32
|
}
|
|
28
33
|
},
|
|
29
34
|
"files": [
|
|
@@ -51,5 +56,22 @@
|
|
|
51
56
|
"devDependencies": {
|
|
52
57
|
"typescript": "^5.6.3",
|
|
53
58
|
"vitest": "^2.1.8"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@catalyst-cloud/read-model": "^0.1.0",
|
|
62
|
+
"@catalyst-cloud/replicate": "^0.1.0",
|
|
63
|
+
"@catalyst-cloud/schema": "^0.1.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@sqlite.org/sqlite-wasm": "*",
|
|
67
|
+
"better-sqlite3": "*"
|
|
68
|
+
},
|
|
69
|
+
"peerDependenciesMeta": {
|
|
70
|
+
"better-sqlite3": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@sqlite.org/sqlite-wasm": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
54
76
|
}
|
|
55
77
|
}
|