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/README.md +76 -51
- package/dist/{client-Zb67giI5.d.ts → client-wZQ9XC9S.d.ts} +11 -0
- package/dist/client.cjs +53 -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 +53 -55
- package/dist/client.js.map +1 -1
- package/dist/cloudflare.cjs +95 -13
- package/dist/cloudflare.cjs.map +1 -1
- package/dist/cloudflare.d.cts +37 -3
- package/dist/cloudflare.d.ts +37 -3
- package/dist/cloudflare.js +95 -13
- package/dist/cloudflare.js.map +1 -1
- package/dist/cluster.cjs +66 -66
- package/dist/cluster.cjs.map +1 -1
- package/dist/cluster.js +66 -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 +8 -5
- package/dist/hub.cjs.map +1 -1
- package/dist/hub.d.cts +3 -1
- package/dist/hub.d.ts +3 -1
- package/dist/hub.js +8 -5
- 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 +56 -65
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +56 -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/cluster.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.
|
|
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;
|
|
@@ -304,6 +297,7 @@ var WebSocketManager = class {
|
|
|
304
297
|
return;
|
|
305
298
|
}
|
|
306
299
|
if (message.type === "pong") {
|
|
300
|
+
this.clearPongTimer();
|
|
307
301
|
if (!this._connectionId || this.state === "connected") return;
|
|
308
302
|
this.clearHandshakeTimer();
|
|
309
303
|
this.lastConnectionError = void 0;
|
|
@@ -332,6 +326,18 @@ var WebSocketManager = class {
|
|
|
332
326
|
socket.close(4e3, "Handshake timeout");
|
|
333
327
|
}, this.config.handshakeTimeout);
|
|
334
328
|
}
|
|
329
|
+
handleClose() {
|
|
330
|
+
this.clearConnectionTimers();
|
|
331
|
+
this.ws = null;
|
|
332
|
+
this._connectionId = null;
|
|
333
|
+
this.setState("disconnected");
|
|
334
|
+
this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
|
|
335
|
+
if (this.config.autoReconnect && !this.intentionalDisconnect) {
|
|
336
|
+
this.scheduleReconnect();
|
|
337
|
+
} else {
|
|
338
|
+
this.rejectWaiters(new Error("WebSocket disconnected"));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
335
341
|
startHeartbeat(socket, generation) {
|
|
336
342
|
this.clearHeartbeatTimer();
|
|
337
343
|
this.heartbeatTimer = setInterval(() => {
|
|
@@ -339,7 +345,17 @@ var WebSocketManager = class {
|
|
|
339
345
|
return;
|
|
340
346
|
}
|
|
341
347
|
socket.send(JSON.stringify({ type: "ping" }));
|
|
342
|
-
|
|
348
|
+
this.pongTimer ??= setTimeout(() => {
|
|
349
|
+
this.pongTimer = null;
|
|
350
|
+
if (!this.isCurrent(socket, generation)) return;
|
|
351
|
+
const failure = new Error("WebSocket heartbeat timed out");
|
|
352
|
+
this.lastConnectionError = failure;
|
|
353
|
+
this.invokeLifecycleCallback("onError", this.config.onError, failure);
|
|
354
|
+
socket.onclose = null;
|
|
355
|
+
socket.close(4001, "Heartbeat timeout");
|
|
356
|
+
this.handleClose();
|
|
357
|
+
}, this.config.heartbeatTimeout);
|
|
358
|
+
}, this.config.heartbeatInterval);
|
|
343
359
|
}
|
|
344
360
|
isCurrent(socket, generation) {
|
|
345
361
|
return this.ws === socket && this.socketGeneration === generation;
|
|
@@ -406,9 +422,15 @@ var WebSocketManager = class {
|
|
|
406
422
|
clearInterval(this.heartbeatTimer);
|
|
407
423
|
this.heartbeatTimer = null;
|
|
408
424
|
}
|
|
425
|
+
clearPongTimer() {
|
|
426
|
+
if (!this.pongTimer) return;
|
|
427
|
+
clearTimeout(this.pongTimer);
|
|
428
|
+
this.pongTimer = null;
|
|
429
|
+
}
|
|
409
430
|
clearConnectionTimers() {
|
|
410
431
|
this.clearHandshakeTimer();
|
|
411
432
|
this.clearHeartbeatTimer();
|
|
433
|
+
this.clearPongTimer();
|
|
412
434
|
}
|
|
413
435
|
clearTimers() {
|
|
414
436
|
this.clearConnectionTimers();
|
|
@@ -508,10 +530,7 @@ var ReactiveClient = class {
|
|
|
508
530
|
this.subscriptionsByKey.set(key, shared);
|
|
509
531
|
this.subscriptionsById.set(shared.id, shared);
|
|
510
532
|
if (this.connectionState === "connected") {
|
|
511
|
-
void this.ensureRegistered(
|
|
512
|
-
shared,
|
|
513
|
-
this.wsManager.generation
|
|
514
|
-
).catch(() => void 0);
|
|
533
|
+
void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
|
|
515
534
|
}
|
|
516
535
|
}
|
|
517
536
|
const leaseId = generateId();
|
|
@@ -520,9 +539,7 @@ var ReactiveClient = class {
|
|
|
520
539
|
onError: options.onError
|
|
521
540
|
});
|
|
522
541
|
if (this.connectionState === "connected" && (!shared.hasRegistered || shared.stale) && !shared.registrationPromise) {
|
|
523
|
-
void this.ensureRegistered(shared, this.wsManager.generation).catch(
|
|
524
|
-
() => void 0
|
|
525
|
-
);
|
|
542
|
+
void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
|
|
526
543
|
}
|
|
527
544
|
const state = shared;
|
|
528
545
|
let released = false;
|
|
@@ -611,9 +628,7 @@ var ReactiveClient = class {
|
|
|
611
628
|
registerActiveSubscriptions(socketGeneration) {
|
|
612
629
|
for (const state of this.subscriptionsByKey.values()) {
|
|
613
630
|
if (state.active) {
|
|
614
|
-
void this.ensureRegistered(state, socketGeneration).catch(
|
|
615
|
-
() => void 0
|
|
616
|
-
);
|
|
631
|
+
void this.ensureRegistered(state, socketGeneration).catch(() => void 0);
|
|
617
632
|
}
|
|
618
633
|
}
|
|
619
634
|
}
|
|
@@ -666,9 +681,7 @@ var ReactiveClient = class {
|
|
|
666
681
|
})
|
|
667
682
|
});
|
|
668
683
|
if (!response.ok) {
|
|
669
|
-
throw new Error(
|
|
670
|
-
`Subscribe failed: ${response.status} ${response.statusText}`
|
|
671
|
-
);
|
|
684
|
+
throw new Error(`Subscribe failed: ${response.status} ${response.statusText}`);
|
|
672
685
|
}
|
|
673
686
|
const result = await response.json();
|
|
674
687
|
if (result.type === "error") {
|
|
@@ -687,12 +700,7 @@ var ReactiveClient = class {
|
|
|
687
700
|
throw cause;
|
|
688
701
|
} finally {
|
|
689
702
|
if ((!state.active || !isCurrent()) && requestIssued) {
|
|
690
|
-
void this.sendUnsubscribe(
|
|
691
|
-
registrationConnectionId,
|
|
692
|
-
state.id,
|
|
693
|
-
generation,
|
|
694
|
-
registrationId
|
|
695
|
-
);
|
|
703
|
+
void this.sendUnsubscribe(registrationConnectionId, state.id, generation, registrationId);
|
|
696
704
|
}
|
|
697
705
|
if (state.registrationPromise === request) {
|
|
698
706
|
state.registrationPromise = void 0;
|
|
@@ -797,15 +805,12 @@ var ReactiveClient = class {
|
|
|
797
805
|
return;
|
|
798
806
|
}
|
|
799
807
|
this.discardStalePatches(state);
|
|
800
|
-
|
|
801
|
-
const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion)
|
|
808
|
+
for (; ; ) {
|
|
809
|
+
const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion).at(0);
|
|
802
810
|
if (!next) break;
|
|
803
811
|
state.patchBuffer.delete(next.messageId);
|
|
804
812
|
try {
|
|
805
|
-
state.authoritativeData = applyPatches(
|
|
806
|
-
state.authoritativeData,
|
|
807
|
-
next.patches
|
|
808
|
-
);
|
|
813
|
+
state.authoritativeData = applyPatches(state.authoritativeData, next.patches);
|
|
809
814
|
state.version = next.toVersion;
|
|
810
815
|
state.appliedMessageIds.add(next.messageId);
|
|
811
816
|
const awaitingReconciliation = this.hasSettledLayers(state);
|
|
@@ -843,12 +848,7 @@ var ReactiveClient = class {
|
|
|
843
848
|
state.stale = true;
|
|
844
849
|
state.loading = true;
|
|
845
850
|
this.notifySubscription(state);
|
|
846
|
-
|
|
847
|
-
request = this.ensureRegistered(
|
|
848
|
-
state,
|
|
849
|
-
this.wsManager.generation,
|
|
850
|
-
true
|
|
851
|
-
).finally(() => {
|
|
851
|
+
const request = this.ensureRegistered(state, this.wsManager.generation, true).finally(() => {
|
|
852
852
|
if (state.resyncPromise === request) {
|
|
853
853
|
state.resyncPromise = void 0;
|
|
854
854
|
state.resyncGeneration = void 0;
|
|
@@ -916,7 +916,10 @@ var ReactiveClient = class {
|
|
|
916
916
|
if (options?.optimisticData === void 0) return void 0;
|
|
917
917
|
return (data) => {
|
|
918
918
|
if (!Array.isArray(data)) return data;
|
|
919
|
-
const optimistic = settled ?
|
|
919
|
+
const optimistic = settled ? (
|
|
920
|
+
// oxlint-disable-next-line typescript/prefer-nullish-coalescing -- null is a valid mutation result.
|
|
921
|
+
result === void 0 ? typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData : result
|
|
922
|
+
) : typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData;
|
|
920
923
|
if (settled && optimistic !== null && typeof optimistic === "object") {
|
|
921
924
|
const optimisticId = optimistic.id;
|
|
922
925
|
if (optimisticId !== void 0 && data.some(
|
|
@@ -931,12 +934,7 @@ var ReactiveClient = class {
|
|
|
931
934
|
settleMutationLayers(mutationId, input, result, options, states) {
|
|
932
935
|
for (const state of states) {
|
|
933
936
|
const layer = state.optimisticLayers.get(mutationId);
|
|
934
|
-
const updater = this.createOptimisticUpdater(
|
|
935
|
-
input,
|
|
936
|
-
options,
|
|
937
|
-
result,
|
|
938
|
-
true
|
|
939
|
-
);
|
|
937
|
+
const updater = this.createOptimisticUpdater(input, options, result, true);
|
|
940
938
|
if (!layer || !updater) continue;
|
|
941
939
|
layer.updater = updater;
|
|
942
940
|
layer.settledRevision = ++state.settlementRevision;
|
|
@@ -1013,6 +1011,7 @@ var DEFAULT_CLUSTER_HTTP_PATH = "/api/reactive";
|
|
|
1013
1011
|
var DEFAULT_RETRY_DELAY_MS = 1e4;
|
|
1014
1012
|
var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
1015
1013
|
var DEFAULT_TICKET_LIFETIME_SECONDS = 60;
|
|
1014
|
+
var DEFAULT_CLUSTER_HEARTBEAT_MS = 3e4;
|
|
1016
1015
|
function isRecord(value) {
|
|
1017
1016
|
return typeof value === "object" && value !== null;
|
|
1018
1017
|
}
|
|
@@ -1049,10 +1048,7 @@ function wait(milliseconds, signal) {
|
|
|
1049
1048
|
}
|
|
1050
1049
|
async function fetchClusterTicket(config) {
|
|
1051
1050
|
const runnerToken = typeof config.runnerToken === "function" ? await config.runnerToken() : config.runnerToken;
|
|
1052
|
-
const endpoint = new URL(
|
|
1053
|
-
config.ticketPath ?? DEFAULT_CLUSTER_TICKET_PATH,
|
|
1054
|
-
config.baseUrl
|
|
1055
|
-
);
|
|
1051
|
+
const endpoint = new URL(config.ticketPath ?? DEFAULT_CLUSTER_TICKET_PATH, config.baseUrl);
|
|
1056
1052
|
const response = await (config.fetch ?? fetch)(endpoint, {
|
|
1057
1053
|
method: "POST",
|
|
1058
1054
|
headers: { authorization: `Bearer ${runnerToken}` },
|
|
@@ -1065,10 +1061,7 @@ async function fetchClusterTicket(config) {
|
|
|
1065
1061
|
}
|
|
1066
1062
|
async function connectClusterWorker(config) {
|
|
1067
1063
|
const retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
|
|
1068
|
-
const httpUrl = new URL(
|
|
1069
|
-
config.httpPath ?? DEFAULT_CLUSTER_HTTP_PATH,
|
|
1070
|
-
config.baseUrl
|
|
1071
|
-
).toString();
|
|
1064
|
+
const httpUrl = new URL(config.httpPath ?? DEFAULT_CLUSTER_HTTP_PATH, config.baseUrl).toString();
|
|
1072
1065
|
for (; ; ) {
|
|
1073
1066
|
if (config.signal?.aborted) throw abortError(config.signal);
|
|
1074
1067
|
let client;
|
|
@@ -1077,6 +1070,7 @@ async function connectClusterWorker(config) {
|
|
|
1077
1070
|
client = new ReactiveClient({
|
|
1078
1071
|
reconnectDelay: retryDelay,
|
|
1079
1072
|
maxReconnectAttempts: Number.MAX_SAFE_INTEGER,
|
|
1073
|
+
heartbeatInterval: DEFAULT_CLUSTER_HEARTBEAT_MS,
|
|
1080
1074
|
...config.client,
|
|
1081
1075
|
url: ticket.url,
|
|
1082
1076
|
httpUrl,
|
|
@@ -1120,7 +1114,11 @@ async function createSignedClusterTicket(options) {
|
|
|
1120
1114
|
encoder.encode(JSON.stringify({ p: options.principal, exp: expiresAt }))
|
|
1121
1115
|
);
|
|
1122
1116
|
const signature = new Uint8Array(
|
|
1123
|
-
await crypto.subtle.sign(
|
|
1117
|
+
await crypto.subtle.sign(
|
|
1118
|
+
"HMAC",
|
|
1119
|
+
await hmacKey(options.secret, "sign"),
|
|
1120
|
+
encoder.encode(payload)
|
|
1121
|
+
)
|
|
1124
1122
|
);
|
|
1125
1123
|
return parseClusterTicket({
|
|
1126
1124
|
token: `${payload}.${base64UrlEncode(signature)}`,
|
|
@@ -1129,8 +1127,10 @@ async function createSignedClusterTicket(options) {
|
|
|
1129
1127
|
});
|
|
1130
1128
|
}
|
|
1131
1129
|
async function verifySignedClusterTicket(token, secret, now = Date.now()) {
|
|
1132
|
-
const
|
|
1133
|
-
if (
|
|
1130
|
+
const parts = token.split(".");
|
|
1131
|
+
if (parts.length !== 2) return void 0;
|
|
1132
|
+
const [payload, signature] = parts;
|
|
1133
|
+
if (!payload || !signature) return void 0;
|
|
1134
1134
|
try {
|
|
1135
1135
|
const valid = await crypto.subtle.verify(
|
|
1136
1136
|
"HMAC",
|