dynamodb-reactive 0.4.0 → 0.6.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.
Files changed (45) hide show
  1. package/README.md +93 -51
  2. package/dist/{client-Zb67giI5.d.ts → client-BPAct-Dz.d.ts} +19 -0
  3. package/dist/client.cjs +68 -55
  4. package/dist/client.cjs.map +1 -1
  5. package/dist/client.d.cts +1 -1
  6. package/dist/client.d.ts +1 -1
  7. package/dist/client.js +68 -55
  8. package/dist/client.js.map +1 -1
  9. package/dist/cloudflare.cjs +223 -36
  10. package/dist/cloudflare.cjs.map +1 -1
  11. package/dist/cloudflare.d.cts +78 -6
  12. package/dist/cloudflare.d.ts +78 -6
  13. package/dist/cloudflare.js +223 -36
  14. package/dist/cloudflare.js.map +1 -1
  15. package/dist/cluster.cjs +81 -66
  16. package/dist/cluster.cjs.map +1 -1
  17. package/dist/cluster.js +81 -66
  18. package/dist/cluster.js.map +1 -1
  19. package/dist/core.cjs +1 -3
  20. package/dist/core.cjs.map +1 -1
  21. package/dist/core.js +1 -3
  22. package/dist/core.js.map +1 -1
  23. package/dist/hub.cjs +35 -10
  24. package/dist/hub.cjs.map +1 -1
  25. package/dist/hub.d.cts +18 -3
  26. package/dist/hub.d.ts +18 -3
  27. package/dist/hub.js +35 -10
  28. package/dist/hub.js.map +1 -1
  29. package/dist/index.cjs +1 -3
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.js +1 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/infra.cjs +19 -33
  34. package/dist/infra.cjs.map +1 -1
  35. package/dist/infra.js +19 -33
  36. package/dist/infra.js.map +1 -1
  37. package/dist/react.cjs +71 -65
  38. package/dist/react.cjs.map +1 -1
  39. package/dist/react.js +71 -65
  40. package/dist/react.js.map +1 -1
  41. package/dist/server.cjs +46 -139
  42. package/dist/server.cjs.map +1 -1
  43. package/dist/server.js +46 -139
  44. package/dist/server.js.map +1 -1
  45. package/package.json +22 -24
package/dist/client.js CHANGED
@@ -23,9 +23,7 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
23
23
  if (Array.isArray(value)) {
24
24
  const items = [];
25
25
  for (let index = 0; index < value.length; index++) {
26
- items.push(
27
- canonicalize(value[index], seen, String(index), true) ?? "null"
28
- );
26
+ items.push(canonicalize(value[index], seen, String(index), true) ?? "null");
29
27
  }
30
28
  return `[${items.join(",")}]`;
31
29
  }
@@ -42,6 +40,9 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
42
40
  seen.delete(value);
43
41
  }
44
42
  }
43
+ case "bigint":
44
+ case "function":
45
+ case "symbol":
45
46
  default:
46
47
  throw new TypeError(`Unsupported query input value: ${typeof value}`);
47
48
  }
@@ -49,7 +50,6 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
49
50
  function getQueryKey(path, input) {
50
51
  return `${JSON.stringify(path)}:${canonicalize(input, /* @__PURE__ */ new Set(), "") ?? "undefined"}`;
51
52
  }
52
- var { applyPatch } = jsonpatch;
53
53
  function applyPatches(document, patches) {
54
54
  if (patches.length === 0) {
55
55
  return document;
@@ -68,7 +68,7 @@ function applyPatches(document, patches) {
68
68
  return op;
69
69
  });
70
70
  try {
71
- const result = applyPatch(
71
+ const result = jsonpatch.applyPatch(
72
72
  structuredClone(document),
73
73
  operations,
74
74
  true,
@@ -88,12 +88,13 @@ function error(...values) {
88
88
  }
89
89
 
90
90
  // ../client/src/websocket.ts
91
- var HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
92
91
  function withDefaults(config) {
93
92
  return {
94
93
  autoReconnect: true,
95
94
  connectionTimeout: 1e4,
96
95
  handshakeTimeout: 1e4,
96
+ heartbeatInterval: 5 * 60 * 1e3,
97
+ heartbeatTimeout: 15e3,
97
98
  maxReconnectAttempts: 10,
98
99
  reconnectDelay: 1e3,
99
100
  ...config
@@ -107,6 +108,7 @@ var WebSocketManager = class {
107
108
  reconnectTimer = null;
108
109
  handshakeTimer = null;
109
110
  heartbeatTimer = null;
111
+ pongTimer = null;
110
112
  messageHandlers = /* @__PURE__ */ new Set();
111
113
  stateHandlers = /* @__PURE__ */ new Set();
112
114
  handshakeHandlers = /* @__PURE__ */ new Set();
@@ -206,16 +208,7 @@ var WebSocketManager = class {
206
208
  };
207
209
  nextSocket.onclose = () => {
208
210
  if (!this.isCurrent(nextSocket, generation)) return;
209
- this.clearConnectionTimers();
210
- this.ws = null;
211
- this._connectionId = null;
212
- this.setState("disconnected");
213
- this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
214
- if (this.config.autoReconnect && !this.intentionalDisconnect) {
215
- this.scheduleReconnect();
216
- } else {
217
- this.rejectWaiters(new Error("WebSocket disconnected"));
218
- }
211
+ this.handleClose();
219
212
  };
220
213
  nextSocket.onerror = () => {
221
214
  if (!this.isCurrent(nextSocket, generation)) return;
@@ -274,6 +267,13 @@ var WebSocketManager = class {
274
267
  this.setState("disconnected");
275
268
  void this.connect().catch(() => void 0);
276
269
  }
270
+ /** Sends an application frame on the open socket; false when not connected. */
271
+ send(frame) {
272
+ const socket = this.ws;
273
+ if (!socket || this.state !== "connected" || socket.readyState !== socket.OPEN) return false;
274
+ socket.send(JSON.stringify(frame));
275
+ return true;
276
+ }
277
277
  onMessage(handler) {
278
278
  this.messageHandlers.add(handler);
279
279
  return () => this.messageHandlers.delete(handler);
@@ -304,6 +304,7 @@ var WebSocketManager = class {
304
304
  return;
305
305
  }
306
306
  if (message.type === "pong") {
307
+ this.clearPongTimer();
307
308
  if (!this._connectionId || this.state === "connected") return;
308
309
  this.clearHandshakeTimer();
309
310
  this.lastConnectionError = void 0;
@@ -332,6 +333,18 @@ var WebSocketManager = class {
332
333
  socket.close(4e3, "Handshake timeout");
333
334
  }, this.config.handshakeTimeout);
334
335
  }
336
+ handleClose() {
337
+ this.clearConnectionTimers();
338
+ this.ws = null;
339
+ this._connectionId = null;
340
+ this.setState("disconnected");
341
+ this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
342
+ if (this.config.autoReconnect && !this.intentionalDisconnect) {
343
+ this.scheduleReconnect();
344
+ } else {
345
+ this.rejectWaiters(new Error("WebSocket disconnected"));
346
+ }
347
+ }
335
348
  startHeartbeat(socket, generation) {
336
349
  this.clearHeartbeatTimer();
337
350
  this.heartbeatTimer = setInterval(() => {
@@ -339,7 +352,17 @@ var WebSocketManager = class {
339
352
  return;
340
353
  }
341
354
  socket.send(JSON.stringify({ type: "ping" }));
342
- }, HEARTBEAT_INTERVAL_MS);
355
+ this.pongTimer ??= setTimeout(() => {
356
+ this.pongTimer = null;
357
+ if (!this.isCurrent(socket, generation)) return;
358
+ const failure = new Error("WebSocket heartbeat timed out");
359
+ this.lastConnectionError = failure;
360
+ this.invokeLifecycleCallback("onError", this.config.onError, failure);
361
+ socket.onclose = null;
362
+ socket.close(4001, "Heartbeat timeout");
363
+ this.handleClose();
364
+ }, this.config.heartbeatTimeout);
365
+ }, this.config.heartbeatInterval);
343
366
  }
344
367
  isCurrent(socket, generation) {
345
368
  return this.ws === socket && this.socketGeneration === generation;
@@ -406,9 +429,15 @@ var WebSocketManager = class {
406
429
  clearInterval(this.heartbeatTimer);
407
430
  this.heartbeatTimer = null;
408
431
  }
432
+ clearPongTimer() {
433
+ if (!this.pongTimer) return;
434
+ clearTimeout(this.pongTimer);
435
+ this.pongTimer = null;
436
+ }
409
437
  clearConnectionTimers() {
410
438
  this.clearHandshakeTimer();
411
439
  this.clearHeartbeatTimer();
440
+ this.clearPongTimer();
412
441
  }
413
442
  clearTimers() {
414
443
  this.clearConnectionTimers();
@@ -468,6 +497,14 @@ var ReactiveClient = class {
468
497
  getConnectionState() {
469
498
  return this.connectionState;
470
499
  }
500
+ /**
501
+ * Sends an application frame over the socket, such as a presence update a
502
+ * hub's `onMessage` handles. Returns false while disconnected; resend from
503
+ * `onConnectionStateChange` once connected again.
504
+ */
505
+ send(frame) {
506
+ return this.wsManager.send(frame);
507
+ }
471
508
  onConnectionStateChange(listener) {
472
509
  this.stateListeners.add(listener);
473
510
  return () => this.stateListeners.delete(listener);
@@ -508,10 +545,7 @@ var ReactiveClient = class {
508
545
  this.subscriptionsByKey.set(key, shared);
509
546
  this.subscriptionsById.set(shared.id, shared);
510
547
  if (this.connectionState === "connected") {
511
- void this.ensureRegistered(
512
- shared,
513
- this.wsManager.generation
514
- ).catch(() => void 0);
548
+ void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
515
549
  }
516
550
  }
517
551
  const leaseId = generateId();
@@ -520,9 +554,7 @@ var ReactiveClient = class {
520
554
  onError: options.onError
521
555
  });
522
556
  if (this.connectionState === "connected" && (!shared.hasRegistered || shared.stale) && !shared.registrationPromise) {
523
- void this.ensureRegistered(shared, this.wsManager.generation).catch(
524
- () => void 0
525
- );
557
+ void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
526
558
  }
527
559
  const state = shared;
528
560
  let released = false;
@@ -611,9 +643,7 @@ var ReactiveClient = class {
611
643
  registerActiveSubscriptions(socketGeneration) {
612
644
  for (const state of this.subscriptionsByKey.values()) {
613
645
  if (state.active) {
614
- void this.ensureRegistered(state, socketGeneration).catch(
615
- () => void 0
616
- );
646
+ void this.ensureRegistered(state, socketGeneration).catch(() => void 0);
617
647
  }
618
648
  }
619
649
  }
@@ -666,9 +696,7 @@ var ReactiveClient = class {
666
696
  })
667
697
  });
668
698
  if (!response.ok) {
669
- throw new Error(
670
- `Subscribe failed: ${response.status} ${response.statusText}`
671
- );
699
+ throw new Error(`Subscribe failed: ${response.status} ${response.statusText}`);
672
700
  }
673
701
  const result = await response.json();
674
702
  if (result.type === "error") {
@@ -687,12 +715,7 @@ var ReactiveClient = class {
687
715
  throw cause;
688
716
  } finally {
689
717
  if ((!state.active || !isCurrent()) && requestIssued) {
690
- void this.sendUnsubscribe(
691
- registrationConnectionId,
692
- state.id,
693
- generation,
694
- registrationId
695
- );
718
+ void this.sendUnsubscribe(registrationConnectionId, state.id, generation, registrationId);
696
719
  }
697
720
  if (state.registrationPromise === request) {
698
721
  state.registrationPromise = void 0;
@@ -797,15 +820,12 @@ var ReactiveClient = class {
797
820
  return;
798
821
  }
799
822
  this.discardStalePatches(state);
800
- while (true) {
801
- const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion)[0];
823
+ for (; ; ) {
824
+ const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion).at(0);
802
825
  if (!next) break;
803
826
  state.patchBuffer.delete(next.messageId);
804
827
  try {
805
- state.authoritativeData = applyPatches(
806
- state.authoritativeData,
807
- next.patches
808
- );
828
+ state.authoritativeData = applyPatches(state.authoritativeData, next.patches);
809
829
  state.version = next.toVersion;
810
830
  state.appliedMessageIds.add(next.messageId);
811
831
  const awaitingReconciliation = this.hasSettledLayers(state);
@@ -843,12 +863,7 @@ var ReactiveClient = class {
843
863
  state.stale = true;
844
864
  state.loading = true;
845
865
  this.notifySubscription(state);
846
- let request;
847
- request = this.ensureRegistered(
848
- state,
849
- this.wsManager.generation,
850
- true
851
- ).finally(() => {
866
+ const request = this.ensureRegistered(state, this.wsManager.generation, true).finally(() => {
852
867
  if (state.resyncPromise === request) {
853
868
  state.resyncPromise = void 0;
854
869
  state.resyncGeneration = void 0;
@@ -916,7 +931,10 @@ var ReactiveClient = class {
916
931
  if (options?.optimisticData === void 0) return void 0;
917
932
  return (data) => {
918
933
  if (!Array.isArray(data)) return data;
919
- const optimistic = settled ? result === void 0 ? typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData : result : typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData;
934
+ const optimistic = settled ? (
935
+ // oxlint-disable-next-line typescript/prefer-nullish-coalescing -- null is a valid mutation result.
936
+ result === void 0 ? typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData : result
937
+ ) : typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData;
920
938
  if (settled && optimistic !== null && typeof optimistic === "object") {
921
939
  const optimisticId = optimistic.id;
922
940
  if (optimisticId !== void 0 && data.some(
@@ -931,12 +949,7 @@ var ReactiveClient = class {
931
949
  settleMutationLayers(mutationId, input, result, options, states) {
932
950
  for (const state of states) {
933
951
  const layer = state.optimisticLayers.get(mutationId);
934
- const updater = this.createOptimisticUpdater(
935
- input,
936
- options,
937
- result,
938
- true
939
- );
952
+ const updater = this.createOptimisticUpdater(input, options, result, true);
940
953
  if (!layer || !updater) continue;
941
954
  layer.updater = updater;
942
955
  layer.settledRevision = ++state.settlementRevision;