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.
Files changed (84) hide show
  1. package/biome.json +4 -0
  2. package/dist/auth/token.d.ts +0 -19
  3. package/dist/auth/token.d.ts.map +1 -1
  4. package/dist/auth/token.js +6 -31
  5. package/dist/auth/token.js.map +1 -1
  6. package/dist/client.d.ts +139 -23
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +165 -52
  9. package/dist/client.js.map +1 -1
  10. package/dist/codec.d.ts +7 -35
  11. package/dist/codec.d.ts.map +1 -1
  12. package/dist/codec.js +13 -65
  13. package/dist/codec.js.map +1 -1
  14. package/dist/constants.d.ts +7 -0
  15. package/dist/constants.d.ts.map +1 -0
  16. package/dist/constants.js +7 -0
  17. package/dist/constants.js.map +1 -0
  18. package/dist/crdt/gcounter.d.ts +18 -9
  19. package/dist/crdt/gcounter.d.ts.map +1 -1
  20. package/dist/crdt/gcounter.js +16 -13
  21. package/dist/crdt/gcounter.js.map +1 -1
  22. package/dist/crdt/lwwregister.d.ts +24 -11
  23. package/dist/crdt/lwwregister.d.ts.map +1 -1
  24. package/dist/crdt/lwwregister.js +25 -19
  25. package/dist/crdt/lwwregister.js.map +1 -1
  26. package/dist/crdt/orset.d.ts +25 -13
  27. package/dist/crdt/orset.d.ts.map +1 -1
  28. package/dist/crdt/orset.js +31 -23
  29. package/dist/crdt/orset.js.map +1 -1
  30. package/dist/crdt/pncounter.d.ts +22 -4
  31. package/dist/crdt/pncounter.d.ts.map +1 -1
  32. package/dist/crdt/pncounter.js +28 -14
  33. package/dist/crdt/pncounter.js.map +1 -1
  34. package/dist/crdt/presence.d.ts +33 -13
  35. package/dist/crdt/presence.d.ts.map +1 -1
  36. package/dist/crdt/presence.js +36 -20
  37. package/dist/crdt/presence.js.map +1 -1
  38. package/dist/errors.d.ts +0 -4
  39. package/dist/errors.d.ts.map +1 -1
  40. package/dist/errors.js +0 -16
  41. package/dist/errors.js.map +1 -1
  42. package/dist/schema.d.ts +3 -9
  43. package/dist/schema.d.ts.map +1 -1
  44. package/dist/schema.js +3 -34
  45. package/dist/schema.js.map +1 -1
  46. package/dist/sync/clock.d.ts +1 -20
  47. package/dist/sync/clock.d.ts.map +1 -1
  48. package/dist/sync/clock.js +20 -46
  49. package/dist/sync/clock.js.map +1 -1
  50. package/dist/sync/delta.d.ts +5 -22
  51. package/dist/sync/delta.d.ts.map +1 -1
  52. package/dist/sync/delta.js +18 -26
  53. package/dist/sync/delta.js.map +1 -1
  54. package/dist/transport/http.d.ts +1 -14
  55. package/dist/transport/http.d.ts.map +1 -1
  56. package/dist/transport/http.js +3 -21
  57. package/dist/transport/http.js.map +1 -1
  58. package/dist/transport/websocket.d.ts +0 -27
  59. package/dist/transport/websocket.d.ts.map +1 -1
  60. package/dist/transport/websocket.js +9 -37
  61. package/dist/transport/websocket.js.map +1 -1
  62. package/dist/utils/to-hex.d.ts +2 -0
  63. package/dist/utils/to-hex.d.ts.map +1 -0
  64. package/dist/utils/to-hex.js +2 -0
  65. package/dist/utils/to-hex.js.map +1 -0
  66. package/package.json +6 -3
  67. package/src/auth/token.ts +6 -34
  68. package/src/client.ts +165 -65
  69. package/src/codec.ts +13 -71
  70. package/src/constants.ts +6 -0
  71. package/src/crdt/gcounter.ts +18 -20
  72. package/src/crdt/lwwregister.ts +27 -26
  73. package/src/crdt/orset.ts +32 -29
  74. package/src/crdt/pncounter.ts +30 -21
  75. package/src/crdt/presence.ts +37 -26
  76. package/src/errors.ts +0 -21
  77. package/src/schema.ts +3 -44
  78. package/src/sync/clock.ts +18 -50
  79. package/src/sync/delta.ts +20 -58
  80. package/src/transport/http.ts +3 -33
  81. package/src/transport/websocket.ts +15 -52
  82. package/src/utils/to-hex.ts +1 -0
  83. package/test/integration.test.ts +2 -3
  84. package/test/sync.test.ts +1 -2
@@ -1,22 +1,13 @@
1
- /**
2
- * WebSocket transport — reconnect FSM + Sync on reconnect.
3
- *
4
- * States:
5
- * DISCONNECTED → CONNECTING → AUTHENTICATING → CONNECTED → CLOSING
6
- *
7
- * On reconnect: re-subscribes to all known CRDTs and sends Sync(localVectorClock)
8
- * so the server can push missed deltas.
9
- *
10
- * Backoff: 100ms → 200ms → 400ms → … → 30s (±20% jitter).
11
- */
12
-
13
1
  import { Effect } from "effect";
14
2
  import { encodeClientMsg, decodeServerMsg, encodeVectorClock } from "../codec.js";
15
3
  import type { ServerMsg, VectorClock } from "../schema.js";
16
-
17
- // ---------------------------------------------------------------------------
18
- // Types
19
- // ---------------------------------------------------------------------------
4
+ import {
5
+ BACKOFF_INITIAL_MS,
6
+ BACKOFF_MAX_MS,
7
+ BACKOFF_MULTIPLIER,
8
+ JITTER_MULTIPLIER,
9
+ DEFAULT_TIMEOUT_MS,
10
+ } from "../constants.js";
20
11
 
21
12
  export type WsState =
22
13
  | "DISCONNECTED"
@@ -25,49 +16,36 @@ export type WsState =
25
16
  | "CLOSING";
26
17
 
27
18
  export interface WsTransportConfig {
28
- /** Full WebSocket URL, e.g. "ws://localhost:3000/v1/namespaces/my-room/connect" */
29
19
  url: string;
30
- /** Bearer token passed as ?token= query param (WS can't set headers). */
31
20
  token: string;
32
- /** Called whenever a ServerMsg arrives. */
33
21
  onMessage: (msg: ServerMsg) => void;
34
- /** Called on state transitions. */
35
22
  onStateChange?: (state: WsState) => void;
36
- /** Maximum reconnect delay in ms. Default: 30_000 */
37
23
  maxBackoffMs?: number;
38
24
  }
39
25
 
40
- // ---------------------------------------------------------------------------
41
- // WsTransport
42
- // ---------------------------------------------------------------------------
43
-
44
26
  export class WsTransport {
45
27
  private readonly config: WsTransportConfig;
46
28
  private readonly maxBackoffMs: number;
47
29
 
48
30
  private ws: WebSocket | null = null;
49
31
  private state: WsState = "DISCONNECTED";
50
- private backoffMs = 100;
32
+ private backoffMs = BACKOFF_INITIAL_MS;
51
33
  private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
52
34
  private closed = false;
53
35
 
54
- /** CRDTs to re-subscribe on reconnect: crdt_id → last known VectorClock */
55
36
  private readonly subscriptions = new Map<string, VectorClock>();
56
37
 
57
38
  constructor(config: WsTransportConfig) {
58
39
  this.config = config;
59
- this.maxBackoffMs = config.maxBackoffMs ?? 30_000;
40
+ this.maxBackoffMs = config.maxBackoffMs ?? BACKOFF_MAX_MS;
60
41
  }
61
42
 
62
- // ---- Public API ----
63
-
64
43
  connect(): void {
65
44
  if (this.closed) return;
66
45
  this.closed = false;
67
46
  this.doConnect();
68
47
  }
69
48
 
70
- /** Gracefully close — will not reconnect. */
71
49
  close(): void {
72
50
  this.closed = true;
73
51
  this.clearReconnectTimer();
@@ -75,11 +53,6 @@ export class WsTransport {
75
53
  this.ws?.close(1000, "client close");
76
54
  }
77
55
 
78
- /**
79
- * Subscribe to a CRDT's deltas.
80
- * If already connected, sends Subscribe immediately.
81
- * On reconnect, the subscription is re-sent automatically.
82
- */
83
56
  subscribe(crdtId: string, sinceVc: VectorClock = {}): void {
84
57
  this.subscriptions.set(crdtId, sinceVc);
85
58
  if (this.state === "CONNECTED") {
@@ -87,12 +60,10 @@ export class WsTransport {
87
60
  }
88
61
  }
89
62
 
90
- /** Update the local vector clock for a CRDT (used for reconnect Sync). */
91
63
  updateClock(crdtId: string, vc: VectorClock): void {
92
64
  this.subscriptions.set(crdtId, vc);
93
65
  }
94
66
 
95
- /** Send a raw ClientMsg. Throws if not connected. */
96
67
  send(msg: Parameters<typeof encodeClientMsg>[0]): void {
97
68
  if (this.state !== "CONNECTED" || this.ws === null) {
98
69
  throw new Error("WsTransport: not connected");
@@ -104,8 +75,7 @@ export class WsTransport {
104
75
  return this.state;
105
76
  }
106
77
 
107
- /** Resolves when the transport reaches CONNECTED state (or rejects on timeout). */
108
- waitForConnected(timeoutMs = 5_000): Promise<void> {
78
+ waitForConnected(timeoutMs = DEFAULT_TIMEOUT_MS): Promise<void> {
109
79
  if (this.state === "CONNECTED") return Promise.resolve();
110
80
  return new Promise((resolve, reject) => {
111
81
  const orig = this.config.onStateChange;
@@ -131,8 +101,6 @@ export class WsTransport {
131
101
  });
132
102
  }
133
103
 
134
- // ---- FSM internals ----
135
-
136
104
  private doConnect(): void {
137
105
  if (this.closed) return;
138
106
  this.transitionTo("CONNECTING");
@@ -143,8 +111,8 @@ export class WsTransport {
143
111
  this.ws = ws;
144
112
 
145
113
  ws.addEventListener("open", () => {
146
- if (ws !== this.ws) return; // stale socket
147
- this.backoffMs = 100; // reset backoff on successful connect
114
+ if (ws !== this.ws) return;
115
+ this.backoffMs = BACKOFF_INITIAL_MS;
148
116
  this.transitionTo("CONNECTED");
149
117
  this.resubscribeAll();
150
118
  });
@@ -168,19 +136,19 @@ export class WsTransport {
168
136
  });
169
137
 
170
138
  ws.addEventListener("error", () => {
171
- // The "close" event fires right after — let that handle reconnect.
139
+ // HACK: The "close" event fires right after an error — let that handler drive reconnect logic.
172
140
  });
173
141
  }
174
142
 
175
143
  private scheduleReconnect(): void {
176
144
  if (this.closed) return;
177
- const jitter = this.backoffMs * 0.2 * (Math.random() * 2 - 1); // ±20%
145
+ const jitter = this.backoffMs * JITTER_MULTIPLIER * (Math.random() * 2 - 1);
178
146
  const delay = Math.round(this.backoffMs + jitter);
179
147
  this.reconnectTimer = setTimeout(() => {
180
148
  this.reconnectTimer = null;
181
149
  this.doConnect();
182
150
  }, delay);
183
- this.backoffMs = Math.min(this.backoffMs * 2, this.maxBackoffMs);
151
+ this.backoffMs = Math.min(this.backoffMs * BACKOFF_MULTIPLIER, this.maxBackoffMs);
184
152
  }
185
153
 
186
154
  private clearReconnectTimer(): void {
@@ -196,7 +164,6 @@ export class WsTransport {
196
164
  this.config.onStateChange?.(next);
197
165
  }
198
166
 
199
- /** On reconnect: re-subscribe and send Sync for each known CRDT. */
200
167
  private resubscribeAll(): void {
201
168
  for (const [crdtId, vc] of this.subscriptions) {
202
169
  this.sendSubscribe(crdtId, vc);
@@ -205,11 +172,7 @@ export class WsTransport {
205
172
 
206
173
  private sendSubscribe(crdtId: string, vc: VectorClock): void {
207
174
  if (this.ws === null || this.state !== "CONNECTED") return;
208
-
209
- // First subscribe so the server starts pushing future deltas
210
175
  this.ws.send(encodeClientMsg({ Subscribe: { crdt_id: crdtId } }));
211
-
212
- // Then sync to get missed deltas since our last known VC
213
176
  const vcBytes = encodeVectorClock(vc);
214
177
  this.ws.send(encodeClientMsg({ Sync: { crdt_id: crdtId, since_vc: vcBytes } }));
215
178
  }
@@ -0,0 +1 @@
1
+ export const toHex = (value: number): string => value.toString(16).padStart(2, "0");
@@ -15,7 +15,7 @@ import { describe, test, expect, beforeAll, afterAll } from "bun:test";
15
15
  import { Effect } from "effect";
16
16
  import { MeridianClient } from "../src/client.js";
17
17
  import { HttpClient } from "../src/transport/http.js";
18
- import { uuidToBytes, wallMsToBigInt } from "../src/codec.js";
18
+ import { uuidToBytes } from "../src/codec.js";
19
19
 
20
20
  // ---------------------------------------------------------------------------
21
21
  // Config — matches the dev signing key [0x42; 32]
@@ -145,8 +145,7 @@ describe("Meridian integration", () => {
145
145
 
146
146
  skip("LwwRegister: set and read", async () => {
147
147
  const key = `lw:${Date.now()}`;
148
- // wall_ms must be BigInt — Rust u64 requires integer msgpack encoding
149
- const now = wallMsToBigInt(Date.now());
148
+ const now = Date.now();
150
149
 
151
150
  await Effect.runPromise(http.postOp(NAMESPACE, key, {
152
151
  LwwRegister: {
package/test/sync.test.ts CHANGED
@@ -8,7 +8,6 @@ import { VectorClockTracker } from "../src/sync/clock.js";
8
8
  import { encode, decode, encodeClientMsg, decodeServerMsg } from "../src/codec.js";
9
9
  import { parseToken, checkTokenExpiry } from "../src/auth/token.js";
10
10
  import { CodecError, TokenParseError, TokenExpiredError } from "../src/errors.js";
11
- import { pack } from "msgpackr";
12
11
 
13
12
  // ---------------------------------------------------------------------------
14
13
  // VectorClockTracker
@@ -124,7 +123,7 @@ describe("codec", () => {
124
123
 
125
124
  describe("parseToken", () => {
126
125
  function makeToken(claims: object): string {
127
- const payload = pack(claims);
126
+ const payload = encode(claims);
128
127
  const b64 = btoa(String.fromCharCode(...payload))
129
128
  .replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
130
129
  return `${b64}.fakesig`;