meridian-sdk 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +19 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +36 -1
- package/dist/client.js.map +1 -1
- package/dist/crdt/crdtmap.d.ts +53 -0
- package/dist/crdt/crdtmap.d.ts.map +1 -0
- package/dist/crdt/crdtmap.js +216 -0
- package/dist/crdt/crdtmap.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/sync/delta.d.ts +15 -0
- package/dist/sync/delta.d.ts.map +1 -1
- package/dist/sync/delta.js +4 -0
- package/dist/sync/delta.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +34 -0
- package/src/crdt/crdtmap.ts +231 -0
- package/src/index.ts +2 -0
- package/src/sync/delta.ts +16 -0
package/dist/client.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { PNCounterHandle } from "./crdt/pncounter.js";
|
|
|
5
5
|
import { ORSetHandle } from "./crdt/orset.js";
|
|
6
6
|
import { LwwRegisterHandle } from "./crdt/lwwregister.js";
|
|
7
7
|
import { PresenceHandle } from "./crdt/presence.js";
|
|
8
|
+
import { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
8
9
|
import type { TokenClaims } from "./schema.js";
|
|
9
10
|
import type { TokenParseError, TokenExpiredError } from "./errors.js";
|
|
10
11
|
export interface MeridianClientConfig {
|
|
@@ -50,6 +51,7 @@ export declare class MeridianClient {
|
|
|
50
51
|
private readonly orHandles;
|
|
51
52
|
private readonly lwHandles;
|
|
52
53
|
private readonly prHandles;
|
|
54
|
+
private readonly cmHandles;
|
|
53
55
|
private constructor();
|
|
54
56
|
/**
|
|
55
57
|
* Creates and validates a new `MeridianClient` from the supplied configuration.
|
|
@@ -164,6 +166,23 @@ export declare class MeridianClient {
|
|
|
164
166
|
* ```
|
|
165
167
|
*/
|
|
166
168
|
presence<T>(crdtId: string, schema?: Schema.Schema<T>): PresenceHandle<T>;
|
|
169
|
+
/**
|
|
170
|
+
* Returns a handle for a CRDTMap — a map of named CRDT values.
|
|
171
|
+
*
|
|
172
|
+
* Handles are cached by `crdtId`; calling this method multiple times with the
|
|
173
|
+
* same id returns the same handle instance and creates only one subscription.
|
|
174
|
+
*
|
|
175
|
+
* @param crdtId - Unique identifier for the CRDT within this namespace.
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* const doc = client.crdtmap('document-1');
|
|
180
|
+
* doc.lwwSet('title', 'Hello World');
|
|
181
|
+
* doc.incrementCounter('views');
|
|
182
|
+
* console.log(doc.value()); // { title: { value: 'Hello World', ... }, views: { total: 1, ... } }
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
crdtmap(crdtId: string): CRDTMapHandle;
|
|
167
186
|
waitForConnected(timeoutMs?: number): Promise<void>;
|
|
168
187
|
/**
|
|
169
188
|
* Closes the underlying WebSocket connection.
|
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;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,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;
|
|
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;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,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,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAUlD,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,cAAc;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAE7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAG1B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAC/D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsC;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2C;IACrE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiD;IAC3E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8C;IACxE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAE9D,OAAO;IAsBP;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,MAAM,CACX,MAAM,EAAE,oBAAoB,GAC3B,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAMrE;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc;IAUxC;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe;IAU1C;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC;IAWnE;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC;IAW/E;;;;;;;;;;;;;;;;;;OAkBG;IACH,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC;IAWzE;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa;IAUtC,gBAAgB,CAAC,SAAS,SAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,eAAe;CAkCxB"}
|
package/dist/client.js
CHANGED
|
@@ -6,7 +6,8 @@ import { PNCounterHandle } from "./crdt/pncounter.js";
|
|
|
6
6
|
import { ORSetHandle } from "./crdt/orset.js";
|
|
7
7
|
import { LwwRegisterHandle } from "./crdt/lwwregister.js";
|
|
8
8
|
import { PresenceHandle } from "./crdt/presence.js";
|
|
9
|
-
import {
|
|
9
|
+
import { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
10
|
+
import { decodeGCounterDelta, decodePNCounterDelta, decodeORSetDelta, decodeLwwDelta, decodePresenceDelta, decodeCRDTMapDelta, } from "./sync/delta.js";
|
|
10
11
|
import { parseAndValidateToken } from "./auth/token.js";
|
|
11
12
|
/**
|
|
12
13
|
* The main entry point for the Meridian real-time CRDT SDK.
|
|
@@ -46,6 +47,7 @@ export class MeridianClient {
|
|
|
46
47
|
orHandles = new Map();
|
|
47
48
|
lwHandles = new Map();
|
|
48
49
|
prHandles = new Map();
|
|
50
|
+
cmHandles = new Map();
|
|
49
51
|
constructor(config, claims) {
|
|
50
52
|
this.namespace = config.namespace;
|
|
51
53
|
this.claims = claims;
|
|
@@ -222,6 +224,31 @@ export class MeridianClient {
|
|
|
222
224
|
}
|
|
223
225
|
return handle;
|
|
224
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Returns a handle for a CRDTMap — a map of named CRDT values.
|
|
229
|
+
*
|
|
230
|
+
* Handles are cached by `crdtId`; calling this method multiple times with the
|
|
231
|
+
* same id returns the same handle instance and creates only one subscription.
|
|
232
|
+
*
|
|
233
|
+
* @param crdtId - Unique identifier for the CRDT within this namespace.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```ts
|
|
237
|
+
* const doc = client.crdtmap('document-1');
|
|
238
|
+
* doc.lwwSet('title', 'Hello World');
|
|
239
|
+
* doc.incrementCounter('views');
|
|
240
|
+
* console.log(doc.value()); // { title: { value: 'Hello World', ... }, views: { total: 1, ... } }
|
|
241
|
+
* ```
|
|
242
|
+
*/
|
|
243
|
+
crdtmap(crdtId) {
|
|
244
|
+
let handle = this.cmHandles.get(crdtId);
|
|
245
|
+
if (!handle) {
|
|
246
|
+
handle = new CRDTMapHandle({ ns: this.namespace, crdtId, clientId: this.clientId, transport: this.transport });
|
|
247
|
+
this.cmHandles.set(crdtId, handle);
|
|
248
|
+
this.transport.subscribe(crdtId);
|
|
249
|
+
}
|
|
250
|
+
return handle;
|
|
251
|
+
}
|
|
225
252
|
waitForConnected(timeoutMs = 5_000) {
|
|
226
253
|
return this.transport.waitForConnected(timeoutMs);
|
|
227
254
|
}
|
|
@@ -277,6 +304,14 @@ export class MeridianClient {
|
|
|
277
304
|
prHandle.applyDelta(decodePresenceDelta(delta_bytes));
|
|
278
305
|
}
|
|
279
306
|
catch { /* stale */ }
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const cmHandle = this.cmHandles.get(crdt_id);
|
|
310
|
+
if (cmHandle) {
|
|
311
|
+
try {
|
|
312
|
+
cmHandle.applyDelta(decodeCRDTMapDelta(delta_bytes));
|
|
313
|
+
}
|
|
314
|
+
catch { /* stale */ }
|
|
280
315
|
}
|
|
281
316
|
}
|
|
282
317
|
}
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,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,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { WsTransport } from "../transport/websocket.js";
|
|
2
|
+
import type { CRDTMapDelta } from "../sync/delta.js";
|
|
3
|
+
export interface CrdtMapValue {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Low-level handle for a CRDTMap — a map of named CRDT values.
|
|
8
|
+
*
|
|
9
|
+
* Each key holds an independent CRDT (GCounter, PNCounter, ORSet, LwwRegister,
|
|
10
|
+
* or Presence). The type of each key is fixed at first write.
|
|
11
|
+
*
|
|
12
|
+
* Obtained via `MeridianClient.crdtmap()`. Prefer the `useCRDTMap` React hook
|
|
13
|
+
* for component-level usage; use this handle directly in non-React environments.
|
|
14
|
+
*/
|
|
15
|
+
export declare class CRDTMapHandle {
|
|
16
|
+
private state;
|
|
17
|
+
private readonly crdtId;
|
|
18
|
+
private readonly clientId;
|
|
19
|
+
private readonly transport;
|
|
20
|
+
private readonly listeners;
|
|
21
|
+
constructor(opts: {
|
|
22
|
+
ns: string;
|
|
23
|
+
crdtId: string;
|
|
24
|
+
clientId: number;
|
|
25
|
+
transport: WsTransport;
|
|
26
|
+
initial?: CrdtMapValue;
|
|
27
|
+
});
|
|
28
|
+
/** Returns a snapshot of the current map value (key → CRDT observable value). */
|
|
29
|
+
value(): Readonly<CrdtMapValue>;
|
|
30
|
+
/** Returns the value at a specific key, or `undefined` if absent. */
|
|
31
|
+
get(key: string): unknown;
|
|
32
|
+
/**
|
|
33
|
+
* Registers a listener that is called whenever any key in the map changes.
|
|
34
|
+
*
|
|
35
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
36
|
+
*/
|
|
37
|
+
onChange(listener: (value: CrdtMapValue) => void): () => void;
|
|
38
|
+
/** Increment a GCounter key by `amount` (default `1`). */
|
|
39
|
+
incrementCounter(key: string, amount?: number): void;
|
|
40
|
+
/** Increment a PNCounter key by `amount` (default `1`). */
|
|
41
|
+
incrementPNCounter(key: string, amount?: number): void;
|
|
42
|
+
/** Decrement a PNCounter key by `amount` (default `1`). */
|
|
43
|
+
decrementPNCounter(key: string, amount?: number): void;
|
|
44
|
+
/** Add an element to an ORSet key. `tag` must be a 16-byte UUID as Uint8Array. */
|
|
45
|
+
orsetAdd(key: string, element: unknown, tag: Uint8Array): void;
|
|
46
|
+
/** Remove an element from an ORSet key. `knownTags` is the set of tags to remove. */
|
|
47
|
+
orsetRemove(key: string, element: unknown, knownTags: Uint8Array[]): void;
|
|
48
|
+
/** Write a value to an LWW-Register key. */
|
|
49
|
+
lwwSet(key: string, value: unknown): void;
|
|
50
|
+
applyDelta(delta: CRDTMapDelta): void;
|
|
51
|
+
private emit;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=crdtmap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crdtmap.d.ts","sourceRoot":"","sources":["../../src/crdt/crdtmap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,kBAAkB,CAAC;AAErE,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IAExC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4C;gBAE1D,IAAI,EAAE;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,WAAW,CAAC;QACvB,OAAO,CAAC,EAAE,YAAY,CAAC;KACxB;IAOD,iFAAiF;IACjF,KAAK,IAAI,QAAQ,CAAC,YAAY,CAAC;IAI/B,qEAAqE;IACrE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI;IAK7D,0DAA0D;IAC1D,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,GAAG,IAAI;IAYvD,2DAA2D;IAC3D,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,GAAG,IAAI;IAYzD,2DAA2D;IAC3D,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,GAAG,IAAI;IAYzD,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;IAW9D,qFAAqF;IACrF,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI;IAWzE,4CAA4C;IAC5C,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAkBzC,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAYrC,OAAO,CAAC,IAAI;CAGb"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { encode } from "../codec.js";
|
|
2
|
+
/**
|
|
3
|
+
* Low-level handle for a CRDTMap — a map of named CRDT values.
|
|
4
|
+
*
|
|
5
|
+
* Each key holds an independent CRDT (GCounter, PNCounter, ORSet, LwwRegister,
|
|
6
|
+
* or Presence). The type of each key is fixed at first write.
|
|
7
|
+
*
|
|
8
|
+
* Obtained via `MeridianClient.crdtmap()`. Prefer the `useCRDTMap` React hook
|
|
9
|
+
* for component-level usage; use this handle directly in non-React environments.
|
|
10
|
+
*/
|
|
11
|
+
export class CRDTMapHandle {
|
|
12
|
+
state = {};
|
|
13
|
+
crdtId;
|
|
14
|
+
clientId;
|
|
15
|
+
transport;
|
|
16
|
+
listeners = new Set();
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
this.crdtId = opts.crdtId;
|
|
19
|
+
this.clientId = opts.clientId;
|
|
20
|
+
this.transport = opts.transport;
|
|
21
|
+
this.state = opts.initial ?? {};
|
|
22
|
+
}
|
|
23
|
+
/** Returns a snapshot of the current map value (key → CRDT observable value). */
|
|
24
|
+
value() {
|
|
25
|
+
return this.state;
|
|
26
|
+
}
|
|
27
|
+
/** Returns the value at a specific key, or `undefined` if absent. */
|
|
28
|
+
get(key) {
|
|
29
|
+
return this.state[key];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Registers a listener that is called whenever any key in the map changes.
|
|
33
|
+
*
|
|
34
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
35
|
+
*/
|
|
36
|
+
onChange(listener) {
|
|
37
|
+
this.listeners.add(listener);
|
|
38
|
+
return () => { this.listeners.delete(listener); };
|
|
39
|
+
}
|
|
40
|
+
/** Increment a GCounter key by `amount` (default `1`). */
|
|
41
|
+
incrementCounter(key, amount = 1) {
|
|
42
|
+
if (amount <= 0)
|
|
43
|
+
throw new RangeError("CRDTMap.incrementCounter: amount must be > 0");
|
|
44
|
+
const op = encode({
|
|
45
|
+
CRDTMap: {
|
|
46
|
+
key,
|
|
47
|
+
crdt_type: "GCounter",
|
|
48
|
+
op: { GCounter: { client_id: this.clientId, amount } },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
52
|
+
}
|
|
53
|
+
/** Increment a PNCounter key by `amount` (default `1`). */
|
|
54
|
+
incrementPNCounter(key, amount = 1) {
|
|
55
|
+
if (amount <= 0)
|
|
56
|
+
throw new RangeError("CRDTMap.incrementPNCounter: amount must be > 0");
|
|
57
|
+
const op = encode({
|
|
58
|
+
CRDTMap: {
|
|
59
|
+
key,
|
|
60
|
+
crdt_type: "PNCounter",
|
|
61
|
+
op: { PNCounter: { Increment: { client_id: this.clientId, amount } } },
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
65
|
+
}
|
|
66
|
+
/** Decrement a PNCounter key by `amount` (default `1`). */
|
|
67
|
+
decrementPNCounter(key, amount = 1) {
|
|
68
|
+
if (amount <= 0)
|
|
69
|
+
throw new RangeError("CRDTMap.decrementPNCounter: amount must be > 0");
|
|
70
|
+
const op = encode({
|
|
71
|
+
CRDTMap: {
|
|
72
|
+
key,
|
|
73
|
+
crdt_type: "PNCounter",
|
|
74
|
+
op: { PNCounter: { Decrement: { client_id: this.clientId, amount } } },
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
78
|
+
}
|
|
79
|
+
/** Add an element to an ORSet key. `tag` must be a 16-byte UUID as Uint8Array. */
|
|
80
|
+
orsetAdd(key, element, tag) {
|
|
81
|
+
const op = encode({
|
|
82
|
+
CRDTMap: {
|
|
83
|
+
key,
|
|
84
|
+
crdt_type: "ORSet",
|
|
85
|
+
op: { ORSet: { Add: { element, tag } } },
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
89
|
+
}
|
|
90
|
+
/** Remove an element from an ORSet key. `knownTags` is the set of tags to remove. */
|
|
91
|
+
orsetRemove(key, element, knownTags) {
|
|
92
|
+
const op = encode({
|
|
93
|
+
CRDTMap: {
|
|
94
|
+
key,
|
|
95
|
+
crdt_type: "ORSet",
|
|
96
|
+
op: { ORSet: { Remove: { element, known_tags: knownTags } } },
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
100
|
+
}
|
|
101
|
+
/** Write a value to an LWW-Register key. */
|
|
102
|
+
lwwSet(key, value) {
|
|
103
|
+
const wallMs = Date.now();
|
|
104
|
+
const op = encode({
|
|
105
|
+
CRDTMap: {
|
|
106
|
+
key,
|
|
107
|
+
crdt_type: "LwwRegister",
|
|
108
|
+
op: {
|
|
109
|
+
LwwRegister: {
|
|
110
|
+
value,
|
|
111
|
+
hlc: { wall_ms: wallMs, logical: 0, node_id: this.clientId },
|
|
112
|
+
author: this.clientId,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
118
|
+
}
|
|
119
|
+
applyDelta(delta) {
|
|
120
|
+
let changed = false;
|
|
121
|
+
for (const [key, valueDelta] of Object.entries(delta.deltas)) {
|
|
122
|
+
const updated = applyValueDelta(this.state[key], valueDelta);
|
|
123
|
+
if (updated !== undefined) {
|
|
124
|
+
this.state[key] = updated;
|
|
125
|
+
changed = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (changed)
|
|
129
|
+
this.emit();
|
|
130
|
+
}
|
|
131
|
+
emit() {
|
|
132
|
+
for (const listener of this.listeners)
|
|
133
|
+
listener(this.state);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const applyValueDelta = (current, delta) => {
|
|
137
|
+
if ("GCounter" in delta) {
|
|
138
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
139
|
+
const currentCounts = current?.counts ?? {};
|
|
140
|
+
const merged = { ...currentCounts };
|
|
141
|
+
for (const [clientId, count] of Object.entries(delta.GCounter.counters)) {
|
|
142
|
+
if ((merged[clientId] ?? 0) < count)
|
|
143
|
+
merged[clientId] = count;
|
|
144
|
+
}
|
|
145
|
+
const total = Object.values(merged).reduce((sum, count) => sum + count, 0);
|
|
146
|
+
return { total, counts: merged };
|
|
147
|
+
}
|
|
148
|
+
if ("PNCounter" in delta) {
|
|
149
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
150
|
+
const currentPn = current;
|
|
151
|
+
const pos = mergeCounterMap(currentPn?.pos ?? {}, delta.PNCounter.pos?.counters ?? {});
|
|
152
|
+
const neg = mergeCounterMap(currentPn?.neg ?? {}, delta.PNCounter.neg?.counters ?? {});
|
|
153
|
+
const value = Object.values(pos).reduce((sum, count) => sum + count, 0)
|
|
154
|
+
- Object.values(neg).reduce((sum, count) => sum + count, 0);
|
|
155
|
+
return { value, pos, neg };
|
|
156
|
+
}
|
|
157
|
+
if ("ORSet" in delta) {
|
|
158
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
159
|
+
const currentOrset = current;
|
|
160
|
+
const existing = new Set((currentOrset?.elements ?? []).map((element) => JSON.stringify(element)));
|
|
161
|
+
for (const key of Object.keys(delta.ORSet.adds)) {
|
|
162
|
+
existing.add(key);
|
|
163
|
+
}
|
|
164
|
+
for (const key of Object.keys(delta.ORSet.removes)) {
|
|
165
|
+
existing.delete(key);
|
|
166
|
+
}
|
|
167
|
+
return { elements: Array.from(existing).map((key) => { try {
|
|
168
|
+
return JSON.parse(key);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return key;
|
|
172
|
+
} }) };
|
|
173
|
+
}
|
|
174
|
+
if ("LwwRegister" in delta) {
|
|
175
|
+
const entry = delta.LwwRegister.entry;
|
|
176
|
+
if (entry === null)
|
|
177
|
+
return current;
|
|
178
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
179
|
+
const currentLww = current;
|
|
180
|
+
if (currentLww?.hlc) {
|
|
181
|
+
const currentWallMs = Number(currentLww.hlc.wall_ms);
|
|
182
|
+
const newWallMs = Number(entry.hlc.wall_ms);
|
|
183
|
+
if (newWallMs < currentWallMs)
|
|
184
|
+
return current;
|
|
185
|
+
if (newWallMs === currentWallMs && entry.hlc.logical < (currentLww.hlc.logical ?? 0))
|
|
186
|
+
return current;
|
|
187
|
+
if (newWallMs === currentWallMs && entry.hlc.logical === (currentLww.hlc.logical ?? 0) && Number(entry.author) <= Number(currentLww.author ?? 0))
|
|
188
|
+
return current;
|
|
189
|
+
}
|
|
190
|
+
return { value: entry.value, updatedAtMs: Number(entry.hlc.wall_ms), author: Number(entry.author) };
|
|
191
|
+
}
|
|
192
|
+
if ("Presence" in delta) {
|
|
193
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
194
|
+
const currentPresence = current;
|
|
195
|
+
const entries = { ...(currentPresence?.entries ?? {}) };
|
|
196
|
+
for (const [clientId, entry] of Object.entries(delta.Presence.changes)) {
|
|
197
|
+
if (entry === null) {
|
|
198
|
+
delete entries[clientId];
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
entries[clientId] = { data: entry.data, expiresAtMs: Number(entry.hlc.wall_ms) + Number(entry.ttl_ms) };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { entries };
|
|
205
|
+
}
|
|
206
|
+
return current;
|
|
207
|
+
};
|
|
208
|
+
const mergeCounterMap = (source, incoming) => {
|
|
209
|
+
const result = { ...source };
|
|
210
|
+
for (const [clientId, count] of Object.entries(incoming)) {
|
|
211
|
+
if ((result[clientId] ?? 0) < count)
|
|
212
|
+
result[clientId] = count;
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=crdtmap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crdtmap.js","sourceRoot":"","sources":["../../src/crdt/crdtmap.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { ORSetHandle } from "./crdt/orset.js";
|
|
|
6
6
|
export { LwwRegisterHandle } from "./crdt/lwwregister.js";
|
|
7
7
|
export { PresenceHandle } from "./crdt/presence.js";
|
|
8
8
|
export type { PresenceEntry } from "./crdt/presence.js";
|
|
9
|
+
export { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
10
|
+
export type { CrdtMapValue } from "./crdt/crdtmap.js";
|
|
9
11
|
export { HttpClient } from "./transport/http.js";
|
|
10
12
|
export type { HttpClientConfig } from "./transport/http.js";
|
|
11
13
|
export { WsTransport } from "./transport/websocket.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,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxD,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;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxD,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
CHANGED
|
@@ -6,6 +6,7 @@ export { PNCounterHandle } from "./crdt/pncounter.js";
|
|
|
6
6
|
export { ORSetHandle } from "./crdt/orset.js";
|
|
7
7
|
export { LwwRegisterHandle } from "./crdt/lwwregister.js";
|
|
8
8
|
export { PresenceHandle } from "./crdt/presence.js";
|
|
9
|
+
export { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
9
10
|
// Transport
|
|
10
11
|
export { HttpClient } from "./transport/http.js";
|
|
11
12
|
export { WsTransport } from "./transport/websocket.js";
|
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;AAG7C,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;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,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"}
|
package/dist/sync/delta.d.ts
CHANGED
|
@@ -38,4 +38,19 @@ export interface PresenceDelta {
|
|
|
38
38
|
changes: Record<string, PresenceEntryDelta | null>;
|
|
39
39
|
}
|
|
40
40
|
export declare const decodePresenceDelta: (bytes: Uint8Array) => PresenceDelta;
|
|
41
|
+
export type CrdtValueDelta = {
|
|
42
|
+
GCounter: GCounterDelta;
|
|
43
|
+
} | {
|
|
44
|
+
PNCounter: PNCounterDelta;
|
|
45
|
+
} | {
|
|
46
|
+
ORSet: ORSetDelta;
|
|
47
|
+
} | {
|
|
48
|
+
LwwRegister: LwwDelta;
|
|
49
|
+
} | {
|
|
50
|
+
Presence: PresenceDelta;
|
|
51
|
+
};
|
|
52
|
+
export interface CRDTMapDelta {
|
|
53
|
+
deltas: Record<string, CrdtValueDelta>;
|
|
54
|
+
}
|
|
55
|
+
export declare const decodeCRDTMapDelta: (bytes: Uint8Array) => CRDTMapDelta;
|
|
41
56
|
//# sourceMappingURL=delta.d.ts.map
|
package/dist/sync/delta.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../../src/sync/delta.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,UAAU,KAAG,aAGvD,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;CAC3B;AAED,eAAO,MAAM,oBAAoB,GAAI,OAAO,UAAU,KAAG,cASxD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,gBAAgB,GAAI,OAAO,UAAU,KAAG,UAUpD,CAAC;AAEF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC7E,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,UAAU,KAAG,QAGlD,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC7E,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACpD;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,UAAU,KAAG,aAGvD,CAAC"}
|
|
1
|
+
{"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../../src/sync/delta.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,UAAU,KAAG,aAGvD,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1B,GAAG,EAAE,aAAa,GAAG,IAAI,CAAC;CAC3B;AAED,eAAO,MAAM,oBAAoB,GAAI,OAAO,UAAU,KAAG,cASxD,CAAC;AAEF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;CACvC;AAED,eAAO,MAAM,gBAAgB,GAAI,OAAO,UAAU,KAAG,UAUpD,CAAC;AAEF,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC7E,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,GAAI,OAAO,UAAU,KAAG,QAGlD,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IAC7E,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACpD;AAED,eAAO,MAAM,mBAAmB,GAAI,OAAO,UAAU,KAAG,aAGvD,CAAC;AAEF,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,GAC3B;IAAE,SAAS,EAAE,cAAc,CAAA;CAAE,GAC7B;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,GACrB;IAAE,WAAW,EAAE,QAAQ,CAAA;CAAE,GACzB;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACxC;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAAU,KAAG,YAGtD,CAAC"}
|
package/dist/sync/delta.js
CHANGED
|
@@ -24,4 +24,8 @@ export const decodePresenceDelta = (bytes) => {
|
|
|
24
24
|
const raw = decode(bytes);
|
|
25
25
|
return { changes: raw.changes ?? {} };
|
|
26
26
|
};
|
|
27
|
+
export const decodeCRDTMapDelta = (bytes) => {
|
|
28
|
+
const raw = decode(bytes);
|
|
29
|
+
return { deltas: (raw.deltas ?? {}) };
|
|
30
|
+
};
|
|
27
31
|
//# sourceMappingURL=delta.js.map
|
package/dist/sync/delta.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/sync/delta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAiB,EAAiB,EAAE;IACtE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAA0C,CAAC;IACnE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AAC1C,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAiB,EAAkB,EAAE;IACxE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAGvB,CAAC;IACF,OAAO;QACL,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI;QAC1D,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI;KAC3D,CAAC;AACJ,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAiB,EAAc,EAAE;IAChE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAGvB,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,KAAc,EAAc,EAAE,CAC7C,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAiB,CAAC,CAAC;IAC1E,MAAM,SAAS,GAAG,CAAC,GAA+B,EAAE,EAAE,CACpD,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;AACxE,CAAC,CAAC;AAYF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAiB,EAAY,EAAE;IAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAgC,CAAC;IACzD,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;AACtC,CAAC,CAAC;AAYF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAiB,EAAiB,EAAE;IACtE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAA4D,CAAC;IACrF,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;AACxC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/sync/delta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAiB,EAAiB,EAAE;IACtE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAA0C,CAAC;IACnE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;AAC1C,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAiB,EAAkB,EAAE;IACxE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAGvB,CAAC;IACF,OAAO;QACL,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI;QAC1D,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI;KAC3D,CAAC;AACJ,CAAC,CAAC;AAOF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAiB,EAAc,EAAE;IAChE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAGvB,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,KAAc,EAAc,EAAE,CAC7C,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAiB,CAAC,CAAC;IAC1E,MAAM,SAAS,GAAG,CAAC,GAA+B,EAAE,EAAE,CACpD,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;AACxE,CAAC,CAAC;AAYF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,KAAiB,EAAY,EAAE;IAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAgC,CAAC;IACzD,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;AACtC,CAAC,CAAC;AAYF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAiB,EAAiB,EAAE;IACtE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAA4D,CAAC;IACrF,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;AACxC,CAAC,CAAC;AAaF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAiB,EAAgB,EAAE;IACpE,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAyC,CAAC;IAClE,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAmC,EAAE,CAAC;AAC1E,CAAC,CAAC"}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -6,12 +6,14 @@ import { PNCounterHandle } from "./crdt/pncounter.js";
|
|
|
6
6
|
import { ORSetHandle } from "./crdt/orset.js";
|
|
7
7
|
import { LwwRegisterHandle } from "./crdt/lwwregister.js";
|
|
8
8
|
import { PresenceHandle } from "./crdt/presence.js";
|
|
9
|
+
import { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
9
10
|
import {
|
|
10
11
|
decodeGCounterDelta,
|
|
11
12
|
decodePNCounterDelta,
|
|
12
13
|
decodeORSetDelta,
|
|
13
14
|
decodeLwwDelta,
|
|
14
15
|
decodePresenceDelta,
|
|
16
|
+
decodeCRDTMapDelta,
|
|
15
17
|
} from "./sync/delta.js";
|
|
16
18
|
import { parseAndValidateToken } from "./auth/token.js";
|
|
17
19
|
import type { ServerMsg, TokenClaims } from "./schema.js";
|
|
@@ -64,6 +66,7 @@ export class MeridianClient {
|
|
|
64
66
|
private readonly orHandles = new Map<string, ORSetHandle<unknown>>();
|
|
65
67
|
private readonly lwHandles = new Map<string, LwwRegisterHandle<unknown>>();
|
|
66
68
|
private readonly prHandles = new Map<string, PresenceHandle<unknown>>();
|
|
69
|
+
private readonly cmHandles = new Map<string, CRDTMapHandle>();
|
|
67
70
|
|
|
68
71
|
private constructor(config: MeridianClientConfig, claims: TokenClaims) {
|
|
69
72
|
this.namespace = config.namespace;
|
|
@@ -255,6 +258,32 @@ export class MeridianClient {
|
|
|
255
258
|
return handle;
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Returns a handle for a CRDTMap — a map of named CRDT values.
|
|
263
|
+
*
|
|
264
|
+
* Handles are cached by `crdtId`; calling this method multiple times with the
|
|
265
|
+
* same id returns the same handle instance and creates only one subscription.
|
|
266
|
+
*
|
|
267
|
+
* @param crdtId - Unique identifier for the CRDT within this namespace.
|
|
268
|
+
*
|
|
269
|
+
* @example
|
|
270
|
+
* ```ts
|
|
271
|
+
* const doc = client.crdtmap('document-1');
|
|
272
|
+
* doc.lwwSet('title', 'Hello World');
|
|
273
|
+
* doc.incrementCounter('views');
|
|
274
|
+
* console.log(doc.value()); // { title: { value: 'Hello World', ... }, views: { total: 1, ... } }
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
crdtmap(crdtId: string): CRDTMapHandle {
|
|
278
|
+
let handle = this.cmHandles.get(crdtId);
|
|
279
|
+
if (!handle) {
|
|
280
|
+
handle = new CRDTMapHandle({ ns: this.namespace, crdtId, clientId: this.clientId, transport: this.transport });
|
|
281
|
+
this.cmHandles.set(crdtId, handle);
|
|
282
|
+
this.transport.subscribe(crdtId);
|
|
283
|
+
}
|
|
284
|
+
return handle;
|
|
285
|
+
}
|
|
286
|
+
|
|
258
287
|
waitForConnected(timeoutMs = 5_000): Promise<void> {
|
|
259
288
|
return this.transport.waitForConnected(timeoutMs);
|
|
260
289
|
}
|
|
@@ -297,6 +326,11 @@ export class MeridianClient {
|
|
|
297
326
|
const prHandle = this.prHandles.get(crdt_id);
|
|
298
327
|
if (prHandle) {
|
|
299
328
|
try { prHandle.applyDelta(decodePresenceDelta(delta_bytes)); } catch { /* stale */ }
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const cmHandle = this.cmHandles.get(crdt_id);
|
|
332
|
+
if (cmHandle) {
|
|
333
|
+
try { cmHandle.applyDelta(decodeCRDTMapDelta(delta_bytes)); } catch { /* stale */ }
|
|
300
334
|
}
|
|
301
335
|
}
|
|
302
336
|
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { encode } from "../codec.js";
|
|
2
|
+
import type { WsTransport } from "../transport/websocket.js";
|
|
3
|
+
import type { CRDTMapDelta, CrdtValueDelta } from "../sync/delta.js";
|
|
4
|
+
|
|
5
|
+
export interface CrdtMapValue {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Low-level handle for a CRDTMap — a map of named CRDT values.
|
|
11
|
+
*
|
|
12
|
+
* Each key holds an independent CRDT (GCounter, PNCounter, ORSet, LwwRegister,
|
|
13
|
+
* or Presence). The type of each key is fixed at first write.
|
|
14
|
+
*
|
|
15
|
+
* Obtained via `MeridianClient.crdtmap()`. Prefer the `useCRDTMap` React hook
|
|
16
|
+
* for component-level usage; use this handle directly in non-React environments.
|
|
17
|
+
*/
|
|
18
|
+
export class CRDTMapHandle {
|
|
19
|
+
private state: CrdtMapValue = {};
|
|
20
|
+
private readonly crdtId: string;
|
|
21
|
+
private readonly clientId: number;
|
|
22
|
+
private readonly transport: WsTransport;
|
|
23
|
+
|
|
24
|
+
private readonly listeners = new Set<(value: CrdtMapValue) => void>();
|
|
25
|
+
|
|
26
|
+
constructor(opts: {
|
|
27
|
+
ns: string;
|
|
28
|
+
crdtId: string;
|
|
29
|
+
clientId: number;
|
|
30
|
+
transport: WsTransport;
|
|
31
|
+
initial?: CrdtMapValue;
|
|
32
|
+
}) {
|
|
33
|
+
this.crdtId = opts.crdtId;
|
|
34
|
+
this.clientId = opts.clientId;
|
|
35
|
+
this.transport = opts.transport;
|
|
36
|
+
this.state = opts.initial ?? {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Returns a snapshot of the current map value (key → CRDT observable value). */
|
|
40
|
+
value(): Readonly<CrdtMapValue> {
|
|
41
|
+
return this.state;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Returns the value at a specific key, or `undefined` if absent. */
|
|
45
|
+
get(key: string): unknown {
|
|
46
|
+
return this.state[key];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Registers a listener that is called whenever any key in the map changes.
|
|
51
|
+
*
|
|
52
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
53
|
+
*/
|
|
54
|
+
onChange(listener: (value: CrdtMapValue) => void): () => void {
|
|
55
|
+
this.listeners.add(listener);
|
|
56
|
+
return () => { this.listeners.delete(listener); };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Increment a GCounter key by `amount` (default `1`). */
|
|
60
|
+
incrementCounter(key: string, amount: number = 1): void {
|
|
61
|
+
if (amount <= 0) throw new RangeError("CRDTMap.incrementCounter: amount must be > 0");
|
|
62
|
+
const op = encode({
|
|
63
|
+
CRDTMap: {
|
|
64
|
+
key,
|
|
65
|
+
crdt_type: "GCounter",
|
|
66
|
+
op: { GCounter: { client_id: this.clientId, amount } },
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Increment a PNCounter key by `amount` (default `1`). */
|
|
73
|
+
incrementPNCounter(key: string, amount: number = 1): void {
|
|
74
|
+
if (amount <= 0) throw new RangeError("CRDTMap.incrementPNCounter: amount must be > 0");
|
|
75
|
+
const op = encode({
|
|
76
|
+
CRDTMap: {
|
|
77
|
+
key,
|
|
78
|
+
crdt_type: "PNCounter",
|
|
79
|
+
op: { PNCounter: { Increment: { client_id: this.clientId, amount } } },
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Decrement a PNCounter key by `amount` (default `1`). */
|
|
86
|
+
decrementPNCounter(key: string, amount: number = 1): void {
|
|
87
|
+
if (amount <= 0) throw new RangeError("CRDTMap.decrementPNCounter: amount must be > 0");
|
|
88
|
+
const op = encode({
|
|
89
|
+
CRDTMap: {
|
|
90
|
+
key,
|
|
91
|
+
crdt_type: "PNCounter",
|
|
92
|
+
op: { PNCounter: { Decrement: { client_id: this.clientId, amount } } },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Add an element to an ORSet key. `tag` must be a 16-byte UUID as Uint8Array. */
|
|
99
|
+
orsetAdd(key: string, element: unknown, tag: Uint8Array): void {
|
|
100
|
+
const op = encode({
|
|
101
|
+
CRDTMap: {
|
|
102
|
+
key,
|
|
103
|
+
crdt_type: "ORSet",
|
|
104
|
+
op: { ORSet: { Add: { element, tag } } },
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Remove an element from an ORSet key. `knownTags` is the set of tags to remove. */
|
|
111
|
+
orsetRemove(key: string, element: unknown, knownTags: Uint8Array[]): void {
|
|
112
|
+
const op = encode({
|
|
113
|
+
CRDTMap: {
|
|
114
|
+
key,
|
|
115
|
+
crdt_type: "ORSet",
|
|
116
|
+
op: { ORSet: { Remove: { element, known_tags: knownTags } } },
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Write a value to an LWW-Register key. */
|
|
123
|
+
lwwSet(key: string, value: unknown): void {
|
|
124
|
+
const wallMs = Date.now();
|
|
125
|
+
const op = encode({
|
|
126
|
+
CRDTMap: {
|
|
127
|
+
key,
|
|
128
|
+
crdt_type: "LwwRegister",
|
|
129
|
+
op: {
|
|
130
|
+
LwwRegister: {
|
|
131
|
+
value,
|
|
132
|
+
hlc: { wall_ms: wallMs, logical: 0, node_id: this.clientId },
|
|
133
|
+
author: this.clientId,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
this.transport.send({ Op: { crdt_id: this.crdtId, op_bytes: op } });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
applyDelta(delta: CRDTMapDelta): void {
|
|
142
|
+
let changed = false;
|
|
143
|
+
for (const [key, valueDelta] of Object.entries(delta.deltas)) {
|
|
144
|
+
const updated = applyValueDelta(this.state[key], valueDelta);
|
|
145
|
+
if (updated !== undefined) {
|
|
146
|
+
this.state[key] = updated;
|
|
147
|
+
changed = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (changed) this.emit();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private emit(): void {
|
|
154
|
+
for (const listener of this.listeners) listener(this.state);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const applyValueDelta = (current: unknown, delta: CrdtValueDelta): unknown => {
|
|
159
|
+
if ("GCounter" in delta) {
|
|
160
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
161
|
+
const currentCounts = (current as { counts?: Record<string, number> } | undefined)?.counts ?? {};
|
|
162
|
+
const merged: Record<string, number> = { ...currentCounts };
|
|
163
|
+
for (const [clientId, count] of Object.entries(delta.GCounter.counters)) {
|
|
164
|
+
if ((merged[clientId] ?? 0) < count) merged[clientId] = count;
|
|
165
|
+
}
|
|
166
|
+
const total = Object.values(merged).reduce((sum, count) => sum + count, 0);
|
|
167
|
+
return { total, counts: merged };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if ("PNCounter" in delta) {
|
|
171
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
172
|
+
const currentPn = current as { pos?: Record<string, number>; neg?: Record<string, number> } | undefined;
|
|
173
|
+
const pos = mergeCounterMap(currentPn?.pos ?? {}, delta.PNCounter.pos?.counters ?? {});
|
|
174
|
+
const neg = mergeCounterMap(currentPn?.neg ?? {}, delta.PNCounter.neg?.counters ?? {});
|
|
175
|
+
const value = Object.values(pos).reduce((sum, count) => sum + count, 0)
|
|
176
|
+
- Object.values(neg).reduce((sum, count) => sum + count, 0);
|
|
177
|
+
return { value, pos, neg };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if ("ORSet" in delta) {
|
|
181
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
182
|
+
const currentOrset = current as { elements?: unknown[] } | undefined;
|
|
183
|
+
const existing = new Set((currentOrset?.elements ?? []).map((element) => JSON.stringify(element)));
|
|
184
|
+
for (const key of Object.keys(delta.ORSet.adds)) {
|
|
185
|
+
existing.add(key);
|
|
186
|
+
}
|
|
187
|
+
for (const key of Object.keys(delta.ORSet.removes)) {
|
|
188
|
+
existing.delete(key);
|
|
189
|
+
}
|
|
190
|
+
return { elements: Array.from(existing).map((key) => { try { return JSON.parse(key); } catch { return key; } }) };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if ("LwwRegister" in delta) {
|
|
194
|
+
const entry = delta.LwwRegister.entry;
|
|
195
|
+
if (entry === null) return current;
|
|
196
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
197
|
+
const currentLww = current as { hlc?: { wall_ms: number; logical: number }; author?: number } | undefined;
|
|
198
|
+
if (currentLww?.hlc) {
|
|
199
|
+
const currentWallMs = Number(currentLww.hlc.wall_ms);
|
|
200
|
+
const newWallMs = Number(entry.hlc.wall_ms);
|
|
201
|
+
if (newWallMs < currentWallMs) return current;
|
|
202
|
+
if (newWallMs === currentWallMs && entry.hlc.logical < (currentLww.hlc.logical ?? 0)) return current;
|
|
203
|
+
if (newWallMs === currentWallMs && entry.hlc.logical === (currentLww.hlc.logical ?? 0) && Number(entry.author) <= Number(currentLww.author ?? 0)) return current;
|
|
204
|
+
}
|
|
205
|
+
return { value: entry.value, updatedAtMs: Number(entry.hlc.wall_ms), author: Number(entry.author) };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if ("Presence" in delta) {
|
|
209
|
+
// HACK: current is unknown — shape is set by previous applyValueDelta call
|
|
210
|
+
const currentPresence = current as { entries?: Record<string, unknown> } | undefined;
|
|
211
|
+
const entries = { ...(currentPresence?.entries ?? {}) };
|
|
212
|
+
for (const [clientId, entry] of Object.entries(delta.Presence.changes)) {
|
|
213
|
+
if (entry === null) {
|
|
214
|
+
delete entries[clientId];
|
|
215
|
+
} else {
|
|
216
|
+
entries[clientId] = { data: entry.data, expiresAtMs: Number(entry.hlc.wall_ms) + Number(entry.ttl_ms) };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { entries };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return current;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const mergeCounterMap = (source: Record<string, number>, incoming: Record<string, number>): Record<string, number> => {
|
|
226
|
+
const result = { ...source };
|
|
227
|
+
for (const [clientId, count] of Object.entries(incoming)) {
|
|
228
|
+
if ((result[clientId] ?? 0) < count) result[clientId] = count;
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { ORSetHandle } from "./crdt/orset.js";
|
|
|
10
10
|
export { LwwRegisterHandle } from "./crdt/lwwregister.js";
|
|
11
11
|
export { PresenceHandle } from "./crdt/presence.js";
|
|
12
12
|
export type { PresenceEntry } from "./crdt/presence.js";
|
|
13
|
+
export { CRDTMapHandle } from "./crdt/crdtmap.js";
|
|
14
|
+
export type { CrdtMapValue } from "./crdt/crdtmap.js";
|
|
13
15
|
|
|
14
16
|
// Transport
|
|
15
17
|
export { HttpClient } from "./transport/http.js";
|
package/src/sync/delta.ts
CHANGED
|
@@ -71,3 +71,19 @@ export const decodePresenceDelta = (bytes: Uint8Array): PresenceDelta => {
|
|
|
71
71
|
const raw = decode(bytes) as { changes?: Record<string, PresenceEntryDelta | null> };
|
|
72
72
|
return { changes: raw.changes ?? {} };
|
|
73
73
|
};
|
|
74
|
+
|
|
75
|
+
export type CrdtValueDelta =
|
|
76
|
+
| { GCounter: GCounterDelta }
|
|
77
|
+
| { PNCounter: PNCounterDelta }
|
|
78
|
+
| { ORSet: ORSetDelta }
|
|
79
|
+
| { LwwRegister: LwwDelta }
|
|
80
|
+
| { Presence: PresenceDelta };
|
|
81
|
+
|
|
82
|
+
export interface CRDTMapDelta {
|
|
83
|
+
deltas: Record<string, CrdtValueDelta>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const decodeCRDTMapDelta = (bytes: Uint8Array): CRDTMapDelta => {
|
|
87
|
+
const raw = decode(bytes) as { deltas?: Record<string, unknown> };
|
|
88
|
+
return { deltas: (raw.deltas ?? {}) as Record<string, CrdtValueDelta> };
|
|
89
|
+
};
|