dynamodb-reactive 0.3.0 → 0.5.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 (48) hide show
  1. package/README.md +121 -43
  2. package/dist/{client-Zb67giI5.d.ts → client-wZQ9XC9S.d.ts} +11 -0
  3. package/dist/client.cjs +53 -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 +53 -55
  8. package/dist/client.js.map +1 -1
  9. package/dist/cloudflare.cjs +461 -0
  10. package/dist/cloudflare.cjs.map +1 -0
  11. package/dist/cloudflare.d.cts +129 -0
  12. package/dist/cloudflare.d.ts +129 -0
  13. package/dist/cloudflare.js +456 -0
  14. package/dist/cloudflare.js.map +1 -0
  15. package/dist/cluster.cjs +66 -66
  16. package/dist/cluster.cjs.map +1 -1
  17. package/dist/cluster.d.cts +2 -78
  18. package/dist/cluster.d.ts +2 -78
  19. package/dist/cluster.js +66 -66
  20. package/dist/cluster.js.map +1 -1
  21. package/dist/core.cjs +1 -3
  22. package/dist/core.cjs.map +1 -1
  23. package/dist/core.js +1 -3
  24. package/dist/core.js.map +1 -1
  25. package/dist/hub.cjs +164 -0
  26. package/dist/hub.cjs.map +1 -0
  27. package/dist/hub.d.cts +91 -0
  28. package/dist/hub.d.ts +91 -0
  29. package/dist/hub.js +157 -0
  30. package/dist/hub.js.map +1 -0
  31. package/dist/index.cjs +1 -3
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.js +1 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/infra.cjs +19 -33
  36. package/dist/infra.cjs.map +1 -1
  37. package/dist/infra.js +19 -33
  38. package/dist/infra.js.map +1 -1
  39. package/dist/protocol-C4Syt_Bl.d.ts +78 -0
  40. package/dist/react.cjs +56 -65
  41. package/dist/react.cjs.map +1 -1
  42. package/dist/react.js +56 -65
  43. package/dist/react.js.map +1 -1
  44. package/dist/server.cjs +46 -139
  45. package/dist/server.cjs.map +1 -1
  46. package/dist/server.js +46 -139
  47. package/dist/server.js.map +1 -1
  48. package/package.json +42 -24
package/dist/react.cjs CHANGED
@@ -33,9 +33,7 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
33
33
  if (Array.isArray(value)) {
34
34
  const items = [];
35
35
  for (let index = 0; index < value.length; index++) {
36
- items.push(
37
- canonicalize(value[index], seen, String(index), true) ?? "null"
38
- );
36
+ items.push(canonicalize(value[index], seen, String(index), true) ?? "null");
39
37
  }
40
38
  return `[${items.join(",")}]`;
41
39
  }
@@ -52,6 +50,9 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
52
50
  seen.delete(value);
53
51
  }
54
52
  }
53
+ case "bigint":
54
+ case "function":
55
+ case "symbol":
55
56
  default:
56
57
  throw new TypeError(`Unsupported query input value: ${typeof value}`);
57
58
  }
@@ -59,7 +60,6 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
59
60
  function getQueryKey(path, input) {
60
61
  return `${JSON.stringify(path)}:${canonicalize(input, /* @__PURE__ */ new Set(), "") ?? "undefined"}`;
61
62
  }
62
- var { applyPatch } = jsonpatch__default.default;
63
63
  function applyPatches(document2, patches) {
64
64
  if (patches.length === 0) {
65
65
  return document2;
@@ -78,7 +78,7 @@ function applyPatches(document2, patches) {
78
78
  return op;
79
79
  });
80
80
  try {
81
- const result = applyPatch(
81
+ const result = jsonpatch__default.default.applyPatch(
82
82
  structuredClone(document2),
83
83
  operations,
84
84
  true,
@@ -106,12 +106,13 @@ function error(...values) {
106
106
  }
107
107
 
108
108
  // ../client/src/websocket.ts
109
- var HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
110
109
  function withDefaults(config) {
111
110
  return {
112
111
  autoReconnect: true,
113
112
  connectionTimeout: 1e4,
114
113
  handshakeTimeout: 1e4,
114
+ heartbeatInterval: 5 * 60 * 1e3,
115
+ heartbeatTimeout: 15e3,
115
116
  maxReconnectAttempts: 10,
116
117
  reconnectDelay: 1e3,
117
118
  ...config
@@ -125,6 +126,7 @@ var WebSocketManager = class {
125
126
  reconnectTimer = null;
126
127
  handshakeTimer = null;
127
128
  heartbeatTimer = null;
129
+ pongTimer = null;
128
130
  messageHandlers = /* @__PURE__ */ new Set();
129
131
  stateHandlers = /* @__PURE__ */ new Set();
130
132
  handshakeHandlers = /* @__PURE__ */ new Set();
@@ -224,16 +226,7 @@ var WebSocketManager = class {
224
226
  };
225
227
  nextSocket.onclose = () => {
226
228
  if (!this.isCurrent(nextSocket, generation)) return;
227
- this.clearConnectionTimers();
228
- this.ws = null;
229
- this._connectionId = null;
230
- this.setState("disconnected");
231
- this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
232
- if (this.config.autoReconnect && !this.intentionalDisconnect) {
233
- this.scheduleReconnect();
234
- } else {
235
- this.rejectWaiters(new Error("WebSocket disconnected"));
236
- }
229
+ this.handleClose();
237
230
  };
238
231
  nextSocket.onerror = () => {
239
232
  if (!this.isCurrent(nextSocket, generation)) return;
@@ -322,6 +315,7 @@ var WebSocketManager = class {
322
315
  return;
323
316
  }
324
317
  if (message.type === "pong") {
318
+ this.clearPongTimer();
325
319
  if (!this._connectionId || this.state === "connected") return;
326
320
  this.clearHandshakeTimer();
327
321
  this.lastConnectionError = void 0;
@@ -350,6 +344,18 @@ var WebSocketManager = class {
350
344
  socket.close(4e3, "Handshake timeout");
351
345
  }, this.config.handshakeTimeout);
352
346
  }
347
+ handleClose() {
348
+ this.clearConnectionTimers();
349
+ this.ws = null;
350
+ this._connectionId = null;
351
+ this.setState("disconnected");
352
+ this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
353
+ if (this.config.autoReconnect && !this.intentionalDisconnect) {
354
+ this.scheduleReconnect();
355
+ } else {
356
+ this.rejectWaiters(new Error("WebSocket disconnected"));
357
+ }
358
+ }
353
359
  startHeartbeat(socket, generation) {
354
360
  this.clearHeartbeatTimer();
355
361
  this.heartbeatTimer = setInterval(() => {
@@ -357,7 +363,17 @@ var WebSocketManager = class {
357
363
  return;
358
364
  }
359
365
  socket.send(JSON.stringify({ type: "ping" }));
360
- }, HEARTBEAT_INTERVAL_MS);
366
+ this.pongTimer ??= setTimeout(() => {
367
+ this.pongTimer = null;
368
+ if (!this.isCurrent(socket, generation)) return;
369
+ const failure = new Error("WebSocket heartbeat timed out");
370
+ this.lastConnectionError = failure;
371
+ this.invokeLifecycleCallback("onError", this.config.onError, failure);
372
+ socket.onclose = null;
373
+ socket.close(4001, "Heartbeat timeout");
374
+ this.handleClose();
375
+ }, this.config.heartbeatTimeout);
376
+ }, this.config.heartbeatInterval);
361
377
  }
362
378
  isCurrent(socket, generation) {
363
379
  return this.ws === socket && this.socketGeneration === generation;
@@ -424,9 +440,15 @@ var WebSocketManager = class {
424
440
  clearInterval(this.heartbeatTimer);
425
441
  this.heartbeatTimer = null;
426
442
  }
443
+ clearPongTimer() {
444
+ if (!this.pongTimer) return;
445
+ clearTimeout(this.pongTimer);
446
+ this.pongTimer = null;
447
+ }
427
448
  clearConnectionTimers() {
428
449
  this.clearHandshakeTimer();
429
450
  this.clearHeartbeatTimer();
451
+ this.clearPongTimer();
430
452
  }
431
453
  clearTimers() {
432
454
  this.clearConnectionTimers();
@@ -526,10 +548,7 @@ var ReactiveClient = class {
526
548
  this.subscriptionsByKey.set(key, shared);
527
549
  this.subscriptionsById.set(shared.id, shared);
528
550
  if (this.connectionState === "connected") {
529
- void this.ensureRegistered(
530
- shared,
531
- this.wsManager.generation
532
- ).catch(() => void 0);
551
+ void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
533
552
  }
534
553
  }
535
554
  const leaseId = generateId();
@@ -538,9 +557,7 @@ var ReactiveClient = class {
538
557
  onError: options.onError
539
558
  });
540
559
  if (this.connectionState === "connected" && (!shared.hasRegistered || shared.stale) && !shared.registrationPromise) {
541
- void this.ensureRegistered(shared, this.wsManager.generation).catch(
542
- () => void 0
543
- );
560
+ void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
544
561
  }
545
562
  const state = shared;
546
563
  let released = false;
@@ -629,9 +646,7 @@ var ReactiveClient = class {
629
646
  registerActiveSubscriptions(socketGeneration) {
630
647
  for (const state of this.subscriptionsByKey.values()) {
631
648
  if (state.active) {
632
- void this.ensureRegistered(state, socketGeneration).catch(
633
- () => void 0
634
- );
649
+ void this.ensureRegistered(state, socketGeneration).catch(() => void 0);
635
650
  }
636
651
  }
637
652
  }
@@ -684,9 +699,7 @@ var ReactiveClient = class {
684
699
  })
685
700
  });
686
701
  if (!response.ok) {
687
- throw new Error(
688
- `Subscribe failed: ${response.status} ${response.statusText}`
689
- );
702
+ throw new Error(`Subscribe failed: ${response.status} ${response.statusText}`);
690
703
  }
691
704
  const result = await response.json();
692
705
  if (result.type === "error") {
@@ -705,12 +718,7 @@ var ReactiveClient = class {
705
718
  throw cause;
706
719
  } finally {
707
720
  if ((!state.active || !isCurrent()) && requestIssued) {
708
- void this.sendUnsubscribe(
709
- registrationConnectionId,
710
- state.id,
711
- generation,
712
- registrationId
713
- );
721
+ void this.sendUnsubscribe(registrationConnectionId, state.id, generation, registrationId);
714
722
  }
715
723
  if (state.registrationPromise === request) {
716
724
  state.registrationPromise = void 0;
@@ -815,15 +823,12 @@ var ReactiveClient = class {
815
823
  return;
816
824
  }
817
825
  this.discardStalePatches(state);
818
- while (true) {
819
- const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion)[0];
826
+ for (; ; ) {
827
+ const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion).at(0);
820
828
  if (!next) break;
821
829
  state.patchBuffer.delete(next.messageId);
822
830
  try {
823
- state.authoritativeData = applyPatches(
824
- state.authoritativeData,
825
- next.patches
826
- );
831
+ state.authoritativeData = applyPatches(state.authoritativeData, next.patches);
827
832
  state.version = next.toVersion;
828
833
  state.appliedMessageIds.add(next.messageId);
829
834
  const awaitingReconciliation = this.hasSettledLayers(state);
@@ -861,12 +866,7 @@ var ReactiveClient = class {
861
866
  state.stale = true;
862
867
  state.loading = true;
863
868
  this.notifySubscription(state);
864
- let request;
865
- request = this.ensureRegistered(
866
- state,
867
- this.wsManager.generation,
868
- true
869
- ).finally(() => {
869
+ const request = this.ensureRegistered(state, this.wsManager.generation, true).finally(() => {
870
870
  if (state.resyncPromise === request) {
871
871
  state.resyncPromise = void 0;
872
872
  state.resyncGeneration = void 0;
@@ -934,7 +934,10 @@ var ReactiveClient = class {
934
934
  if (options?.optimisticData === void 0) return void 0;
935
935
  return (data) => {
936
936
  if (!Array.isArray(data)) return data;
937
- 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;
937
+ const optimistic = settled ? (
938
+ // oxlint-disable-next-line typescript/prefer-nullish-coalescing -- null is a valid mutation result.
939
+ result === void 0 ? typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData : result
940
+ ) : typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData;
938
941
  if (settled && optimistic !== null && typeof optimistic === "object") {
939
942
  const optimisticId = optimistic.id;
940
943
  if (optimisticId !== void 0 && data.some(
@@ -949,12 +952,7 @@ var ReactiveClient = class {
949
952
  settleMutationLayers(mutationId, input, result, options, states) {
950
953
  for (const state of states) {
951
954
  const layer = state.optimisticLayers.get(mutationId);
952
- const updater = this.createOptimisticUpdater(
953
- input,
954
- options,
955
- result,
956
- true
957
- );
955
+ const updater = this.createOptimisticUpdater(input, options, result, true);
958
956
  if (!layer || !updater) continue;
959
957
  layer.updater = updater;
960
958
  layer.settledRevision = ++state.settlementRevision;
@@ -1100,9 +1098,7 @@ function ReactiveClientProvider({
1100
1098
  const state = renderedManaged.client.getConnectionState();
1101
1099
  if (state === "connected" || state === "connecting") return;
1102
1100
  if (state === "reconnecting") {
1103
- debug(
1104
- "[dynamodb-reactive] Tab active during reconnect; reconnecting now"
1105
- );
1101
+ debug("[dynamodb-reactive] Tab active during reconnect; reconnecting now");
1106
1102
  renderedManaged.client.forceReconnect();
1107
1103
  } else {
1108
1104
  void renderedManaged.client.connect().catch(() => void 0);
@@ -1120,9 +1116,7 @@ function ReactiveClientProvider({
1120
1116
  }, [renderedManaged]);
1121
1117
  react.useEffect(() => {
1122
1118
  if (!config?.url && !externalClient) {
1123
- warn(
1124
- "[dynamodb-reactive] WebSocket URL not configured; real-time features disabled"
1125
- );
1119
+ warn("[dynamodb-reactive] WebSocket URL not configured; real-time features disabled");
1126
1120
  }
1127
1121
  }, [config?.url, externalClient]);
1128
1122
  return /* @__PURE__ */ jsxRuntime.jsx(ReactiveClientContext.Provider, { value: renderedManaged?.client ?? null, children });
@@ -1170,10 +1164,7 @@ function useSubscription(path, options = {}) {
1170
1164
  };
1171
1165
  const setData = (updater) => {
1172
1166
  if (client && subscription) {
1173
- client.applyOptimisticUpdate(
1174
- subscription.id,
1175
- updater
1176
- );
1167
+ client.applyOptimisticUpdate(subscription.id, updater);
1177
1168
  }
1178
1169
  };
1179
1170
  return {