@catalyst-cloud/sdk 0.6.0 → 0.8.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/README.md +92 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +24 -0
- package/dist/browser.js.map +1 -0
- package/dist/live-sync-client.d.ts +203 -13
- package/dist/live-sync-client.d.ts.map +1 -1
- package/dist/live-sync-client.js +605 -50
- package/dist/live-sync-client.js.map +1 -1
- package/dist/replica/browser/apply.d.ts +29 -0
- package/dist/replica/browser/apply.d.ts.map +1 -0
- package/dist/replica/browser/apply.js +59 -0
- package/dist/replica/browser/apply.js.map +1 -0
- package/dist/replica/browser/browser-lock.d.ts +25 -0
- package/dist/replica/browser/browser-lock.d.ts.map +1 -0
- package/dist/replica/browser/browser-lock.js +91 -0
- package/dist/replica/browser/browser-lock.js.map +1 -0
- package/dist/replica/browser/browser-replica.d.ts +237 -0
- package/dist/replica/browser/browser-replica.d.ts.map +1 -0
- package/dist/replica/browser/browser-replica.js +952 -0
- package/dist/replica/browser/browser-replica.js.map +1 -0
- package/dist/replica/browser/db.worker.d.ts +2 -0
- package/dist/replica/browser/db.worker.d.ts.map +1 -0
- package/dist/replica/browser/db.worker.js +40 -0
- package/dist/replica/browser/db.worker.js.map +1 -0
- package/dist/replica/browser/delta-queue.d.ts +187 -0
- package/dist/replica/browser/delta-queue.d.ts.map +1 -0
- package/dist/replica/browser/delta-queue.js +328 -0
- package/dist/replica/browser/delta-queue.js.map +1 -0
- package/dist/replica/browser/ports.d.ts +39 -0
- package/dist/replica/browser/ports.d.ts.map +1 -0
- package/dist/replica/browser/ports.js +144 -0
- package/dist/replica/browser/ports.js.map +1 -0
- package/dist/replica/browser/protocol.d.ts +129 -0
- package/dist/replica/browser/protocol.d.ts.map +1 -0
- package/dist/replica/browser/protocol.js +12 -0
- package/dist/replica/browser/protocol.js.map +1 -0
- package/dist/replica/browser/seed-read-gate.d.ts +13 -0
- package/dist/replica/browser/seed-read-gate.d.ts.map +1 -0
- package/dist/replica/browser/seed-read-gate.js +36 -0
- package/dist/replica/browser/seed-read-gate.js.map +1 -0
- package/dist/replica/browser/seed-session.d.ts +23 -0
- package/dist/replica/browser/seed-session.d.ts.map +1 -0
- package/dist/replica/browser/seed-session.js +57 -0
- package/dist/replica/browser/seed-session.js.map +1 -0
- package/dist/replica/browser/snapshot-stream.d.ts +24 -0
- package/dist/replica/browser/snapshot-stream.d.ts.map +1 -0
- package/dist/replica/browser/snapshot-stream.js +113 -0
- package/dist/replica/browser/snapshot-stream.js.map +1 -0
- package/dist/replica/browser/sqlite-db.d.ts +9 -0
- package/dist/replica/browser/sqlite-db.d.ts.map +1 -0
- package/dist/replica/browser/sqlite-db.js +28 -0
- package/dist/replica/browser/sqlite-db.js.map +1 -0
- package/dist/replica/browser/support.d.ts +2 -0
- package/dist/replica/browser/support.d.ts.map +1 -0
- package/dist/replica/browser/support.js +30 -0
- package/dist/replica/browser/support.js.map +1 -0
- package/dist/replica/browser/validate.d.ts +17 -0
- package/dist/replica/browser/validate.d.ts.map +1 -0
- package/dist/replica/browser/validate.js +47 -0
- package/dist/replica/browser/validate.js.map +1 -0
- package/dist/replica/browser/worker-core.d.ts +10 -0
- package/dist/replica/browser/worker-core.d.ts.map +1 -0
- package/dist/replica/browser/worker-core.js +181 -0
- package/dist/replica/browser/worker-core.js.map +1 -0
- package/dist/replica/catalyst-replica.d.ts +34 -10
- package/dist/replica/catalyst-replica.d.ts.map +1 -1
- package/dist/replica/catalyst-replica.js +195 -83
- package/dist/replica/catalyst-replica.js.map +1 -1
- package/dist/replica/migration-shape.d.ts +11 -0
- package/dist/replica/migration-shape.d.ts.map +1 -0
- package/dist/replica/migration-shape.js +25 -0
- package/dist/replica/migration-shape.js.map +1 -0
- package/package.json +16 -3
package/README.md
CHANGED
|
@@ -127,6 +127,98 @@ void client.start(); // never await in the browser
|
|
|
127
127
|
|
|
128
128
|
Requires a platform `WebSocket` (browser, Bun, or Node ≥22). On older Node, inject a `wsFactory` that wraps the [`ws`](https://www.npmjs.com/package/ws) package.
|
|
129
129
|
|
|
130
|
+
### Browser — managed OPFS replica (`/browser`)
|
|
131
|
+
|
|
132
|
+
`BrowserReplica` is the browser twin of `/node`'s `CatalystReplica`: an OPFS-persisted SQLite replica
|
|
133
|
+
in a dedicated Web Worker, seeded from `/snapshot` in bounded streamed batches, kept live off the same
|
|
134
|
+
change feed, and read through the same `@catalyst-cloud/read-model` views — no hand-written worker,
|
|
135
|
+
apply, or seeding code in your app.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import {
|
|
139
|
+
BrowserReplica,
|
|
140
|
+
isBrowserReplicaSupported,
|
|
141
|
+
type ReplicaStatus,
|
|
142
|
+
} from "@catalyst-cloud/sdk/browser";
|
|
143
|
+
|
|
144
|
+
let status: ReplicaStatus = "loading";
|
|
145
|
+
|
|
146
|
+
if (isBrowserReplicaSupported()) {
|
|
147
|
+
const replica = new BrowserReplica(
|
|
148
|
+
{
|
|
149
|
+
onChanged: () => void refreshList(), // after the seed and after every applied delta drain
|
|
150
|
+
onStatus: (s) => {
|
|
151
|
+
status = s; // "loading" | "live" | "reconnecting" | "error" | "secondary" | …
|
|
152
|
+
setBadge(s);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
baseUrl: "/api/v1", // same-origin, cookie-authed; accountId optional
|
|
157
|
+
// REQUIRED. The OPFS database is shared by every replica on the origin and a warm cursor skips
|
|
158
|
+
// the snapshot, so without a fence a change of signed-in user serves the PREVIOUS user's rows.
|
|
159
|
+
// Pass a stable per-user id from your session — e.g. the WorkOS `user.id`. It never leaves the
|
|
160
|
+
// browser; it is only compared against the value stamped in the local DB, and a mismatch wipes
|
|
161
|
+
// the replica and forces a re-seed.
|
|
162
|
+
identity: session.user.id,
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
// AWAIT it. start() suspends on the Web Lock before the worker exists, so a query issued against a
|
|
166
|
+
// not-yet-started replica rejects with "replica client closed".
|
|
167
|
+
await replica.start(); // resolves once seeded — or immediately, with status "secondary"
|
|
168
|
+
|
|
169
|
+
// Another tab already owns the origin's replica. That is a normal outcome, not an error: this tab
|
|
170
|
+
// should read via its usual fetch path and leave the local DB alone.
|
|
171
|
+
if (status !== "secondary") {
|
|
172
|
+
const rows = await replica.queryIssues(); // read-model IssueView[], served locally
|
|
173
|
+
}
|
|
174
|
+
// replica.close() on teardown — cooperatively releases the OPFS handles, then terminates the worker
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`status` above is whatever your `onStatus` handler last recorded. `start()` REJECTS if the boot fails
|
|
179
|
+
(bad origin, missing worker chunk, OPFS unavailable) — it does not resolve into a broken state, so a
|
|
180
|
+
`try`/`catch` around it is where you surface the error to the user.
|
|
181
|
+
|
|
182
|
+
Built in, because browsers need them:
|
|
183
|
+
|
|
184
|
+
- **Single-owner election** (Web Locks): OPFS SAHPool is single-connection-per-origin, so exactly one
|
|
185
|
+
tab boots the replica; every other tab gets status `"secondary"` (a clean state, not an error) and
|
|
186
|
+
should read via its normal fetch path.
|
|
187
|
+
- **Backpressure**: live deltas are coalesced into batched, single-flight applies with a bounded
|
|
188
|
+
buffer; a backlog too deep to replay escalates to a fresh snapshot instead of growing.
|
|
189
|
+
|
|
190
|
+
**Peer dependency**: install [`@sqlite.org/sqlite-wasm`](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm)
|
|
191
|
+
yourself — the SDK never bundles the wasm.
|
|
192
|
+
|
|
193
|
+
**Bundlers**: the worker is created with `new Worker(new URL("./db.worker.js", import.meta.url),
|
|
194
|
+
{ type: "module" })`, which Vite, webpack 5, and Rollup detect statically and split into its own chunk
|
|
195
|
+
(nothing wasm-related touches your main bundle; `/node` consumers never see it at all). Notes:
|
|
196
|
+
|
|
197
|
+
- **Vite**: works out of the box in `build`. In dev, if pre-bundling interferes with the worker URL,
|
|
198
|
+
add `optimizeDeps: { exclude: ["@catalyst-cloud/sdk"] }`.
|
|
199
|
+
- **webpack 5 / Rollup**: the `new Worker(new URL(...))` syntax is supported natively (Rollup needs
|
|
200
|
+
worker support in your config, e.g. `@surma/rollup-plugin-off-main-thread` or equivalent).
|
|
201
|
+
- **Exotic bundlers**: pass `createWorker` in `BrowserReplicaOptions` and construct the worker
|
|
202
|
+
however your toolchain requires. The worker module is published at the dedicated subpath
|
|
203
|
+
**`@catalyst-cloud/sdk/browser/db-worker`** — that specifier is the supported entry point:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
// Vite
|
|
207
|
+
import DbWorker from "@catalyst-cloud/sdk/browser/db-worker?worker";
|
|
208
|
+
createWorker: () => new DbWorker();
|
|
209
|
+
|
|
210
|
+
// webpack 5 / anything that resolves a bare specifier to a URL
|
|
211
|
+
createWorker: () =>
|
|
212
|
+
new Worker(
|
|
213
|
+
new URL("@catalyst-cloud/sdk/browser/db-worker", import.meta.url),
|
|
214
|
+
{ type: "module" },
|
|
215
|
+
);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The worker is a **side-effect module** (it registers a message handler and exports nothing), so it
|
|
219
|
+
is listed in the package's `sideEffects` array — do not configure your bundler to tree-shake it, or
|
|
220
|
+
it will load and register nothing and every replica call will hang.
|
|
221
|
+
|
|
130
222
|
## API
|
|
131
223
|
|
|
132
224
|
| Export | What it is |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { BrowserReplica, streamSeedIntoWorker, type BrowserReplicaOptions, type ReplicaHandlers, type ReplicaStatus, } from "./replica/browser/browser-replica.js";
|
|
2
|
+
export { isBrowserReplicaSupported } from "./replica/browser/support.js";
|
|
3
|
+
export { acquireReplicaLock, type ReplicaLockHandle, type ReplicaLockOptions, } from "./replica/browser/browser-lock.js";
|
|
4
|
+
export { DeltaQueue, MAX_APPLY_BATCH_ROWS, MAX_INBOX_DEPTH, type SeqChange, type DeltaQueueOptions, } from "./replica/browser/delta-queue.js";
|
|
5
|
+
export { streamSnapshotBatches, SEED_BATCH_ROWS } from "./replica/browser/snapshot-stream.js";
|
|
6
|
+
export type { WireChange, ReplicaRequest, ReplicaResponse, Envelope, ResultMap, ApplyChangesResult, } from "./replica/browser/protocol.js";
|
|
7
|
+
export { LiveSyncClient, type AuthStrategy, type LiveSyncStatus, type LogLevel, type WebSocketLike, type WebSocketFactory, } from "./live-sync-client.js";
|
|
8
|
+
export { ENTITY_NAMES, CHANGE_OPS, type AccountId, type EntityName, type ChangeOp, type ChangeFrame, } from "./types.js";
|
|
9
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAgBA,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAEzE,OAAO,EACL,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,KAAK,SAAS,EACd,KAAK,iBAAiB,GACvB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAE9F,YAAY,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,QAAQ,EACR,SAAS,EACT,kBAAkB,GACnB,MAAM,+BAA+B,CAAC;AAGvC,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"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @catalyst-cloud/sdk/browser — the managed replica for the browser (CTC-114, milestone 2 of the
|
|
2
|
+
// managed-replica roadmap; ADR-0002).
|
|
3
|
+
//
|
|
4
|
+
// `BrowserReplica` is the browser twin of `/node`'s CatalystReplica behind ONE import: claim
|
|
5
|
+
// single-ownership of the origin's replica (Web Locks — a losing tab degrades to "secondary", never a
|
|
6
|
+
// red error), open + migrate an OPFS SAHPool SQLite DB inside a dedicated Worker, stream-seed
|
|
7
|
+
// /snapshot in bounded batches, subscribe to the live change feed (the SDK's LiveSyncClient) through a
|
|
8
|
+
// coalescing, depth-bounded delta queue, and serve the @catalyst-cloud/read-model `build*View`
|
|
9
|
+
// queries over it — the "same query, four runtimes" proof, in the browser.
|
|
10
|
+
//
|
|
11
|
+
// BUNDLER CONTRACT: the worker is reached via `new Worker(new URL("./db.worker.js",
|
|
12
|
+
// import.meta.url), { type: "module" })`, which Vite, webpack 5 and Rollup detect statically and emit
|
|
13
|
+
// as a separate chunk; `@sqlite.org/sqlite-wasm` is a PEER dependency resolved by the consumer inside
|
|
14
|
+
// that chunk. Import this subpath ONLY from browser code — the root ("@catalyst-cloud/sdk") stays the
|
|
15
|
+
// transport-only isomorphic surface, and '/node' never pulls any of this into a node bundle.
|
|
16
|
+
export { BrowserReplica, streamSeedIntoWorker, } from "./replica/browser/browser-replica.js";
|
|
17
|
+
export { isBrowserReplicaSupported } from "./replica/browser/support.js";
|
|
18
|
+
export { acquireReplicaLock, } from "./replica/browser/browser-lock.js";
|
|
19
|
+
export { DeltaQueue, MAX_APPLY_BATCH_ROWS, MAX_INBOX_DEPTH, } from "./replica/browser/delta-queue.js";
|
|
20
|
+
export { streamSnapshotBatches, SEED_BATCH_ROWS } from "./replica/browser/snapshot-stream.js";
|
|
21
|
+
// Re-export the transport surface so a consumer can stay on one import.
|
|
22
|
+
export { LiveSyncClient, } from "./live-sync-client.js";
|
|
23
|
+
export { ENTITY_NAMES, CHANGE_OPS, } from "./types.js";
|
|
24
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,sCAAsC;AACtC,EAAE;AACF,6FAA6F;AAC7F,sGAAsG;AACtG,8FAA8F;AAC9F,uGAAuG;AACvG,+FAA+F;AAC/F,2EAA2E;AAC3E,EAAE;AACF,oFAAoF;AACpF,sGAAsG;AACtG,sGAAsG;AACtG,sGAAsG;AACtG,6FAA6F;AAE7F,OAAO,EACL,cAAc,EACd,oBAAoB,GAIrB,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAEzE,OAAO,EACL,kBAAkB,GAGnB,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,eAAe,GAGhB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAW9F,wEAAwE;AACxE,OAAO,EACL,cAAc,GAMf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,YAAY,EACZ,UAAU,GAKX,MAAM,YAAY,CAAC"}
|
|
@@ -45,8 +45,20 @@ export interface LiveSyncClientOptions {
|
|
|
45
45
|
* verbatim (path-preserving). A trailing slash is trimmed.
|
|
46
46
|
*/
|
|
47
47
|
baseUrl: string;
|
|
48
|
-
/**
|
|
49
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The tenant id = the mirror's name; sent as `?account=` on the connect URL.
|
|
50
|
+
*
|
|
51
|
+
* OPTIONAL, and omitting it is the browser default: the OPEN read plane scopes a cookie-authed
|
|
52
|
+
* connection to the session user's own tenant, and an explicit account is the tenant-SWITCHER path.
|
|
53
|
+
* When absent the parameter is omitted entirely rather than serialized as `?account=` — an empty
|
|
54
|
+
* value is not a mirror name, and freezing it into the wire contract would leave the one nearby
|
|
55
|
+
* server-side fallback that a bare empty value can reach (`adminAccountScope`'s `|| "tenant-0"`)
|
|
56
|
+
* cross-tenant-shaped.
|
|
57
|
+
*
|
|
58
|
+
* REQUIRED for `auth: {kind:"token"}` — a host has no session to fall back to, so the constructor
|
|
59
|
+
* throws rather than letting it fail later as an opaque server 401.
|
|
60
|
+
*/
|
|
61
|
+
accountId?: string;
|
|
50
62
|
/**
|
|
51
63
|
* The connect route. Default "/connect"; the service dual-serves it at "/api/v1/connect" too, so a
|
|
52
64
|
* path-prefixed `baseUrl` ("…/api/v1") with the default "/connect" resolves to "…/api/v1/connect".
|
|
@@ -58,9 +70,26 @@ export interface LiveSyncClientOptions {
|
|
|
58
70
|
* Re-seed the consumer's store from a full snapshot and resolve to the FRESH cursor. Called on a
|
|
59
71
|
* `{type:"resync"}` underflow frame and (optionally) before the first connect. The host pulls
|
|
60
72
|
* /snapshot into bun:sqlite; the browser re-runs its OPFS seed(). The client closes the socket
|
|
61
|
-
* before calling this so no live frame interleaves with the seed.
|
|
73
|
+
* before calling this so no live frame interleaves with the seed. The await is bounded by
|
|
74
|
+
* {@link reseedTimeoutMs} (CTC-281) — a hanging reseed is abandoned (its late settle discarded)
|
|
75
|
+
* and the client re-enters the backoff path, so this callback can never wedge the transport; still,
|
|
76
|
+
* bound your own I/O (as the replica's seedFromSnapshot does) so a dead fetch fails fast, not at
|
|
77
|
+
* the backstop.
|
|
78
|
+
*
|
|
79
|
+
* `signal` ABORTS when the transport gives up on this attempt — the {@link reseedTimeoutMs}
|
|
80
|
+
* deadline expiring, or `stop()` (CTC-114 review round 10). Honour it: the transport considers the
|
|
81
|
+
* attempt over the moment it fires, and RECONNECTS. A callback that keeps writing after that is a
|
|
82
|
+
* second writer racing the socket the transport just reopened — the browser replica's concrete
|
|
83
|
+
* failure was frames delivered past the abandoned seed's cursor being dropped by its paused queue,
|
|
84
|
+
* after which a late seed completion left the socket advanced over a hole that later deltas sealed
|
|
85
|
+
* permanently.
|
|
86
|
+
*
|
|
87
|
+
* This is why "abandon" alone was not enough. Two layers bounding one operation with NO
|
|
88
|
+
* cancellation link between them is the defect; the signal is the link. Optional parameter, so
|
|
89
|
+
* existing zero-arg callbacks still typecheck — but a consumer that ignores it keeps the old
|
|
90
|
+
* two-writer hazard.
|
|
62
91
|
*/
|
|
63
|
-
reseed: () => Promise<number>;
|
|
92
|
+
reseed: (signal?: AbortSignal) => Promise<number>;
|
|
64
93
|
/**
|
|
65
94
|
* Read the consumer's durable cursor (the last applied feed seq), or null/undefined if it has
|
|
66
95
|
* never seeded. Drives the `{type:"sync", after}` catch-up request. Return `null` to send
|
|
@@ -93,6 +122,37 @@ export interface LiveSyncClientOptions {
|
|
|
93
122
|
* socket half-open and force-reconnecting through the normal backoff path. Default `15_000`.
|
|
94
123
|
*/
|
|
95
124
|
pongTimeoutMs?: number;
|
|
125
|
+
/**
|
|
126
|
+
* Connect/open deadline (CTC-281): how long (ms) after constructing a socket to wait for `onopen`
|
|
127
|
+
* before treating the attempt as failed (tear the socket down, status "reconnecting", backoff
|
|
128
|
+
* reconnect). Without it, a TCP-connect or HTTP-upgrade stall that never fires open/close/error
|
|
129
|
+
* (undici sets no upgrade deadline; injected impls are trusted blindly) wedges the client in
|
|
130
|
+
* "connecting" with ZERO pending timers — restart-only. Default `20_000`; `0` disables.
|
|
131
|
+
*/
|
|
132
|
+
openTimeoutMs?: number;
|
|
133
|
+
/**
|
|
134
|
+
* Re-seed deadline (CTC-281): how long (ms) to wait for the injected {@link reseed} callback to
|
|
135
|
+
* settle before abandoning the attempt (status "reconnecting", backoff reconnect; the abandoned
|
|
136
|
+
* reseed's late settle is discarded). While resyncing the client holds NO socket and suppresses
|
|
137
|
+
* scheduleReconnect, so an unbounded reseed hang was a zero-timer restart-only wedge — the exact
|
|
138
|
+
* ticket-ask-3 state. A TOTAL-duration backstop, deliberately generous: the replica's
|
|
139
|
+
* seedFromSnapshot already fails fast on a dead stream via its own IDLE abort
|
|
140
|
+
* (`snapshotIdleTimeoutMs`); this bound exists for reseed impls with no bound of their own (e.g. a
|
|
141
|
+
* browser OPFS seed over a default fetch). Raise it if a legitimate full seed can stream longer.
|
|
142
|
+
* Default `600_000` (10 min); `0` disables.
|
|
143
|
+
*/
|
|
144
|
+
reseedTimeoutMs?: number;
|
|
145
|
+
/**
|
|
146
|
+
* How long a CANCELLED reseed gets to unwind before the transport settles and reconnects anyway.
|
|
147
|
+
* Default 250ms; the value must cover the consumer's OWN cleanup bound (CTC-114 review round 14).
|
|
148
|
+
*
|
|
149
|
+
* Reconnecting before the consumer has unwound is not merely early: its store may still be latched,
|
|
150
|
+
* so replayed frames are refused while this client's `deliveredSeq` advances over them — and the
|
|
151
|
+
* next frame it does accept carries the durable cursor past the gap, permanently. The transport
|
|
152
|
+
* cannot know how long a given consumer takes to unwind, so the consumer sets this. The browser
|
|
153
|
+
* replica raises it to cover its worker-RPC deadline, since its cleanup is a `seedAbort` RPC.
|
|
154
|
+
*/
|
|
155
|
+
cancelCleanupGraceMs?: number;
|
|
96
156
|
/**
|
|
97
157
|
* Gap detection (CTL-1402): how long (ms) to wait for a `{type:"sync"}` gap re-request to finish
|
|
98
158
|
* redelivering the detected hole before retrying (or, once {@link gapRetryLimit} re-requests have
|
|
@@ -134,7 +194,7 @@ export declare function stripTrailingSlashes(s: string): string;
|
|
|
134
194
|
export declare function buildConnectUrl(opts: {
|
|
135
195
|
baseUrl: string;
|
|
136
196
|
connectPath: string;
|
|
137
|
-
accountId
|
|
197
|
+
accountId?: string;
|
|
138
198
|
auth: AuthStrategy;
|
|
139
199
|
}): string;
|
|
140
200
|
export declare class LiveSyncClient {
|
|
@@ -151,6 +211,9 @@ export declare class LiveSyncClient {
|
|
|
151
211
|
private readonly maxBackoffMs;
|
|
152
212
|
private readonly pingIntervalMs;
|
|
153
213
|
private readonly pongTimeoutMs;
|
|
214
|
+
private readonly openTimeoutMs;
|
|
215
|
+
private readonly reseedTimeoutMs;
|
|
216
|
+
private readonly cancelCleanupGraceMs;
|
|
154
217
|
private readonly gapTimeoutMs;
|
|
155
218
|
private readonly gapRetryLimit;
|
|
156
219
|
private readonly wsFactory;
|
|
@@ -158,6 +221,36 @@ export declare class LiveSyncClient {
|
|
|
158
221
|
private readonly telemetryConfig;
|
|
159
222
|
private ws;
|
|
160
223
|
private stopped;
|
|
224
|
+
/**
|
|
225
|
+
* Has start() been entered? `stopped` alone cannot answer this — it is false BEFORE the first
|
|
226
|
+
* start() as well as during a run, so every "am I running?" guard read true on a client that had
|
|
227
|
+
* never booted. Only the public `requestResync()` can reach that window (CTC-114 review round 5):
|
|
228
|
+
* it would reseed and open a socket with no lifecycle deferred and no telemetry resolved, and the
|
|
229
|
+
* later real start() would then openSocket() again — overwriting `this.ws`, so the first socket
|
|
230
|
+
* kept delivering duplicate frames and could no longer be closed through the stored reference.
|
|
231
|
+
*/
|
|
232
|
+
private started;
|
|
233
|
+
/** The in-flight boot task, or null once it settles — `requestResync()` serializes behind it. */
|
|
234
|
+
private bootTask;
|
|
235
|
+
/** Settles an in-flight `boundedReseed` wrapper on stop(), so an awaited resync cannot hang. */
|
|
236
|
+
private abandonReseed;
|
|
237
|
+
/**
|
|
238
|
+
* The in-flight resync, so a concurrent caller AWAITS it instead of being handed an
|
|
239
|
+
* already-resolved promise and acting on a store still being rebuilt (round 10).
|
|
240
|
+
*/
|
|
241
|
+
private activeResync;
|
|
242
|
+
/** Did the boot this request waited on perform a COLD re-seed? Only then may it be absorbed. */
|
|
243
|
+
private bootColdSeeded;
|
|
244
|
+
/**
|
|
245
|
+
* Did the last boot REJECT? Latched until the next `start()` (CTC-114 review round 9).
|
|
246
|
+
*
|
|
247
|
+
* Round 8 read the failure from the awaited `bootTask`, but that handle is nulled once the boot
|
|
248
|
+
* settles — so a `requestResync()` arriving after that microtask found no record of the failure,
|
|
249
|
+
* with `started` still true and `stopped` still false, and sailed past the guard into a reseed that
|
|
250
|
+
* could open a live socket under an application already told startup had failed. The outcome has to
|
|
251
|
+
* outlive the handle.
|
|
252
|
+
*/
|
|
253
|
+
private bootFailed;
|
|
161
254
|
private resyncing;
|
|
162
255
|
private backoff;
|
|
163
256
|
private reconnectTimer;
|
|
@@ -191,11 +284,36 @@ export declare class LiveSyncClient {
|
|
|
191
284
|
private pingSentAt;
|
|
192
285
|
/** Client-lifetime: consecutive opened-then-never-ponged connections. Reset to 0 by ANY pong. */
|
|
193
286
|
private probeFailures;
|
|
194
|
-
/** Client-lifetime: after PROBE_FAILURE_LIMIT never-ponged connections
|
|
195
|
-
* (an old server without auto-pong)
|
|
196
|
-
|
|
287
|
+
/** Client-lifetime until a pong: after PROBE_FAILURE_LIMIT never-ponged connections the watchdog
|
|
288
|
+
* DEGRADES to a {@link DEGRADED_PROBE_MULTIPLIER}x-slower re-probe (an old server without auto-pong)
|
|
289
|
+
* — it never turns off outright, so detection is never a restart-only casualty (CTC-281). Only
|
|
290
|
+
* reachable while pong capability is UNPROVEN ({@link pongEverObserved}); the first pong clears it. */
|
|
291
|
+
private watchdogDegraded;
|
|
292
|
+
/** Client-lifetime pong latch (CTC-281): has ANY connection EVER answered a ping? Once true, the
|
|
293
|
+
* server's auto-pong capability is PROVEN for good — a later never-ponged connection is a liveness
|
|
294
|
+
* failure (the incident's open-but-silent socket), never feature-detect evidence, so the watchdog
|
|
295
|
+
* can no longer even degrade itself. PER-PROCESS by design — which is exactly why the degrade must
|
|
296
|
+
* be soft (see {@link DEGRADED_PROBE_MULTIPLIER}): a restart mid-incident resets this latch. */
|
|
297
|
+
private pongEverObserved;
|
|
197
298
|
private pingTimer;
|
|
198
299
|
private pongDeadline;
|
|
300
|
+
/** Per-CONNECTION connect/open deadline (CTC-281): armed when the socket is constructed, cleared on
|
|
301
|
+
* open/close/teardown. The only timer pending between openSocket() and onopen — the guarantee that
|
|
302
|
+
* a never-firing ws impl cannot leave the client wedged in "connecting" with nothing scheduled. */
|
|
303
|
+
private connectTimer;
|
|
304
|
+
/** Per-CONNECTION onerror→onclose fallback (CTC-281): armed by onerror, fires forceReconnect once
|
|
305
|
+
* if the impl never follows error with close (undici #3697/#3546). Cleared on open/close/teardown. */
|
|
306
|
+
private errorFallbackTimer;
|
|
307
|
+
/** Per-CONNECTION: has THIS socket fired onopen? The connect-deadline's late-timer guard (a
|
|
308
|
+
* throttled background tab can fire the deadline after onopen already ran and cleared it). */
|
|
309
|
+
private socketOpened;
|
|
310
|
+
/** The pending reseed deadline (CTC-281) — the timer that makes "resyncing" (no socket, reconnect
|
|
311
|
+
* suppressed) a bounded state instead of a restart-only wedge. Cleared when the reseed settles in
|
|
312
|
+
* time and by stop() (ask 4: stop() leaves NOTHING pending). At most one reseed is ever in flight:
|
|
313
|
+
* `resyncing` guards the resync path, and the boot cold seed SETS that same flag for its duration.
|
|
314
|
+
* (It used to rely on "the boot seed runs before any socket exists" — true only while a resync
|
|
315
|
+
* needed a server frame. The public `requestResync()` added in 0.8.0 needs no socket.) */
|
|
316
|
+
private reseedTimer;
|
|
199
317
|
constructor(opts: LiveSyncClientOptions);
|
|
200
318
|
/**
|
|
201
319
|
* Start the client: seed first if the consumer has no cursor, then open the live socket and keep it
|
|
@@ -208,6 +326,13 @@ export declare class LiveSyncClient {
|
|
|
208
326
|
stop(): void;
|
|
209
327
|
/** The ws(s):// URL this client opens, for diagnostics/tests. Re-derived from the options. */
|
|
210
328
|
connectUrl(): string;
|
|
329
|
+
/**
|
|
330
|
+
* The tenant label for telemetry. Attributes are `Record<string, string>`, and an empty string reads
|
|
331
|
+
* as a MISSING attribute in Loki/Tempo — which silently merges every session-scoped browser client
|
|
332
|
+
* into one unlabelled bucket. `"session"` names the case instead: this client is scoped to whatever
|
|
333
|
+
* tenant the cookie resolves to.
|
|
334
|
+
*/
|
|
335
|
+
private get tenantAttr();
|
|
211
336
|
private setStatus;
|
|
212
337
|
private openSocket;
|
|
213
338
|
/** End the in-flight connect span exactly once (idempotent — nulls the handle). */
|
|
@@ -263,6 +388,45 @@ export declare class LiveSyncClient {
|
|
|
263
388
|
/** Drop all gap state (pending hole + timer) — on (re)open and on the resync/re-seed path, both of
|
|
264
389
|
* which re-request/rebuild from the durable cursor and so supersede any pending re-request. */
|
|
265
390
|
private clearGapState;
|
|
391
|
+
private clearReseedTimer;
|
|
392
|
+
/**
|
|
393
|
+
* Run the injected reseed() bounded by {@link LiveSyncClientOptions.reseedTimeoutMs} (CTC-281).
|
|
394
|
+
* The injected callback is a trust boundary like the ws impl: while it runs there is NO socket and
|
|
395
|
+
* scheduleReconnect is suppressed, so an unbounded await here was the last zero-timer wedge — the
|
|
396
|
+
* deadline below is the pending timer that upholds the header invariant for the "resyncing" state.
|
|
397
|
+
* On timeout the attempt is both CANCELLED and abandoned (CTC-114 review round 10). It used to be
|
|
398
|
+
* abandoned only, justified as "the callback owns its own I/O bounds" — but the callback owning
|
|
399
|
+
* bounds is exactly what makes a SECOND, independent deadline here dangerous. The browser seed is
|
|
400
|
+
* bounded by network idleness and by per-RPC worker deadlines, both of which a legitimately slow
|
|
401
|
+
* ~100 MB snapshot satisfies indefinitely; this total deadline could therefore fire on a seed that
|
|
402
|
+
* was making honest progress, whereupon the transport reconnected while the callback kept writing.
|
|
403
|
+
* Frames past the abandoned seed's cursor were then accepted by the socket and discarded by the
|
|
404
|
+
* replica's paused queue, and a late seed completion left the socket advanced over a hole that
|
|
405
|
+
* later deltas sealed for good.
|
|
406
|
+
*
|
|
407
|
+
* So the deadline now fires an AbortSignal FIRST and rejects second: whoever is told the attempt is
|
|
408
|
+
* over is also told to stop. A late settle is still discarded via the `settled` latch, and a late
|
|
409
|
+
* REJECTION is still swallowed so it can never surface as an unhandled rejection.
|
|
410
|
+
*/
|
|
411
|
+
private boundedReseed;
|
|
412
|
+
/**
|
|
413
|
+
* Ask the client to drop the socket, re-seed, and reconnect from the fresh cursor — the same path a
|
|
414
|
+
* server `{type:"resync"}` frame drives, exposed for a consumer that discovers ON ITS OWN SIDE that
|
|
415
|
+
* its store can no longer be caught up by deltas.
|
|
416
|
+
*
|
|
417
|
+
* The browser replica is the motivating caller: when its delta queue overflows (or its applies keep
|
|
418
|
+
* rejecting) the buffered frames are dropped, so the transport's notion of what has been delivered is
|
|
419
|
+
* now ahead of what the store actually holds. Re-seeding through here — rather than calling the
|
|
420
|
+
* `reseed` callback directly — is what makes that safe: this CLOSES THE SOCKET FIRST, so no live
|
|
421
|
+
* frame interleaves with the snapshot and lands in the window that is in neither the snapshot nor the
|
|
422
|
+
* store.
|
|
423
|
+
*
|
|
424
|
+
* Delegates to the existing resync path in full — same `resyncing` re-entrancy guard, same
|
|
425
|
+
* `boundedReseed` deadline, same span, same failure→backoff behaviour. NEVER rejects: a failed
|
|
426
|
+
* re-seed is already handled internally by re-entering the reconnect path, and this is called from
|
|
427
|
+
* event handlers and `void` contexts where a rejection would surface as an unhandled promise.
|
|
428
|
+
*/
|
|
429
|
+
requestResync(): Promise<void>;
|
|
266
430
|
/**
|
|
267
431
|
* Cursor underflow: the deltas we need were evicted from the service's retained change buffer. Close the socket
|
|
268
432
|
* (so no live frame interleaves with the re-seed), re-seed via the injected callback, then reconnect
|
|
@@ -270,6 +434,21 @@ export declare class LiveSyncClient {
|
|
|
270
434
|
* frame and suppresses scheduleReconnect for the duration so we reopen exactly once.
|
|
271
435
|
*/
|
|
272
436
|
private handleResync;
|
|
437
|
+
/**
|
|
438
|
+
* The resync body. Never call directly — `handleResync()` owns the in-flight handle.
|
|
439
|
+
*
|
|
440
|
+
* TWO FLAGS, ONE FACT — documented rather than consolidated here, deliberately. `resyncing` and
|
|
441
|
+
* `activeResync` both mean "a re-seed is in flight", and they can disagree: the boot path sets
|
|
442
|
+
* `resyncing` directly (so a resync cannot race startup) without ever creating an `activeResync`
|
|
443
|
+
* handle. In that window a call landing here would hit the guard below and resolve silently, which
|
|
444
|
+
* is the very shape round 10 fixed for the live path.
|
|
445
|
+
*
|
|
446
|
+
* It is NOT reachable today, and both reasons are load-bearing: `requestResync()` serialises behind
|
|
447
|
+
* `bootTask` before it can get here, and the server-frame path needs a socket, which does not exist
|
|
448
|
+
* until the boot opens one. Consolidating the two into a single handle is the right fix and belongs
|
|
449
|
+
* with the wider lifecycle rework — not in a release candidate at round eleven.
|
|
450
|
+
*/
|
|
451
|
+
private runResync;
|
|
273
452
|
/** Epoch ms of the last inbound frame (change, pong, or malformed) — null before the first frame.
|
|
274
453
|
* Any inbound bytes prove the socket is alive; the catalyst daemon's stall classifier reads this to
|
|
275
454
|
* tell a quiet feed from a half-open socket (the per-frame timestamp it previously lacked). */
|
|
@@ -284,8 +463,10 @@ export declare class LiveSyncClient {
|
|
|
284
463
|
/** Any inbound frame: stamp liveness, cancel a pending pong deadline (it was answered), and postpone
|
|
285
464
|
* the next ping so a busy stream never sends one. Runs synchronously in onmessage before parsing. */
|
|
286
465
|
private onInboundFrame;
|
|
287
|
-
/** (Re)arm the idle-ping timer. No-op when the watchdog is
|
|
288
|
-
*
|
|
466
|
+
/** (Re)arm the idle-ping timer. No-op when the watchdog is off or there is no live socket, so it is
|
|
467
|
+
* safe to call on every frame. A setTimeout chain (not setInterval): each frame resets it. A
|
|
468
|
+
* DEGRADED watchdog still arms — at {@link DEGRADED_PROBE_MULTIPLIER}x the interval — so detection
|
|
469
|
+
* is never permanently off (CTC-281): every live socket always has a probe pending. */
|
|
289
470
|
private armPing;
|
|
290
471
|
/** The feed has been idle for a full interval: send one liveness ping and start the pong deadline. A
|
|
291
472
|
* synchronous send throw means the socket is already dead — treat it as an unanswered probe now. */
|
|
@@ -295,9 +476,16 @@ export declare class LiveSyncClient {
|
|
|
295
476
|
* is proven — re-arm and move on (detection latency grows; correctness holds). Otherwise: half-open. */
|
|
296
477
|
private onPongDeadline;
|
|
297
478
|
/** A ping went unanswered (deadline elapsed or the send threw). If this connection had already proven
|
|
298
|
-
* pong capability it is a genuine liveness timeout
|
|
299
|
-
*
|
|
300
|
-
* server
|
|
479
|
+
* pong capability it is a genuine liveness timeout. If pong capability was proven EARLIER in this
|
|
480
|
+
* client's lifetime (CTC-281), a never-ponged connection is the incident signature — a half-open
|
|
481
|
+
* socket against a server we KNOW auto-pongs — so it too is a liveness failure and must NEVER count
|
|
482
|
+
* toward the feature-detect (during the Jul 17-23 windows, 3 such sockets permanently disabled
|
|
483
|
+
* detection). Only while capability is UNPROVEN does the failure count toward the DEGRADE — after
|
|
484
|
+
* PROBE_FAILURE_LIMIT never-ponged connections the watchdog backs its probes off to
|
|
485
|
+
* DEGRADED_PROBE_MULTIPLIER x pingIntervalMs (an old server without auto-pong costs one bounded
|
|
486
|
+
* reconnect per degraded window; a mid-incident restart — per-process latch reset — still detects
|
|
487
|
+
* the next half-open within one degraded window, never restart-only; CTC-281). Every path
|
|
488
|
+
* force-reconnects through the existing backoff. */
|
|
301
489
|
private onProbeUnanswered;
|
|
302
490
|
/** Tear the socket down and reconnect through the normal backoff. closeSocket detaches handlers (so
|
|
303
491
|
* onclose won't also fire) and clears the liveness timers, so we schedule the reopen ourselves. */
|
|
@@ -305,6 +493,8 @@ export declare class LiveSyncClient {
|
|
|
305
493
|
private clearPingTimer;
|
|
306
494
|
private clearPongDeadline;
|
|
307
495
|
private clearLivenessTimers;
|
|
496
|
+
/** Clear the per-connection connect/open deadline + onerror→onclose fallback (CTC-281). */
|
|
497
|
+
private clearConnectTimers;
|
|
308
498
|
}
|
|
309
499
|
/** Parse a WS frame (string or ArrayBuffer) into a known server frame, or null for anything malformed. */
|
|
310
500
|
export declare function parseFrame(data: unknown): ServerFrame | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-sync-client.d.ts","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"live-sync-client.d.ts","sourceRoot":"","sources":["../src/live-sync-client.ts"],"names":[],"mappings":"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"}
|