@xnetjs/runtime 0.3.1 → 0.3.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/dist/index.d.ts CHANGED
@@ -605,10 +605,7 @@ declare class WebSocketSyncProvider {
605
605
  readonly url: string;
606
606
  readonly awareness: Awareness;
607
607
  private ws;
608
- private reconnectDelay;
609
- private maxReconnectAttempts;
610
- private reconnectAttempts;
611
- private reconnectTimer;
608
+ private reconnectScheduler;
612
609
  private destroyed;
613
610
  private connected;
614
611
  private synced;
package/dist/index.js CHANGED
@@ -147,6 +147,43 @@ function createBlobSyncProvider(config) {
147
147
  };
148
148
  }
149
149
 
150
+ // src/sync/connection-manager.ts
151
+ import { capped, exponential, fixed, jittered, limitAttempts } from "@xnetjs/core";
152
+
153
+ // src/sync/reconnect-scheduler.ts
154
+ function createReconnectScheduler(options) {
155
+ let attempts = 0;
156
+ let timer = null;
157
+ return {
158
+ get attempts() {
159
+ return attempts;
160
+ },
161
+ get pending() {
162
+ return timer !== null;
163
+ },
164
+ schedule() {
165
+ if (timer) return false;
166
+ const delay = options.policy().delayFor(attempts + 1);
167
+ if (delay === null) return false;
168
+ attempts++;
169
+ timer = setTimeout(() => {
170
+ timer = null;
171
+ options.onRetry();
172
+ }, delay);
173
+ return true;
174
+ },
175
+ reset() {
176
+ attempts = 0;
177
+ },
178
+ cancel() {
179
+ if (timer) {
180
+ clearTimeout(timer);
181
+ timer = null;
182
+ }
183
+ }
184
+ };
185
+ }
186
+
150
187
  // src/sync/connection-manager.ts
151
188
  function log(...args) {
152
189
  if (typeof localStorage !== "undefined" && localStorage.getItem("xnet:sync:debug") === "true") {
@@ -156,8 +193,6 @@ function log(...args) {
156
193
  function createConnectionManager(config) {
157
194
  let ws = null;
158
195
  let status = "disconnected";
159
- let reconnectAttempts = 0;
160
- let reconnectTimer = null;
161
196
  let connectTimer = null;
162
197
  let destroyed = false;
163
198
  let connectInProgress = false;
@@ -168,6 +203,15 @@ function createConnectionManager(config) {
168
203
  const maxReconnects = config.maxReconnects ?? Infinity;
169
204
  const connectTimeout = config.connectTimeout ?? 1e4;
170
205
  const initialConnectTimeout = config.initialConnectTimeout ?? (connectTimeout > 0 ? Math.min(connectTimeout, 6e3) : 0);
206
+ const reconnectPolicy = limitAttempts(
207
+ capped(exponential(reconnectDelay), maxReconnectDelay),
208
+ maxReconnects
209
+ );
210
+ const rateLimitPolicy = limitAttempts(jittered(fixed(rateLimitBackoffMs)), maxReconnects);
211
+ const reconnectScheduler = createReconnectScheduler({
212
+ policy: () => policyViolation ? rateLimitPolicy : reconnectPolicy,
213
+ onRetry: () => void doConnect()
214
+ });
171
215
  function clearConnectTimer() {
172
216
  if (connectTimer) {
173
217
  clearTimeout(connectTimer);
@@ -197,7 +241,7 @@ function createConnectionManager(config) {
197
241
  scheduleReconnect();
198
242
  }
199
243
  function armConnectTimeout() {
200
- const timeout = reconnectAttempts === 0 ? initialConnectTimeout : connectTimeout;
244
+ const timeout = reconnectScheduler.attempts === 0 ? initialConnectTimeout : connectTimeout;
201
245
  if (timeout <= 0 || !Number.isFinite(timeout)) return;
202
246
  connectTimer = setTimeout(onConnectTimeout, timeout);
203
247
  }
@@ -317,7 +361,7 @@ function createConnectionManager(config) {
317
361
  connectInProgress = false;
318
362
  log("WebSocket connected");
319
363
  setStatus("connected");
320
- reconnectAttempts = 0;
364
+ reconnectScheduler.reset();
321
365
  policyViolation = false;
322
366
  if (rooms.size > 0) {
323
367
  const roomList = Array.from(rooms.keys());
@@ -350,20 +394,10 @@ function createConnectionManager(config) {
350
394
  }
351
395
  }
352
396
  function scheduleReconnect() {
353
- if (destroyed || reconnectAttempts >= maxReconnects) return;
354
- if (reconnectTimer) return;
355
- reconnectAttempts++;
356
- let backoff;
357
- if (policyViolation) {
397
+ if (destroyed) return;
398
+ if (reconnectScheduler.schedule()) {
358
399
  policyViolation = false;
359
- backoff = rateLimitBackoffMs + Math.floor(Math.random() * rateLimitBackoffMs * 0.5);
360
- } else {
361
- backoff = Math.min(reconnectDelay * 2 ** (reconnectAttempts - 1), maxReconnectDelay);
362
400
  }
363
- reconnectTimer = setTimeout(() => {
364
- reconnectTimer = null;
365
- void doConnect();
366
- }, backoff);
367
401
  }
368
402
  return {
369
403
  get status() {
@@ -379,10 +413,7 @@ function createConnectionManager(config) {
379
413
  disconnect() {
380
414
  destroyed = true;
381
415
  clearConnectTimer();
382
- if (reconnectTimer) {
383
- clearTimeout(reconnectTimer);
384
- reconnectTimer = null;
385
- }
416
+ reconnectScheduler.cancel();
386
417
  if (ws) {
387
418
  if (rooms.size > 0) {
388
419
  send({ type: "unsubscribe", topics: Array.from(rooms.keys()) });
@@ -2865,6 +2896,7 @@ async function runAdapterConformance(makeClient) {
2865
2896
  }
2866
2897
 
2867
2898
  // src/sync/WebSocketSyncProvider.ts
2899
+ import { fixed as fixed2, limitAttempts as limitAttempts2 } from "@xnetjs/core";
2868
2900
  import { resolveSyncReplicationPolicy as resolveSyncReplicationPolicy2, signYjsUpdate as signYjsUpdate2, verifyYjsEnvelopeV1 as verifyYjsEnvelopeV12 } from "@xnetjs/sync";
2869
2901
  import {
2870
2902
  Awareness as Awareness2,
@@ -2930,10 +2962,7 @@ var WebSocketSyncProvider = class {
2930
2962
  url;
2931
2963
  awareness;
2932
2964
  ws = null;
2933
- reconnectDelay;
2934
- maxReconnectAttempts;
2935
- reconnectAttempts = 0;
2936
- reconnectTimer = null;
2965
+ reconnectScheduler;
2937
2966
  destroyed = false;
2938
2967
  connected = false;
2939
2968
  synced = false;
@@ -2947,8 +2976,14 @@ var WebSocketSyncProvider = class {
2947
2976
  this.room = options.room;
2948
2977
  this.url = options.url;
2949
2978
  this.options = options;
2950
- this.reconnectDelay = options.reconnectDelay ?? 2e3;
2951
- this.maxReconnectAttempts = options.maxReconnectAttempts ?? Infinity;
2979
+ const reconnectPolicy = limitAttempts2(
2980
+ fixed2(options.reconnectDelay ?? 2e3),
2981
+ options.maxReconnectAttempts ?? Infinity
2982
+ );
2983
+ this.reconnectScheduler = createReconnectScheduler({
2984
+ policy: () => reconnectPolicy,
2985
+ onRetry: () => this._connect()
2986
+ });
2952
2987
  this.replicationPolicy = resolveSyncReplicationPolicy2(options.replication);
2953
2988
  this.peerId = Math.random().toString(36).slice(2, 10);
2954
2989
  this.awareness = new Awareness2(doc);
@@ -2988,10 +3023,7 @@ var WebSocketSyncProvider = class {
2988
3023
  this.doc.off("update", this._onDocUpdate);
2989
3024
  this.awareness.off("update", this._onAwarenessUpdate);
2990
3025
  removeAwarenessStates2(this.awareness, [this.doc.clientID], this);
2991
- if (this.reconnectTimer) {
2992
- clearTimeout(this.reconnectTimer);
2993
- this.reconnectTimer = null;
2994
- }
3026
+ this.reconnectScheduler.cancel();
2995
3027
  if (this.ws) {
2996
3028
  this._send({ type: "unsubscribe", topics: [this.room] });
2997
3029
  this.ws.close();
@@ -3008,7 +3040,7 @@ var WebSocketSyncProvider = class {
3008
3040
  this.ws.onopen = () => {
3009
3041
  log3(this, "WebSocket connected to", this.url);
3010
3042
  this.connected = true;
3011
- this.reconnectAttempts = 0;
3043
+ this.reconnectScheduler.reset();
3012
3044
  this.emit("status", { connected: true });
3013
3045
  log3(this, "Subscribing to room:", this.room);
3014
3046
  this._send({ type: "subscribe", topics: [this.room] });
@@ -3055,11 +3087,7 @@ var WebSocketSyncProvider = class {
3055
3087
  }
3056
3088
  _scheduleReconnect() {
3057
3089
  if (this.destroyed) return;
3058
- if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
3059
- this.reconnectAttempts++;
3060
- this.reconnectTimer = setTimeout(() => {
3061
- this._connect();
3062
- }, this.reconnectDelay);
3090
+ this.reconnectScheduler.schedule();
3063
3091
  }
3064
3092
  _send(msg) {
3065
3093
  if (this.ws?.readyState === WebSocket.OPEN) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnetjs/runtime",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Framework-agnostic xNet runtime: createXNetClient() and sync orchestration, usable from any framework, a CLI, a worker, or a Node service",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,15 +28,15 @@
28
28
  "dependencies": {
29
29
  "y-protocols": "^1.0.6",
30
30
  "yjs": "^13.6.24",
31
- "@xnetjs/core": "0.11.1",
32
- "@xnetjs/crypto": "0.11.1",
33
- "@xnetjs/data": "0.11.1",
34
- "@xnetjs/data-bridge": "0.11.1",
35
- "@xnetjs/history": "0.11.1",
36
- "@xnetjs/identity": "0.11.1",
37
- "@xnetjs/plugins": "0.11.1",
38
- "@xnetjs/storage": "0.11.1",
39
- "@xnetjs/sync": "0.11.1"
31
+ "@xnetjs/core": "0.12.0",
32
+ "@xnetjs/crypto": "0.12.0",
33
+ "@xnetjs/data": "0.12.0",
34
+ "@xnetjs/data-bridge": "0.12.0",
35
+ "@xnetjs/history": "0.12.0",
36
+ "@xnetjs/identity": "0.12.0",
37
+ "@xnetjs/plugins": "0.12.0",
38
+ "@xnetjs/storage": "0.12.0",
39
+ "@xnetjs/sync": "0.12.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "jsdom": "^26.0.0",