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.
- package/README.md +93 -51
- package/dist/{client-Zb67giI5.d.ts → client-BPAct-Dz.d.ts} +19 -0
- package/dist/client.cjs +68 -55
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +68 -55
- package/dist/client.js.map +1 -1
- package/dist/cloudflare.cjs +223 -36
- package/dist/cloudflare.cjs.map +1 -1
- package/dist/cloudflare.d.cts +78 -6
- package/dist/cloudflare.d.ts +78 -6
- package/dist/cloudflare.js +223 -36
- package/dist/cloudflare.js.map +1 -1
- package/dist/cluster.cjs +81 -66
- package/dist/cluster.cjs.map +1 -1
- package/dist/cluster.js +81 -66
- package/dist/cluster.js.map +1 -1
- package/dist/core.cjs +1 -3
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +1 -3
- package/dist/core.js.map +1 -1
- package/dist/hub.cjs +35 -10
- package/dist/hub.cjs.map +1 -1
- package/dist/hub.d.cts +18 -3
- package/dist/hub.d.ts +18 -3
- package/dist/hub.js +35 -10
- package/dist/hub.js.map +1 -1
- package/dist/index.cjs +1 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/infra.cjs +19 -33
- package/dist/infra.cjs.map +1 -1
- package/dist/infra.js +19 -33
- package/dist/infra.js.map +1 -1
- package/dist/react.cjs +71 -65
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +71 -65
- package/dist/react.js.map +1 -1
- package/dist/server.cjs +46 -139
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +46 -139
- package/dist/server.js.map +1 -1
- package/package.json +22 -24
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.
|
|
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;
|
|
@@ -286,6 +279,13 @@ var WebSocketManager = class {
|
|
|
286
279
|
this.setState("disconnected");
|
|
287
280
|
void this.connect().catch(() => void 0);
|
|
288
281
|
}
|
|
282
|
+
/** Sends an application frame on the open socket; false when not connected. */
|
|
283
|
+
send(frame) {
|
|
284
|
+
const socket = this.ws;
|
|
285
|
+
if (!socket || this.state !== "connected" || socket.readyState !== socket.OPEN) return false;
|
|
286
|
+
socket.send(JSON.stringify(frame));
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
289
|
onMessage(handler) {
|
|
290
290
|
this.messageHandlers.add(handler);
|
|
291
291
|
return () => this.messageHandlers.delete(handler);
|
|
@@ -316,6 +316,7 @@ var WebSocketManager = class {
|
|
|
316
316
|
return;
|
|
317
317
|
}
|
|
318
318
|
if (message.type === "pong") {
|
|
319
|
+
this.clearPongTimer();
|
|
319
320
|
if (!this._connectionId || this.state === "connected") return;
|
|
320
321
|
this.clearHandshakeTimer();
|
|
321
322
|
this.lastConnectionError = void 0;
|
|
@@ -344,6 +345,18 @@ var WebSocketManager = class {
|
|
|
344
345
|
socket.close(4e3, "Handshake timeout");
|
|
345
346
|
}, this.config.handshakeTimeout);
|
|
346
347
|
}
|
|
348
|
+
handleClose() {
|
|
349
|
+
this.clearConnectionTimers();
|
|
350
|
+
this.ws = null;
|
|
351
|
+
this._connectionId = null;
|
|
352
|
+
this.setState("disconnected");
|
|
353
|
+
this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
|
|
354
|
+
if (this.config.autoReconnect && !this.intentionalDisconnect) {
|
|
355
|
+
this.scheduleReconnect();
|
|
356
|
+
} else {
|
|
357
|
+
this.rejectWaiters(new Error("WebSocket disconnected"));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
347
360
|
startHeartbeat(socket, generation) {
|
|
348
361
|
this.clearHeartbeatTimer();
|
|
349
362
|
this.heartbeatTimer = setInterval(() => {
|
|
@@ -351,7 +364,17 @@ var WebSocketManager = class {
|
|
|
351
364
|
return;
|
|
352
365
|
}
|
|
353
366
|
socket.send(JSON.stringify({ type: "ping" }));
|
|
354
|
-
|
|
367
|
+
this.pongTimer ??= setTimeout(() => {
|
|
368
|
+
this.pongTimer = null;
|
|
369
|
+
if (!this.isCurrent(socket, generation)) return;
|
|
370
|
+
const failure = new Error("WebSocket heartbeat timed out");
|
|
371
|
+
this.lastConnectionError = failure;
|
|
372
|
+
this.invokeLifecycleCallback("onError", this.config.onError, failure);
|
|
373
|
+
socket.onclose = null;
|
|
374
|
+
socket.close(4001, "Heartbeat timeout");
|
|
375
|
+
this.handleClose();
|
|
376
|
+
}, this.config.heartbeatTimeout);
|
|
377
|
+
}, this.config.heartbeatInterval);
|
|
355
378
|
}
|
|
356
379
|
isCurrent(socket, generation) {
|
|
357
380
|
return this.ws === socket && this.socketGeneration === generation;
|
|
@@ -418,9 +441,15 @@ var WebSocketManager = class {
|
|
|
418
441
|
clearInterval(this.heartbeatTimer);
|
|
419
442
|
this.heartbeatTimer = null;
|
|
420
443
|
}
|
|
444
|
+
clearPongTimer() {
|
|
445
|
+
if (!this.pongTimer) return;
|
|
446
|
+
clearTimeout(this.pongTimer);
|
|
447
|
+
this.pongTimer = null;
|
|
448
|
+
}
|
|
421
449
|
clearConnectionTimers() {
|
|
422
450
|
this.clearHandshakeTimer();
|
|
423
451
|
this.clearHeartbeatTimer();
|
|
452
|
+
this.clearPongTimer();
|
|
424
453
|
}
|
|
425
454
|
clearTimers() {
|
|
426
455
|
this.clearConnectionTimers();
|
|
@@ -480,6 +509,14 @@ var ReactiveClient = class {
|
|
|
480
509
|
getConnectionState() {
|
|
481
510
|
return this.connectionState;
|
|
482
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Sends an application frame over the socket, such as a presence update a
|
|
514
|
+
* hub's `onMessage` handles. Returns false while disconnected; resend from
|
|
515
|
+
* `onConnectionStateChange` once connected again.
|
|
516
|
+
*/
|
|
517
|
+
send(frame) {
|
|
518
|
+
return this.wsManager.send(frame);
|
|
519
|
+
}
|
|
483
520
|
onConnectionStateChange(listener) {
|
|
484
521
|
this.stateListeners.add(listener);
|
|
485
522
|
return () => this.stateListeners.delete(listener);
|
|
@@ -520,10 +557,7 @@ var ReactiveClient = class {
|
|
|
520
557
|
this.subscriptionsByKey.set(key, shared);
|
|
521
558
|
this.subscriptionsById.set(shared.id, shared);
|
|
522
559
|
if (this.connectionState === "connected") {
|
|
523
|
-
void this.ensureRegistered(
|
|
524
|
-
shared,
|
|
525
|
-
this.wsManager.generation
|
|
526
|
-
).catch(() => void 0);
|
|
560
|
+
void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
|
|
527
561
|
}
|
|
528
562
|
}
|
|
529
563
|
const leaseId = generateId();
|
|
@@ -532,9 +566,7 @@ var ReactiveClient = class {
|
|
|
532
566
|
onError: options.onError
|
|
533
567
|
});
|
|
534
568
|
if (this.connectionState === "connected" && (!shared.hasRegistered || shared.stale) && !shared.registrationPromise) {
|
|
535
|
-
void this.ensureRegistered(shared, this.wsManager.generation).catch(
|
|
536
|
-
() => void 0
|
|
537
|
-
);
|
|
569
|
+
void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
|
|
538
570
|
}
|
|
539
571
|
const state = shared;
|
|
540
572
|
let released = false;
|
|
@@ -623,9 +655,7 @@ var ReactiveClient = class {
|
|
|
623
655
|
registerActiveSubscriptions(socketGeneration) {
|
|
624
656
|
for (const state of this.subscriptionsByKey.values()) {
|
|
625
657
|
if (state.active) {
|
|
626
|
-
void this.ensureRegistered(state, socketGeneration).catch(
|
|
627
|
-
() => void 0
|
|
628
|
-
);
|
|
658
|
+
void this.ensureRegistered(state, socketGeneration).catch(() => void 0);
|
|
629
659
|
}
|
|
630
660
|
}
|
|
631
661
|
}
|
|
@@ -678,9 +708,7 @@ var ReactiveClient = class {
|
|
|
678
708
|
})
|
|
679
709
|
});
|
|
680
710
|
if (!response.ok) {
|
|
681
|
-
throw new Error(
|
|
682
|
-
`Subscribe failed: ${response.status} ${response.statusText}`
|
|
683
|
-
);
|
|
711
|
+
throw new Error(`Subscribe failed: ${response.status} ${response.statusText}`);
|
|
684
712
|
}
|
|
685
713
|
const result = await response.json();
|
|
686
714
|
if (result.type === "error") {
|
|
@@ -699,12 +727,7 @@ var ReactiveClient = class {
|
|
|
699
727
|
throw cause;
|
|
700
728
|
} finally {
|
|
701
729
|
if ((!state.active || !isCurrent()) && requestIssued) {
|
|
702
|
-
void this.sendUnsubscribe(
|
|
703
|
-
registrationConnectionId,
|
|
704
|
-
state.id,
|
|
705
|
-
generation,
|
|
706
|
-
registrationId
|
|
707
|
-
);
|
|
730
|
+
void this.sendUnsubscribe(registrationConnectionId, state.id, generation, registrationId);
|
|
708
731
|
}
|
|
709
732
|
if (state.registrationPromise === request) {
|
|
710
733
|
state.registrationPromise = void 0;
|
|
@@ -809,15 +832,12 @@ var ReactiveClient = class {
|
|
|
809
832
|
return;
|
|
810
833
|
}
|
|
811
834
|
this.discardStalePatches(state);
|
|
812
|
-
|
|
813
|
-
const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion)
|
|
835
|
+
for (; ; ) {
|
|
836
|
+
const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion).at(0);
|
|
814
837
|
if (!next) break;
|
|
815
838
|
state.patchBuffer.delete(next.messageId);
|
|
816
839
|
try {
|
|
817
|
-
state.authoritativeData = applyPatches(
|
|
818
|
-
state.authoritativeData,
|
|
819
|
-
next.patches
|
|
820
|
-
);
|
|
840
|
+
state.authoritativeData = applyPatches(state.authoritativeData, next.patches);
|
|
821
841
|
state.version = next.toVersion;
|
|
822
842
|
state.appliedMessageIds.add(next.messageId);
|
|
823
843
|
const awaitingReconciliation = this.hasSettledLayers(state);
|
|
@@ -855,12 +875,7 @@ var ReactiveClient = class {
|
|
|
855
875
|
state.stale = true;
|
|
856
876
|
state.loading = true;
|
|
857
877
|
this.notifySubscription(state);
|
|
858
|
-
|
|
859
|
-
request = this.ensureRegistered(
|
|
860
|
-
state,
|
|
861
|
-
this.wsManager.generation,
|
|
862
|
-
true
|
|
863
|
-
).finally(() => {
|
|
878
|
+
const request = this.ensureRegistered(state, this.wsManager.generation, true).finally(() => {
|
|
864
879
|
if (state.resyncPromise === request) {
|
|
865
880
|
state.resyncPromise = void 0;
|
|
866
881
|
state.resyncGeneration = void 0;
|
|
@@ -928,7 +943,10 @@ var ReactiveClient = class {
|
|
|
928
943
|
if (options?.optimisticData === void 0) return void 0;
|
|
929
944
|
return (data) => {
|
|
930
945
|
if (!Array.isArray(data)) return data;
|
|
931
|
-
const optimistic = settled ?
|
|
946
|
+
const optimistic = settled ? (
|
|
947
|
+
// oxlint-disable-next-line typescript/prefer-nullish-coalescing -- null is a valid mutation result.
|
|
948
|
+
result === void 0 ? typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData : result
|
|
949
|
+
) : typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData;
|
|
932
950
|
if (settled && optimistic !== null && typeof optimistic === "object") {
|
|
933
951
|
const optimisticId = optimistic.id;
|
|
934
952
|
if (optimisticId !== void 0 && data.some(
|
|
@@ -943,12 +961,7 @@ var ReactiveClient = class {
|
|
|
943
961
|
settleMutationLayers(mutationId, input, result, options, states) {
|
|
944
962
|
for (const state of states) {
|
|
945
963
|
const layer = state.optimisticLayers.get(mutationId);
|
|
946
|
-
const updater = this.createOptimisticUpdater(
|
|
947
|
-
input,
|
|
948
|
-
options,
|
|
949
|
-
result,
|
|
950
|
-
true
|
|
951
|
-
);
|
|
964
|
+
const updater = this.createOptimisticUpdater(input, options, result, true);
|
|
952
965
|
if (!layer || !updater) continue;
|
|
953
966
|
layer.updater = updater;
|
|
954
967
|
layer.settledRevision = ++state.settlementRevision;
|
|
@@ -1094,9 +1107,7 @@ function ReactiveClientProvider({
|
|
|
1094
1107
|
const state = renderedManaged.client.getConnectionState();
|
|
1095
1108
|
if (state === "connected" || state === "connecting") return;
|
|
1096
1109
|
if (state === "reconnecting") {
|
|
1097
|
-
debug(
|
|
1098
|
-
"[dynamodb-reactive] Tab active during reconnect; reconnecting now"
|
|
1099
|
-
);
|
|
1110
|
+
debug("[dynamodb-reactive] Tab active during reconnect; reconnecting now");
|
|
1100
1111
|
renderedManaged.client.forceReconnect();
|
|
1101
1112
|
} else {
|
|
1102
1113
|
void renderedManaged.client.connect().catch(() => void 0);
|
|
@@ -1114,9 +1125,7 @@ function ReactiveClientProvider({
|
|
|
1114
1125
|
}, [renderedManaged]);
|
|
1115
1126
|
useEffect(() => {
|
|
1116
1127
|
if (!config?.url && !externalClient) {
|
|
1117
|
-
warn(
|
|
1118
|
-
"[dynamodb-reactive] WebSocket URL not configured; real-time features disabled"
|
|
1119
|
-
);
|
|
1128
|
+
warn("[dynamodb-reactive] WebSocket URL not configured; real-time features disabled");
|
|
1120
1129
|
}
|
|
1121
1130
|
}, [config?.url, externalClient]);
|
|
1122
1131
|
return /* @__PURE__ */ jsx(ReactiveClientContext.Provider, { value: renderedManaged?.client ?? null, children });
|
|
@@ -1164,10 +1173,7 @@ function useSubscription(path, options = {}) {
|
|
|
1164
1173
|
};
|
|
1165
1174
|
const setData = (updater) => {
|
|
1166
1175
|
if (client && subscription) {
|
|
1167
|
-
client.applyOptimisticUpdate(
|
|
1168
|
-
subscription.id,
|
|
1169
|
-
updater
|
|
1170
|
-
);
|
|
1176
|
+
client.applyOptimisticUpdate(subscription.id, updater);
|
|
1171
1177
|
}
|
|
1172
1178
|
};
|
|
1173
1179
|
return {
|