@catalyst-cloud/sdk 0.14.0 → 0.14.2
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/README.md +6 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/live-sync-client.d.ts +4 -0
- package/dist/live-sync-client.d.ts.map +1 -1
- package/dist/live-sync-client.js +36 -1
- package/dist/live-sync-client.js.map +1 -1
- package/dist/ndjson.d.ts.map +1 -1
- package/dist/ndjson.js +12 -9
- package/dist/ndjson.js.map +1 -1
- package/dist/node.js +82 -37
- package/dist/replica/catalyst-replica.d.ts +13 -7
- package/dist/replica/catalyst-replica.d.ts.map +1 -1
- package/dist/replica/catalyst-replica.js +99 -41
- package/dist/replica/catalyst-replica.js.map +1 -1
- package/dist/tenant-client.d.ts +175 -3
- package/dist/tenant-client.d.ts.map +1 -1
- package/dist/tenant-client.js +168 -7
- package/dist/tenant-client.js.map +1 -1
- package/dist/tenant-contract.d.ts +8 -2
- package/dist/tenant-contract.d.ts.map +1 -1
- package/dist/tenant-contract.js +6 -1
- package/dist/tenant-contract.js.map +1 -1
- package/package.json +6 -6
package/dist/node.js
CHANGED
|
@@ -7305,7 +7305,8 @@ function searchIndexesOver(db, tables) {
|
|
|
7305
7305
|
);
|
|
7306
7306
|
const pairs = JSON.parse(String(row?.["j"] ?? "[]"));
|
|
7307
7307
|
return pairs.filter(([, ddl]) => {
|
|
7308
|
-
const
|
|
7308
|
+
const m = /content\s*=\s*(?:'([^']*)'|"([^"]*)"|([A-Za-z_]\w*))/i.exec(ddl);
|
|
7309
|
+
const content = m?.[1] ?? m?.[2] ?? m?.[3];
|
|
7309
7310
|
return content !== void 0 && tables.has(content);
|
|
7310
7311
|
}).map(([name]) => name);
|
|
7311
7312
|
}
|
|
@@ -7619,6 +7620,9 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
7619
7620
|
fetchImpl;
|
|
7620
7621
|
log;
|
|
7621
7622
|
engine = null;
|
|
7623
|
+
/** The dynamic engine open is owned until it settles; closeAndWait must not release the lock first. */
|
|
7624
|
+
engineOpening = null;
|
|
7625
|
+
shutdownTask = null;
|
|
7622
7626
|
writeDb = null;
|
|
7623
7627
|
sqlExecutor = null;
|
|
7624
7628
|
client = null;
|
|
@@ -7632,11 +7636,9 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
7632
7636
|
readonlyMode = false;
|
|
7633
7637
|
/** The claimed single-writer lock (writers only; null for readers / `:memory:` / disabled). */
|
|
7634
7638
|
writerLock = null;
|
|
7635
|
-
/** The in-flight seed's AbortController (CTC-281),
|
|
7636
|
-
*
|
|
7637
|
-
*
|
|
7638
|
-
* process can't exit" hostage mechanism from the incident chain. Also the seed idle-timeout's
|
|
7639
|
-
* abort seam. One controller per seed RUN (a retried seed gets a fresh one). */
|
|
7639
|
+
/** The in-flight seed's AbortController (CTC-281), synchronously aborted by closeAndWait before it
|
|
7640
|
+
* joins the fetch/body-reader unwind. Also the seed idle-timeout's abort seam. One controller per
|
|
7641
|
+
* seed RUN (a retried seed gets a fresh one). */
|
|
7640
7642
|
seedAbort = null;
|
|
7641
7643
|
/** Resolved on the first 'live' status (start() = caught-up + ready to read); rejected on close /
|
|
7642
7644
|
* an initial seed failure. */
|
|
@@ -7728,7 +7730,18 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
7728
7730
|
if (this.started) throw new Error("CatalystReplica: start() already called");
|
|
7729
7731
|
this.started = true;
|
|
7730
7732
|
this.writerLock = claimWriterLock(this.opts.dbPath, this.opts.writerGuard ?? {}, this.log);
|
|
7731
|
-
const
|
|
7733
|
+
const engineOpening = this.resolveEngine();
|
|
7734
|
+
this.engineOpening = engineOpening;
|
|
7735
|
+
let engine;
|
|
7736
|
+
try {
|
|
7737
|
+
engine = await engineOpening;
|
|
7738
|
+
} finally {
|
|
7739
|
+
if (this.engineOpening === engineOpening) this.engineOpening = null;
|
|
7740
|
+
}
|
|
7741
|
+
if (this.closed) {
|
|
7742
|
+
this.engine = engine;
|
|
7743
|
+
throw new Error("CatalystReplica: start cancelled by close()");
|
|
7744
|
+
}
|
|
7732
7745
|
this.engine = engine;
|
|
7733
7746
|
const stored = readAccountStamp(engine);
|
|
7734
7747
|
const mySource = this.opts.accountSource ?? DEFAULT_ACCOUNT_SOURCE;
|
|
@@ -7827,13 +7840,14 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
7827
7840
|
tracerName: DEFAULT_SCOPE_NAME,
|
|
7828
7841
|
meterName: DEFAULT_SCOPE_NAME
|
|
7829
7842
|
});
|
|
7843
|
+
if (this.closed) throw new Error("CatalystReplica: start cancelled by close()");
|
|
7830
7844
|
this.registerMetrics();
|
|
7831
7845
|
this.client = new LiveSyncClient({
|
|
7832
7846
|
baseUrl: this.baseUrl,
|
|
7833
7847
|
accountId: this.opts.account,
|
|
7834
7848
|
connectPath: this.opts.connectPath,
|
|
7835
7849
|
auth: this.opts.auth,
|
|
7836
|
-
reseed: () => this.seedFromSnapshot(),
|
|
7850
|
+
reseed: (signal) => this.seedFromSnapshot(signal),
|
|
7837
7851
|
getCursor: () => getCursor(this.writeDb),
|
|
7838
7852
|
connectParams: () => this.workflowRevParams(),
|
|
7839
7853
|
onChange: (frame) => this.applyFrame(frame),
|
|
@@ -7868,13 +7882,11 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
7868
7882
|
const timer = setTimeout(() => {
|
|
7869
7883
|
if (settled) return;
|
|
7870
7884
|
settled = true;
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
);
|
|
7877
|
-
});
|
|
7885
|
+
const timeoutMessage = `CatalystReplica.start() did not reach 'live' within ${timeoutMs}ms (account=${this.opts.account}, dbPath=${this.opts.dbPath})`;
|
|
7886
|
+
void this.close().then(
|
|
7887
|
+
() => reject(new Error(timeoutMessage)),
|
|
7888
|
+
(cleanupError) => reject(new Error(timeoutMessage, { cause: cleanupError }))
|
|
7889
|
+
);
|
|
7878
7890
|
}, timeoutMs);
|
|
7879
7891
|
timer.unref?.();
|
|
7880
7892
|
livePromise.then(
|
|
@@ -7906,14 +7918,23 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
7906
7918
|
if (!this.client) throw new Error("CatalystReplica: resume() before start()");
|
|
7907
7919
|
this.client.resume();
|
|
7908
7920
|
}
|
|
7909
|
-
/**
|
|
7910
|
-
*
|
|
7911
|
-
|
|
7912
|
-
|
|
7921
|
+
/**
|
|
7922
|
+
* Compatibility shutdown entrypoint. It now acknowledges actual seed/engine settlement before
|
|
7923
|
+
* releasing the writer guard or closing SQLite; callers that need an explicit lifecycle name can
|
|
7924
|
+
* use closeAndWait().
|
|
7925
|
+
*/
|
|
7926
|
+
close() {
|
|
7927
|
+
return this.closeAndWait();
|
|
7928
|
+
}
|
|
7929
|
+
/** Stop the socket and join engine-open and transport seed/reseed work before releasing ownership. */
|
|
7930
|
+
async closeAndWait() {
|
|
7931
|
+
if (this.shutdownTask) return this.shutdownTask;
|
|
7913
7932
|
this.closed = true;
|
|
7914
7933
|
this.client?.stop();
|
|
7915
7934
|
try {
|
|
7916
|
-
|
|
7935
|
+
const abortError = new Error("CatalystReplica closed");
|
|
7936
|
+
abortError.name = "AbortError";
|
|
7937
|
+
this.seedAbort?.abort(abortError);
|
|
7917
7938
|
} catch (err) {
|
|
7918
7939
|
this.log("warn", "seed abort threw", err);
|
|
7919
7940
|
}
|
|
@@ -7921,25 +7942,45 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
7921
7942
|
const rej = this.liveReject;
|
|
7922
7943
|
this.clearLiveDeferred();
|
|
7923
7944
|
rej?.(new Error("CatalystReplica: closed before first 'live'"));
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
this.
|
|
7928
|
-
|
|
7929
|
-
this.writerLock = null;
|
|
7930
|
-
for (const reg of this.metricRegs) {
|
|
7945
|
+
const client = this.client;
|
|
7946
|
+
const shutdown = async () => {
|
|
7947
|
+
const failures = [];
|
|
7948
|
+
if (this.engineOpening) await this.engineOpening.catch(() => void 0);
|
|
7949
|
+
let seedIoAcknowledged = false;
|
|
7931
7950
|
try {
|
|
7932
|
-
|
|
7951
|
+
await client?.stopAndWait();
|
|
7952
|
+
seedIoAcknowledged = true;
|
|
7933
7953
|
} catch (err) {
|
|
7934
|
-
|
|
7954
|
+
failures.push(err);
|
|
7935
7955
|
}
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7956
|
+
for (const reg of this.metricRegs) {
|
|
7957
|
+
try {
|
|
7958
|
+
reg.remove();
|
|
7959
|
+
} catch (err) {
|
|
7960
|
+
failures.push(err);
|
|
7961
|
+
}
|
|
7962
|
+
}
|
|
7963
|
+
this.metricRegs = [];
|
|
7964
|
+
let engineClosed = this.engine == null;
|
|
7965
|
+
try {
|
|
7966
|
+
this.engine?.close();
|
|
7967
|
+
engineClosed = true;
|
|
7968
|
+
this.engine = null;
|
|
7969
|
+
} catch (err) {
|
|
7970
|
+
failures.push(err);
|
|
7971
|
+
}
|
|
7972
|
+
if (engineClosed && seedIoAcknowledged) {
|
|
7973
|
+
try {
|
|
7974
|
+
this.writerLock?.release();
|
|
7975
|
+
this.writerLock = null;
|
|
7976
|
+
} catch (err) {
|
|
7977
|
+
failures.push(err);
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
if (failures.length) throw new AggregateError(failures, "CatalystReplica shutdown failed");
|
|
7981
|
+
};
|
|
7982
|
+
this.shutdownTask = shutdown();
|
|
7983
|
+
return this.shutdownTask;
|
|
7943
7984
|
}
|
|
7944
7985
|
// ── Reads ───────────────────────────────────────────────────────────────────────────────────────
|
|
7945
7986
|
/** The read-model SqlExecutor over the replica. `buildIssuesView(replica.sql, …)` is unchanged. */
|
|
@@ -8258,16 +8299,19 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
8258
8299
|
* interrupted seed self-heals (getCursor → null → re-seed on the next start), preserving host-sync's
|
|
8259
8300
|
* atomic truncate+apply+setCursor safety without holding one giant transaction.
|
|
8260
8301
|
*/
|
|
8261
|
-
async seedFromSnapshot() {
|
|
8302
|
+
async seedFromSnapshot(attemptSignal) {
|
|
8262
8303
|
return this.telemetry.withActiveSpan(
|
|
8263
8304
|
REPLICA_SPAN.seed,
|
|
8264
8305
|
{ [CATALYST_ATTR.tenant]: this.opts.account },
|
|
8265
8306
|
async (span) => {
|
|
8266
8307
|
const engine = this.engine;
|
|
8267
8308
|
const writeDb = this.writeDb;
|
|
8309
|
+
if (attemptSignal?.aborted) throw attemptSignal.reason;
|
|
8268
8310
|
if (this.closed) throw new Error("CatalystReplica: seed aborted (already closed)");
|
|
8269
8311
|
const abort = new AbortController();
|
|
8270
8312
|
this.seedAbort = abort;
|
|
8313
|
+
const onAttemptAbort = () => abort.abort(attemptSignal?.reason);
|
|
8314
|
+
attemptSignal?.addEventListener("abort", onAttemptAbort, { once: true });
|
|
8271
8315
|
const idleMs = this.opts.snapshotIdleTimeoutMs ?? 3e4;
|
|
8272
8316
|
let idleTimer = null;
|
|
8273
8317
|
const clearIdle = () => {
|
|
@@ -8377,6 +8421,7 @@ var CatalystReplica = class _CatalystReplica {
|
|
|
8377
8421
|
throw err;
|
|
8378
8422
|
} finally {
|
|
8379
8423
|
clearIdle();
|
|
8424
|
+
attemptSignal?.removeEventListener("abort", onAttemptAbort);
|
|
8380
8425
|
if (this.seedAbort === abort) this.seedAbort = null;
|
|
8381
8426
|
}
|
|
8382
8427
|
}
|
|
@@ -177,6 +177,9 @@ export declare class CatalystReplica {
|
|
|
177
177
|
private readonly fetchImpl;
|
|
178
178
|
private readonly log;
|
|
179
179
|
private engine;
|
|
180
|
+
/** The dynamic engine open is owned until it settles; closeAndWait must not release the lock first. */
|
|
181
|
+
private engineOpening;
|
|
182
|
+
private shutdownTask;
|
|
180
183
|
private writeDb;
|
|
181
184
|
private sqlExecutor;
|
|
182
185
|
private client;
|
|
@@ -190,11 +193,9 @@ export declare class CatalystReplica {
|
|
|
190
193
|
private readonlyMode;
|
|
191
194
|
/** The claimed single-writer lock (writers only; null for readers / `:memory:` / disabled). */
|
|
192
195
|
private writerLock;
|
|
193
|
-
/** The in-flight seed's AbortController (CTC-281),
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* process can't exit" hostage mechanism from the incident chain. Also the seed idle-timeout's
|
|
197
|
-
* abort seam. One controller per seed RUN (a retried seed gets a fresh one). */
|
|
196
|
+
/** The in-flight seed's AbortController (CTC-281), synchronously aborted by closeAndWait before it
|
|
197
|
+
* joins the fetch/body-reader unwind. Also the seed idle-timeout's abort seam. One controller per
|
|
198
|
+
* seed RUN (a retried seed gets a fresh one). */
|
|
198
199
|
private seedAbort;
|
|
199
200
|
/** Resolved on the first 'live' status (start() = caught-up + ready to read); rejected on close /
|
|
200
201
|
* an initial seed failure. */
|
|
@@ -263,9 +264,14 @@ export declare class CatalystReplica {
|
|
|
263
264
|
* after `close()`.
|
|
264
265
|
*/
|
|
265
266
|
resume(): void;
|
|
266
|
-
/**
|
|
267
|
-
*
|
|
267
|
+
/**
|
|
268
|
+
* Compatibility shutdown entrypoint. It now acknowledges actual seed/engine settlement before
|
|
269
|
+
* releasing the writer guard or closing SQLite; callers that need an explicit lifecycle name can
|
|
270
|
+
* use closeAndWait().
|
|
271
|
+
*/
|
|
268
272
|
close(): Promise<void>;
|
|
273
|
+
/** Stop the socket and join engine-open and transport seed/reseed work before releasing ownership. */
|
|
274
|
+
closeAndWait(): Promise<void>;
|
|
269
275
|
/** The read-model SqlExecutor over the replica. `buildIssuesView(replica.sql, …)` is unchanged. */
|
|
270
276
|
get sql(): SqlExecutor;
|
|
271
277
|
issues(opts?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalyst-replica.d.ts","sourceRoot":"","sources":["../../src/replica/catalyst-replica.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"catalyst-replica.d.ts","sourceRoot":"","sources":["../../src/replica/catalyst-replica.ts"],"names":[],"mappings":"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"}
|
|
@@ -132,6 +132,9 @@ export class CatalystReplica {
|
|
|
132
132
|
fetchImpl;
|
|
133
133
|
log;
|
|
134
134
|
engine = null;
|
|
135
|
+
/** The dynamic engine open is owned until it settles; closeAndWait must not release the lock first. */
|
|
136
|
+
engineOpening = null;
|
|
137
|
+
shutdownTask = null;
|
|
135
138
|
writeDb = null;
|
|
136
139
|
sqlExecutor = null;
|
|
137
140
|
client = null;
|
|
@@ -145,11 +148,9 @@ export class CatalystReplica {
|
|
|
145
148
|
readonlyMode = false;
|
|
146
149
|
/** The claimed single-writer lock (writers only; null for readers / `:memory:` / disabled). */
|
|
147
150
|
writerLock = null;
|
|
148
|
-
/** The in-flight seed's AbortController (CTC-281),
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* process can't exit" hostage mechanism from the incident chain. Also the seed idle-timeout's
|
|
152
|
-
* abort seam. One controller per seed RUN (a retried seed gets a fresh one). */
|
|
151
|
+
/** The in-flight seed's AbortController (CTC-281), synchronously aborted by closeAndWait before it
|
|
152
|
+
* joins the fetch/body-reader unwind. Also the seed idle-timeout's abort seam. One controller per
|
|
153
|
+
* seed RUN (a retried seed gets a fresh one). */
|
|
153
154
|
seedAbort = null;
|
|
154
155
|
/** Resolved on the first 'live' status (start() = caught-up + ready to read); rejected on close /
|
|
155
156
|
* an initial seed failure. */
|
|
@@ -250,7 +251,22 @@ export class CatalystReplica {
|
|
|
250
251
|
// Single-writer guard: claim sole ownership of the file BEFORE opening it, so a second concurrent
|
|
251
252
|
// writer rejects here instead of racing the cursor/seed. A no-op for ':memory:' or when disabled.
|
|
252
253
|
this.writerLock = claimWriterLock(this.opts.dbPath, this.opts.writerGuard ?? {}, this.log);
|
|
253
|
-
const
|
|
254
|
+
const engineOpening = this.resolveEngine();
|
|
255
|
+
this.engineOpening = engineOpening;
|
|
256
|
+
let engine;
|
|
257
|
+
try {
|
|
258
|
+
engine = await engineOpening;
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
if (this.engineOpening === engineOpening)
|
|
262
|
+
this.engineOpening = null;
|
|
263
|
+
}
|
|
264
|
+
// close() may have been requested while the sqlite driver was opening. The lock stays held until
|
|
265
|
+
// that opener settles, and a late opener is closed before migrations or any other writes begin.
|
|
266
|
+
if (this.closed) {
|
|
267
|
+
this.engine = engine;
|
|
268
|
+
throw new Error("CatalystReplica: start cancelled by close()");
|
|
269
|
+
}
|
|
254
270
|
this.engine = engine;
|
|
255
271
|
// ── CTC-582: THE ACCOUNT FENCE ────────────────────────────────────────────────────────────────
|
|
256
272
|
//
|
|
@@ -446,13 +462,15 @@ export class CatalystReplica {
|
|
|
446
462
|
tracerName: DEFAULT_SCOPE_NAME,
|
|
447
463
|
meterName: DEFAULT_SCOPE_NAME,
|
|
448
464
|
});
|
|
465
|
+
if (this.closed)
|
|
466
|
+
throw new Error("CatalystReplica: start cancelled by close()");
|
|
449
467
|
this.registerMetrics();
|
|
450
468
|
this.client = new LiveSyncClient({
|
|
451
469
|
baseUrl: this.baseUrl,
|
|
452
470
|
accountId: this.opts.account,
|
|
453
471
|
connectPath: this.opts.connectPath,
|
|
454
472
|
auth: this.opts.auth,
|
|
455
|
-
reseed: () => this.seedFromSnapshot(),
|
|
473
|
+
reseed: (signal) => this.seedFromSnapshot(signal),
|
|
456
474
|
getCursor: () => getCursor(this.writeDb),
|
|
457
475
|
connectParams: () => this.workflowRevParams(),
|
|
458
476
|
onChange: (frame) => this.applyFrame(frame),
|
|
@@ -496,10 +514,11 @@ export class CatalystReplica {
|
|
|
496
514
|
if (settled)
|
|
497
515
|
return;
|
|
498
516
|
settled = true;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
517
|
+
const timeoutMessage = `CatalystReplica.start() did not reach 'live' within ${timeoutMs}ms ` +
|
|
518
|
+
`(account=${this.opts.account}, dbPath=${this.opts.dbPath})`;
|
|
519
|
+
// Consume both shutdown outcomes. An ignored finally() promise would reject independently
|
|
520
|
+
// when cleanup fails, leaving the caller with only the timeout and an unhandled rejection.
|
|
521
|
+
void this.close().then(() => reject(new Error(timeoutMessage)), (cleanupError) => reject(new Error(timeoutMessage, { cause: cleanupError })));
|
|
503
522
|
}, timeoutMs);
|
|
504
523
|
timer.unref?.();
|
|
505
524
|
livePromise.then(() => {
|
|
@@ -532,19 +551,25 @@ export class CatalystReplica {
|
|
|
532
551
|
throw new Error("CatalystReplica: resume() before start()");
|
|
533
552
|
this.client.resume();
|
|
534
553
|
}
|
|
535
|
-
/**
|
|
536
|
-
*
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
554
|
+
/**
|
|
555
|
+
* Compatibility shutdown entrypoint. It now acknowledges actual seed/engine settlement before
|
|
556
|
+
* releasing the writer guard or closing SQLite; callers that need an explicit lifecycle name can
|
|
557
|
+
* use closeAndWait().
|
|
558
|
+
*/
|
|
559
|
+
close() {
|
|
560
|
+
return this.closeAndWait();
|
|
561
|
+
}
|
|
562
|
+
/** Stop the socket and join engine-open and transport seed/reseed work before releasing ownership. */
|
|
563
|
+
async closeAndWait() {
|
|
564
|
+
if (this.shutdownTask)
|
|
565
|
+
return this.shutdownTask;
|
|
540
566
|
this.closed = true;
|
|
541
567
|
this.client?.stop();
|
|
542
|
-
// Abort any in-flight seed
|
|
543
|
-
// stay one-tick). Without this, the pending /snapshot fetch + un-cancelled body reader kept the
|
|
544
|
-
// TCP connection referenced after close() resolved, holding a supervised process's exit hostage.
|
|
545
|
-
// stop() ran first, so the seed's rejection lands in paths that already refuse to reopen.
|
|
568
|
+
// Abort any in-flight seed synchronously, then join its actual I/O in LiveSyncClient.stopAndWait.
|
|
546
569
|
try {
|
|
547
|
-
|
|
570
|
+
const abortError = new Error("CatalystReplica closed");
|
|
571
|
+
abortError.name = "AbortError";
|
|
572
|
+
this.seedAbort?.abort(abortError);
|
|
548
573
|
}
|
|
549
574
|
catch (err) {
|
|
550
575
|
this.log("warn", "seed abort threw", err);
|
|
@@ -553,29 +578,55 @@ export class CatalystReplica {
|
|
|
553
578
|
const rej = this.liveReject;
|
|
554
579
|
this.clearLiveDeferred();
|
|
555
580
|
rej?.(new Error("CatalystReplica: closed before first 'live'"));
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
for (const reg of this.metricRegs) {
|
|
581
|
+
const client = this.client;
|
|
582
|
+
const shutdown = async () => {
|
|
583
|
+
const failures = [];
|
|
584
|
+
// The replica lock remains held while either an engine opener or client-owned seed can still
|
|
585
|
+
// write. Startup checks `closed` immediately after the opener and before client construction.
|
|
586
|
+
if (this.engineOpening)
|
|
587
|
+
await this.engineOpening.catch(() => undefined);
|
|
588
|
+
let seedIoAcknowledged = false;
|
|
565
589
|
try {
|
|
566
|
-
|
|
590
|
+
await client?.stopAndWait();
|
|
591
|
+
seedIoAcknowledged = true;
|
|
567
592
|
}
|
|
568
593
|
catch (err) {
|
|
569
|
-
|
|
594
|
+
failures.push(err);
|
|
570
595
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
596
|
+
for (const reg of this.metricRegs) {
|
|
597
|
+
try {
|
|
598
|
+
reg.remove();
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
failures.push(err);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
this.metricRegs = [];
|
|
605
|
+
let engineClosed = this.engine == null;
|
|
606
|
+
try {
|
|
607
|
+
this.engine?.close();
|
|
608
|
+
engineClosed = true;
|
|
609
|
+
this.engine = null;
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
failures.push(err);
|
|
613
|
+
}
|
|
614
|
+
// Do not admit a successor while the old handle may still be live. Keep the sidecar on an
|
|
615
|
+
// engine-close or seed-I/O acknowledgement failure so ownership remains conservatively held.
|
|
616
|
+
if (engineClosed && seedIoAcknowledged) {
|
|
617
|
+
try {
|
|
618
|
+
this.writerLock?.release();
|
|
619
|
+
this.writerLock = null;
|
|
620
|
+
}
|
|
621
|
+
catch (err) {
|
|
622
|
+
failures.push(err);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (failures.length)
|
|
626
|
+
throw new AggregateError(failures, "CatalystReplica shutdown failed");
|
|
627
|
+
};
|
|
628
|
+
this.shutdownTask = shutdown();
|
|
629
|
+
return this.shutdownTask;
|
|
579
630
|
}
|
|
580
631
|
// ── Reads ───────────────────────────────────────────────────────────────────────────────────────
|
|
581
632
|
/** The read-model SqlExecutor over the replica. `buildIssuesView(replica.sql, …)` is unchanged. */
|
|
@@ -919,7 +970,7 @@ export class CatalystReplica {
|
|
|
919
970
|
* interrupted seed self-heals (getCursor → null → re-seed on the next start), preserving host-sync's
|
|
920
971
|
* atomic truncate+apply+setCursor safety without holding one giant transaction.
|
|
921
972
|
*/
|
|
922
|
-
async seedFromSnapshot() {
|
|
973
|
+
async seedFromSnapshot(attemptSignal) {
|
|
923
974
|
// The whole seed (fetch + truncate + batched apply + setCursor) runs inside ONE active span so it
|
|
924
975
|
// nests under the resync span when triggered by a {type:"resync"} frame, and stands alone on a cold
|
|
925
976
|
// start. Active so the per-batch apply spans auto-parent under it.
|
|
@@ -930,10 +981,16 @@ export class CatalystReplica {
|
|
|
930
981
|
// it synchronously. Every await below (fetch, each body chunk) is bounded by it — a hung
|
|
931
982
|
// /snapshot can no longer wedge the transport in "resyncing" (no socket, no timers) or keep
|
|
932
983
|
// the event loop referenced after close().
|
|
984
|
+
if (attemptSignal?.aborted)
|
|
985
|
+
throw attemptSignal.reason;
|
|
933
986
|
if (this.closed)
|
|
934
987
|
throw new Error("CatalystReplica: seed aborted (already closed)");
|
|
935
988
|
const abort = new AbortController();
|
|
936
989
|
this.seedAbort = abort;
|
|
990
|
+
// Carry the transport's exact owned cancellation into fetch/body reads. A distinct cleanup
|
|
991
|
+
// error, even one named AbortError, must remain distinguishable at stopAndWait().
|
|
992
|
+
const onAttemptAbort = () => abort.abort(attemptSignal?.reason);
|
|
993
|
+
attemptSignal?.addEventListener("abort", onAttemptAbort, { once: true });
|
|
937
994
|
// The idle (NO-PROGRESS) deadline: (re)armed before the fetch and on every body chunk, so a
|
|
938
995
|
// big-but-flowing seed never trips it while a silent half-open connection always does.
|
|
939
996
|
// Unref'd — the timer must never itself hold the process open.
|
|
@@ -1085,6 +1142,7 @@ export class CatalystReplica {
|
|
|
1085
1142
|
}
|
|
1086
1143
|
finally {
|
|
1087
1144
|
clearIdle();
|
|
1145
|
+
attemptSignal?.removeEventListener("abort", onAttemptAbort);
|
|
1088
1146
|
if (this.seedAbort === abort)
|
|
1089
1147
|
this.seedAbort = null;
|
|
1090
1148
|
}
|