@xyo-network/event-kit-node 1.0.0 → 2.1.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/node/filesystemCursorStore.d.ts.map +1 -1
- package/dist/node/filesystemPublisherEndpointChallengeStore.d.ts.map +1 -1
- package/dist/node/handshake.mjs +16 -7
- package/dist/node/handshake.mjs.map +2 -2
- package/dist/node/handshakeClients.d.ts +5 -1
- package/dist/node/handshakeClients.d.ts.map +1 -1
- package/dist/node/handshakeMutationIntent.d.ts +128 -66
- package/dist/node/handshakeMutationIntent.d.ts.map +1 -1
- package/dist/node/index.mjs +288 -116
- package/dist/node/index.mjs.map +4 -4
- package/dist/node/runtime/handshakeDiscovery.d.ts +4 -0
- package/dist/node/runtime/handshakeDiscovery.d.ts.map +1 -1
- package/dist/node/runtime/httpsTransport.d.ts.map +1 -1
- package/dist/node/runtime/publisherDeliveryState.d.ts +107 -0
- package/dist/node/runtime/publisherDeliveryState.d.ts.map +1 -0
- package/dist/node/runtime/publisherRuntime.d.ts +17 -1
- package/dist/node/runtime/publisherRuntime.d.ts.map +1 -1
- package/dist/node/runtime/sourceSigner.d.ts +1 -0
- package/dist/node/runtime/sourceSigner.d.ts.map +1 -1
- package/dist/node/runtime/sqliteHandshakeStores.d.ts +3 -1
- package/dist/node/runtime/sqliteHandshakeStores.d.ts.map +1 -1
- package/dist/node/runtime.mjs +271 -105
- package/dist/node/runtime.mjs.map +4 -4
- package/package.json +24 -24
package/dist/node/runtime.mjs
CHANGED
|
@@ -141,6 +141,7 @@ function createBoundedPublisherEthersTransport(options = {}) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
// src/runtime/handshakeDiscovery.ts
|
|
144
|
+
import { isDeepStrictEqual } from "node:util";
|
|
144
145
|
import { decryptWakeSubscription, sourceWakeFilterHash } from "@xyo-network/event-kit-protocol";
|
|
145
146
|
import { EndpointChallengeResponseZod } from "@xyo-network/event-kit-schemas";
|
|
146
147
|
function createPublisherHandshakeDiscovery(options) {
|
|
@@ -156,6 +157,9 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
156
157
|
rejectedSubscriptions: 0,
|
|
157
158
|
challengeFailures: 0
|
|
158
159
|
};
|
|
160
|
+
function selectedTuple(tuple) {
|
|
161
|
+
return (options.serviceHash === void 0 || tuple.service.objectHash === options.serviceHash) && (options.deliveryMode === void 0 || tuple.service.body.deliveryMode === options.deliveryMode) && (options.deliveryMode === void 0 || tuple.subscription.body.deliveryMode === options.deliveryMode) && (options.deliveryMode === void 0 || isDeepStrictEqual(tuple.service.body.bestEffort, options.bestEffort));
|
|
162
|
+
}
|
|
159
163
|
async function activeView(signal) {
|
|
160
164
|
signal.throwIfAborted();
|
|
161
165
|
viewing ??= (async () => {
|
|
@@ -173,7 +177,7 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
173
177
|
...status,
|
|
174
178
|
finalizedHead: view.finalizedHead,
|
|
175
179
|
indexedThrough: view.indexedThrough,
|
|
176
|
-
pendingSubscriptions: view.subscriptions.filter((item) => item.state === "pending").length,
|
|
180
|
+
pendingSubscriptions: view.subscriptions.filter((item) => item.state === "pending" && item.tuples.some(selectedTuple)).length,
|
|
177
181
|
rejectedSubscriptions: view.rejections.length
|
|
178
182
|
};
|
|
179
183
|
return view;
|
|
@@ -238,7 +242,7 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
238
242
|
if (!found.ok)
|
|
239
243
|
throw new Error("Publisher acceptance candidates are unavailable");
|
|
240
244
|
const offset = options.state.get("challenge-rate", "offset") ?? 0;
|
|
241
|
-
const candidates = found.candidates;
|
|
245
|
+
const candidates = found.candidates.filter((candidate) => selectedTuple(candidate.tuple));
|
|
242
246
|
let attempted = 0;
|
|
243
247
|
for (let index = 0; index < Math.min(candidates.length, 128) && attempted < 4; index += 1) {
|
|
244
248
|
const candidate = candidates[(offset + index) % candidates.length];
|
|
@@ -254,30 +258,40 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
254
258
|
await acceptCandidate(candidate.subscriber, candidate.tuple.subscription.objectHash, signal);
|
|
255
259
|
}
|
|
256
260
|
}
|
|
261
|
+
async function loadScopedKeys(view, active) {
|
|
262
|
+
const scopedKeys = active.flatMap((item) => item.tuples.filter((tuple) => tuple.subscription.objectHash === item.selectedSubscription).map((tuple) => ({
|
|
263
|
+
encryptionKey: tuple.subscription.body.encryptionKey,
|
|
264
|
+
keyId: tuple.subscription.body.keyId,
|
|
265
|
+
publisher: options.publisher
|
|
266
|
+
})));
|
|
267
|
+
if (view.hasCurrentAdvertisement && (options.serviceHash === void 0 || view.service.objectHash === options.serviceHash) && (options.deliveryMode === void 0 || view.service.body.deliveryMode === options.deliveryMode)) {
|
|
268
|
+
scopedKeys.push({
|
|
269
|
+
encryptionKey: view.key.objectHash,
|
|
270
|
+
keyId: view.key.body.keyId,
|
|
271
|
+
publisher: options.publisher
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const loadedKeys = await Promise.all(scopedKeys.map(async (key) => await options.keys.getPrivateKey(key)));
|
|
275
|
+
status = {
|
|
276
|
+
...status,
|
|
277
|
+
keyLoaded: scopedKeys.length > 0 && loadedKeys.every((key) => key !== void 0),
|
|
278
|
+
activeKeyIds: [...new Set(scopedKeys.map((key) => key.keyId))]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
257
281
|
return {
|
|
258
282
|
async discover(signal) {
|
|
259
283
|
const view = await activeView(signal);
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
encryptionKey: key.objectHash,
|
|
263
|
-
keyId: key.body.keyId,
|
|
264
|
-
publisher: options.publisher
|
|
265
|
-
});
|
|
266
|
-
status = {
|
|
267
|
-
...status,
|
|
268
|
-
keyLoaded: loaded !== void 0,
|
|
269
|
-
activeKeyIds: key === void 0 ? [] : [key.body.keyId]
|
|
270
|
-
};
|
|
271
|
-
const subscriptions = [];
|
|
272
|
-
const active = view.subscriptions.filter((item) => item.state === "active");
|
|
284
|
+
const active = view.subscriptions.filter((item) => item.state === "active" && item.tuples.some((tuple) => tuple.subscription.objectHash === item.selectedSubscription && selectedTuple(tuple)));
|
|
285
|
+
await loadScopedKeys(view, active);
|
|
273
286
|
if (active.length > (options.maxSubscriptions ?? 64))
|
|
274
287
|
throw new Error("Publisher active subscription limit exceeded");
|
|
288
|
+
const subscriptions = [];
|
|
275
289
|
for (const lifecycle of active) {
|
|
276
290
|
if (lifecycle.state !== "active" || lifecycle.activeFromBlock === void 0)
|
|
277
291
|
continue;
|
|
278
292
|
try {
|
|
279
293
|
const tuple = lifecycle.tuples.find((item) => item.subscription.objectHash === lifecycle.selectedSubscription);
|
|
280
|
-
if (tuple === void 0)
|
|
294
|
+
if (tuple === void 0 || !selectedTuple(tuple))
|
|
281
295
|
throw new Error("Active publisher subscription has no selected tuple");
|
|
282
296
|
const encrypted = tuple.subscription.body;
|
|
283
297
|
if (encrypted.chainId !== options.authorizationChainId)
|
|
@@ -297,6 +311,9 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
297
311
|
options.transport.assertAllowed(plaintext.endpoint);
|
|
298
312
|
subscriptions.push({
|
|
299
313
|
subscriptionHash: tuple.subscription.objectHash,
|
|
314
|
+
serviceHash: tuple.service.objectHash,
|
|
315
|
+
deliveryMode: plaintext.filter.deliveryMode,
|
|
316
|
+
...plaintext.filter.bestEffort === void 0 ? {} : { bestEffort: plaintext.filter.bestEffort },
|
|
300
317
|
authorizationChainId: options.authorizationChainId,
|
|
301
318
|
controller: tuple.subscriber,
|
|
302
319
|
audience: plaintext.audience,
|
|
@@ -316,7 +333,7 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
316
333
|
},
|
|
317
334
|
async isActive(subscription, signal) {
|
|
318
335
|
const view = await activeView(signal);
|
|
319
|
-
return view.subscriptions.some((item) => item.state === "active" && item.selectedSubscription === subscription.subscriptionHash && item.tuples.some((tuple) => tuple.subscription.objectHash === subscription.subscriptionHash && tuple.subscriber === subscription.controller));
|
|
336
|
+
return view.subscriptions.some((item) => item.state === "active" && item.selectedSubscription === subscription.subscriptionHash && item.tuples.some((tuple) => tuple.subscription.objectHash === subscription.subscriptionHash && tuple.subscriber === subscription.controller && selectedTuple(tuple)));
|
|
320
337
|
},
|
|
321
338
|
async acceptPending(signal) {
|
|
322
339
|
if (accepting === void 0) {
|
|
@@ -535,11 +552,7 @@ function loadPublisherAddressPolicy() {
|
|
|
535
552
|
}
|
|
536
553
|
function isPublicPublisherAddress(address) {
|
|
537
554
|
const policy = loadPublisherAddressPolicy();
|
|
538
|
-
|
|
539
|
-
return !policy.nonPublicIpv4.check(address, "ipv4");
|
|
540
|
-
if (isIP(address) !== 6)
|
|
541
|
-
return false;
|
|
542
|
-
return policy.globalIpv6.check(address, "ipv6") && !policy.nonPublicIpv6.check(address, "ipv6");
|
|
555
|
+
return isIP(address) === 4 ? !policy.nonPublicIpv4.check(address, "ipv4") : isIP(address) === 6 && policy.globalIpv6.check(address, "ipv6") && !policy.nonPublicIpv6.check(address, "ipv6");
|
|
543
556
|
}
|
|
544
557
|
function createPublisherHttpsTransport(input) {
|
|
545
558
|
const parsed = OptionsZod.safeParse(input);
|
|
@@ -646,18 +659,128 @@ function createPublisherHttpsTransport(input) {
|
|
|
646
659
|
}
|
|
647
660
|
|
|
648
661
|
// src/runtime/publisherRuntime.ts
|
|
649
|
-
import {
|
|
650
|
-
import { isDeepStrictEqual } from "node:util";
|
|
662
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
|
|
651
663
|
import { createMemoryLocator, PeriodicActor } from "@ariestools/actor";
|
|
652
664
|
import { creatable } from "@ariestools/sdk";
|
|
653
|
-
import {
|
|
665
|
+
import { BestEffortAdmissionAcknowledgmentZod } from "@xyo-network/event-kit-protocol";
|
|
666
|
+
import { BestEffortDeliveryPolicyZod, WakeDeliveryModeZod } from "@xyo-network/event-kit-schemas";
|
|
654
667
|
import { z as z2 } from "zod";
|
|
655
668
|
|
|
656
|
-
// src/runtime/
|
|
669
|
+
// src/runtime/publisherDeliveryState.ts
|
|
657
670
|
import { randomBytes } from "node:crypto";
|
|
671
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
672
|
+
import { WakeEnvelopeSchema, WakeEnvelopeZod } from "@xyo-network/event-kit-schemas";
|
|
673
|
+
function assertDeliveryState(record, subscription) {
|
|
674
|
+
if (!isDeepStrictEqual2(record.subscription, subscription))
|
|
675
|
+
throw new Error("Subscription revision changed without a new identity");
|
|
676
|
+
if (!Number.isSafeInteger(record.cursor) || record.cursor < subscription.fromPosition - 1 || [record.delivered, record.failures, record.nextAttemptMs].some((value) => !(Number.isSafeInteger(value) && value >= 0)) || [record.attempts, record.superseded, record.abandoned, record.totalAttempts, record.lastHintAt, record.lastNotifiedAt, record.lastHintTarget].some((value) => value !== void 0 && !(Number.isSafeInteger(value) && value >= 0)) || !["active", "blocked", "cancelled"].includes(record.state))
|
|
677
|
+
throw new Error("Persisted publisher delivery state is invalid");
|
|
678
|
+
if (record.pending === void 0) return;
|
|
679
|
+
const pending = WakeEnvelopeZod.parse(record.pending);
|
|
680
|
+
if (subscription.deliveryMode === "durable" && pending.fromPosition !== record.cursor + 1 || pending.deliveryMode !== subscription.deliveryMode || pending.toPosition - pending.fromPosition + 1 > subscription.maxPositionsPerWake || pending.authorizationChainId !== subscription.authorizationChainId || pending.deploymentId !== subscription.deploymentId || pending.queueId !== subscription.queueId || pending.filterHash !== subscription.filterHash || !isDeepStrictEqual2(pending.source, subscription.source))
|
|
681
|
+
throw new Error("Persisted publisher outbox binding is invalid");
|
|
682
|
+
}
|
|
683
|
+
function makeEvent(subscription, fromPosition, toPosition, now, wakeId = randomBytes(16).toString("hex")) {
|
|
684
|
+
return WakeEnvelopeZod.parse({
|
|
685
|
+
schema: WakeEnvelopeSchema,
|
|
686
|
+
authorizationChainId: subscription.authorizationChainId,
|
|
687
|
+
deliveryMode: subscription.deliveryMode,
|
|
688
|
+
deploymentId: subscription.deploymentId,
|
|
689
|
+
filterHash: subscription.filterHash,
|
|
690
|
+
fromPosition,
|
|
691
|
+
issuedAt: now,
|
|
692
|
+
source: subscription.source,
|
|
693
|
+
toPosition,
|
|
694
|
+
wakeId,
|
|
695
|
+
...subscription.queueId === void 0 ? {} : { queueId: subscription.queueId }
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
var DeliveryResponseError = class extends Error {
|
|
699
|
+
retryAfterMs;
|
|
700
|
+
status;
|
|
701
|
+
constructor(status, retryAfterMs) {
|
|
702
|
+
super("Publisher admission failed");
|
|
703
|
+
this.status = status;
|
|
704
|
+
this.retryAfterMs = retryAfterMs;
|
|
705
|
+
}
|
|
706
|
+
get retryable() {
|
|
707
|
+
return this.status === 408 || this.status === 429 || this.status >= 500;
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
function prepareBestEffortDelivery(options) {
|
|
711
|
+
const {
|
|
712
|
+
bestEffort,
|
|
713
|
+
first,
|
|
714
|
+
heads,
|
|
715
|
+
now,
|
|
716
|
+
record,
|
|
717
|
+
state
|
|
718
|
+
} = options;
|
|
719
|
+
const subscription = record.subscription;
|
|
720
|
+
const head = heads.get(publisherSourceKey(subscription.source));
|
|
721
|
+
const prior = record.pending;
|
|
722
|
+
const expired = isHintExpired(record, now, bestEffort);
|
|
723
|
+
if (head === void 0 || head instanceof Error || head.position < subscription.fromPosition) {
|
|
724
|
+
if (prior !== void 0 && !expired) return { ...record, pending: prior };
|
|
725
|
+
if (prior !== void 0) state.set("best-effort-deliveries", subscription.subscriptionHash, {
|
|
726
|
+
...record,
|
|
727
|
+
pending: void 0,
|
|
728
|
+
abandoned: (record.abandoned ?? 0) + 1
|
|
729
|
+
});
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const refresh = refreshDue(record, now, bestEffort);
|
|
733
|
+
if (freshHintRequired(record, head.position, first, refresh, expired)) {
|
|
734
|
+
const pending = makeEvent(subscription, head.position, head.position, now);
|
|
735
|
+
const prepared = {
|
|
736
|
+
...record,
|
|
737
|
+
pending,
|
|
738
|
+
attempts: 0,
|
|
739
|
+
lastHintAt: now,
|
|
740
|
+
lastHintTarget: head.position,
|
|
741
|
+
superseded: (record.superseded ?? 0) + (prior !== void 0 && !expired ? 1 : 0),
|
|
742
|
+
abandoned: (record.abandoned ?? 0) + (expired ? 1 : 0),
|
|
743
|
+
nextAttemptMs: 0,
|
|
744
|
+
state: "active"
|
|
745
|
+
};
|
|
746
|
+
state.set("best-effort-deliveries", subscription.subscriptionHash, prepared);
|
|
747
|
+
return prepared;
|
|
748
|
+
}
|
|
749
|
+
if (expired) state.set("best-effort-deliveries", subscription.subscriptionHash, {
|
|
750
|
+
...record,
|
|
751
|
+
pending: void 0,
|
|
752
|
+
abandoned: (record.abandoned ?? 0) + 1
|
|
753
|
+
});
|
|
754
|
+
return prior === void 0 || expired ? void 0 : { ...record, pending: prior };
|
|
755
|
+
}
|
|
756
|
+
function isHintExpired(record, now, policy) {
|
|
757
|
+
return record.pending !== void 0 && (now > record.pending.issuedAt + policy.maxHintAgeSeconds || (record.attempts ?? 0) >= policy.maxAttempts);
|
|
758
|
+
}
|
|
759
|
+
function refreshDue(record, now, policy) {
|
|
760
|
+
return now - (record.lastHintAt ?? record.lastNotifiedAt ?? 0) >= policy.refreshIntervalSeconds;
|
|
761
|
+
}
|
|
762
|
+
function freshHintRequired(record, head, first, refresh, expired) {
|
|
763
|
+
return first || head > (record.pending?.toPosition ?? record.lastHintTarget ?? record.cursor) || refresh && (record.pending === void 0 || expired);
|
|
764
|
+
}
|
|
765
|
+
function failedDelivery(record, current, mode, error, now) {
|
|
766
|
+
const bestEffort = mode === "best-effort";
|
|
767
|
+
const wait = error instanceof DeliveryResponseError && error.retryAfterMs !== void 0 ? error.retryAfterMs : Math.min(3e5, 1e3 * 2 ** Math.min(record.failures, 8));
|
|
768
|
+
return {
|
|
769
|
+
...record,
|
|
770
|
+
failures: record.failures + 1,
|
|
771
|
+
totalAttempts: bestEffort ? current?.totalAttempts ?? record.totalAttempts ?? 0 : record.totalAttempts,
|
|
772
|
+
attempts: bestEffort ? current?.attempts ?? record.attempts ?? 0 : record.attempts,
|
|
773
|
+
state: !bestEffort && error instanceof DeliveryResponseError && !error.retryable ? "blocked" : "active",
|
|
774
|
+
nextAttemptMs: now * 1e3 + Math.min(36e5, wait),
|
|
775
|
+
lastError: "delivery-failed"
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// src/runtime/sourceSigner.ts
|
|
780
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
658
781
|
import {
|
|
659
782
|
NormalizedAddressZod,
|
|
660
|
-
WakeEnvelopeZod,
|
|
783
|
+
WakeEnvelopeZod as WakeEnvelopeZod2,
|
|
661
784
|
WakeJwtSchema
|
|
662
785
|
} from "@xyo-network/event-kit-schemas";
|
|
663
786
|
import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
|
|
@@ -667,14 +790,14 @@ function createAccountSourceWakeSigner(options) {
|
|
|
667
790
|
throw new Error("Publisher signing account mismatch");
|
|
668
791
|
return {
|
|
669
792
|
async sign(input, audience) {
|
|
670
|
-
const event =
|
|
793
|
+
const event = WakeEnvelopeZod2.parse(input);
|
|
671
794
|
const eventHash = await PayloadBuilder2.dataHash(event);
|
|
672
795
|
const now = options.nowSeconds?.() ?? Math.floor(Date.now() / 1e3);
|
|
673
796
|
if (!Number.isSafeInteger(now) || now < 0)
|
|
674
797
|
throw new Error("Publisher signing clock is invalid");
|
|
675
798
|
const { token: jwt } = await options.account.signJwt({
|
|
676
799
|
audience,
|
|
677
|
-
claims: { eventHash, jti:
|
|
800
|
+
claims: { eventHash, jti: randomBytes2(16).toString("hex") },
|
|
678
801
|
now,
|
|
679
802
|
schema: WakeJwtSchema,
|
|
680
803
|
ttl: 60
|
|
@@ -693,31 +816,6 @@ function createAccountSourceWakeSigner(options) {
|
|
|
693
816
|
function activationReady(status) {
|
|
694
817
|
return (status?.keyLoaded ?? true) && (status?.pendingSubscriptions ?? 0) === 0;
|
|
695
818
|
}
|
|
696
|
-
function assertDeliveryState(record, subscription) {
|
|
697
|
-
if (!isDeepStrictEqual(record.subscription, subscription))
|
|
698
|
-
throw new Error("Subscription revision changed without a new identity");
|
|
699
|
-
if (!Number.isSafeInteger(record.cursor) || record.cursor < subscription.fromPosition - 1 || [record.delivered, record.failures, record.nextAttemptMs].some((value) => !(Number.isSafeInteger(value) && value >= 0)) || !["active", "blocked", "cancelled"].includes(record.state))
|
|
700
|
-
throw new Error("Persisted publisher delivery state is invalid");
|
|
701
|
-
if (record.pending !== void 0) {
|
|
702
|
-
const pending = WakeEnvelopeZod2.parse(record.pending);
|
|
703
|
-
if (pending.fromPosition !== record.cursor + 1 || pending.toPosition - pending.fromPosition + 1 > subscription.maxPositionsPerWake || pending.authorizationChainId !== subscription.authorizationChainId || pending.deploymentId !== subscription.deploymentId || pending.queueId !== subscription.queueId || pending.filterHash !== subscription.filterHash || !isDeepStrictEqual(pending.source, subscription.source))
|
|
704
|
-
throw new Error("Persisted publisher outbox binding is invalid");
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
function makeEvent(subscription, fromPosition, toPosition, now, wakeId = randomBytes2(16).toString("hex")) {
|
|
708
|
-
return WakeEnvelopeZod2.parse({
|
|
709
|
-
schema: WakeEnvelopeSchema,
|
|
710
|
-
authorizationChainId: subscription.authorizationChainId,
|
|
711
|
-
deploymentId: subscription.deploymentId,
|
|
712
|
-
filterHash: subscription.filterHash,
|
|
713
|
-
fromPosition,
|
|
714
|
-
issuedAt: now,
|
|
715
|
-
source: subscription.source,
|
|
716
|
-
toPosition,
|
|
717
|
-
wakeId,
|
|
718
|
-
...subscription.queueId === void 0 ? {} : { queueId: subscription.queueId }
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
819
|
var PublisherRuntimeActor = class extends PeriodicActor {
|
|
722
820
|
async runPass(context) {
|
|
723
821
|
await this.params.tick(context.signal);
|
|
@@ -732,20 +830,24 @@ var TimingZod = z2.object({
|
|
|
732
830
|
maxSubscriptions: z2.number().int().min(1).max(1024).default(64),
|
|
733
831
|
requestTimeoutMs: z2.number().int().min(100).max(6e4).default(1e4)
|
|
734
832
|
});
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
};
|
|
833
|
+
function runtimeSettings(options) {
|
|
834
|
+
const deliveryMode = WakeDeliveryModeZod.parse(options.deliveryMode);
|
|
835
|
+
const bestEffort = options.bestEffort === void 0 ? void 0 : BestEffortDeliveryPolicyZod.parse(options.bestEffort);
|
|
836
|
+
if (deliveryMode === "best-effort" !== (bestEffort !== void 0)) throw new TypeError("Explicit delivery mode requires its exact policy");
|
|
837
|
+
return {
|
|
838
|
+
bestEffort,
|
|
839
|
+
deliveryMode,
|
|
840
|
+
deliveryNamespace: deliveryMode === "best-effort" ? "best-effort-deliveries" : "deliveries",
|
|
841
|
+
timing: TimingZod.parse(options)
|
|
842
|
+
};
|
|
843
|
+
}
|
|
747
844
|
async function createPersistentEventPublisherRuntime(options) {
|
|
748
|
-
const
|
|
845
|
+
const {
|
|
846
|
+
bestEffort,
|
|
847
|
+
deliveryMode,
|
|
848
|
+
deliveryNamespace,
|
|
849
|
+
timing
|
|
850
|
+
} = runtimeSettings(options);
|
|
749
851
|
if (options.sources.length < 1 || options.sources.length > 16)
|
|
750
852
|
throw new Error("Publisher requires between one and sixteen bounded sources");
|
|
751
853
|
const clock = options.nowSeconds ?? (() => Math.floor(Date.now() / 1e3));
|
|
@@ -757,7 +859,12 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
757
859
|
const sourceReaders = new Map(options.sources.map((reader) => [publisherSourceKey(reader.source), reader]));
|
|
758
860
|
if (sourceReaders.size !== options.sources.length)
|
|
759
861
|
throw new Error("Duplicate publisher source identity");
|
|
862
|
+
const state = options.state;
|
|
863
|
+
const existingMode = state.get("metadata", "runtime-delivery-mode");
|
|
864
|
+
if (existingMode !== void 0 && existingMode !== deliveryMode) throw new Error("Publisher state delivery mode mismatch; preserve custody and use an explicitly qualified state binding");
|
|
865
|
+
state.set("metadata", "runtime-delivery-mode", deliveryMode);
|
|
760
866
|
const abort = new AbortController();
|
|
867
|
+
const seenThisProcess = /* @__PURE__ */ new Set();
|
|
761
868
|
let running = false;
|
|
762
869
|
let ready = false;
|
|
763
870
|
let clockHealthy = true;
|
|
@@ -765,11 +872,16 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
765
872
|
let lastError;
|
|
766
873
|
let sourcesHealthy = false;
|
|
767
874
|
let stop;
|
|
768
|
-
const state = options.state;
|
|
769
875
|
const timed = (promise, signal) => boundedPublisherOperation(promise, signal, timing.requestTimeoutMs);
|
|
770
876
|
function loadDelivery(subscription, now) {
|
|
877
|
+
if (subscription.deliveryMode !== deliveryMode || !isDeepStrictEqual3(subscription.bestEffort, bestEffort))
|
|
878
|
+
throw new Error("Subscription delivery mode or policy disagrees with runtime");
|
|
879
|
+
if (deliveryMode === "best-effort" && (subscription.maxPositionsPerWake !== 1 || subscription.serviceHash === void 0)) throw new Error("Best Effort requires point range");
|
|
880
|
+
if (options.serviceHash !== void 0 && subscription.serviceHash !== options.serviceHash)
|
|
881
|
+
throw new Error("Subscription does not match the configured exact service descriptor");
|
|
771
882
|
const key = subscription.subscriptionHash;
|
|
772
|
-
|
|
883
|
+
if (deliveryMode === "best-effort" && state.get(deliveryNamespace, key) === void 0 && state.entries(deliveryNamespace).length >= timing.maxSubscriptions) throw new Error("Best Effort state capacity exceeded; live records are not evicted");
|
|
884
|
+
let record = state.get(deliveryNamespace, key);
|
|
773
885
|
if (record === void 0) {
|
|
774
886
|
makeEvent(subscription, subscription.fromPosition, subscription.fromPosition, now, "0".repeat(32));
|
|
775
887
|
if (!Number.isSafeInteger(subscription.maxPositionsPerWake) || subscription.maxPositionsPerWake < 1 || subscription.maxPositionsPerWake > 4096) {
|
|
@@ -783,12 +895,26 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
783
895
|
nextAttemptMs: 0,
|
|
784
896
|
state: "active"
|
|
785
897
|
};
|
|
786
|
-
state.set(
|
|
898
|
+
state.set(deliveryNamespace, key, record);
|
|
787
899
|
}
|
|
788
900
|
assertDeliveryState(record, subscription);
|
|
789
901
|
return record;
|
|
790
902
|
}
|
|
791
903
|
function prepareDelivery(record, now, heads) {
|
|
904
|
+
if (deliveryMode === "best-effort" && bestEffort !== void 0) {
|
|
905
|
+
const first = !seenThisProcess.has(record.subscription.subscriptionHash);
|
|
906
|
+
const head2 = heads.get(publisherSourceKey(record.subscription.source));
|
|
907
|
+
if (head2 !== void 0 && !(head2 instanceof Error) && head2.position >= record.subscription.fromPosition)
|
|
908
|
+
seenThisProcess.add(record.subscription.subscriptionHash);
|
|
909
|
+
return prepareBestEffortDelivery({
|
|
910
|
+
bestEffort,
|
|
911
|
+
first,
|
|
912
|
+
heads,
|
|
913
|
+
now,
|
|
914
|
+
record,
|
|
915
|
+
state
|
|
916
|
+
});
|
|
917
|
+
}
|
|
792
918
|
if (record.pending !== void 0) return { ...record, pending: record.pending };
|
|
793
919
|
const subscription = record.subscription;
|
|
794
920
|
const head = heads.get(publisherSourceKey(subscription.source));
|
|
@@ -800,43 +926,74 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
800
926
|
state.transaction(() => {
|
|
801
927
|
const logicalKey = JSON.stringify([subscription.subscriptionHash, pending.fromPosition, pending.toPosition]);
|
|
802
928
|
if (!state.insert("delivery-events", logicalKey, pending)) throw new Error("Publisher logical range already exists outside its contiguous cursor");
|
|
803
|
-
state.set(
|
|
929
|
+
state.set(deliveryNamespace, subscription.subscriptionHash, prepared);
|
|
804
930
|
});
|
|
805
931
|
return prepared;
|
|
806
932
|
}
|
|
807
933
|
async function stillActive(record, signal) {
|
|
808
934
|
if (await timed(options.discovery.isActive(record.subscription, signal), signal)) return true;
|
|
809
|
-
state.set(
|
|
935
|
+
state.set(deliveryNamespace, record.subscription.subscriptionHash, {
|
|
810
936
|
...record,
|
|
811
937
|
pending: void 0,
|
|
812
938
|
state: "cancelled"
|
|
813
939
|
});
|
|
814
940
|
return false;
|
|
815
941
|
}
|
|
942
|
+
function acknowledgeBestEffort(record, body, eventHash, signal, now) {
|
|
943
|
+
const { pending, subscription } = record;
|
|
944
|
+
const acknowledgment = BestEffortAdmissionAcknowledgmentZod.safeParse(body);
|
|
945
|
+
if (!acknowledgment.success || acknowledgment.data.eventHash !== eventHash || acknowledgment.data.wakeId !== pending.wakeId || acknowledgment.data.expiresAt !== pending.issuedAt + (bestEffort?.maxHintAgeSeconds ?? 0) + 60 || acknowledgment.data.acceptedAt < pending.issuedAt - 60 || acknowledgment.data.acceptedAt > now + 60)
|
|
946
|
+
throw new Error("Publisher did not receive a matching finite acknowledgment");
|
|
947
|
+
signal.throwIfAborted();
|
|
948
|
+
state.set(deliveryNamespace, subscription.subscriptionHash, {
|
|
949
|
+
subscription,
|
|
950
|
+
cursor: pending.toPosition,
|
|
951
|
+
delivered: record.delivered + 1,
|
|
952
|
+
failures: 0,
|
|
953
|
+
attempts: 0,
|
|
954
|
+
superseded: record.superseded ?? 0,
|
|
955
|
+
abandoned: record.abandoned ?? 0,
|
|
956
|
+
totalAttempts: (record.totalAttempts ?? 0) + 1,
|
|
957
|
+
lastNotifiedAt: now,
|
|
958
|
+
lastHintAt: pending.issuedAt,
|
|
959
|
+
lastHintTarget: pending.toPosition,
|
|
960
|
+
nextAttemptMs: 0,
|
|
961
|
+
state: "active"
|
|
962
|
+
});
|
|
963
|
+
}
|
|
816
964
|
async function sendDelivery(record, signal, now) {
|
|
817
965
|
const { pending, subscription } = record;
|
|
818
966
|
signal.throwIfAborted();
|
|
819
967
|
const signed = await signer.sign(pending, subscription.audience);
|
|
820
968
|
signal.throwIfAborted();
|
|
821
|
-
const attempt = state.get("delivery-attempts", pending.wakeId)?.count ?? 0;
|
|
822
|
-
state.set(
|
|
969
|
+
const attempt = deliveryMode === "best-effort" ? record.attempts ?? 0 : state.get("delivery-attempts", pending.wakeId)?.count ?? 0;
|
|
970
|
+
if (deliveryMode === "best-effort") state.set(deliveryNamespace, subscription.subscriptionHash, {
|
|
971
|
+
...record,
|
|
972
|
+
attempts: attempt + 1,
|
|
973
|
+
totalAttempts: (record.totalAttempts ?? 0) + 1
|
|
974
|
+
});
|
|
975
|
+
else state.set("delivery-attempts", pending.wakeId, {
|
|
823
976
|
count: attempt + 1,
|
|
824
977
|
startedAt: now,
|
|
825
978
|
status: "in-flight"
|
|
826
979
|
});
|
|
827
980
|
const response = await options.transport.post(subscription.endpoint, signed.event, signal, signed.jwt);
|
|
828
|
-
state.set("delivery-attempts", pending.wakeId, {
|
|
981
|
+
if (deliveryMode === "durable") state.set("delivery-attempts", pending.wakeId, {
|
|
829
982
|
count: attempt + 1,
|
|
830
983
|
startedAt: now,
|
|
831
984
|
status: response.status
|
|
832
985
|
});
|
|
833
986
|
if (response.status !== 200 && response.status !== 202) throw new DeliveryResponseError(response.status, response.retryAfterMs);
|
|
987
|
+
if (deliveryMode === "best-effort") {
|
|
988
|
+
acknowledgeBestEffort(record, response.body, signed.eventHash, signal, now);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
834
991
|
const receipt = z2.object({ receiptId: z2.string().min(1), wakeId: z2.literal(pending.wakeId) }).safeParse(response.body);
|
|
835
992
|
if (!receipt.success) throw new Error("Publisher admission did not return a matching durable receipt");
|
|
836
993
|
signal.throwIfAborted();
|
|
837
994
|
state.transaction(() => {
|
|
838
995
|
state.set("delivery-receipts", pending.wakeId, { receiptId: receipt.data.receiptId, receivedAt: now });
|
|
839
|
-
state.set(
|
|
996
|
+
state.set(deliveryNamespace, subscription.subscriptionHash, {
|
|
840
997
|
subscription,
|
|
841
998
|
cursor: pending.toPosition,
|
|
842
999
|
delivered: record.delivered + 1,
|
|
@@ -848,7 +1005,7 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
848
1005
|
}
|
|
849
1006
|
async function deliver(subscription, signal, now, heads) {
|
|
850
1007
|
let record = loadDelivery(subscription, now);
|
|
851
|
-
if (record.state !== "active" || record.nextAttemptMs > now * 1e3) return;
|
|
1008
|
+
if (deliveryMode === "durable" && (record.state !== "active" || record.nextAttemptMs > now * 1e3)) return;
|
|
852
1009
|
try {
|
|
853
1010
|
if (!await stillActive(record, signal)) return;
|
|
854
1011
|
options.transport.assertAllowed(subscription.endpoint);
|
|
@@ -856,17 +1013,16 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
856
1013
|
const prepared = prepareDelivery(record, now, heads);
|
|
857
1014
|
if (prepared === void 0) return;
|
|
858
1015
|
record = prepared;
|
|
1016
|
+
if (record.nextAttemptMs > now * 1e3) return;
|
|
859
1017
|
if (!await stillActive(record, signal)) return;
|
|
860
1018
|
await sendDelivery(prepared, signal, now);
|
|
861
1019
|
} catch (error) {
|
|
862
1020
|
if (signal.aborted) return;
|
|
863
|
-
state.set(
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
state
|
|
867
|
-
|
|
868
|
-
lastError: "delivery-failed"
|
|
869
|
-
});
|
|
1021
|
+
state.set(
|
|
1022
|
+
deliveryNamespace,
|
|
1023
|
+
subscription.subscriptionHash,
|
|
1024
|
+
failedDelivery(record, state.get(deliveryNamespace, subscription.subscriptionHash), deliveryMode, error, now)
|
|
1025
|
+
);
|
|
870
1026
|
}
|
|
871
1027
|
}
|
|
872
1028
|
async function readSources(signal) {
|
|
@@ -896,13 +1052,13 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
896
1052
|
if (subscriptions.length > timing.maxSubscriptions) throw new Error("Publisher subscription limit exceeded");
|
|
897
1053
|
const active = new Set(subscriptions.map((subscription) => subscription.subscriptionHash));
|
|
898
1054
|
if (active.size !== subscriptions.length) throw new Error("Duplicate subscription identity");
|
|
899
|
-
const unavailable = state.entries(
|
|
1055
|
+
const unavailable = state.entries(deliveryNamespace).filter((item) => !active.has(item.key) && item.value.state !== "cancelled");
|
|
900
1056
|
let available = true;
|
|
901
1057
|
for (let offset = 0; offset < unavailable.length; offset += timing.concurrency) {
|
|
902
1058
|
signal.throwIfAborted();
|
|
903
1059
|
const results = await Promise.allSettled(unavailable.slice(offset, offset + timing.concurrency).map(async ({ key, value }) => {
|
|
904
1060
|
const stillAuthorized = await timed(options.discovery.isActive(value.subscription, signal), signal);
|
|
905
|
-
if (!stillAuthorized) state.set(
|
|
1061
|
+
if (!stillAuthorized) state.set(deliveryNamespace, key, {
|
|
906
1062
|
...value,
|
|
907
1063
|
pending: void 0,
|
|
908
1064
|
state: "cancelled"
|
|
@@ -960,10 +1116,11 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
960
1116
|
async start() {
|
|
961
1117
|
if (stop !== void 0)
|
|
962
1118
|
throw new Error("Publisher is stopped");
|
|
963
|
-
if (
|
|
964
|
-
|
|
965
|
-
running = true;
|
|
1119
|
+
if (running) {
|
|
1120
|
+
return;
|
|
966
1121
|
}
|
|
1122
|
+
await actor.start();
|
|
1123
|
+
running = true;
|
|
967
1124
|
},
|
|
968
1125
|
async runOnce() {
|
|
969
1126
|
if (stop !== void 0)
|
|
@@ -995,9 +1152,14 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
995
1152
|
throw new AggregateError(failures, "Publisher runtime shutdown failed");
|
|
996
1153
|
})(),
|
|
997
1154
|
status() {
|
|
998
|
-
const deliveries = state.entries(
|
|
1155
|
+
const deliveries = state.entries(deliveryNamespace).map((item) => item.value);
|
|
999
1156
|
return {
|
|
1000
1157
|
running,
|
|
1158
|
+
deliveryMode,
|
|
1159
|
+
...bestEffort === void 0 ? {} : { bestEffort: Object.freeze({ ...bestEffort }) },
|
|
1160
|
+
superseded: deliveries.reduce((sum, item) => sum + (item.superseded ?? 0), 0),
|
|
1161
|
+
abandoned: deliveries.reduce((sum, item) => sum + (item.abandoned ?? 0), 0),
|
|
1162
|
+
totalAttempts: deliveries.reduce((sum, item) => sum + (item.totalAttempts ?? 0), 0),
|
|
1001
1163
|
ready: running && ready,
|
|
1002
1164
|
clockHealthy,
|
|
1003
1165
|
degraded: !ready || deliveries.some((item) => item.state !== "cancelled" && item.failures > 0),
|
|
@@ -1009,10 +1171,15 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
1009
1171
|
passes,
|
|
1010
1172
|
...lastError === void 0 ? {} : { lastError },
|
|
1011
1173
|
...options.discovery.status === void 0 ? {} : { handshake: options.discovery.status() },
|
|
1012
|
-
sources: state.entries("source-heads").map((item) => ({ id: item.key, ...item.value })),
|
|
1013
|
-
subscriptions: state.entries(
|
|
1174
|
+
sources: state.entries("source-heads").filter((item) => sourceReaders.has(item.key)).map((item) => ({ id: item.key, ...item.value })),
|
|
1175
|
+
subscriptions: state.entries(deliveryNamespace).map((item) => ({
|
|
1014
1176
|
id: item.key,
|
|
1015
1177
|
cursor: item.value.cursor,
|
|
1178
|
+
...deliveryMode === "best-effort" ? {
|
|
1179
|
+
...item.value.cursor < item.value.subscription.fromPosition ? {} : { lastAdmissionTarget: item.value.cursor },
|
|
1180
|
+
attempts: item.value.attempts ?? 0,
|
|
1181
|
+
lastHintAt: item.value.lastHintAt
|
|
1182
|
+
} : {},
|
|
1016
1183
|
state: item.value.state,
|
|
1017
1184
|
pending: item.value.pending !== void 0,
|
|
1018
1185
|
failures: item.value.failures
|
|
@@ -1103,7 +1270,7 @@ async function openPublisherDataLake(options) {
|
|
|
1103
1270
|
}
|
|
1104
1271
|
|
|
1105
1272
|
// src/runtime/sqliteHandshakeStores.ts
|
|
1106
|
-
import { isDeepStrictEqual as
|
|
1273
|
+
import { isDeepStrictEqual as isDeepStrictEqual4 } from "node:util";
|
|
1107
1274
|
import { endpointChallengeHash } from "@xyo-network/event-kit-protocol";
|
|
1108
1275
|
import {
|
|
1109
1276
|
Base64Url32BytesZod as Base64Url32BytesZod2,
|
|
@@ -1334,7 +1501,7 @@ function requireTime(value) {
|
|
|
1334
1501
|
if (!Number.isSafeInteger(value) || value < 0) throw new Error("Publisher clock is invalid");
|
|
1335
1502
|
}
|
|
1336
1503
|
function samePublicationIntent(left, right) {
|
|
1337
|
-
return left.chainId === right.chainId && left.source === right.source && left.operationId === right.operationId &&
|
|
1504
|
+
return left.chainId === right.chainId && left.source === right.source && left.operationId === right.operationId && isDeepStrictEqual4(left.eventHashes, right.eventHashes) && isDeepStrictEqual4(left.attachmentHashes, right.attachmentHashes);
|
|
1338
1505
|
}
|
|
1339
1506
|
function publicationKey(intent) {
|
|
1340
1507
|
if (!/^[0-9a-f]{40}$/u.test(intent.chainId) || !/^[0-9a-f]{40}$/u.test(intent.source) || intent.operationId.length === 0 || intent.operationId.length > 256 || [...intent.eventHashes, ...intent.attachmentHashes].some((hash) => !HashZod2.safeParse(hash).success)) {
|
|
@@ -1342,11 +1509,12 @@ function publicationKey(intent) {
|
|
|
1342
1509
|
}
|
|
1343
1510
|
return JSON.stringify([intent.chainId, intent.source, intent.operationId]);
|
|
1344
1511
|
}
|
|
1345
|
-
function createSqlitePublisherHandshakeStores(state) {
|
|
1512
|
+
function createSqlitePublisherHandshakeStores(state, options = {}) {
|
|
1513
|
+
const replayKey = options.replayNamespace === void 0 ? "checkpoint" : `checkpoint:${z4.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/u).parse(options.replayNamespace)}`;
|
|
1346
1514
|
const replay = {
|
|
1347
|
-
load: async () => state.get("replay",
|
|
1515
|
+
load: async () => state.get("replay", replayKey),
|
|
1348
1516
|
async save(checkpoint) {
|
|
1349
|
-
state.set("replay",
|
|
1517
|
+
state.set("replay", replayKey, checkpoint);
|
|
1350
1518
|
}
|
|
1351
1519
|
};
|
|
1352
1520
|
const intents = {
|
|
@@ -1384,7 +1552,7 @@ function createSqlitePublisherHandshakeStores(state) {
|
|
|
1384
1552
|
const key = `${record.publisher}:${record.keyId}`;
|
|
1385
1553
|
const prior = state.get("encryption-keys", key);
|
|
1386
1554
|
if (prior !== void 0) {
|
|
1387
|
-
if (!
|
|
1555
|
+
if (!isDeepStrictEqual4(prior, record)) throw new Error("Encryption key collision");
|
|
1388
1556
|
return "duplicate";
|
|
1389
1557
|
}
|
|
1390
1558
|
state.set("encryption-keys", key, record);
|
|
@@ -1404,7 +1572,7 @@ function createSqlitePublisherHandshakeStores(state) {
|
|
|
1404
1572
|
const priorNonce = state.get("challenge-nonces", challenge.nonce);
|
|
1405
1573
|
if (priorNonce !== void 0 && priorNonce !== hash) return { kind: "collision" };
|
|
1406
1574
|
const prior = current(hash);
|
|
1407
|
-
if (prior !== void 0) return
|
|
1575
|
+
if (prior !== void 0) return isDeepStrictEqual4(prior.challenge, challenge) ? { kind: "duplicate", record: prior } : { kind: "collision" };
|
|
1408
1576
|
const record = {
|
|
1409
1577
|
challenge,
|
|
1410
1578
|
challengeHash: hash,
|
|
@@ -1457,7 +1625,7 @@ function createSqlitePublisherHandshakeStores(state) {
|
|
|
1457
1625
|
for (const phase of phases) {
|
|
1458
1626
|
const record = state.get(`publication:${phase}`, key);
|
|
1459
1627
|
if (record === void 0) continue;
|
|
1460
|
-
if (record.phase !== phase || !samePublicationIntent(record, intent) || !
|
|
1628
|
+
if (record.phase !== phase || !samePublicationIntent(record, intent) || !isDeepStrictEqual4(record.evidence, signed.evidence)) {
|
|
1461
1629
|
throw new Error("Publication evidence collision");
|
|
1462
1630
|
}
|
|
1463
1631
|
latest = record;
|
|
@@ -1471,12 +1639,12 @@ function createSqlitePublisherHandshakeStores(state) {
|
|
|
1471
1639
|
state.transaction(() => {
|
|
1472
1640
|
const signed = state.get("publication:signed", key);
|
|
1473
1641
|
if (signed === void 0 && record.phase !== "signed") throw new Error("Publication must bind signed evidence first");
|
|
1474
|
-
if (signed !== void 0 && (!samePublicationIntent(signed, record) || !
|
|
1642
|
+
if (signed !== void 0 && (!samePublicationIntent(signed, record) || !isDeepStrictEqual4(signed.evidence, record.evidence))) {
|
|
1475
1643
|
throw new Error("Publication intent or evidence collision");
|
|
1476
1644
|
}
|
|
1477
1645
|
const namespace = `publication:${record.phase}`;
|
|
1478
1646
|
const prior = state.get(namespace, key);
|
|
1479
|
-
if (prior !== void 0 && !
|
|
1647
|
+
if (prior !== void 0 && !isDeepStrictEqual4(prior, record)) throw new Error("Publication phase collision");
|
|
1480
1648
|
if (prior === void 0) state.set(namespace, key, record);
|
|
1481
1649
|
});
|
|
1482
1650
|
}
|
|
@@ -1512,9 +1680,7 @@ function replaceJsonValue(_key, candidate) {
|
|
|
1512
1680
|
// src/runtime/sqlitePublisherState.ts
|
|
1513
1681
|
function decode(value) {
|
|
1514
1682
|
return JSON.parse(value, (_key, candidate) => {
|
|
1515
|
-
|
|
1516
|
-
return BigInt(candidate.$eventKitBigInt);
|
|
1517
|
-
return candidate;
|
|
1683
|
+
return typeof candidate === "object" && candidate !== null && Object.keys(candidate).length === 1 && "$eventKitBigInt" in candidate && typeof candidate.$eventKitBigInt === "string" && /^(?:0|[1-9][0-9]*)$/u.test(candidate.$eventKitBigInt) ? BigInt(candidate.$eventKitBigInt) : candidate;
|
|
1518
1684
|
});
|
|
1519
1685
|
}
|
|
1520
1686
|
function processExists(pid) {
|