meridian-sdk 0.3.1 → 0.3.3
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 +23 -0
- package/dist/client.d.ts +85 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +93 -0
- package/dist/client.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/transport/websocket.d.ts +9 -0
- package/dist/transport/websocket.d.ts.map +1 -1
- package/dist/transport/websocket.js +48 -2
- package/dist/transport/websocket.js.map +1 -1
- package/package.json +11 -1
- package/src/client.ts +157 -0
- package/src/constants.ts +1 -0
- package/src/index.ts +12 -1
- package/src/transport/websocket.ts +50 -1
- package/test/offline-queue.test.ts +156 -0
package/README.md
CHANGED
|
@@ -97,6 +97,29 @@ await Effect.runPromise(
|
|
|
97
97
|
|
|
98
98
|
Without a schema, `T = unknown`. With a schema, incoming deltas are validated at runtime via `Schema.decodeUnknownSync`.
|
|
99
99
|
|
|
100
|
+
### Offline queue
|
|
101
|
+
|
|
102
|
+
Operations sent while disconnected are buffered automatically and flushed in order on reconnect. No configuration needed — it works transparently for all CRDT handles.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// Check how many ops are pending (e.g. for a UI indicator)
|
|
106
|
+
client.pendingOpCount; // number
|
|
107
|
+
|
|
108
|
+
// Subscribe to connection state changes
|
|
109
|
+
const unsub = client.onStateChange(state => {
|
|
110
|
+
console.log("connection state:", state); // "CONNECTED" | "DISCONNECTED" | "CONNECTING" | "CLOSING"
|
|
111
|
+
});
|
|
112
|
+
unsub(); // unsubscribe
|
|
113
|
+
|
|
114
|
+
// Subscribe to incoming deltas (devtools / debugging)
|
|
115
|
+
const unsubDelta = client.onDelta(event => {
|
|
116
|
+
console.log(event.crdtId, event.type, event.at); // "gc:views", "gcounter", 1718000000000
|
|
117
|
+
});
|
|
118
|
+
unsubDelta();
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The queue holds up to 500 ops. If the limit is reached, the oldest op is dropped to make room for the newest.
|
|
122
|
+
|
|
100
123
|
### HTTP client (`client.http`)
|
|
101
124
|
|
|
102
125
|
All methods return `Effect<T, HttpError | NetworkError>`:
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Effect, type Schema } from "effect";
|
|
2
|
+
import type { WsState } from "./transport/websocket.js";
|
|
3
|
+
import type { CrdtMapValue } from "./crdt/crdtmap.js";
|
|
2
4
|
import { HttpClient } from "./transport/http.js";
|
|
3
5
|
import { GCounterHandle } from "./crdt/gcounter.js";
|
|
4
6
|
import { PNCounterHandle } from "./crdt/pncounter.js";
|
|
@@ -8,6 +10,58 @@ import { PresenceHandle } from "./crdt/presence.js";
|
|
|
8
10
|
import { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
9
11
|
import type { TokenClaims } from "./schema.js";
|
|
10
12
|
import type { TokenParseError, TokenExpiredError } from "./errors.js";
|
|
13
|
+
export interface GCounterSnapshotEntry {
|
|
14
|
+
type: "gcounter";
|
|
15
|
+
crdtId: string;
|
|
16
|
+
value: number;
|
|
17
|
+
counts: Readonly<Record<string, number>>;
|
|
18
|
+
}
|
|
19
|
+
export interface PNCounterSnapshotEntry {
|
|
20
|
+
type: "pncounter";
|
|
21
|
+
crdtId: string;
|
|
22
|
+
value: number;
|
|
23
|
+
}
|
|
24
|
+
export interface ORSetSnapshotEntry {
|
|
25
|
+
type: "orset";
|
|
26
|
+
crdtId: string;
|
|
27
|
+
elements: unknown[];
|
|
28
|
+
}
|
|
29
|
+
export interface LwwRegisterSnapshotEntry {
|
|
30
|
+
type: "lwwregister";
|
|
31
|
+
crdtId: string;
|
|
32
|
+
value: unknown;
|
|
33
|
+
meta: {
|
|
34
|
+
updatedAtMs: number;
|
|
35
|
+
author: number;
|
|
36
|
+
} | null;
|
|
37
|
+
}
|
|
38
|
+
export interface PresenceSnapshotEntry {
|
|
39
|
+
type: "presence";
|
|
40
|
+
crdtId: string;
|
|
41
|
+
online: {
|
|
42
|
+
clientId: number;
|
|
43
|
+
data: unknown;
|
|
44
|
+
expiresAtMs: number;
|
|
45
|
+
}[];
|
|
46
|
+
}
|
|
47
|
+
export interface CRDTMapSnapshotEntry {
|
|
48
|
+
type: "crdtmap";
|
|
49
|
+
crdtId: string;
|
|
50
|
+
value: Readonly<CrdtMapValue>;
|
|
51
|
+
}
|
|
52
|
+
export type CRDTSnapshotEntry = GCounterSnapshotEntry | PNCounterSnapshotEntry | ORSetSnapshotEntry | LwwRegisterSnapshotEntry | PresenceSnapshotEntry | CRDTMapSnapshotEntry;
|
|
53
|
+
export interface DeltaEvent {
|
|
54
|
+
crdtId: string;
|
|
55
|
+
type: CRDTSnapshotEntry["type"];
|
|
56
|
+
at: number;
|
|
57
|
+
}
|
|
58
|
+
export interface ClientSnapshot {
|
|
59
|
+
namespace: string;
|
|
60
|
+
clientId: number;
|
|
61
|
+
wsState: WsState;
|
|
62
|
+
pendingOpCount: number;
|
|
63
|
+
crdts: CRDTSnapshotEntry[];
|
|
64
|
+
}
|
|
11
65
|
export interface MeridianClientConfig {
|
|
12
66
|
url: string;
|
|
13
67
|
namespace: string;
|
|
@@ -52,6 +106,9 @@ export declare class MeridianClient {
|
|
|
52
106
|
private readonly lwHandles;
|
|
53
107
|
private readonly prHandles;
|
|
54
108
|
private readonly cmHandles;
|
|
109
|
+
private readonly anyListeners;
|
|
110
|
+
private readonly deltaListeners;
|
|
111
|
+
private readonly handleUnsubs;
|
|
55
112
|
private constructor();
|
|
56
113
|
/**
|
|
57
114
|
* Creates and validates a new `MeridianClient` from the supplied configuration.
|
|
@@ -184,6 +241,30 @@ export declare class MeridianClient {
|
|
|
184
241
|
*/
|
|
185
242
|
crdtmap(crdtId: string): CRDTMapHandle;
|
|
186
243
|
waitForConnected(timeoutMs?: number): Promise<void>;
|
|
244
|
+
/** Number of ops buffered locally, waiting to be sent on reconnect. */
|
|
245
|
+
get pendingOpCount(): number;
|
|
246
|
+
/**
|
|
247
|
+
* Subscribe to connection state changes. Returns an unsubscribe function.
|
|
248
|
+
* Useful for building "syncing" indicators in the UI.
|
|
249
|
+
*/
|
|
250
|
+
onStateChange(listener: (state: WsState) => void): () => void;
|
|
251
|
+
/**
|
|
252
|
+
* Subscribe to any state change (CRDT value or connection state).
|
|
253
|
+
* Fires whenever any handle emits or the WebSocket transitions.
|
|
254
|
+
* Used by `<MeridianDevtools>` to avoid multiple `onStateChange` subscriptions.
|
|
255
|
+
*/
|
|
256
|
+
onAnyChange(listener: () => void): () => void;
|
|
257
|
+
/**
|
|
258
|
+
* Subscribe to incoming CRDT deltas from the server.
|
|
259
|
+
* Fires after each delta is applied to the local handle.
|
|
260
|
+
* Intended for devtools — not for production data flows.
|
|
261
|
+
*/
|
|
262
|
+
onDelta(listener: (event: DeltaEvent) => void): () => void;
|
|
263
|
+
/**
|
|
264
|
+
* Returns a point-in-time snapshot of all active CRDT handles and transport state.
|
|
265
|
+
* Intended for devtools — not for production data flows.
|
|
266
|
+
*/
|
|
267
|
+
snapshot(): ClientSnapshot;
|
|
187
268
|
/**
|
|
188
269
|
* Closes the underlying WebSocket connection.
|
|
189
270
|
*
|
|
@@ -192,6 +273,10 @@ export declare class MeridianClient {
|
|
|
192
273
|
* on unmount.
|
|
193
274
|
*/
|
|
194
275
|
close(): void;
|
|
276
|
+
/** Re-opens the WebSocket after a `close()`. Used by MeridianProvider to survive React StrictMode double-mount. */
|
|
277
|
+
reopen(): void;
|
|
278
|
+
private notifyAnyChange;
|
|
195
279
|
private handleServerMsg;
|
|
280
|
+
private notifyDelta;
|
|
196
281
|
}
|
|
197
282
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -48,6 +48,9 @@ export class MeridianClient {
|
|
|
48
48
|
lwHandles = new Map();
|
|
49
49
|
prHandles = new Map();
|
|
50
50
|
cmHandles = new Map();
|
|
51
|
+
anyListeners = new Set();
|
|
52
|
+
deltaListeners = new Set();
|
|
53
|
+
handleUnsubs = [];
|
|
51
54
|
constructor(config, claims) {
|
|
52
55
|
this.namespace = config.namespace;
|
|
53
56
|
this.claims = claims;
|
|
@@ -65,6 +68,9 @@ export class MeridianClient {
|
|
|
65
68
|
if (config.autoConnect !== false) {
|
|
66
69
|
this.transport.connect();
|
|
67
70
|
}
|
|
71
|
+
// Internal listener: routes transport state changes through onAnyChange
|
|
72
|
+
// so devtools only needs one subscription (avoids WsTransport chaining issue).
|
|
73
|
+
this.transport.onStateChange(() => { this.notifyAnyChange(); });
|
|
68
74
|
}
|
|
69
75
|
/**
|
|
70
76
|
* Creates and validates a new `MeridianClient` from the supplied configuration.
|
|
@@ -111,6 +117,7 @@ export class MeridianClient {
|
|
|
111
117
|
handle = new GCounterHandle({ ns: this.namespace, crdtId, clientId: this.clientId, transport: this.transport });
|
|
112
118
|
this.gcHandles.set(crdtId, handle);
|
|
113
119
|
this.transport.subscribe(crdtId);
|
|
120
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
114
121
|
}
|
|
115
122
|
return handle;
|
|
116
123
|
}
|
|
@@ -136,6 +143,7 @@ export class MeridianClient {
|
|
|
136
143
|
handle = new PNCounterHandle({ ns: this.namespace, crdtId, clientId: this.clientId, transport: this.transport });
|
|
137
144
|
this.pnHandles.set(crdtId, handle);
|
|
138
145
|
this.transport.subscribe(crdtId);
|
|
146
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
139
147
|
}
|
|
140
148
|
return handle;
|
|
141
149
|
}
|
|
@@ -164,6 +172,7 @@ export class MeridianClient {
|
|
|
164
172
|
handle = schema ? new ORSetHandle({ ...base, schema }) : new ORSetHandle(base);
|
|
165
173
|
this.orHandles.set(crdtId, handle);
|
|
166
174
|
this.transport.subscribe(crdtId);
|
|
175
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
167
176
|
}
|
|
168
177
|
return handle;
|
|
169
178
|
}
|
|
@@ -192,6 +201,7 @@ export class MeridianClient {
|
|
|
192
201
|
handle = schema ? new LwwRegisterHandle({ ...base, schema }) : new LwwRegisterHandle(base);
|
|
193
202
|
this.lwHandles.set(crdtId, handle);
|
|
194
203
|
this.transport.subscribe(crdtId);
|
|
204
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
195
205
|
}
|
|
196
206
|
return handle;
|
|
197
207
|
}
|
|
@@ -221,6 +231,7 @@ export class MeridianClient {
|
|
|
221
231
|
handle = schema ? new PresenceHandle({ ...base, schema }) : new PresenceHandle(base);
|
|
222
232
|
this.prHandles.set(crdtId, handle);
|
|
223
233
|
this.transport.subscribe(crdtId);
|
|
234
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
224
235
|
}
|
|
225
236
|
return handle;
|
|
226
237
|
}
|
|
@@ -246,12 +257,68 @@ export class MeridianClient {
|
|
|
246
257
|
handle = new CRDTMapHandle({ ns: this.namespace, crdtId, clientId: this.clientId, transport: this.transport });
|
|
247
258
|
this.cmHandles.set(crdtId, handle);
|
|
248
259
|
this.transport.subscribe(crdtId);
|
|
260
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
249
261
|
}
|
|
250
262
|
return handle;
|
|
251
263
|
}
|
|
252
264
|
waitForConnected(timeoutMs = 5_000) {
|
|
253
265
|
return this.transport.waitForConnected(timeoutMs);
|
|
254
266
|
}
|
|
267
|
+
/** Number of ops buffered locally, waiting to be sent on reconnect. */
|
|
268
|
+
get pendingOpCount() {
|
|
269
|
+
return this.transport.pendingOpCount;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Subscribe to connection state changes. Returns an unsubscribe function.
|
|
273
|
+
* Useful for building "syncing" indicators in the UI.
|
|
274
|
+
*/
|
|
275
|
+
onStateChange(listener) {
|
|
276
|
+
return this.transport.onStateChange(listener);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Subscribe to any state change (CRDT value or connection state).
|
|
280
|
+
* Fires whenever any handle emits or the WebSocket transitions.
|
|
281
|
+
* Used by `<MeridianDevtools>` to avoid multiple `onStateChange` subscriptions.
|
|
282
|
+
*/
|
|
283
|
+
onAnyChange(listener) {
|
|
284
|
+
this.anyListeners.add(listener);
|
|
285
|
+
return () => { this.anyListeners.delete(listener); };
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Subscribe to incoming CRDT deltas from the server.
|
|
289
|
+
* Fires after each delta is applied to the local handle.
|
|
290
|
+
* Intended for devtools — not for production data flows.
|
|
291
|
+
*/
|
|
292
|
+
onDelta(listener) {
|
|
293
|
+
this.deltaListeners.add(listener);
|
|
294
|
+
return () => { this.deltaListeners.delete(listener); };
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Returns a point-in-time snapshot of all active CRDT handles and transport state.
|
|
298
|
+
* Intended for devtools — not for production data flows.
|
|
299
|
+
*/
|
|
300
|
+
snapshot() {
|
|
301
|
+
const crdts = [];
|
|
302
|
+
for (const [crdtId, h] of this.gcHandles)
|
|
303
|
+
crdts.push({ type: "gcounter", crdtId, value: h.value(), counts: h.counts() });
|
|
304
|
+
for (const [crdtId, h] of this.pnHandles)
|
|
305
|
+
crdts.push({ type: "pncounter", crdtId, value: h.value() });
|
|
306
|
+
for (const [crdtId, h] of this.orHandles)
|
|
307
|
+
crdts.push({ type: "orset", crdtId, elements: h.elements() });
|
|
308
|
+
for (const [crdtId, h] of this.lwHandles)
|
|
309
|
+
crdts.push({ type: "lwwregister", crdtId, value: h.value(), meta: h.meta() });
|
|
310
|
+
for (const [crdtId, h] of this.prHandles)
|
|
311
|
+
crdts.push({ type: "presence", crdtId, online: h.online() });
|
|
312
|
+
for (const [crdtId, h] of this.cmHandles)
|
|
313
|
+
crdts.push({ type: "crdtmap", crdtId, value: h.value() });
|
|
314
|
+
return {
|
|
315
|
+
namespace: this.namespace,
|
|
316
|
+
clientId: this.clientId,
|
|
317
|
+
wsState: this.transport.currentState,
|
|
318
|
+
pendingOpCount: this.transport.pendingOpCount,
|
|
319
|
+
crdts,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
255
322
|
/**
|
|
256
323
|
* Closes the underlying WebSocket connection.
|
|
257
324
|
*
|
|
@@ -260,8 +327,21 @@ export class MeridianClient {
|
|
|
260
327
|
* on unmount.
|
|
261
328
|
*/
|
|
262
329
|
close() {
|
|
330
|
+
for (const unsub of this.handleUnsubs)
|
|
331
|
+
unsub();
|
|
332
|
+
this.handleUnsubs.length = 0;
|
|
333
|
+
this.anyListeners.clear();
|
|
334
|
+
this.deltaListeners.clear();
|
|
263
335
|
this.transport.close();
|
|
264
336
|
}
|
|
337
|
+
/** Re-opens the WebSocket after a `close()`. Used by MeridianProvider to survive React StrictMode double-mount. */
|
|
338
|
+
reopen() {
|
|
339
|
+
this.transport.reopen();
|
|
340
|
+
}
|
|
341
|
+
notifyAnyChange() {
|
|
342
|
+
for (const fn of this.anyListeners)
|
|
343
|
+
fn();
|
|
344
|
+
}
|
|
265
345
|
handleServerMsg(msg) {
|
|
266
346
|
if (!("Delta" in msg))
|
|
267
347
|
return;
|
|
@@ -272,6 +352,7 @@ export class MeridianClient {
|
|
|
272
352
|
gcHandle.applyDelta(decodeGCounterDelta(delta_bytes));
|
|
273
353
|
}
|
|
274
354
|
catch { /* stale */ }
|
|
355
|
+
this.notifyDelta(crdt_id, "gcounter");
|
|
275
356
|
return;
|
|
276
357
|
}
|
|
277
358
|
const pnHandle = this.pnHandles.get(crdt_id);
|
|
@@ -280,6 +361,7 @@ export class MeridianClient {
|
|
|
280
361
|
pnHandle.applyDelta(decodePNCounterDelta(delta_bytes));
|
|
281
362
|
}
|
|
282
363
|
catch { /* stale */ }
|
|
364
|
+
this.notifyDelta(crdt_id, "pncounter");
|
|
283
365
|
return;
|
|
284
366
|
}
|
|
285
367
|
const orHandle = this.orHandles.get(crdt_id);
|
|
@@ -288,6 +370,7 @@ export class MeridianClient {
|
|
|
288
370
|
orHandle.applyDelta(decodeORSetDelta(delta_bytes));
|
|
289
371
|
}
|
|
290
372
|
catch { /* stale */ }
|
|
373
|
+
this.notifyDelta(crdt_id, "orset");
|
|
291
374
|
return;
|
|
292
375
|
}
|
|
293
376
|
const lwHandle = this.lwHandles.get(crdt_id);
|
|
@@ -296,6 +379,7 @@ export class MeridianClient {
|
|
|
296
379
|
lwHandle.applyDelta(decodeLwwDelta(delta_bytes));
|
|
297
380
|
}
|
|
298
381
|
catch { /* stale */ }
|
|
382
|
+
this.notifyDelta(crdt_id, "lwwregister");
|
|
299
383
|
return;
|
|
300
384
|
}
|
|
301
385
|
const prHandle = this.prHandles.get(crdt_id);
|
|
@@ -304,6 +388,7 @@ export class MeridianClient {
|
|
|
304
388
|
prHandle.applyDelta(decodePresenceDelta(delta_bytes));
|
|
305
389
|
}
|
|
306
390
|
catch { /* stale */ }
|
|
391
|
+
this.notifyDelta(crdt_id, "presence");
|
|
307
392
|
return;
|
|
308
393
|
}
|
|
309
394
|
const cmHandle = this.cmHandles.get(crdt_id);
|
|
@@ -312,7 +397,15 @@ export class MeridianClient {
|
|
|
312
397
|
cmHandle.applyDelta(decodeCRDTMapDelta(delta_bytes));
|
|
313
398
|
}
|
|
314
399
|
catch { /* stale */ }
|
|
400
|
+
this.notifyDelta(crdt_id, "crdtmap");
|
|
315
401
|
}
|
|
316
402
|
}
|
|
403
|
+
notifyDelta(crdtId, type) {
|
|
404
|
+
if (this.deltaListeners.size === 0)
|
|
405
|
+
return;
|
|
406
|
+
const event = { crdtId, type, at: Date.now() };
|
|
407
|
+
for (const fn of this.deltaListeners)
|
|
408
|
+
fn(event);
|
|
409
|
+
}
|
|
317
410
|
}
|
|
318
411
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export declare const BACKOFF_MULTIPLIER = 2;
|
|
|
4
4
|
export declare const JITTER_MULTIPLIER = 0.2;
|
|
5
5
|
export declare const DEFAULT_TIMEOUT_MS = 5000;
|
|
6
6
|
export declare const TOKEN_SKEW_MS = 5000;
|
|
7
|
+
export declare const OFFLINE_QUEUE_MAX = 500;
|
|
7
8
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AACxC,eAAO,MAAM,aAAa,OAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,kBAAkB,OAAQ,CAAC;AACxC,eAAO,MAAM,aAAa,OAAQ,CAAC;AACnC,eAAO,MAAM,iBAAiB,MAAM,CAAC"}
|
package/dist/constants.js
CHANGED
package/dist/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AACtC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AACrC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AACpC,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AACrC,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACxC,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AACtC,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AACrC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AACpC,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AACrC,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,CAAC;AACxC,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AACnC,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { MeridianClient } from "./client.js";
|
|
2
|
-
export type { MeridianClientConfig } from "./client.js";
|
|
2
|
+
export type { MeridianClientConfig, ClientSnapshot, DeltaEvent, CRDTSnapshotEntry, GCounterSnapshotEntry, PNCounterSnapshotEntry, ORSetSnapshotEntry, LwwRegisterSnapshotEntry, PresenceSnapshotEntry, CRDTMapSnapshotEntry, } from "./client.js";
|
|
3
3
|
export { GCounterHandle } from "./crdt/gcounter.js";
|
|
4
4
|
export { PNCounterHandle } from "./crdt/pncounter.js";
|
|
5
5
|
export { ORSetHandle } from "./crdt/orset.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAG3E,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGlG,OAAO,EACL,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,SAAS,EACT,aAAa,EACb,cAAc,EACd,UAAU,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGzD,OAAO,EACL,MAAM,EACN,MAAM,EACN,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAc7C,eAAe;AACf,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGlD,YAAY;AACZ,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD,OAAO;AACP,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElG,iEAAiE;AACjE,OAAO,EACL,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,qEAAqE;AACrE,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,SAAS,EACT,aAAa,EACb,cAAc,EACd,UAAU,EACV,QAAQ,EACR,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,GACd,MAAM,aAAa,CAAC;AAGrB,qCAAqC;AACrC,OAAO,EACL,MAAM,EACN,MAAM,EACN,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,YAAY,CAAC"}
|
|
@@ -17,12 +17,20 @@ export declare class WsTransport {
|
|
|
17
17
|
private reconnectTimer;
|
|
18
18
|
private closed;
|
|
19
19
|
private readonly subscriptions;
|
|
20
|
+
private readonly pendingOps;
|
|
20
21
|
constructor(config: WsTransportConfig);
|
|
21
22
|
connect(): void;
|
|
22
23
|
close(): void;
|
|
24
|
+
reopen(): void;
|
|
23
25
|
subscribe(crdtId: string, sinceVc?: VectorClock): void;
|
|
24
26
|
updateClock(crdtId: string, vc: VectorClock): void;
|
|
25
27
|
send(msg: Parameters<typeof encodeClientMsg>[0]): void;
|
|
28
|
+
get pendingOpCount(): number;
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to connection state changes. Returns an unsubscribe function.
|
|
31
|
+
* Compatible with React's `useSyncExternalStore` subscribe parameter.
|
|
32
|
+
*/
|
|
33
|
+
onStateChange(listener: (state: WsState) => void): () => void;
|
|
26
34
|
get currentState(): WsState;
|
|
27
35
|
waitForConnected(timeoutMs?: number): Promise<void>;
|
|
28
36
|
private doConnect;
|
|
@@ -30,6 +38,7 @@ export declare class WsTransport {
|
|
|
30
38
|
private clearReconnectTimer;
|
|
31
39
|
private transitionTo;
|
|
32
40
|
private resubscribeAll;
|
|
41
|
+
private flushPendingOps;
|
|
33
42
|
private sendSubscribe;
|
|
34
43
|
}
|
|
35
44
|
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAsC,MAAM,aAAa,CAAC;AAClF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAsC,MAAM,aAAa,CAAC;AAClF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAU3D,MAAM,MAAM,OAAO,GACf,cAAc,GACd,YAAY,GACZ,WAAW,GACX,SAAS,CAAC;AAEd,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,MAAM,CAAS;IAEvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAChE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+C;gBAE9D,MAAM,EAAE,iBAAiB;IAKrC,OAAO,IAAI,IAAI;IAMf,KAAK,IAAI,IAAI;IAQb,MAAM,IAAI,IAAI;IAKd,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,IAAI;IAO1D,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI;IAIlD,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI;IAWtD,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED;;;OAGG;IACH,aAAa,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;IAe7D,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,gBAAgB,CAAC,SAAS,SAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B/D,OAAO,CAAC,SAAS;IAuCjB,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,aAAa;CAMtB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { encodeClientMsg, decodeServerMsg, encodeVectorClock } from "../codec.js";
|
|
3
|
-
import { BACKOFF_INITIAL_MS, BACKOFF_MAX_MS, BACKOFF_MULTIPLIER, JITTER_MULTIPLIER, DEFAULT_TIMEOUT_MS, } from "../constants.js";
|
|
3
|
+
import { BACKOFF_INITIAL_MS, BACKOFF_MAX_MS, BACKOFF_MULTIPLIER, JITTER_MULTIPLIER, DEFAULT_TIMEOUT_MS, OFFLINE_QUEUE_MAX, } from "../constants.js";
|
|
4
4
|
export class WsTransport {
|
|
5
5
|
config;
|
|
6
6
|
maxBackoffMs;
|
|
@@ -10,6 +10,7 @@ export class WsTransport {
|
|
|
10
10
|
reconnectTimer = null;
|
|
11
11
|
closed = false;
|
|
12
12
|
subscriptions = new Map();
|
|
13
|
+
pendingOps = [];
|
|
13
14
|
constructor(config) {
|
|
14
15
|
this.config = config;
|
|
15
16
|
this.maxBackoffMs = config.maxBackoffMs ?? BACKOFF_MAX_MS;
|
|
@@ -22,10 +23,15 @@ export class WsTransport {
|
|
|
22
23
|
}
|
|
23
24
|
close() {
|
|
24
25
|
this.closed = true;
|
|
26
|
+
this.pendingOps.length = 0;
|
|
25
27
|
this.clearReconnectTimer();
|
|
26
28
|
this.transitionTo("CLOSING");
|
|
27
29
|
this.ws?.close(1000, "client close");
|
|
28
30
|
}
|
|
31
|
+
reopen() {
|
|
32
|
+
this.closed = false;
|
|
33
|
+
this.doConnect();
|
|
34
|
+
}
|
|
29
35
|
subscribe(crdtId, sinceVc = {}) {
|
|
30
36
|
this.subscriptions.set(crdtId, sinceVc);
|
|
31
37
|
if (this.state === "CONNECTED") {
|
|
@@ -37,10 +43,36 @@ export class WsTransport {
|
|
|
37
43
|
}
|
|
38
44
|
send(msg) {
|
|
39
45
|
if (this.state !== "CONNECTED" || this.ws === null) {
|
|
40
|
-
|
|
46
|
+
if (this.pendingOps.length >= OFFLINE_QUEUE_MAX) {
|
|
47
|
+
this.pendingOps.shift(); // drop oldest to make room
|
|
48
|
+
}
|
|
49
|
+
this.pendingOps.push(msg);
|
|
50
|
+
return;
|
|
41
51
|
}
|
|
42
52
|
this.ws.send(encodeClientMsg(msg));
|
|
43
53
|
}
|
|
54
|
+
get pendingOpCount() {
|
|
55
|
+
return this.pendingOps.length;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to connection state changes. Returns an unsubscribe function.
|
|
59
|
+
* Compatible with React's `useSyncExternalStore` subscribe parameter.
|
|
60
|
+
*/
|
|
61
|
+
onStateChange(listener) {
|
|
62
|
+
const prev = this.config.onStateChange;
|
|
63
|
+
this.config.onStateChange = (s) => {
|
|
64
|
+
prev?.(s);
|
|
65
|
+
listener(s);
|
|
66
|
+
};
|
|
67
|
+
return () => {
|
|
68
|
+
if (prev !== undefined) {
|
|
69
|
+
this.config.onStateChange = prev;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
delete this.config.onStateChange;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
44
76
|
get currentState() {
|
|
45
77
|
return this.state;
|
|
46
78
|
}
|
|
@@ -132,6 +164,20 @@ export class WsTransport {
|
|
|
132
164
|
for (const [crdtId, vc] of this.subscriptions) {
|
|
133
165
|
this.sendSubscribe(crdtId, vc);
|
|
134
166
|
}
|
|
167
|
+
this.flushPendingOps();
|
|
168
|
+
}
|
|
169
|
+
flushPendingOps() {
|
|
170
|
+
const ops = this.pendingOps.splice(0);
|
|
171
|
+
for (let i = 0; i < ops.length; i++) {
|
|
172
|
+
try {
|
|
173
|
+
this.ws.send(encodeClientMsg(ops[i]));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// WebSocket closed between open and flush — re-queue remaining ops.
|
|
177
|
+
this.pendingOps.unshift(...ops.slice(i));
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
135
181
|
}
|
|
136
182
|
sendSubscribe(crdtId, vc) {
|
|
137
183
|
if (this.ws === null || this.state !== "CONNECTED")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAElF,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/transport/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAElF,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAgBzB,MAAM,OAAO,WAAW;IACL,MAAM,CAAoB;IAC1B,YAAY,CAAS;IAE9B,EAAE,GAAqB,IAAI,CAAC;IAC5B,KAAK,GAAY,cAAc,CAAC;IAChC,SAAS,GAAG,kBAAkB,CAAC;IAC/B,cAAc,GAAyC,IAAI,CAAC;IAC5D,MAAM,GAAG,KAAK,CAAC;IAEN,aAAa,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC/C,UAAU,GAA4C,EAAE,CAAC;IAE1E,YAAY,MAAyB;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY,IAAI,cAAc,CAAC;IAC5D,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAC7B,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IACvC,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAED,SAAS,CAAC,MAAc,EAAE,UAAuB,EAAE;QACjD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxC,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,WAAW,CAAC,MAAc,EAAE,EAAe;QACzC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,CAAC,GAA0C;QAC7C,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;YACnD,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,iBAAiB,EAAE,CAAC;gBAChD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,2BAA2B;YACtD,CAAC;YACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1B,OAAO;QACT,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,QAAkC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,CAAC,CAAC,EAAE,EAAE;YAChC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YACV,QAAQ,CAAC,CAAC,CAAC,CAAC;QACd,CAAC,CAAC;QACF,OAAO,GAAG,EAAE;YACV,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC;YACnC,CAAC;iBAAM,CAAC;gBACN,OAAQ,IAAI,CAAC,MAAqC,CAAC,aAAa,CAAC;YACnE,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,gBAAgB,CAAC,SAAS,GAAG,kBAAkB;QAC7C,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QACzD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC;YACvC,MAAM,OAAO,GAAG,GAAG,EAAE;gBACnB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;oBACvB,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC;gBACnC,CAAC;qBAAM,CAAC;oBACN,OAAQ,IAAI,CAAC,MAAqC,CAAC,aAAa,CAAC;gBACnE,CAAC;YACH,CAAC,CAAC;YACF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC5B,OAAO,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;YACpD,CAAC,EAAE,SAAS,CAAC,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,aAAa,GAAG,CAAC,CAAC,EAAE,EAAE;gBAChC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBACV,IAAI,CAAC,KAAK,WAAW,EAAE,CAAC;oBACtB,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,OAAO,EAAE,CAAC;oBACV,OAAO,EAAE,CAAC;gBACZ,CAAC;YACH,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,SAAS;QACf,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAEhC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,SAAS,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3H,MAAM,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;QAC9B,EAAE,CAAC,UAAU,GAAG,aAAa,CAAC;QAC9B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QAEb,EAAE,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE;YAC/B,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;gBAAE,OAAO;YAC3B,IAAI,CAAC,SAAS,GAAG,kBAAkB,CAAC;YACpC,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YAC/B,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,KAAmB,EAAE,EAAE;YACrD,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;gBAAE,OAAO;YAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,IAAmB,CAAC,CAAC;YACxD,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAC5C,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EACxC,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,4CAA4C,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAC1E,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YAChC,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;gBAAE,OAAO;YAC3B,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACf,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACjB,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;gBAClC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YAChC,+FAA+F;QACjG,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,iBAAiB;QACvB,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,iBAAiB,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC3B,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,CAAC,EAAE,KAAK,CAAC,CAAC;QACV,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,kBAAkB,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IACpF,CAAC;IAEO,mBAAmB;QACzB,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YACjC,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAEO,YAAY,CAAC,IAAa;QAChC,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI;YAAE,OAAO;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IAEO,cAAc;QACpB,KAAK,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YAC9C,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,IAAI,CAAC,eAAe,EAAE,CAAC;IACzB,CAAC;IAEO,eAAe;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,IAAI,CAAC,EAAG,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,oEAAoE;gBACpE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzC,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,MAAc,EAAE,EAAe;QACnD,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW;YAAE,OAAO;QAC3D,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;QAClE,MAAM,OAAO,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAClF,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meridian-sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "TypeScript SDK for Meridian CRDT server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,5 +30,15 @@
|
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"typescript": ">=5.0.0"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/Chahine-tech/meridian.git",
|
|
38
|
+
"directory": "packages/sdk"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/Chahine-tech/meridian#readme",
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/Chahine-tech/meridian/issues"
|
|
33
43
|
}
|
|
34
44
|
}
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Effect, type Schema } from "effect";
|
|
2
|
+
import type { WsState } from "./transport/websocket.js";
|
|
2
3
|
import { WsTransport } from "./transport/websocket.js";
|
|
4
|
+
import type { CrdtMapValue } from "./crdt/crdtmap.js";
|
|
3
5
|
import { HttpClient } from "./transport/http.js";
|
|
4
6
|
import { GCounterHandle } from "./crdt/gcounter.js";
|
|
5
7
|
import { PNCounterHandle } from "./crdt/pncounter.js";
|
|
@@ -19,6 +21,62 @@ import { parseAndValidateToken } from "./auth/token.js";
|
|
|
19
21
|
import type { ServerMsg, TokenClaims } from "./schema.js";
|
|
20
22
|
import type { TokenParseError, TokenExpiredError } from "./errors.js";
|
|
21
23
|
|
|
24
|
+
// --- Devtools snapshot types ---
|
|
25
|
+
|
|
26
|
+
export interface GCounterSnapshotEntry {
|
|
27
|
+
type: "gcounter";
|
|
28
|
+
crdtId: string;
|
|
29
|
+
value: number;
|
|
30
|
+
counts: Readonly<Record<string, number>>;
|
|
31
|
+
}
|
|
32
|
+
export interface PNCounterSnapshotEntry {
|
|
33
|
+
type: "pncounter";
|
|
34
|
+
crdtId: string;
|
|
35
|
+
value: number;
|
|
36
|
+
}
|
|
37
|
+
export interface ORSetSnapshotEntry {
|
|
38
|
+
type: "orset";
|
|
39
|
+
crdtId: string;
|
|
40
|
+
elements: unknown[];
|
|
41
|
+
}
|
|
42
|
+
export interface LwwRegisterSnapshotEntry {
|
|
43
|
+
type: "lwwregister";
|
|
44
|
+
crdtId: string;
|
|
45
|
+
value: unknown;
|
|
46
|
+
meta: { updatedAtMs: number; author: number } | null;
|
|
47
|
+
}
|
|
48
|
+
export interface PresenceSnapshotEntry {
|
|
49
|
+
type: "presence";
|
|
50
|
+
crdtId: string;
|
|
51
|
+
online: { clientId: number; data: unknown; expiresAtMs: number }[];
|
|
52
|
+
}
|
|
53
|
+
export interface CRDTMapSnapshotEntry {
|
|
54
|
+
type: "crdtmap";
|
|
55
|
+
crdtId: string;
|
|
56
|
+
value: Readonly<CrdtMapValue>;
|
|
57
|
+
}
|
|
58
|
+
export type CRDTSnapshotEntry =
|
|
59
|
+
| GCounterSnapshotEntry
|
|
60
|
+
| PNCounterSnapshotEntry
|
|
61
|
+
| ORSetSnapshotEntry
|
|
62
|
+
| LwwRegisterSnapshotEntry
|
|
63
|
+
| PresenceSnapshotEntry
|
|
64
|
+
| CRDTMapSnapshotEntry;
|
|
65
|
+
|
|
66
|
+
export interface DeltaEvent {
|
|
67
|
+
crdtId: string;
|
|
68
|
+
type: CRDTSnapshotEntry["type"];
|
|
69
|
+
at: number; // Date.now()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ClientSnapshot {
|
|
73
|
+
namespace: string;
|
|
74
|
+
clientId: number;
|
|
75
|
+
wsState: WsState;
|
|
76
|
+
pendingOpCount: number;
|
|
77
|
+
crdts: CRDTSnapshotEntry[];
|
|
78
|
+
}
|
|
79
|
+
|
|
22
80
|
export interface MeridianClientConfig {
|
|
23
81
|
url: string;
|
|
24
82
|
namespace: string;
|
|
@@ -68,6 +126,10 @@ export class MeridianClient {
|
|
|
68
126
|
private readonly prHandles = new Map<string, PresenceHandle<unknown>>();
|
|
69
127
|
private readonly cmHandles = new Map<string, CRDTMapHandle>();
|
|
70
128
|
|
|
129
|
+
private readonly anyListeners = new Set<() => void>();
|
|
130
|
+
private readonly deltaListeners = new Set<(event: DeltaEvent) => void>();
|
|
131
|
+
private readonly handleUnsubs: Array<() => void> = [];
|
|
132
|
+
|
|
71
133
|
private constructor(config: MeridianClientConfig, claims: TokenClaims) {
|
|
72
134
|
this.namespace = config.namespace;
|
|
73
135
|
this.claims = claims;
|
|
@@ -88,6 +150,10 @@ export class MeridianClient {
|
|
|
88
150
|
if (config.autoConnect !== false) {
|
|
89
151
|
this.transport.connect();
|
|
90
152
|
}
|
|
153
|
+
|
|
154
|
+
// Internal listener: routes transport state changes through onAnyChange
|
|
155
|
+
// so devtools only needs one subscription (avoids WsTransport chaining issue).
|
|
156
|
+
this.transport.onStateChange(() => { this.notifyAnyChange(); });
|
|
91
157
|
}
|
|
92
158
|
|
|
93
159
|
/**
|
|
@@ -140,6 +206,7 @@ export class MeridianClient {
|
|
|
140
206
|
handle = new GCounterHandle({ ns: this.namespace, crdtId, clientId: this.clientId, transport: this.transport });
|
|
141
207
|
this.gcHandles.set(crdtId, handle);
|
|
142
208
|
this.transport.subscribe(crdtId);
|
|
209
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
143
210
|
}
|
|
144
211
|
return handle;
|
|
145
212
|
}
|
|
@@ -166,6 +233,7 @@ export class MeridianClient {
|
|
|
166
233
|
handle = new PNCounterHandle({ ns: this.namespace, crdtId, clientId: this.clientId, transport: this.transport });
|
|
167
234
|
this.pnHandles.set(crdtId, handle);
|
|
168
235
|
this.transport.subscribe(crdtId);
|
|
236
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
169
237
|
}
|
|
170
238
|
return handle;
|
|
171
239
|
}
|
|
@@ -195,6 +263,7 @@ export class MeridianClient {
|
|
|
195
263
|
handle = schema ? new ORSetHandle<T>({ ...base, schema }) : new ORSetHandle<T>(base);
|
|
196
264
|
this.orHandles.set(crdtId, handle as ORSetHandle<unknown>);
|
|
197
265
|
this.transport.subscribe(crdtId);
|
|
266
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
198
267
|
}
|
|
199
268
|
return handle;
|
|
200
269
|
}
|
|
@@ -224,6 +293,7 @@ export class MeridianClient {
|
|
|
224
293
|
handle = schema ? new LwwRegisterHandle<T>({ ...base, schema }) : new LwwRegisterHandle<T>(base);
|
|
225
294
|
this.lwHandles.set(crdtId, handle as LwwRegisterHandle<unknown>);
|
|
226
295
|
this.transport.subscribe(crdtId);
|
|
296
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
227
297
|
}
|
|
228
298
|
return handle;
|
|
229
299
|
}
|
|
@@ -254,6 +324,7 @@ export class MeridianClient {
|
|
|
254
324
|
handle = schema ? new PresenceHandle<T>({ ...base, schema }) : new PresenceHandle<T>(base);
|
|
255
325
|
this.prHandles.set(crdtId, handle as PresenceHandle<unknown>);
|
|
256
326
|
this.transport.subscribe(crdtId);
|
|
327
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
257
328
|
}
|
|
258
329
|
return handle;
|
|
259
330
|
}
|
|
@@ -280,6 +351,7 @@ export class MeridianClient {
|
|
|
280
351
|
handle = new CRDTMapHandle({ ns: this.namespace, crdtId, clientId: this.clientId, transport: this.transport });
|
|
281
352
|
this.cmHandles.set(crdtId, handle);
|
|
282
353
|
this.transport.subscribe(crdtId);
|
|
354
|
+
this.handleUnsubs.push(handle.onChange(() => { this.notifyAnyChange(); }));
|
|
283
355
|
}
|
|
284
356
|
return handle;
|
|
285
357
|
}
|
|
@@ -288,6 +360,66 @@ export class MeridianClient {
|
|
|
288
360
|
return this.transport.waitForConnected(timeoutMs);
|
|
289
361
|
}
|
|
290
362
|
|
|
363
|
+
/** Number of ops buffered locally, waiting to be sent on reconnect. */
|
|
364
|
+
get pendingOpCount(): number {
|
|
365
|
+
return this.transport.pendingOpCount;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Subscribe to connection state changes. Returns an unsubscribe function.
|
|
370
|
+
* Useful for building "syncing" indicators in the UI.
|
|
371
|
+
*/
|
|
372
|
+
onStateChange(listener: (state: WsState) => void): () => void {
|
|
373
|
+
return this.transport.onStateChange(listener);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Subscribe to any state change (CRDT value or connection state).
|
|
378
|
+
* Fires whenever any handle emits or the WebSocket transitions.
|
|
379
|
+
* Used by `<MeridianDevtools>` to avoid multiple `onStateChange` subscriptions.
|
|
380
|
+
*/
|
|
381
|
+
onAnyChange(listener: () => void): () => void {
|
|
382
|
+
this.anyListeners.add(listener);
|
|
383
|
+
return () => { this.anyListeners.delete(listener); };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Subscribe to incoming CRDT deltas from the server.
|
|
388
|
+
* Fires after each delta is applied to the local handle.
|
|
389
|
+
* Intended for devtools — not for production data flows.
|
|
390
|
+
*/
|
|
391
|
+
onDelta(listener: (event: DeltaEvent) => void): () => void {
|
|
392
|
+
this.deltaListeners.add(listener);
|
|
393
|
+
return () => { this.deltaListeners.delete(listener); };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Returns a point-in-time snapshot of all active CRDT handles and transport state.
|
|
398
|
+
* Intended for devtools — not for production data flows.
|
|
399
|
+
*/
|
|
400
|
+
snapshot(): ClientSnapshot {
|
|
401
|
+
const crdts: CRDTSnapshotEntry[] = [];
|
|
402
|
+
for (const [crdtId, h] of this.gcHandles)
|
|
403
|
+
crdts.push({ type: "gcounter", crdtId, value: h.value(), counts: h.counts() });
|
|
404
|
+
for (const [crdtId, h] of this.pnHandles)
|
|
405
|
+
crdts.push({ type: "pncounter", crdtId, value: h.value() });
|
|
406
|
+
for (const [crdtId, h] of this.orHandles)
|
|
407
|
+
crdts.push({ type: "orset", crdtId, elements: h.elements() as unknown[] });
|
|
408
|
+
for (const [crdtId, h] of this.lwHandles)
|
|
409
|
+
crdts.push({ type: "lwwregister", crdtId, value: h.value(), meta: h.meta() });
|
|
410
|
+
for (const [crdtId, h] of this.prHandles)
|
|
411
|
+
crdts.push({ type: "presence", crdtId, online: h.online() as { clientId: number; data: unknown; expiresAtMs: number }[] });
|
|
412
|
+
for (const [crdtId, h] of this.cmHandles)
|
|
413
|
+
crdts.push({ type: "crdtmap", crdtId, value: h.value() });
|
|
414
|
+
return {
|
|
415
|
+
namespace: this.namespace,
|
|
416
|
+
clientId: this.clientId,
|
|
417
|
+
wsState: this.transport.currentState,
|
|
418
|
+
pendingOpCount: this.transport.pendingOpCount,
|
|
419
|
+
crdts,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
291
423
|
/**
|
|
292
424
|
* Closes the underlying WebSocket connection.
|
|
293
425
|
*
|
|
@@ -296,9 +428,22 @@ export class MeridianClient {
|
|
|
296
428
|
* on unmount.
|
|
297
429
|
*/
|
|
298
430
|
close(): void {
|
|
431
|
+
for (const unsub of this.handleUnsubs) unsub();
|
|
432
|
+
this.handleUnsubs.length = 0;
|
|
433
|
+
this.anyListeners.clear();
|
|
434
|
+
this.deltaListeners.clear();
|
|
299
435
|
this.transport.close();
|
|
300
436
|
}
|
|
301
437
|
|
|
438
|
+
/** Re-opens the WebSocket after a `close()`. Used by MeridianProvider to survive React StrictMode double-mount. */
|
|
439
|
+
reopen(): void {
|
|
440
|
+
this.transport.reopen();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private notifyAnyChange(): void {
|
|
444
|
+
for (const fn of this.anyListeners) fn();
|
|
445
|
+
}
|
|
446
|
+
|
|
302
447
|
private handleServerMsg(msg: ServerMsg): void {
|
|
303
448
|
if (!("Delta" in msg)) return;
|
|
304
449
|
const { crdt_id, delta_bytes } = msg.Delta;
|
|
@@ -306,31 +451,43 @@ export class MeridianClient {
|
|
|
306
451
|
const gcHandle = this.gcHandles.get(crdt_id);
|
|
307
452
|
if (gcHandle) {
|
|
308
453
|
try { gcHandle.applyDelta(decodeGCounterDelta(delta_bytes)); } catch { /* stale */ }
|
|
454
|
+
this.notifyDelta(crdt_id, "gcounter");
|
|
309
455
|
return;
|
|
310
456
|
}
|
|
311
457
|
const pnHandle = this.pnHandles.get(crdt_id);
|
|
312
458
|
if (pnHandle) {
|
|
313
459
|
try { pnHandle.applyDelta(decodePNCounterDelta(delta_bytes)); } catch { /* stale */ }
|
|
460
|
+
this.notifyDelta(crdt_id, "pncounter");
|
|
314
461
|
return;
|
|
315
462
|
}
|
|
316
463
|
const orHandle = this.orHandles.get(crdt_id);
|
|
317
464
|
if (orHandle) {
|
|
318
465
|
try { orHandle.applyDelta(decodeORSetDelta(delta_bytes)); } catch { /* stale */ }
|
|
466
|
+
this.notifyDelta(crdt_id, "orset");
|
|
319
467
|
return;
|
|
320
468
|
}
|
|
321
469
|
const lwHandle = this.lwHandles.get(crdt_id);
|
|
322
470
|
if (lwHandle) {
|
|
323
471
|
try { lwHandle.applyDelta(decodeLwwDelta(delta_bytes)); } catch { /* stale */ }
|
|
472
|
+
this.notifyDelta(crdt_id, "lwwregister");
|
|
324
473
|
return;
|
|
325
474
|
}
|
|
326
475
|
const prHandle = this.prHandles.get(crdt_id);
|
|
327
476
|
if (prHandle) {
|
|
328
477
|
try { prHandle.applyDelta(decodePresenceDelta(delta_bytes)); } catch { /* stale */ }
|
|
478
|
+
this.notifyDelta(crdt_id, "presence");
|
|
329
479
|
return;
|
|
330
480
|
}
|
|
331
481
|
const cmHandle = this.cmHandles.get(crdt_id);
|
|
332
482
|
if (cmHandle) {
|
|
333
483
|
try { cmHandle.applyDelta(decodeCRDTMapDelta(delta_bytes)); } catch { /* stale */ }
|
|
484
|
+
this.notifyDelta(crdt_id, "crdtmap");
|
|
334
485
|
}
|
|
335
486
|
}
|
|
487
|
+
|
|
488
|
+
private notifyDelta(crdtId: string, type: CRDTSnapshotEntry["type"]): void {
|
|
489
|
+
if (this.deltaListeners.size === 0) return;
|
|
490
|
+
const event: DeltaEvent = { crdtId, type, at: Date.now() };
|
|
491
|
+
for (const fn of this.deltaListeners) fn(event);
|
|
492
|
+
}
|
|
336
493
|
}
|
package/src/constants.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
// Public SDK surface
|
|
2
2
|
|
|
3
3
|
export { MeridianClient } from "./client.js";
|
|
4
|
-
export type {
|
|
4
|
+
export type {
|
|
5
|
+
MeridianClientConfig,
|
|
6
|
+
ClientSnapshot,
|
|
7
|
+
DeltaEvent,
|
|
8
|
+
CRDTSnapshotEntry,
|
|
9
|
+
GCounterSnapshotEntry,
|
|
10
|
+
PNCounterSnapshotEntry,
|
|
11
|
+
ORSetSnapshotEntry,
|
|
12
|
+
LwwRegisterSnapshotEntry,
|
|
13
|
+
PresenceSnapshotEntry,
|
|
14
|
+
CRDTMapSnapshotEntry,
|
|
15
|
+
} from "./client.js";
|
|
5
16
|
|
|
6
17
|
// CRDT handles
|
|
7
18
|
export { GCounterHandle } from "./crdt/gcounter.js";
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
BACKOFF_MULTIPLIER,
|
|
8
8
|
JITTER_MULTIPLIER,
|
|
9
9
|
DEFAULT_TIMEOUT_MS,
|
|
10
|
+
OFFLINE_QUEUE_MAX,
|
|
10
11
|
} from "../constants.js";
|
|
11
12
|
|
|
12
13
|
export type WsState =
|
|
@@ -34,6 +35,7 @@ export class WsTransport {
|
|
|
34
35
|
private closed = false;
|
|
35
36
|
|
|
36
37
|
private readonly subscriptions = new Map<string, VectorClock>();
|
|
38
|
+
private readonly pendingOps: Parameters<typeof encodeClientMsg>[0][] = [];
|
|
37
39
|
|
|
38
40
|
constructor(config: WsTransportConfig) {
|
|
39
41
|
this.config = config;
|
|
@@ -48,11 +50,17 @@ export class WsTransport {
|
|
|
48
50
|
|
|
49
51
|
close(): void {
|
|
50
52
|
this.closed = true;
|
|
53
|
+
this.pendingOps.length = 0;
|
|
51
54
|
this.clearReconnectTimer();
|
|
52
55
|
this.transitionTo("CLOSING");
|
|
53
56
|
this.ws?.close(1000, "client close");
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
reopen(): void {
|
|
60
|
+
this.closed = false;
|
|
61
|
+
this.doConnect();
|
|
62
|
+
}
|
|
63
|
+
|
|
56
64
|
subscribe(crdtId: string, sinceVc: VectorClock = {}): void {
|
|
57
65
|
this.subscriptions.set(crdtId, sinceVc);
|
|
58
66
|
if (this.state === "CONNECTED") {
|
|
@@ -66,11 +74,38 @@ export class WsTransport {
|
|
|
66
74
|
|
|
67
75
|
send(msg: Parameters<typeof encodeClientMsg>[0]): void {
|
|
68
76
|
if (this.state !== "CONNECTED" || this.ws === null) {
|
|
69
|
-
|
|
77
|
+
if (this.pendingOps.length >= OFFLINE_QUEUE_MAX) {
|
|
78
|
+
this.pendingOps.shift(); // drop oldest to make room
|
|
79
|
+
}
|
|
80
|
+
this.pendingOps.push(msg);
|
|
81
|
+
return;
|
|
70
82
|
}
|
|
71
83
|
this.ws.send(encodeClientMsg(msg));
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
get pendingOpCount(): number {
|
|
87
|
+
return this.pendingOps.length;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Subscribe to connection state changes. Returns an unsubscribe function.
|
|
92
|
+
* Compatible with React's `useSyncExternalStore` subscribe parameter.
|
|
93
|
+
*/
|
|
94
|
+
onStateChange(listener: (state: WsState) => void): () => void {
|
|
95
|
+
const prev = this.config.onStateChange;
|
|
96
|
+
this.config.onStateChange = (s) => {
|
|
97
|
+
prev?.(s);
|
|
98
|
+
listener(s);
|
|
99
|
+
};
|
|
100
|
+
return () => {
|
|
101
|
+
if (prev !== undefined) {
|
|
102
|
+
this.config.onStateChange = prev;
|
|
103
|
+
} else {
|
|
104
|
+
delete (this.config as Partial<WsTransportConfig>).onStateChange;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
74
109
|
get currentState(): WsState {
|
|
75
110
|
return this.state;
|
|
76
111
|
}
|
|
@@ -168,6 +203,20 @@ export class WsTransport {
|
|
|
168
203
|
for (const [crdtId, vc] of this.subscriptions) {
|
|
169
204
|
this.sendSubscribe(crdtId, vc);
|
|
170
205
|
}
|
|
206
|
+
this.flushPendingOps();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private flushPendingOps(): void {
|
|
210
|
+
const ops = this.pendingOps.splice(0);
|
|
211
|
+
for (let i = 0; i < ops.length; i++) {
|
|
212
|
+
try {
|
|
213
|
+
this.ws!.send(encodeClientMsg(ops[i]!));
|
|
214
|
+
} catch {
|
|
215
|
+
// WebSocket closed between open and flush — re-queue remaining ops.
|
|
216
|
+
this.pendingOps.unshift(...ops.slice(i));
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
171
220
|
}
|
|
172
221
|
|
|
173
222
|
private sendSubscribe(crdtId: string, vc: VectorClock): void {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for WsTransport offline op queue.
|
|
3
|
+
*
|
|
4
|
+
* We stub the global WebSocket to control connection state without a real server.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
8
|
+
import { WsTransport } from "../src/transport/websocket.js";
|
|
9
|
+
import { OFFLINE_QUEUE_MAX } from "../src/constants.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Fake WebSocket
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
type WsEventName = "open" | "message" | "close" | "error";
|
|
16
|
+
|
|
17
|
+
class FakeWebSocket {
|
|
18
|
+
binaryType: string = "arraybuffer";
|
|
19
|
+
readonly sent: Uint8Array[] = [];
|
|
20
|
+
private readonly listeners = new Map<WsEventName, (() => void)[]>();
|
|
21
|
+
closeCode: number | undefined;
|
|
22
|
+
closeReason: string | undefined;
|
|
23
|
+
|
|
24
|
+
addEventListener(event: WsEventName, handler: () => void): void {
|
|
25
|
+
const list = this.listeners.get(event) ?? [];
|
|
26
|
+
list.push(handler);
|
|
27
|
+
this.listeners.set(event, list);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
send(data: Uint8Array): void {
|
|
31
|
+
this.sent.push(data);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
close(code: number, reason: string): void {
|
|
35
|
+
this.closeCode = code;
|
|
36
|
+
this.closeReason = reason;
|
|
37
|
+
this.emit("close");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
emit(event: WsEventName): void {
|
|
41
|
+
for (const handler of this.listeners.get(event) ?? []) {
|
|
42
|
+
handler();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let fakeWs: FakeWebSocket;
|
|
48
|
+
|
|
49
|
+
// Patch globalThis.WebSocket before each test.
|
|
50
|
+
beforeEach(() => {
|
|
51
|
+
fakeWs = new FakeWebSocket();
|
|
52
|
+
(globalThis as unknown as Record<string, unknown>).WebSocket = function () {
|
|
53
|
+
return fakeWs;
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
delete (globalThis as unknown as Record<string, unknown>).WebSocket;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
function makeTransport(onStateChange?: (s: string) => void): WsTransport {
|
|
62
|
+
return new WsTransport({
|
|
63
|
+
url: "ws://localhost:3000",
|
|
64
|
+
token: "tok",
|
|
65
|
+
onMessage: () => {},
|
|
66
|
+
onStateChange,
|
|
67
|
+
// Use tiny backoff so reconnect tests don't wait
|
|
68
|
+
maxBackoffMs: 10,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Tests
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
describe("WsTransport offline queue", () => {
|
|
77
|
+
it("buffers ops sent while DISCONNECTED", () => {
|
|
78
|
+
const t = makeTransport();
|
|
79
|
+
t.connect();
|
|
80
|
+
// Not yet open — state is CONNECTING
|
|
81
|
+
expect(t.currentState).toBe("CONNECTING");
|
|
82
|
+
|
|
83
|
+
t.send({ Op: { crdt_id: "gc:views", op_bytes: new Uint8Array([1]) } });
|
|
84
|
+
t.send({ Op: { crdt_id: "gc:views", op_bytes: new Uint8Array([2]) } });
|
|
85
|
+
|
|
86
|
+
expect(t.pendingOpCount).toBe(2);
|
|
87
|
+
expect(fakeWs.sent).toHaveLength(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("flushes buffered ops in order on reconnect (open event)", () => {
|
|
91
|
+
const t = makeTransport();
|
|
92
|
+
t.connect();
|
|
93
|
+
|
|
94
|
+
t.send({ Op: { crdt_id: "gc:views", op_bytes: new Uint8Array([1]) } });
|
|
95
|
+
t.send({ Op: { crdt_id: "gc:views", op_bytes: new Uint8Array([2]) } });
|
|
96
|
+
expect(t.pendingOpCount).toBe(2);
|
|
97
|
+
|
|
98
|
+
// Simulate server accepting the connection
|
|
99
|
+
fakeWs.emit("open");
|
|
100
|
+
|
|
101
|
+
expect(t.currentState).toBe("CONNECTED");
|
|
102
|
+
expect(t.pendingOpCount).toBe(0);
|
|
103
|
+
// fakeWs.sent contains: Subscribe msgs from resubscribeAll + 2 flushed ops
|
|
104
|
+
// We just verify the queue was drained and sends happened
|
|
105
|
+
expect(fakeWs.sent.length).toBeGreaterThanOrEqual(2);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("sends immediately when already CONNECTED", () => {
|
|
109
|
+
const t = makeTransport();
|
|
110
|
+
t.connect();
|
|
111
|
+
fakeWs.emit("open");
|
|
112
|
+
expect(t.currentState).toBe("CONNECTED");
|
|
113
|
+
|
|
114
|
+
const before = fakeWs.sent.length;
|
|
115
|
+
t.send({ Op: { crdt_id: "gc:views", op_bytes: new Uint8Array([42]) } });
|
|
116
|
+
|
|
117
|
+
expect(t.pendingOpCount).toBe(0);
|
|
118
|
+
expect(fakeWs.sent.length).toBe(before + 1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("drops the oldest op when queue exceeds OFFLINE_QUEUE_MAX", () => {
|
|
122
|
+
const t = makeTransport();
|
|
123
|
+
t.connect();
|
|
124
|
+
// Fill the queue beyond the limit
|
|
125
|
+
for (let i = 0; i <= OFFLINE_QUEUE_MAX; i++) {
|
|
126
|
+
t.send({ Op: { crdt_id: "gc:x", op_bytes: new Uint8Array([i % 256]) } });
|
|
127
|
+
}
|
|
128
|
+
// Queue should be capped at OFFLINE_QUEUE_MAX
|
|
129
|
+
expect(t.pendingOpCount).toBe(OFFLINE_QUEUE_MAX);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("clears the queue on close()", () => {
|
|
133
|
+
const t = makeTransport();
|
|
134
|
+
t.connect();
|
|
135
|
+
|
|
136
|
+
t.send({ Op: { crdt_id: "gc:views", op_bytes: new Uint8Array([1]) } });
|
|
137
|
+
t.send({ Op: { crdt_id: "gc:views", op_bytes: new Uint8Array([2]) } });
|
|
138
|
+
expect(t.pendingOpCount).toBe(2);
|
|
139
|
+
|
|
140
|
+
t.close();
|
|
141
|
+
|
|
142
|
+
expect(t.pendingOpCount).toBe(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("does not send buffered ops after close()", () => {
|
|
146
|
+
const t = makeTransport();
|
|
147
|
+
t.connect();
|
|
148
|
+
|
|
149
|
+
t.send({ Op: { crdt_id: "gc:views", op_bytes: new Uint8Array([1]) } });
|
|
150
|
+
t.close();
|
|
151
|
+
|
|
152
|
+
// Even if open fires somehow, ops should not be sent
|
|
153
|
+
fakeWs.emit("open");
|
|
154
|
+
expect(fakeWs.sent).toHaveLength(0);
|
|
155
|
+
});
|
|
156
|
+
});
|