meridian-sdk 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/biome.json +4 -0
- package/dist/auth/token.d.ts +0 -19
- package/dist/auth/token.d.ts.map +1 -1
- package/dist/auth/token.js +6 -31
- package/dist/auth/token.js.map +1 -1
- package/dist/client.d.ts +139 -23
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +165 -52
- package/dist/client.js.map +1 -1
- package/dist/codec.d.ts +7 -35
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +13 -65
- package/dist/codec.js.map +1 -1
- package/dist/constants.d.ts +7 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +7 -0
- package/dist/constants.js.map +1 -0
- package/dist/crdt/gcounter.d.ts +18 -9
- package/dist/crdt/gcounter.d.ts.map +1 -1
- package/dist/crdt/gcounter.js +16 -13
- package/dist/crdt/gcounter.js.map +1 -1
- package/dist/crdt/lwwregister.d.ts +24 -11
- package/dist/crdt/lwwregister.d.ts.map +1 -1
- package/dist/crdt/lwwregister.js +25 -19
- package/dist/crdt/lwwregister.js.map +1 -1
- package/dist/crdt/orset.d.ts +25 -13
- package/dist/crdt/orset.d.ts.map +1 -1
- package/dist/crdt/orset.js +31 -23
- package/dist/crdt/orset.js.map +1 -1
- package/dist/crdt/pncounter.d.ts +22 -4
- package/dist/crdt/pncounter.d.ts.map +1 -1
- package/dist/crdt/pncounter.js +28 -14
- package/dist/crdt/pncounter.js.map +1 -1
- package/dist/crdt/presence.d.ts +33 -13
- package/dist/crdt/presence.d.ts.map +1 -1
- package/dist/crdt/presence.js +36 -20
- package/dist/crdt/presence.js.map +1 -1
- package/dist/errors.d.ts +0 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +0 -16
- package/dist/errors.js.map +1 -1
- package/dist/schema.d.ts +3 -9
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -34
- package/dist/schema.js.map +1 -1
- package/dist/sync/clock.d.ts +1 -20
- package/dist/sync/clock.d.ts.map +1 -1
- package/dist/sync/clock.js +20 -46
- package/dist/sync/clock.js.map +1 -1
- package/dist/sync/delta.d.ts +5 -22
- package/dist/sync/delta.d.ts.map +1 -1
- package/dist/sync/delta.js +18 -26
- package/dist/sync/delta.js.map +1 -1
- package/dist/transport/http.d.ts +1 -14
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +3 -21
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/websocket.d.ts +0 -27
- package/dist/transport/websocket.d.ts.map +1 -1
- package/dist/transport/websocket.js +9 -37
- package/dist/transport/websocket.js.map +1 -1
- package/dist/utils/to-hex.d.ts +2 -0
- package/dist/utils/to-hex.d.ts.map +1 -0
- package/dist/utils/to-hex.js +2 -0
- package/dist/utils/to-hex.js.map +1 -0
- package/package.json +6 -3
- package/src/auth/token.ts +6 -34
- package/src/client.ts +165 -65
- package/src/codec.ts +13 -71
- package/src/constants.ts +6 -0
- package/src/crdt/gcounter.ts +18 -20
- package/src/crdt/lwwregister.ts +27 -26
- package/src/crdt/orset.ts +32 -29
- package/src/crdt/pncounter.ts +30 -21
- package/src/crdt/presence.ts +37 -26
- package/src/errors.ts +0 -21
- package/src/schema.ts +3 -44
- package/src/sync/clock.ts +18 -50
- package/src/sync/delta.ts +20 -58
- package/src/transport/http.ts +3 -33
- package/src/transport/websocket.ts +15 -52
- package/src/utils/to-hex.ts +1 -0
- package/test/integration.test.ts +2 -3
- package/test/sync.test.ts +1 -2
package/dist/crdt/orset.js
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { encode, uuidToBytes } from "../codec.js";
|
|
3
|
+
import { toHex } from "../utils/to-hex.js";
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Each element has a set of add-tags (UUIDs). Remove only removes tags
|
|
5
|
-
* known at remove time — a concurrent add with a new tag survives.
|
|
5
|
+
* Low-level handle for an Observed-Remove Set (OR-Set) CRDT.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Pass a `schema` to get runtime validation of elements deserialized from deltas:
|
|
10
|
-
* client.orset("id", Schema.Struct({ id: Schema.String }))
|
|
7
|
+
* Obtained via `MeridianClient.orset()`. Prefer the `useORSet` React hook for
|
|
8
|
+
* component-level usage; use this handle directly in non-React environments.
|
|
11
9
|
*/
|
|
12
|
-
import { Schema } from "effect";
|
|
13
|
-
import { encode, uuidToBytes } from "../codec.js";
|
|
14
10
|
export class ORSetHandle {
|
|
15
|
-
/** element (JSON-stringified) → Set of live add-tags */
|
|
16
11
|
tags = new Map();
|
|
17
12
|
crdtId;
|
|
18
13
|
clientId;
|
|
@@ -25,30 +20,40 @@ export class ORSetHandle {
|
|
|
25
20
|
this.transport = opts.transport;
|
|
26
21
|
this.schema = opts.schema ?? null;
|
|
27
22
|
}
|
|
28
|
-
|
|
29
|
-
/** Returns all live elements (add-wins). */
|
|
23
|
+
/** Returns the current set elements as an array, decoded via the optional schema. */
|
|
30
24
|
elements() {
|
|
31
25
|
return Array.from(this.tags.keys())
|
|
32
26
|
.filter(k => (this.tags.get(k)?.size ?? 0) > 0)
|
|
33
27
|
.map(k => this.decode(JSON.parse(k)));
|
|
34
28
|
}
|
|
29
|
+
/** Returns `true` if the set currently contains `element`. */
|
|
35
30
|
has(element) {
|
|
36
31
|
const key = JSON.stringify(element);
|
|
37
32
|
return (this.tags.get(key)?.size ?? 0) > 0;
|
|
38
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Registers a listener that is called whenever the set contents change.
|
|
36
|
+
*
|
|
37
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
38
|
+
*/
|
|
39
39
|
onChange(listener) {
|
|
40
40
|
this.listeners.add(listener);
|
|
41
41
|
return () => { this.listeners.delete(listener); };
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Adds `element` to the set and broadcasts the operation.
|
|
45
|
+
*
|
|
46
|
+
* Each call generates a unique tag so concurrent adds of the same value
|
|
47
|
+
* are treated as distinct entries.
|
|
48
|
+
*/
|
|
44
49
|
add(element) {
|
|
45
50
|
const tag = crypto.randomUUID();
|
|
46
51
|
const key = JSON.stringify(element);
|
|
47
52
|
if (!this.tags.has(key))
|
|
48
53
|
this.tags.set(key, new Set());
|
|
49
|
-
this.tags.get(key)
|
|
54
|
+
const tagSet = this.tags.get(key);
|
|
55
|
+
tagSet.add(tag);
|
|
50
56
|
this.emit();
|
|
51
|
-
// Rust Uuid is serialized as 16-byte bin — encode tag as bytes
|
|
52
57
|
this.transport.send({
|
|
53
58
|
Op: {
|
|
54
59
|
crdt_id: this.crdtId,
|
|
@@ -56,6 +61,12 @@ export class ORSetHandle {
|
|
|
56
61
|
},
|
|
57
62
|
});
|
|
58
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Removes `element` from the set and broadcasts the operation.
|
|
66
|
+
*
|
|
67
|
+
* Only the tags observed locally at the time of this call are removed;
|
|
68
|
+
* concurrently added copies on other clients are left intact.
|
|
69
|
+
*/
|
|
59
70
|
remove(element) {
|
|
60
71
|
const key = JSON.stringify(element);
|
|
61
72
|
const currentTags = Array.from(this.tags.get(key) ?? []);
|
|
@@ -63,7 +74,6 @@ export class ORSetHandle {
|
|
|
63
74
|
return;
|
|
64
75
|
this.tags.delete(key);
|
|
65
76
|
this.emit();
|
|
66
|
-
// Rust expects known_tags as a set of 16-byte UUIDs
|
|
67
77
|
this.transport.send({
|
|
68
78
|
Op: {
|
|
69
79
|
crdt_id: this.crdtId,
|
|
@@ -71,7 +81,6 @@ export class ORSetHandle {
|
|
|
71
81
|
},
|
|
72
82
|
});
|
|
73
83
|
}
|
|
74
|
-
// ---- Delta application ----
|
|
75
84
|
applyDelta(delta) {
|
|
76
85
|
let changed = false;
|
|
77
86
|
for (const [elem, addedTags] of Object.entries(delta.adds)) {
|
|
@@ -79,7 +88,7 @@ export class ORSetHandle {
|
|
|
79
88
|
this.tags.set(elem, new Set());
|
|
80
89
|
const set = this.tags.get(elem);
|
|
81
90
|
for (const tag of addedTags) {
|
|
82
|
-
const tagStr = Array.from(tag).map(
|
|
91
|
+
const tagStr = Array.from(tag).map(toHex).join("");
|
|
83
92
|
if (!set.has(tagStr)) {
|
|
84
93
|
set.add(tagStr);
|
|
85
94
|
changed = true;
|
|
@@ -91,7 +100,7 @@ export class ORSetHandle {
|
|
|
91
100
|
if (!set)
|
|
92
101
|
continue;
|
|
93
102
|
for (const tag of removedTags) {
|
|
94
|
-
const tagStr = Array.from(tag).map(
|
|
103
|
+
const tagStr = Array.from(tag).map(toHex).join("");
|
|
95
104
|
if (set.has(tagStr)) {
|
|
96
105
|
set.delete(tagStr);
|
|
97
106
|
changed = true;
|
|
@@ -103,7 +112,6 @@ export class ORSetHandle {
|
|
|
103
112
|
if (changed)
|
|
104
113
|
this.emit();
|
|
105
114
|
}
|
|
106
|
-
// ---- Internal ----
|
|
107
115
|
decode(raw) {
|
|
108
116
|
if (this.schema !== null) {
|
|
109
117
|
return Schema.decodeUnknownSync(this.schema)(raw);
|
|
@@ -112,8 +120,8 @@ export class ORSetHandle {
|
|
|
112
120
|
}
|
|
113
121
|
emit() {
|
|
114
122
|
const elems = this.elements();
|
|
115
|
-
for (const
|
|
116
|
-
|
|
123
|
+
for (const listener of this.listeners)
|
|
124
|
+
listener(elems);
|
|
117
125
|
}
|
|
118
126
|
}
|
|
119
127
|
//# sourceMappingURL=orset.js.map
|
package/dist/crdt/orset.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orset.js","sourceRoot":"","sources":["../../src/crdt/orset.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"orset.js","sourceRoot":"","sources":["../../src/crdt/orset.ts"],"names":[],"mappings":"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"}
|
package/dist/crdt/pncounter.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PNCounter handle — increment and decrement counter.
|
|
3
|
-
* value = sum(increments) - sum(decrements). Can be negative.
|
|
4
|
-
*/
|
|
5
1
|
import type { WsTransport } from "../transport/websocket.js";
|
|
6
2
|
import type { PNCounterDelta } from "../sync/delta.js";
|
|
7
3
|
export interface PNCounterState {
|
|
8
4
|
p: Record<string, number>;
|
|
9
5
|
n: Record<string, number>;
|
|
10
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Low-level handle for a positive-negative counter (PNCounter) CRDT.
|
|
9
|
+
*
|
|
10
|
+
* Obtained via `MeridianClient.pncounter()`. Prefer the `usePNCounter` React hook
|
|
11
|
+
* for component-level usage; use this handle directly in non-React environments.
|
|
12
|
+
*/
|
|
11
13
|
export declare class PNCounterHandle {
|
|
12
14
|
private state;
|
|
13
15
|
private readonly clientId;
|
|
@@ -21,9 +23,25 @@ export declare class PNCounterHandle {
|
|
|
21
23
|
transport: WsTransport;
|
|
22
24
|
initial?: PNCounterState;
|
|
23
25
|
});
|
|
26
|
+
/** Returns the current net counter value (sum of increments minus sum of decrements). */
|
|
24
27
|
value(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Registers a listener that is called whenever the counter value changes.
|
|
30
|
+
*
|
|
31
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
32
|
+
*/
|
|
25
33
|
onChange(listener: (value: number) => void): () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Increments the counter by `amount` (default `1`) and broadcasts the delta.
|
|
36
|
+
*
|
|
37
|
+
* @throws {RangeError} If `amount` is not greater than zero.
|
|
38
|
+
*/
|
|
26
39
|
increment(amount?: number): void;
|
|
40
|
+
/**
|
|
41
|
+
* Decrements the counter by `amount` (default `1`) and broadcasts the delta.
|
|
42
|
+
*
|
|
43
|
+
* @throws {RangeError} If `amount` is not greater than zero.
|
|
44
|
+
*/
|
|
27
45
|
decrement(amount?: number): void;
|
|
28
46
|
applyDelta(delta: PNCounterDelta): void;
|
|
29
47
|
private sendOp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pncounter.d.ts","sourceRoot":"","sources":["../../src/crdt/pncounter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pncounter.d.ts","sourceRoot":"","sources":["../../src/crdt/pncounter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsC;gBAEpD,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,cAAc,CAAC;KAC1B;IAOD,yFAAyF;IACzF,KAAK,IAAI,MAAM;IAMf;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAKvD;;;;OAIG;IACH,SAAS,CAAC,MAAM,GAAE,MAAU,GAAG,IAAI;IAQnC;;;;OAIG;IACH,SAAS,CAAC,MAAM,GAAE,MAAU,GAAG,IAAI;IAQnC,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAavC,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,IAAI;CAIb"}
|
package/dist/crdt/pncounter.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { encode } from "../codec.js";
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
3
|
+
* Low-level handle for a positive-negative counter (PNCounter) CRDT.
|
|
4
|
+
*
|
|
5
|
+
* Obtained via `MeridianClient.pncounter()`. Prefer the `usePNCounter` React hook
|
|
6
|
+
* for component-level usage; use this handle directly in non-React environments.
|
|
4
7
|
*/
|
|
5
|
-
import { encode } from "../codec.js";
|
|
6
8
|
export class PNCounterHandle {
|
|
7
9
|
state;
|
|
8
10
|
clientId;
|
|
@@ -15,17 +17,26 @@ export class PNCounterHandle {
|
|
|
15
17
|
this.transport = opts.transport;
|
|
16
18
|
this.state = opts.initial ?? { p: {}, n: {} };
|
|
17
19
|
}
|
|
18
|
-
|
|
20
|
+
/** Returns the current net counter value (sum of increments minus sum of decrements). */
|
|
19
21
|
value() {
|
|
20
22
|
const pos = Object.values(this.state.p).reduce((a, b) => a + b, 0);
|
|
21
23
|
const neg = Object.values(this.state.n).reduce((a, b) => a + b, 0);
|
|
22
24
|
return pos - neg;
|
|
23
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Registers a listener that is called whenever the counter value changes.
|
|
28
|
+
*
|
|
29
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
30
|
+
*/
|
|
24
31
|
onChange(listener) {
|
|
25
32
|
this.listeners.add(listener);
|
|
26
33
|
return () => { this.listeners.delete(listener); };
|
|
27
34
|
}
|
|
28
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Increments the counter by `amount` (default `1`) and broadcasts the delta.
|
|
37
|
+
*
|
|
38
|
+
* @throws {RangeError} If `amount` is not greater than zero.
|
|
39
|
+
*/
|
|
29
40
|
increment(amount = 1) {
|
|
30
41
|
if (amount <= 0)
|
|
31
42
|
throw new RangeError("PNCounter: increment amount must be > 0");
|
|
@@ -34,6 +45,11 @@ export class PNCounterHandle {
|
|
|
34
45
|
this.emit();
|
|
35
46
|
this.sendOp({ Increment: { client_id: this.clientId, amount } });
|
|
36
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Decrements the counter by `amount` (default `1`) and broadcasts the delta.
|
|
50
|
+
*
|
|
51
|
+
* @throws {RangeError} If `amount` is not greater than zero.
|
|
52
|
+
*/
|
|
37
53
|
decrement(amount = 1) {
|
|
38
54
|
if (amount <= 0)
|
|
39
55
|
throw new RangeError("PNCounter: decrement amount must be > 0");
|
|
@@ -42,19 +58,18 @@ export class PNCounterHandle {
|
|
|
42
58
|
this.emit();
|
|
43
59
|
this.sendOp({ Decrement: { client_id: this.clientId, amount } });
|
|
44
60
|
}
|
|
45
|
-
// ---- Delta application ----
|
|
46
61
|
applyDelta(delta) {
|
|
47
62
|
let changed = false;
|
|
48
63
|
for (const [id, count] of Object.entries(delta.pos?.counters ?? {})) {
|
|
49
|
-
const
|
|
50
|
-
if (count >
|
|
64
|
+
const currentCount = this.state.p[id] ?? 0;
|
|
65
|
+
if (count > currentCount) {
|
|
51
66
|
this.state.p[id] = count;
|
|
52
67
|
changed = true;
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
70
|
for (const [id, count] of Object.entries(delta.neg?.counters ?? {})) {
|
|
56
|
-
const
|
|
57
|
-
if (count >
|
|
71
|
+
const currentCount = this.state.n[id] ?? 0;
|
|
72
|
+
if (count > currentCount) {
|
|
58
73
|
this.state.n[id] = count;
|
|
59
74
|
changed = true;
|
|
60
75
|
}
|
|
@@ -62,16 +77,15 @@ export class PNCounterHandle {
|
|
|
62
77
|
if (changed)
|
|
63
78
|
this.emit();
|
|
64
79
|
}
|
|
65
|
-
// ---- Internal ----
|
|
66
80
|
sendOp(op) {
|
|
67
81
|
this.transport.send({
|
|
68
82
|
Op: { crdt_id: this.crdtId, op_bytes: encode({ PNCounter: op }) },
|
|
69
83
|
});
|
|
70
84
|
}
|
|
71
85
|
emit() {
|
|
72
|
-
const
|
|
73
|
-
for (const
|
|
74
|
-
|
|
86
|
+
const currentValue = this.value();
|
|
87
|
+
for (const listener of this.listeners)
|
|
88
|
+
listener(currentValue);
|
|
75
89
|
}
|
|
76
90
|
}
|
|
77
91
|
//# sourceMappingURL=pncounter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pncounter.js","sourceRoot":"","sources":["../../src/crdt/pncounter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pncounter.js","sourceRoot":"","sources":["../../src/crdt/pncounter.ts"],"names":[],"mappings":"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"}
|
package/dist/crdt/presence.d.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Presence handle — TTL-aware presence tracking.
|
|
3
|
-
*
|
|
4
|
-
* `heartbeat(data, ttlMs)` upserts the local client's entry.
|
|
5
|
-
* `leave()` creates an explicit tombstone (ttl = 0).
|
|
6
|
-
* Background GC on the server prunes expired entries every 5s.
|
|
7
|
-
*
|
|
8
|
-
* Pass a `schema` to get runtime validation of incoming delta data:
|
|
9
|
-
* client.presence("id", Schema.Struct({ cursor: Schema.Tuple(Schema.Number, Schema.Number) }))
|
|
10
|
-
*/
|
|
11
1
|
import { Schema } from "effect";
|
|
12
2
|
import type { WsTransport } from "../transport/websocket.js";
|
|
13
3
|
import type { PresenceDelta } from "../sync/delta.js";
|
|
@@ -16,6 +6,13 @@ export interface PresenceEntry<T> {
|
|
|
16
6
|
data: T;
|
|
17
7
|
expiresAtMs: number;
|
|
18
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Low-level handle for a presence channel CRDT.
|
|
11
|
+
*
|
|
12
|
+
* Obtained via `MeridianClient.presence()`. Prefer the `usePresence` React hook
|
|
13
|
+
* for component-level usage (it manages heartbeat timers and cleanup automatically);
|
|
14
|
+
* use this handle directly in non-React environments.
|
|
15
|
+
*/
|
|
19
16
|
export declare class PresenceHandle<T> {
|
|
20
17
|
private readonly entries;
|
|
21
18
|
private readonly crdtId;
|
|
@@ -30,12 +27,35 @@ export declare class PresenceHandle<T> {
|
|
|
30
27
|
transport: WsTransport;
|
|
31
28
|
schema?: Schema.Schema<T>;
|
|
32
29
|
});
|
|
33
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Returns all currently online entries — those whose TTL has not yet elapsed
|
|
32
|
+
* as of the call time.
|
|
33
|
+
*/
|
|
34
34
|
online(): PresenceEntry<T>[];
|
|
35
|
+
/**
|
|
36
|
+
* Registers a listener that is called whenever the online entries change.
|
|
37
|
+
*
|
|
38
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
39
|
+
*/
|
|
35
40
|
onChange(listener: (entries: PresenceEntry<T>[]) => void): () => void;
|
|
36
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Broadcasts a heartbeat that marks the current client as online for `ttlMs`
|
|
43
|
+
* milliseconds (default `30 000`).
|
|
44
|
+
*
|
|
45
|
+
* Call this periodically (at least once per `ttlMs`) to remain visible to
|
|
46
|
+
* other clients. The `usePresence` hook manages this automatically when
|
|
47
|
+
* `opts.data` is provided.
|
|
48
|
+
*
|
|
49
|
+
* @param data - Arbitrary payload broadcast to other clients.
|
|
50
|
+
* @param ttlMs - Time-to-live in milliseconds before the entry is considered stale.
|
|
51
|
+
*/
|
|
37
52
|
heartbeat(data: T, ttlMs?: number): void;
|
|
38
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Marks the current client as offline and broadcasts a leave operation.
|
|
55
|
+
*
|
|
56
|
+
* After calling this, the client's entry is removed from the `online()` list
|
|
57
|
+
* on all peers. The `usePresence` hook calls this automatically on unmount.
|
|
58
|
+
*/
|
|
39
59
|
leave(): void;
|
|
40
60
|
applyDelta(delta: PresenceDelta): void;
|
|
41
61
|
private decode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/crdt/presence.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../src/crdt/presence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAsB,MAAM,kBAAkB,CAAC;AAE1E,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;IACR,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,qBAAa,cAAc,CAAC,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoD;gBAElE,IAAI,EAAE;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,WAAW,CAAC;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;KAC3B;IAOD;;;OAGG;IACH,MAAM,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE;IAS5B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,GAAG,MAAM,IAAI;IAKrE;;;;;;;;;;OAUG;IACH,SAAS,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,GAAE,MAAe,GAAG,IAAI;IAqBhD;;;;;OAKG;IACH,KAAK,IAAI,IAAI;IAeb,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI;IAetC,OAAO,CAAC,MAAM;IAOd,OAAO,CAAC,UAAU;IA+BlB,OAAO,CAAC,IAAI;CAIb"}
|
package/dist/crdt/presence.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { encode } from "../codec.js";
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* `heartbeat(data, ttlMs)` upserts the local client's entry.
|
|
5
|
-
* `leave()` creates an explicit tombstone (ttl = 0).
|
|
6
|
-
* Background GC on the server prunes expired entries every 5s.
|
|
4
|
+
* Low-level handle for a presence channel CRDT.
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Obtained via `MeridianClient.presence()`. Prefer the `usePresence` React hook
|
|
7
|
+
* for component-level usage (it manages heartbeat timers and cleanup automatically);
|
|
8
|
+
* use this handle directly in non-React environments.
|
|
10
9
|
*/
|
|
11
|
-
import { Schema } from "effect";
|
|
12
|
-
import { encode, wallMsToBigInt } from "../codec.js";
|
|
13
10
|
export class PresenceHandle {
|
|
14
11
|
entries = new Map();
|
|
15
12
|
crdtId;
|
|
@@ -23,8 +20,10 @@ export class PresenceHandle {
|
|
|
23
20
|
this.transport = opts.transport;
|
|
24
21
|
this.schema = opts.schema ?? null;
|
|
25
22
|
}
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Returns all currently online entries — those whose TTL has not yet elapsed
|
|
25
|
+
* as of the call time.
|
|
26
|
+
*/
|
|
28
27
|
online() {
|
|
29
28
|
const now = Date.now();
|
|
30
29
|
const live = [];
|
|
@@ -34,15 +33,29 @@ export class PresenceHandle {
|
|
|
34
33
|
}
|
|
35
34
|
return live;
|
|
36
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Registers a listener that is called whenever the online entries change.
|
|
38
|
+
*
|
|
39
|
+
* @returns An unsubscribe function — call it to stop receiving updates.
|
|
40
|
+
*/
|
|
37
41
|
onChange(listener) {
|
|
38
42
|
this.listeners.add(listener);
|
|
39
43
|
return () => { this.listeners.delete(listener); };
|
|
40
44
|
}
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Broadcasts a heartbeat that marks the current client as online for `ttlMs`
|
|
47
|
+
* milliseconds (default `30 000`).
|
|
48
|
+
*
|
|
49
|
+
* Call this periodically (at least once per `ttlMs`) to remain visible to
|
|
50
|
+
* other clients. The `usePresence` hook manages this automatically when
|
|
51
|
+
* `opts.data` is provided.
|
|
52
|
+
*
|
|
53
|
+
* @param data - Arbitrary payload broadcast to other clients.
|
|
54
|
+
* @param ttlMs - Time-to-live in milliseconds before the entry is considered stale.
|
|
55
|
+
*/
|
|
43
56
|
heartbeat(data, ttlMs = 30_000) {
|
|
44
57
|
const wallMs = Date.now();
|
|
45
|
-
const hlc = { wall_ms:
|
|
58
|
+
const hlc = { wall_ms: wallMs, logical: 0, node_id: this.clientId };
|
|
46
59
|
this.entries.set(String(this.clientId), {
|
|
47
60
|
clientId: this.clientId,
|
|
48
61
|
data,
|
|
@@ -58,10 +71,15 @@ export class PresenceHandle {
|
|
|
58
71
|
},
|
|
59
72
|
});
|
|
60
73
|
}
|
|
61
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Marks the current client as offline and broadcasts a leave operation.
|
|
76
|
+
*
|
|
77
|
+
* After calling this, the client's entry is removed from the `online()` list
|
|
78
|
+
* on all peers. The `usePresence` hook calls this automatically on unmount.
|
|
79
|
+
*/
|
|
62
80
|
leave() {
|
|
63
81
|
const wallMs = Date.now();
|
|
64
|
-
const hlc = { wall_ms:
|
|
82
|
+
const hlc = { wall_ms: wallMs, logical: 0, node_id: this.clientId };
|
|
65
83
|
this.entries.delete(String(this.clientId));
|
|
66
84
|
this.emit();
|
|
67
85
|
this.transport.send({
|
|
@@ -71,7 +89,6 @@ export class PresenceHandle {
|
|
|
71
89
|
},
|
|
72
90
|
});
|
|
73
91
|
}
|
|
74
|
-
// ---- Delta application ----
|
|
75
92
|
applyDelta(delta) {
|
|
76
93
|
let changed = false;
|
|
77
94
|
for (const [clientIdStr, entry] of Object.entries(delta.changes)) {
|
|
@@ -88,7 +105,6 @@ export class PresenceHandle {
|
|
|
88
105
|
if (changed)
|
|
89
106
|
this.emit();
|
|
90
107
|
}
|
|
91
|
-
// ---- Internal ----
|
|
92
108
|
decode(raw) {
|
|
93
109
|
if (this.schema !== null) {
|
|
94
110
|
return Schema.decodeUnknownSync(this.schema)(raw);
|
|
@@ -126,8 +142,8 @@ export class PresenceHandle {
|
|
|
126
142
|
}
|
|
127
143
|
emit() {
|
|
128
144
|
const live = this.online();
|
|
129
|
-
for (const
|
|
130
|
-
|
|
145
|
+
for (const listener of this.listeners)
|
|
146
|
+
listener(live);
|
|
131
147
|
}
|
|
132
148
|
}
|
|
133
149
|
//# sourceMappingURL=presence.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/crdt/presence.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"presence.js","sourceRoot":"","sources":["../../src/crdt/presence.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed errors for the Meridian SDK — all extend Data.TaggedError so they
|
|
3
|
-
* can be matched with Effect.catchTag / Effect.catchAll.
|
|
4
|
-
*/
|
|
5
1
|
import type { ErrorResponse } from "./schema.js";
|
|
6
2
|
declare const CodecError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
|
|
7
3
|
readonly _tag: "CodecError";
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;;;;AAEjD,qBAAa,UAAW,SAAQ,gBAA+B;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC;CAC1B,CAAC;CAAG;;;;AAEL,qBAAa,eAAgB,SAAQ,qBAAoC;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;;;;AAEL,qBAAa,iBAAkB,SAAQ,uBAAsC;IAC3E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;CAAG;;;;AAEL,qBAAa,SAAU,SAAQ,eAA8B;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;CAC9B,CAAC;CAAG;;;;AAEL,qBAAa,YAAa,SAAQ,kBAAiC;IACjE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;CAAG;;;;AAEL,qBAAa,cAAe,SAAQ,oBAAmC;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;CAAG"}
|
package/dist/errors.js
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed errors for the Meridian SDK — all extend Data.TaggedError so they
|
|
3
|
-
* can be matched with Effect.catchTag / Effect.catchAll.
|
|
4
|
-
*/
|
|
5
1
|
import { Data } from "effect";
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// Codec
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
2
|
export class CodecError extends Data.TaggedError("CodecError") {
|
|
10
3
|
}
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
// Auth / Token
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
4
|
export class TokenParseError extends Data.TaggedError("TokenParseError") {
|
|
15
5
|
}
|
|
16
6
|
export class TokenExpiredError extends Data.TaggedError("TokenExpiredError") {
|
|
17
7
|
}
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// HTTP
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
8
|
export class HttpError extends Data.TaggedError("HttpError") {
|
|
22
9
|
}
|
|
23
10
|
export class NetworkError extends Data.TaggedError("NetworkError") {
|
|
24
11
|
}
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// WebSocket / Transport
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
12
|
export class TransportError extends Data.TaggedError("TransportError") {
|
|
29
13
|
}
|
|
30
14
|
//# sourceMappingURL=errors.js.map
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B,MAAM,OAAO,UAAW,SAAQ,IAAI,CAAC,WAAW,CAAC,YAAY,CAG3D;CAAG;AAEL,MAAM,OAAO,eAAgB,SAAQ,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAErE;CAAG;AAEL,MAAM,OAAO,iBAAkB,SAAQ,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAEzE;CAAG;AAEL,MAAM,OAAO,SAAU,SAAQ,IAAI,CAAC,WAAW,CAAC,WAAW,CAGzD;CAAG;AAEL,MAAM,OAAO,YAAa,SAAQ,IAAI,CAAC,WAAW,CAAC,cAAc,CAG/D;CAAG;AAEL,MAAM,OAAO,cAAe,SAAQ,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAGnE;CAAG"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -5,16 +5,10 @@
|
|
|
5
5
|
* Plain TypeScript types are inferred via Schema.Type<typeof S>.
|
|
6
6
|
*/
|
|
7
7
|
import { Schema } from "effect";
|
|
8
|
-
/**
|
|
9
|
-
* Unix timestamp in milliseconds.
|
|
10
|
-
* msgpackr decodes Rust u64 as BigInt — accept both for compatibility.
|
|
11
|
-
*/
|
|
8
|
+
/** Unix timestamp in milliseconds. Rust u64 decodes as bigint when > Number.MAX_SAFE_INTEGER — normalise to number. */
|
|
12
9
|
export declare const TimestampMs: Schema.Union<[typeof Schema.Number, Schema.transform<typeof Schema.BigIntFromSelf, typeof Schema.Number>]>;
|
|
13
10
|
export type TimestampMs = number;
|
|
14
|
-
/**
|
|
15
|
-
* client_id / author — Rust u64, decoded by msgpackr as BigInt or number.
|
|
16
|
-
* Accept both and normalise to number.
|
|
17
|
-
*/
|
|
11
|
+
/** client_id / author — Rust u64, may decode as bigint for large values. Normalise to number. */
|
|
18
12
|
export declare const ClientId: Schema.Union<[typeof Schema.Number, Schema.transform<typeof Schema.BigIntFromSelf, typeof Schema.Number>]>;
|
|
19
13
|
export type ClientId = number;
|
|
20
14
|
export declare const Permissions: Schema.Struct<{
|
|
@@ -56,7 +50,7 @@ export type ClientMsg = typeof ClientMsg.Type;
|
|
|
56
50
|
export declare const ServerMsg: Schema.Union<[Schema.Struct<{
|
|
57
51
|
Delta: Schema.Struct<{
|
|
58
52
|
crdt_id: typeof Schema.String;
|
|
59
|
-
delta_bytes:
|
|
53
|
+
delta_bytes: typeof Schema.Uint8ArrayFromSelf;
|
|
60
54
|
}>;
|
|
61
55
|
}>, Schema.Struct<{
|
|
62
56
|
Ack: Schema.Struct<{
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,uHAAuH;AACvH,eAAO,MAAM,WAAW,4GAGrB,CAAC;AACJ,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC,iGAAiG;AACjG,eAAO,MAAM,QAAQ,4GAGlB,CAAC;AACJ,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,eAAO,MAAM,WAAW;;;;EAItB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAC;AAElD,eAAO,MAAM,WAAW;;;;;;;;;EAKtB,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAC;AAElD,wEAAwE;AACxE,eAAO,MAAM,WAAW,4DAA8D,CAAC;AACvF,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAC;AAElD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;IAQrB,CAAC;AACF,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAC;AAE9C,eAAO,MAAM,SAAS;;;;;;;;;;;;;;IAWrB,CAAC;AACF,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAC;AAE9C,eAAO,MAAM,aAAa;;;EAGxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,eAAO,MAAM,cAAc;;EAEzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAExD,eAAO,MAAM,UAAU;;EAErB,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,IAAI,CAAC;AAEhD,eAAO,MAAM,QAAQ;;;;EAInB,CAAC;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAE5C,eAAO,MAAM,iBAAiB;;;EAG5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAC;AAE9D,eAAO,MAAM,aAAa;;;;;EAExB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,qFAAqF;AACrF,eAAO,MAAM,eAAe,uBAAiB,CAAC;AAC9C,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC;AAEtC,0FAA0F;AAC1F,eAAO,MAAM,cAAc,uBAAiB,CAAC;AAC7C,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC;AAErC,eAAO,MAAM,kBAAkB;;EAE7B,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAC;AAEhE,eAAO,MAAM,aAAa;;;EAGxB,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC"}
|