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.
- package/README.md +121 -43
- 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 +461 -0
- package/dist/cloudflare.cjs.map +1 -0
- package/dist/cloudflare.d.cts +129 -0
- package/dist/cloudflare.d.ts +129 -0
- package/dist/cloudflare.js +456 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/cluster.cjs +66 -66
- package/dist/cluster.cjs.map +1 -1
- package/dist/cluster.d.cts +2 -78
- package/dist/cluster.d.ts +2 -78
- 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 +164 -0
- package/dist/hub.cjs.map +1 -0
- package/dist/hub.d.cts +91 -0
- package/dist/hub.d.ts +91 -0
- package/dist/hub.js +157 -0
- package/dist/hub.js.map +1 -0
- 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/protocol-C4Syt_Bl.d.ts +78 -0
- 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 +42 -24
package/dist/cluster.cjs
CHANGED
|
@@ -29,9 +29,7 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
|
|
|
29
29
|
if (Array.isArray(value)) {
|
|
30
30
|
const items = [];
|
|
31
31
|
for (let index = 0; index < value.length; index++) {
|
|
32
|
-
items.push(
|
|
33
|
-
canonicalize(value[index], seen, String(index), true) ?? "null"
|
|
34
|
-
);
|
|
32
|
+
items.push(canonicalize(value[index], seen, String(index), true) ?? "null");
|
|
35
33
|
}
|
|
36
34
|
return `[${items.join(",")}]`;
|
|
37
35
|
}
|
|
@@ -48,6 +46,9 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
|
|
|
48
46
|
seen.delete(value);
|
|
49
47
|
}
|
|
50
48
|
}
|
|
49
|
+
case "bigint":
|
|
50
|
+
case "function":
|
|
51
|
+
case "symbol":
|
|
51
52
|
default:
|
|
52
53
|
throw new TypeError(`Unsupported query input value: ${typeof value}`);
|
|
53
54
|
}
|
|
@@ -55,7 +56,6 @@ function canonicalize(value, seen, key, arrayItem = false, applyToJSON = true) {
|
|
|
55
56
|
function getQueryKey(path, input) {
|
|
56
57
|
return `${JSON.stringify(path)}:${canonicalize(input, /* @__PURE__ */ new Set(), "") ?? "undefined"}`;
|
|
57
58
|
}
|
|
58
|
-
var { applyPatch } = jsonpatch__default.default;
|
|
59
59
|
function applyPatches(document, patches) {
|
|
60
60
|
if (patches.length === 0) {
|
|
61
61
|
return document;
|
|
@@ -74,7 +74,7 @@ function applyPatches(document, patches) {
|
|
|
74
74
|
return op;
|
|
75
75
|
});
|
|
76
76
|
try {
|
|
77
|
-
const result = applyPatch(
|
|
77
|
+
const result = jsonpatch__default.default.applyPatch(
|
|
78
78
|
structuredClone(document),
|
|
79
79
|
operations,
|
|
80
80
|
true,
|
|
@@ -94,12 +94,13 @@ function error(...values) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
// ../client/src/websocket.ts
|
|
97
|
-
var HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
|
|
98
97
|
function withDefaults(config) {
|
|
99
98
|
return {
|
|
100
99
|
autoReconnect: true,
|
|
101
100
|
connectionTimeout: 1e4,
|
|
102
101
|
handshakeTimeout: 1e4,
|
|
102
|
+
heartbeatInterval: 5 * 60 * 1e3,
|
|
103
|
+
heartbeatTimeout: 15e3,
|
|
103
104
|
maxReconnectAttempts: 10,
|
|
104
105
|
reconnectDelay: 1e3,
|
|
105
106
|
...config
|
|
@@ -113,6 +114,7 @@ var WebSocketManager = class {
|
|
|
113
114
|
reconnectTimer = null;
|
|
114
115
|
handshakeTimer = null;
|
|
115
116
|
heartbeatTimer = null;
|
|
117
|
+
pongTimer = null;
|
|
116
118
|
messageHandlers = /* @__PURE__ */ new Set();
|
|
117
119
|
stateHandlers = /* @__PURE__ */ new Set();
|
|
118
120
|
handshakeHandlers = /* @__PURE__ */ new Set();
|
|
@@ -212,16 +214,7 @@ var WebSocketManager = class {
|
|
|
212
214
|
};
|
|
213
215
|
nextSocket.onclose = () => {
|
|
214
216
|
if (!this.isCurrent(nextSocket, generation)) return;
|
|
215
|
-
this.
|
|
216
|
-
this.ws = null;
|
|
217
|
-
this._connectionId = null;
|
|
218
|
-
this.setState("disconnected");
|
|
219
|
-
this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
|
|
220
|
-
if (this.config.autoReconnect && !this.intentionalDisconnect) {
|
|
221
|
-
this.scheduleReconnect();
|
|
222
|
-
} else {
|
|
223
|
-
this.rejectWaiters(new Error("WebSocket disconnected"));
|
|
224
|
-
}
|
|
217
|
+
this.handleClose();
|
|
225
218
|
};
|
|
226
219
|
nextSocket.onerror = () => {
|
|
227
220
|
if (!this.isCurrent(nextSocket, generation)) return;
|
|
@@ -310,6 +303,7 @@ var WebSocketManager = class {
|
|
|
310
303
|
return;
|
|
311
304
|
}
|
|
312
305
|
if (message.type === "pong") {
|
|
306
|
+
this.clearPongTimer();
|
|
313
307
|
if (!this._connectionId || this.state === "connected") return;
|
|
314
308
|
this.clearHandshakeTimer();
|
|
315
309
|
this.lastConnectionError = void 0;
|
|
@@ -338,6 +332,18 @@ var WebSocketManager = class {
|
|
|
338
332
|
socket.close(4e3, "Handshake timeout");
|
|
339
333
|
}, this.config.handshakeTimeout);
|
|
340
334
|
}
|
|
335
|
+
handleClose() {
|
|
336
|
+
this.clearConnectionTimers();
|
|
337
|
+
this.ws = null;
|
|
338
|
+
this._connectionId = null;
|
|
339
|
+
this.setState("disconnected");
|
|
340
|
+
this.invokeLifecycleCallback("onDisconnect", this.config.onDisconnect);
|
|
341
|
+
if (this.config.autoReconnect && !this.intentionalDisconnect) {
|
|
342
|
+
this.scheduleReconnect();
|
|
343
|
+
} else {
|
|
344
|
+
this.rejectWaiters(new Error("WebSocket disconnected"));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
341
347
|
startHeartbeat(socket, generation) {
|
|
342
348
|
this.clearHeartbeatTimer();
|
|
343
349
|
this.heartbeatTimer = setInterval(() => {
|
|
@@ -345,7 +351,17 @@ var WebSocketManager = class {
|
|
|
345
351
|
return;
|
|
346
352
|
}
|
|
347
353
|
socket.send(JSON.stringify({ type: "ping" }));
|
|
348
|
-
|
|
354
|
+
this.pongTimer ??= setTimeout(() => {
|
|
355
|
+
this.pongTimer = null;
|
|
356
|
+
if (!this.isCurrent(socket, generation)) return;
|
|
357
|
+
const failure = new Error("WebSocket heartbeat timed out");
|
|
358
|
+
this.lastConnectionError = failure;
|
|
359
|
+
this.invokeLifecycleCallback("onError", this.config.onError, failure);
|
|
360
|
+
socket.onclose = null;
|
|
361
|
+
socket.close(4001, "Heartbeat timeout");
|
|
362
|
+
this.handleClose();
|
|
363
|
+
}, this.config.heartbeatTimeout);
|
|
364
|
+
}, this.config.heartbeatInterval);
|
|
349
365
|
}
|
|
350
366
|
isCurrent(socket, generation) {
|
|
351
367
|
return this.ws === socket && this.socketGeneration === generation;
|
|
@@ -412,9 +428,15 @@ var WebSocketManager = class {
|
|
|
412
428
|
clearInterval(this.heartbeatTimer);
|
|
413
429
|
this.heartbeatTimer = null;
|
|
414
430
|
}
|
|
431
|
+
clearPongTimer() {
|
|
432
|
+
if (!this.pongTimer) return;
|
|
433
|
+
clearTimeout(this.pongTimer);
|
|
434
|
+
this.pongTimer = null;
|
|
435
|
+
}
|
|
415
436
|
clearConnectionTimers() {
|
|
416
437
|
this.clearHandshakeTimer();
|
|
417
438
|
this.clearHeartbeatTimer();
|
|
439
|
+
this.clearPongTimer();
|
|
418
440
|
}
|
|
419
441
|
clearTimers() {
|
|
420
442
|
this.clearConnectionTimers();
|
|
@@ -514,10 +536,7 @@ var ReactiveClient = class {
|
|
|
514
536
|
this.subscriptionsByKey.set(key, shared);
|
|
515
537
|
this.subscriptionsById.set(shared.id, shared);
|
|
516
538
|
if (this.connectionState === "connected") {
|
|
517
|
-
void this.ensureRegistered(
|
|
518
|
-
shared,
|
|
519
|
-
this.wsManager.generation
|
|
520
|
-
).catch(() => void 0);
|
|
539
|
+
void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
|
|
521
540
|
}
|
|
522
541
|
}
|
|
523
542
|
const leaseId = generateId();
|
|
@@ -526,9 +545,7 @@ var ReactiveClient = class {
|
|
|
526
545
|
onError: options.onError
|
|
527
546
|
});
|
|
528
547
|
if (this.connectionState === "connected" && (!shared.hasRegistered || shared.stale) && !shared.registrationPromise) {
|
|
529
|
-
void this.ensureRegistered(shared, this.wsManager.generation).catch(
|
|
530
|
-
() => void 0
|
|
531
|
-
);
|
|
548
|
+
void this.ensureRegistered(shared, this.wsManager.generation).catch(() => void 0);
|
|
532
549
|
}
|
|
533
550
|
const state = shared;
|
|
534
551
|
let released = false;
|
|
@@ -617,9 +634,7 @@ var ReactiveClient = class {
|
|
|
617
634
|
registerActiveSubscriptions(socketGeneration) {
|
|
618
635
|
for (const state of this.subscriptionsByKey.values()) {
|
|
619
636
|
if (state.active) {
|
|
620
|
-
void this.ensureRegistered(state, socketGeneration).catch(
|
|
621
|
-
() => void 0
|
|
622
|
-
);
|
|
637
|
+
void this.ensureRegistered(state, socketGeneration).catch(() => void 0);
|
|
623
638
|
}
|
|
624
639
|
}
|
|
625
640
|
}
|
|
@@ -672,9 +687,7 @@ var ReactiveClient = class {
|
|
|
672
687
|
})
|
|
673
688
|
});
|
|
674
689
|
if (!response.ok) {
|
|
675
|
-
throw new Error(
|
|
676
|
-
`Subscribe failed: ${response.status} ${response.statusText}`
|
|
677
|
-
);
|
|
690
|
+
throw new Error(`Subscribe failed: ${response.status} ${response.statusText}`);
|
|
678
691
|
}
|
|
679
692
|
const result = await response.json();
|
|
680
693
|
if (result.type === "error") {
|
|
@@ -693,12 +706,7 @@ var ReactiveClient = class {
|
|
|
693
706
|
throw cause;
|
|
694
707
|
} finally {
|
|
695
708
|
if ((!state.active || !isCurrent()) && requestIssued) {
|
|
696
|
-
void this.sendUnsubscribe(
|
|
697
|
-
registrationConnectionId,
|
|
698
|
-
state.id,
|
|
699
|
-
generation,
|
|
700
|
-
registrationId
|
|
701
|
-
);
|
|
709
|
+
void this.sendUnsubscribe(registrationConnectionId, state.id, generation, registrationId);
|
|
702
710
|
}
|
|
703
711
|
if (state.registrationPromise === request) {
|
|
704
712
|
state.registrationPromise = void 0;
|
|
@@ -803,15 +811,12 @@ var ReactiveClient = class {
|
|
|
803
811
|
return;
|
|
804
812
|
}
|
|
805
813
|
this.discardStalePatches(state);
|
|
806
|
-
|
|
807
|
-
const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion)
|
|
814
|
+
for (; ; ) {
|
|
815
|
+
const next = [...state.patchBuffer.values()].filter((patch) => patch.fromVersion === state.version).sort((left, right) => left.toVersion - right.toVersion).at(0);
|
|
808
816
|
if (!next) break;
|
|
809
817
|
state.patchBuffer.delete(next.messageId);
|
|
810
818
|
try {
|
|
811
|
-
state.authoritativeData = applyPatches(
|
|
812
|
-
state.authoritativeData,
|
|
813
|
-
next.patches
|
|
814
|
-
);
|
|
819
|
+
state.authoritativeData = applyPatches(state.authoritativeData, next.patches);
|
|
815
820
|
state.version = next.toVersion;
|
|
816
821
|
state.appliedMessageIds.add(next.messageId);
|
|
817
822
|
const awaitingReconciliation = this.hasSettledLayers(state);
|
|
@@ -849,12 +854,7 @@ var ReactiveClient = class {
|
|
|
849
854
|
state.stale = true;
|
|
850
855
|
state.loading = true;
|
|
851
856
|
this.notifySubscription(state);
|
|
852
|
-
|
|
853
|
-
request = this.ensureRegistered(
|
|
854
|
-
state,
|
|
855
|
-
this.wsManager.generation,
|
|
856
|
-
true
|
|
857
|
-
).finally(() => {
|
|
857
|
+
const request = this.ensureRegistered(state, this.wsManager.generation, true).finally(() => {
|
|
858
858
|
if (state.resyncPromise === request) {
|
|
859
859
|
state.resyncPromise = void 0;
|
|
860
860
|
state.resyncGeneration = void 0;
|
|
@@ -922,7 +922,10 @@ var ReactiveClient = class {
|
|
|
922
922
|
if (options?.optimisticData === void 0) return void 0;
|
|
923
923
|
return (data) => {
|
|
924
924
|
if (!Array.isArray(data)) return data;
|
|
925
|
-
const optimistic = settled ?
|
|
925
|
+
const optimistic = settled ? (
|
|
926
|
+
// oxlint-disable-next-line typescript/prefer-nullish-coalescing -- null is a valid mutation result.
|
|
927
|
+
result === void 0 ? typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData : result
|
|
928
|
+
) : typeof options.optimisticData === "function" ? options.optimisticData(input, data) : options.optimisticData;
|
|
926
929
|
if (settled && optimistic !== null && typeof optimistic === "object") {
|
|
927
930
|
const optimisticId = optimistic.id;
|
|
928
931
|
if (optimisticId !== void 0 && data.some(
|
|
@@ -937,12 +940,7 @@ var ReactiveClient = class {
|
|
|
937
940
|
settleMutationLayers(mutationId, input, result, options, states) {
|
|
938
941
|
for (const state of states) {
|
|
939
942
|
const layer = state.optimisticLayers.get(mutationId);
|
|
940
|
-
const updater = this.createOptimisticUpdater(
|
|
941
|
-
input,
|
|
942
|
-
options,
|
|
943
|
-
result,
|
|
944
|
-
true
|
|
945
|
-
);
|
|
943
|
+
const updater = this.createOptimisticUpdater(input, options, result, true);
|
|
946
944
|
if (!layer || !updater) continue;
|
|
947
945
|
layer.updater = updater;
|
|
948
946
|
layer.settledRevision = ++state.settlementRevision;
|
|
@@ -1019,6 +1017,7 @@ var DEFAULT_CLUSTER_HTTP_PATH = "/api/reactive";
|
|
|
1019
1017
|
var DEFAULT_RETRY_DELAY_MS = 1e4;
|
|
1020
1018
|
var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
|
|
1021
1019
|
var DEFAULT_TICKET_LIFETIME_SECONDS = 60;
|
|
1020
|
+
var DEFAULT_CLUSTER_HEARTBEAT_MS = 3e4;
|
|
1022
1021
|
function isRecord(value) {
|
|
1023
1022
|
return typeof value === "object" && value !== null;
|
|
1024
1023
|
}
|
|
@@ -1055,10 +1054,7 @@ function wait(milliseconds, signal) {
|
|
|
1055
1054
|
}
|
|
1056
1055
|
async function fetchClusterTicket(config) {
|
|
1057
1056
|
const runnerToken = typeof config.runnerToken === "function" ? await config.runnerToken() : config.runnerToken;
|
|
1058
|
-
const endpoint = new URL(
|
|
1059
|
-
config.ticketPath ?? DEFAULT_CLUSTER_TICKET_PATH,
|
|
1060
|
-
config.baseUrl
|
|
1061
|
-
);
|
|
1057
|
+
const endpoint = new URL(config.ticketPath ?? DEFAULT_CLUSTER_TICKET_PATH, config.baseUrl);
|
|
1062
1058
|
const response = await (config.fetch ?? fetch)(endpoint, {
|
|
1063
1059
|
method: "POST",
|
|
1064
1060
|
headers: { authorization: `Bearer ${runnerToken}` },
|
|
@@ -1071,10 +1067,7 @@ async function fetchClusterTicket(config) {
|
|
|
1071
1067
|
}
|
|
1072
1068
|
async function connectClusterWorker(config) {
|
|
1073
1069
|
const retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
|
|
1074
|
-
const httpUrl = new URL(
|
|
1075
|
-
config.httpPath ?? DEFAULT_CLUSTER_HTTP_PATH,
|
|
1076
|
-
config.baseUrl
|
|
1077
|
-
).toString();
|
|
1070
|
+
const httpUrl = new URL(config.httpPath ?? DEFAULT_CLUSTER_HTTP_PATH, config.baseUrl).toString();
|
|
1078
1071
|
for (; ; ) {
|
|
1079
1072
|
if (config.signal?.aborted) throw abortError(config.signal);
|
|
1080
1073
|
let client;
|
|
@@ -1083,6 +1076,7 @@ async function connectClusterWorker(config) {
|
|
|
1083
1076
|
client = new ReactiveClient({
|
|
1084
1077
|
reconnectDelay: retryDelay,
|
|
1085
1078
|
maxReconnectAttempts: Number.MAX_SAFE_INTEGER,
|
|
1079
|
+
heartbeatInterval: DEFAULT_CLUSTER_HEARTBEAT_MS,
|
|
1086
1080
|
...config.client,
|
|
1087
1081
|
url: ticket.url,
|
|
1088
1082
|
httpUrl,
|
|
@@ -1126,7 +1120,11 @@ async function createSignedClusterTicket(options) {
|
|
|
1126
1120
|
encoder.encode(JSON.stringify({ p: options.principal, exp: expiresAt }))
|
|
1127
1121
|
);
|
|
1128
1122
|
const signature = new Uint8Array(
|
|
1129
|
-
await crypto.subtle.sign(
|
|
1123
|
+
await crypto.subtle.sign(
|
|
1124
|
+
"HMAC",
|
|
1125
|
+
await hmacKey(options.secret, "sign"),
|
|
1126
|
+
encoder.encode(payload)
|
|
1127
|
+
)
|
|
1130
1128
|
);
|
|
1131
1129
|
return parseClusterTicket({
|
|
1132
1130
|
token: `${payload}.${base64UrlEncode(signature)}`,
|
|
@@ -1135,8 +1133,10 @@ async function createSignedClusterTicket(options) {
|
|
|
1135
1133
|
});
|
|
1136
1134
|
}
|
|
1137
1135
|
async function verifySignedClusterTicket(token, secret, now = Date.now()) {
|
|
1138
|
-
const
|
|
1139
|
-
if (
|
|
1136
|
+
const parts = token.split(".");
|
|
1137
|
+
if (parts.length !== 2) return void 0;
|
|
1138
|
+
const [payload, signature] = parts;
|
|
1139
|
+
if (!payload || !signature) return void 0;
|
|
1140
1140
|
try {
|
|
1141
1141
|
const valid = await crypto.subtle.verify(
|
|
1142
1142
|
"HMAC",
|