@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,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,34 @@
|
|
|
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 stable identity for THIS logical (singleton) writer — e.g. `<host>-<tenant>`. A relaunch whose
|
|
14
|
+
* ownerKey matches an existing lock with a DIFFERENT pid reclaims it IMMEDIATELY (its own crashed
|
|
15
|
+
* predecessor), bypassing the staleMs/pid-liveness gate. Only set this when you guarantee a single
|
|
16
|
+
* writer per ownerKey per dbPath; the default (unset) keeps full two-writer protection.
|
|
17
|
+
*/
|
|
18
|
+
ownerKey?: string;
|
|
19
|
+
}
|
|
20
|
+
/** A claimed writer lock. `release()` stops the heartbeat and removes the file if still ours. Idempotent. */
|
|
21
|
+
export interface WriterLockHandle {
|
|
22
|
+
/** The sidecar lock file path (`dbPath + '.writer.lock'`). */
|
|
23
|
+
readonly path: string;
|
|
24
|
+
release(): void;
|
|
25
|
+
}
|
|
26
|
+
type Logger = (level: LogLevel, msg: string, extra?: unknown) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Best-effort claim sole ownership of `dbPath` for a writer. Returns a {@link WriterLockHandle} (or
|
|
29
|
+
* `null` when the guard is disabled or `dbPath` is in-memory). Throws a clear error when another LIVE
|
|
30
|
+
* writer already holds the lock and `override` is not set.
|
|
31
|
+
*/
|
|
32
|
+
export declare function claimWriterLock(dbPath: string, opts: WriterGuardOptions, log?: Logger): WriterLockHandle | null;
|
|
33
|
+
export {};
|
|
34
|
+
//# 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;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,6GAA6G;AAC7G,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAcD,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,CAsFzB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
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(), ownerKey: opts.ownerKey });
|
|
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
|
+
// FAST-RECLAIM: a relaunch of THIS logical writer (same ownerKey) but a DIFFERENT pid is our own
|
|
82
|
+
// crashed predecessor — reclaim it IMMEDIATELY, bypassing the staleMs/pid-liveness gate. This
|
|
83
|
+
// dodges the kill -9 + fast-relaunch window where a zombie/pid-reuse makes pidAlive() spuriously
|
|
84
|
+
// true and would otherwise block the relaunched writer for the full staleMs window.
|
|
85
|
+
if (opts.ownerKey != null && existing?.ownerKey === opts.ownerKey && existing.pid !== pid) {
|
|
86
|
+
log?.("info", `writer-lock: reclaiming own crashed predecessor (ownerKey match) at ${lockPath}`);
|
|
87
|
+
writeFresh();
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const held = existing != null && Date.now() - existing.heartbeat < staleMs && pidAlive(existing.pid);
|
|
91
|
+
if (held && !opts.override) {
|
|
92
|
+
throw new Error(`CatalystReplica: another writer owns this replica at ${dbPath} ` +
|
|
93
|
+
`(pid=${existing.pid}, last heartbeat ${Date.now() - existing.heartbeat}ms ago). ` +
|
|
94
|
+
`Only ONE writer may hold a replica file (ADR-0008 single-writer/many-reader); open it ` +
|
|
95
|
+
`read-only with CatalystReplica.openReadOnly() instead. Pass writerGuard:{override:true} ` +
|
|
96
|
+
`to take it over, or writerGuard:{disabled:true} to skip the check. NOTE: this guard is ` +
|
|
97
|
+
`advisory (a sidecar ${lockPath}), not a hard OS lock.`);
|
|
98
|
+
}
|
|
99
|
+
log?.("warn", held
|
|
100
|
+
? `writer-lock: overriding a live writer at ${lockPath} (writerGuard.override)`
|
|
101
|
+
: `writer-lock: reclaiming a stale/abandoned lock at ${lockPath}`);
|
|
102
|
+
writeFresh();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Keep the lock fresh so peers see it's alive; unref so it never holds the event loop open.
|
|
106
|
+
const timer = setInterval(() => {
|
|
107
|
+
try {
|
|
108
|
+
writeFresh();
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
log?.("warn", "writer-lock heartbeat failed", err);
|
|
112
|
+
}
|
|
113
|
+
}, heartbeatMs);
|
|
114
|
+
timer.unref?.();
|
|
115
|
+
let released = false;
|
|
116
|
+
return {
|
|
117
|
+
path: lockPath,
|
|
118
|
+
release() {
|
|
119
|
+
if (released)
|
|
120
|
+
return;
|
|
121
|
+
released = true;
|
|
122
|
+
clearInterval(timer);
|
|
123
|
+
const cur = readLock(lockPath);
|
|
124
|
+
if (cur && cur.owner === owner) {
|
|
125
|
+
try {
|
|
126
|
+
fs.unlinkSync(lockPath);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// already gone — fine
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
//# 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.1",
|
|
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
|
}
|