dynamodb-reactive 0.4.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.
package/dist/react.js CHANGED
@@ -27,9 +27,7 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
27
27
  if (Array.isArray(value)) {
28
28
  const items = [];
29
29
  for (let index = 0; index < value.length; index++) {
30
- items.push(
31
- canonicalize(value[index], seen, String(index), true) ?? "null"
32
- );
30
+ items.push(canonicalize(value[index], seen, String(index), true) ?? "null");
33
31
  }
34
32
  return `[${items.join(",")}]`;
35
33
  }
@@ -46,6 +44,9 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
46
44
  seen.delete(value);
47
45
  }
48
46
  }
47
+ case "bigint":
48
+ case "function":
49
+ case "symbol":
49
50
  default:
50
51
  throw new TypeError(`Unsupported query input value: ${typeof value}`);
51
52
  }
@@ -53,7 +54,6 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
53
54
  function getQueryKey(path, input) {
54
55
  return `${JSON.stringify(path)}:${canonicalize(input, /* @__PURE__ */ new Set(), "") ?? "undefined"}`;
55
56
  }
56
- var { applyPatch } = jsonpatch;
57
57
  function applyPatches(document2, patches) {
58
58
  if (patches.length === 0) {
59
59
  return document2;
@@ -72,7 +72,7 @@ function applyPatches(document2, patches) {
72
72
  return op;
73
73
  });
74
74
  try {
75
- const result = applyPatch(
75
+ const result = jsonpatch.applyPatch(
76
76
  structuredClone(document2),
77
77
  operations,
78
78
  true,
@@ -100,12 +100,13 @@ function error(...values) {
100
100
  }
101
101
 
102
102
  // ../client/src/websocket.ts
103
- var HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
104
103
  function withDefaults(config) {
105
104
  return {
106
105
  autoReconnect: true,
107
106
  connectionTimeout: 1e4,
108
107
  handshakeTimeout: 1e4,
108
+ heartbeatInterval: 5 * 60 * 1e3,
109
+ heartbeatTimeout: 15e3,
109
110
  maxReconnectAttempts: 10,
110
111
  reconnectDelay: 1e3,
111
112
  ...config
@@ -119,6 +120,7 @@ var WebSocketManager = class {
119
120
  reconnectTimer = null;
120
121
  handshakeTimer = null;
121
122
  heartbeatTimer = null;
123
+ pongTimer = null;
122
124
  messageHandlers = /* @__PURE__ */ new Set();
123
125
  stateHandlers = /* @__PURE__ */ new Set();
124
126
  handshakeHandlers = /* @__PURE__ */ new Set();
@@ -218,16 +220,7 @@ var WebSocketManager = class {
218
220
  };
219
221
  nextSocket.onclose = () => {
220
222
  if (!this.isCurrent(nextSocket, generation)) return;
221
- this.clearConnectionTimers();
222
- this.ws = null;
223
- this._connectionId = null;
224
- this.setState("disconnected");
225
- this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
226
- if (this.config.autoReconnect && !this.intentionalDisconnect) {
227
- this.scheduleReconnect();
228
- } else {
229
- this.rejectWaiters(new Error("WebSocket disconnected"));
230
- }
223
+ this.handleClose();
231
224
  };
232
225
  nextSocket.onerror = () => {
233
226
  if (!this.isCurrent(nextSocket, generation)) return;
@@ -316,6 +309,7 @@ var WebSocketManager = class {
316
309
  return;
317
310
  }
318
311
  if (message.type === "pong") {
312
+ this.clearPongTimer();
319
313
  if (!this._connectionId || this.state === "connected") return;
320
314
  this.clearHandshakeTimer();
321
315
  this.lastConnectionError = void 0;
@@ -344,6 +338,18 @@ var WebSocketManager = class {
344
338
  socket.close(4e3, "Handshake timeout");
345
339
  }, this.config.handshakeTimeout);
346
340
  }
341
+ handleClose() {
342
+ this.clearConnectionTimers();
343
+ this.ws = null;
344
+ this._connectionId = null;
345
+ this.setState("disconnected");
346
+ this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
347
+ if (this.config.autoReconnect && !this.intentionalDisconnect) {
348
+ this.scheduleReconnect();
349
+ } else {
350
+ this.rejectWaiters(new Error("WebSocket disconnected"));
351
+ }
352
+ }
347
353
  startHeartbeat(socket, generation) {
348
354
  this.clearHeartbeatTimer();
349
355
  this.heartbeatTimer = setInterval(() => {
@@ -351,7 +357,17 @@ var WebSocketManager = class {
351
357
  return;
352
358
  }
353
359
  socket.send(JSON.stringify({ type: "ping" }));
354
- }, HEARTBEAT_INTERVAL_MS);
360
+ this.pongTimer ??= setTimeout(() => {
361
+ this.pongTimer = null;
362
+ if (!this.isCurrent(socket, generation)) return;
363
+ const failure = new Error("WebSocket heartbeat timed out");
364
+ this.lastConnectionError = failure;
365
+ this.invokeLifecycleCallback("onError", this.config.onError, failure);
366
+ socket.onclose = null;
367
+ socket.close(4001, "Heartbeat timeout");
368
+ this.handleClose();
369
+ }, this.config.heartbeatTimeout);
370
+ }, this.config.heartbeatInterval);
355
371
  }
356
372
  isCurrent(socket, generation) {
357
373
  return this.ws === socket && this.socketGeneration === generation;
@@ -418,9 +434,15 @@ var WebSocketManager = class {
418
434
  clearInterval(this.heartbeatTimer);
419
435
  this.heartbeatTimer = null;
420
436
  }
437
+ clearPongTimer() {
438
+ if (!this.pongTimer) return;
439
+ clearTimeout(this.pongTimer);
440
+ this.pongTimer = null;
441
+ }
421
442
  clearConnectionTimers() {
422
443
  this.clearHandshakeTimer();
423
444
  this.clearHeartbeatTimer();
445
+ this.clearPongTimer();
424
446
  }
425
447
  clearTimers() {
426
448
  this.clearConnectionTimers();
@@ -520,10 +542,7 @@ var ReactiveClient = class {
520
542
  this.subscriptionsByKey.set(key, shared);
521
543
  this.subscriptionsById.set(shared.id, shared);
522
544
  if (this.connectionState === "connected") {
523
- void this.ensureRegistered(
524
- shared,
525
- this.wsManager.generation
526
- ).catch(() => void 0);
545
+ void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
527
546
  }
528
547
  }
529
548
  const leaseId = generateId();
@@ -532,9 +551,7 @@ var ReactiveClient = class {
532
551
  onError: options.onError
533
552
  });
534
553
  if (this.connectionState === "connected" && (!shared.hasRegistered || shared.stale) && !shared.registrationPromise) {
535
- void this.ensureRegistered(shared, this.wsManager.generation).catch(
536
- () => void 0
537
- );
554
+ void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
538
555
  }
539
556
  const state = shared;
540
557
  let released = false;
@@ -623,9 +640,7 @@ var ReactiveClient = class {
623
640
  registerActiveSubscriptions(socketGeneration) {
624
641
  for (const state of this.subscriptionsByKey.values()) {
625
642
  if (state.active) {
626
- void this.ensureRegistered(state, socketGeneration).catch(
627
- () => void 0
628
- );
643
+ void this.ensureRegistered(state, socketGeneration).catch(() => void 0);
629
644
  }
630
645
  }
631
646
  }
@@ -678,9 +693,7 @@ var ReactiveClient = class {
678
693
  })
679
694
  });
680
695
  if (!response.ok) {
681
- throw new Error(
682
- `Subscribe failed: ${response.status} ${response.statusText}`
683
- );
696
+ throw new Error(`Subscribe failed: ${response.status} ${response.statusText}`);
684
697
  }
685
698
  const result = await response.json();
686
699
  if (result.type === "error") {
@@ -699,12 +712,7 @@ var ReactiveClient = class {
699
712
  throw cause;
700
713
  } finally {
701
714
  if ((!state.active || !isCurrent()) && requestIssued) {
702
- void this.sendUnsubscribe(
703
- registrationConnectionId,
704
- state.id,
705
- generation,
706
- registrationId
707
- );
715
+ void this.sendUnsubscribe(registrationConnectionId, state.id, generation, registrationId);
708
716
  }
709
717
  if (state.registrationPromise === request) {
710
718
  state.registrationPromise = void 0;
@@ -809,15 +817,12 @@ var ReactiveClient = class {
809
817
  return;
810
818
  }
811
819
  this.discardStalePatches(state);
812
- while (true) {
813
- const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion)[0];
820
+ for (; ; ) {
821
+ const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion).at(0);
814
822
  if (!next) break;
815
823
  state.patchBuffer.delete(next.messageId);
816
824
  try {
817
- state.authoritativeData = applyPatches(
818
- state.authoritativeData,
819
- next.patches
820
- );
825
+ state.authoritativeData = applyPatches(state.authoritativeData, next.patches);
821
826
  state.version = next.toVersion;
822
827
  state.appliedMessageIds.add(next.messageId);
823
828
  const awaitingReconciliation = this.hasSettledLayers(state);
@@ -855,12 +860,7 @@ var ReactiveClient = class {
855
860
  state.stale = true;
856
861
  state.loading = true;
857
862
  this.notifySubscription(state);
858
- let request;
859
- request = this.ensureRegistered(
860
- state,
861
- this.wsManager.generation,
862
- true
863
- ).finally(() => {
863
+ const request = this.ensureRegistered(state, this.wsManager.generation, true).finally(() => {
864
864
  if (state.resyncPromise === request) {
865
865
  state.resyncPromise = void 0;
866
866
  state.resyncGeneration = void 0;
@@ -928,7 +928,10 @@ var ReactiveClient = class {
928
928
  if (options?.optimisticData === void 0) return void 0;
929
929
  return (data) => {
930
930
  if (!Array.isArray(data)) return data;
931
- 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;
931
+ const optimistic = settled ? (
932
+ // oxlint-disable-next-line typescript/prefer-nullish-coalescing -- null is a valid mutation result.
933
+ result === void 0 ? typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData : result
934
+ ) : typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData;
932
935
  if (settled && optimistic !== null && typeof optimistic === "object") {
933
936
  const optimisticId = optimistic.id;
934
937
  if (optimisticId !== void 0 && data.some(
@@ -943,12 +946,7 @@ var ReactiveClient = class {
943
946
  settleMutationLayers(mutationId, input, result, options, states) {
944
947
  for (const state of states) {
945
948
  const layer = state.optimisticLayers.get(mutationId);
946
- const updater = this.createOptimisticUpdater(
947
- input,
948
- options,
949
- result,
950
- true
951
- );
949
+ const updater = this.createOptimisticUpdater(input, options, result, true);
952
950
  if (!layer || !updater) continue;
953
951
  layer.updater = updater;
954
952
  layer.settledRevision = ++state.settlementRevision;
@@ -1094,9 +1092,7 @@ function ReactiveClientProvider({
1094
1092
  const state = renderedManaged.client.getConnectionState();
1095
1093
  if (state === "connected" || state === "connecting") return;
1096
1094
  if (state === "reconnecting") {
1097
- debug(
1098
- "[dynamodb-reactive] Tab active during reconnect; reconnecting now"
1099
- );
1095
+ debug("[dynamodb-reactive] Tab active during reconnect; reconnecting now");
1100
1096
  renderedManaged.client.forceReconnect();
1101
1097
  } else {
1102
1098
  void renderedManaged.client.connect().catch(() => void 0);
@@ -1114,9 +1110,7 @@ function ReactiveClientProvider({
1114
1110
  }, [renderedManaged]);
1115
1111
  useEffect(() => {
1116
1112
  if (!config?.url && !externalClient) {
1117
- warn(
1118
- "[dynamodb-reactive] WebSocket URL not configured; real-time features disabled"
1119
- );
1113
+ warn("[dynamodb-reactive] WebSocket URL not configured; real-time features disabled");
1120
1114
  }
1121
1115
  }, [config?.url, externalClient]);
1122
1116
  return /* @__PURE__ */ jsx(ReactiveClientContext.Provider, { value: renderedManaged?.client ?? null, children });
@@ -1164,10 +1158,7 @@ function useSubscription(path, options = {}) {
1164
1158
  };
1165
1159
  const setData = (updater) => {
1166
1160
  if (client && subscription) {
1167
- client.applyOptimisticUpdate(
1168
- subscription.id,
1169
- updater
1170
- );
1161
+ client.applyOptimisticUpdate(subscription.id, updater);
1171
1162
  }
1172
1163
  };
1173
1164
  return {