@xyo-network/event-kit-node 1.0.1 → 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/handshake.mjs +12 -0
- 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 +277 -93
- 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/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 +265 -93
- package/dist/node/runtime.mjs.map +4 -4
- package/package.json +8 -8
package/dist/node/index.mjs
CHANGED
|
@@ -1349,6 +1349,8 @@ async function persistMutationIntent(store, intent) {
|
|
|
1349
1349
|
}
|
|
1350
1350
|
async function publisherServiceRequestFingerprint(input) {
|
|
1351
1351
|
return await handshakeMutationRequestFingerprint({
|
|
1352
|
+
deliveryMode: input.deliveryMode,
|
|
1353
|
+
bestEffort: input.bestEffort ?? null,
|
|
1352
1354
|
expiresAtBlock: input.expiresAtBlock ?? null,
|
|
1353
1355
|
maxPositionsPerWake: input.maxPositionsPerWake,
|
|
1354
1356
|
sources: input.sources,
|
|
@@ -1368,6 +1370,8 @@ async function publisherRotationRequestFingerprint(input) {
|
|
|
1368
1370
|
}
|
|
1369
1371
|
async function subscriberSubscriptionRequestFingerprint(input) {
|
|
1370
1372
|
return await handshakeMutationRequestFingerprint({
|
|
1373
|
+
deliveryMode: input.deliveryMode,
|
|
1374
|
+
bestEffort: input.bestEffort ?? null,
|
|
1371
1375
|
audience: input.audience,
|
|
1372
1376
|
challengeSecret: input.challengeSecret,
|
|
1373
1377
|
deploymentId: input.deploymentId,
|
|
@@ -1754,6 +1758,8 @@ function createPublisherHandshakeClient(options) {
|
|
|
1754
1758
|
validFromBlock
|
|
1755
1759
|
};
|
|
1756
1760
|
const service = createPublisherService({
|
|
1761
|
+
deliveryMode: input.deliveryMode,
|
|
1762
|
+
...input.bestEffort === void 0 ? {} : { bestEffort: input.bestEffort },
|
|
1757
1763
|
...serviceInput,
|
|
1758
1764
|
authorizationChainId: options.reader.chainId,
|
|
1759
1765
|
sources: input.sources,
|
|
@@ -1873,6 +1879,8 @@ function createPublisherHandshakeClient(options) {
|
|
|
1873
1879
|
validFromBlock
|
|
1874
1880
|
};
|
|
1875
1881
|
const service = createPublisherService({
|
|
1882
|
+
deliveryMode: selected.service.body.deliveryMode,
|
|
1883
|
+
...selected.service.body.bestEffort === void 0 ? {} : { bestEffort: selected.service.body.bestEffort },
|
|
1876
1884
|
...serviceInput,
|
|
1877
1885
|
authorizationChainId: options.reader.chainId,
|
|
1878
1886
|
sources: selected.service.body.events.map((event) => event.source),
|
|
@@ -2166,6 +2174,8 @@ function createSubscriberHandshakeClient(options) {
|
|
|
2166
2174
|
return Object.freeze(client);
|
|
2167
2175
|
}
|
|
2168
2176
|
function subscriberPlaintext(service, input, maxPositionsPerWake) {
|
|
2177
|
+
if (input.deliveryMode !== service.deliveryMode || canonicalJson(input.bestEffort ?? null) !== canonicalJson(service.bestEffort ?? null))
|
|
2178
|
+
throw new Error("Requested delivery terms do not match exact selected service");
|
|
2169
2179
|
const plaintextInput = {
|
|
2170
2180
|
audience: input.audience,
|
|
2171
2181
|
challengeSecret: input.challengeSecret,
|
|
@@ -2178,6 +2188,8 @@ function subscriberPlaintext(service, input, maxPositionsPerWake) {
|
|
|
2178
2188
|
throw new Error("Requested source is not advertised by the selected service");
|
|
2179
2189
|
}
|
|
2180
2190
|
return createWakeSubscriptionPlaintext({
|
|
2191
|
+
deliveryMode: input.deliveryMode,
|
|
2192
|
+
...input.bestEffort === void 0 ? {} : { bestEffort: input.bestEffort },
|
|
2181
2193
|
...plaintextInput,
|
|
2182
2194
|
source,
|
|
2183
2195
|
fromPosition: input.fromPosition,
|
|
@@ -2317,6 +2329,7 @@ function createBoundedPublisherEthersTransport(options = {}) {
|
|
|
2317
2329
|
}
|
|
2318
2330
|
|
|
2319
2331
|
// src/runtime/handshakeDiscovery.ts
|
|
2332
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
2320
2333
|
import { decryptWakeSubscription, sourceWakeFilterHash } from "@xyo-network/event-kit-protocol";
|
|
2321
2334
|
import { EndpointChallengeResponseZod as EndpointChallengeResponseZod2 } from "@xyo-network/event-kit-schemas";
|
|
2322
2335
|
function createPublisherHandshakeDiscovery(options) {
|
|
@@ -2332,6 +2345,9 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
2332
2345
|
rejectedSubscriptions: 0,
|
|
2333
2346
|
challengeFailures: 0
|
|
2334
2347
|
};
|
|
2348
|
+
function selectedTuple(tuple) {
|
|
2349
|
+
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 || isDeepStrictEqual2(tuple.service.body.bestEffort, options.bestEffort));
|
|
2350
|
+
}
|
|
2335
2351
|
async function activeView(signal) {
|
|
2336
2352
|
signal.throwIfAborted();
|
|
2337
2353
|
viewing ??= (async () => {
|
|
@@ -2349,7 +2365,7 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
2349
2365
|
...status,
|
|
2350
2366
|
finalizedHead: view.finalizedHead,
|
|
2351
2367
|
indexedThrough: view.indexedThrough,
|
|
2352
|
-
pendingSubscriptions: view.subscriptions.filter((item) => item.state === "pending").length,
|
|
2368
|
+
pendingSubscriptions: view.subscriptions.filter((item) => item.state === "pending" && item.tuples.some(selectedTuple)).length,
|
|
2353
2369
|
rejectedSubscriptions: view.rejections.length
|
|
2354
2370
|
};
|
|
2355
2371
|
return view;
|
|
@@ -2414,7 +2430,7 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
2414
2430
|
if (!found.ok)
|
|
2415
2431
|
throw new Error("Publisher acceptance candidates are unavailable");
|
|
2416
2432
|
const offset = options.state.get("challenge-rate", "offset") ?? 0;
|
|
2417
|
-
const candidates = found.candidates;
|
|
2433
|
+
const candidates = found.candidates.filter((candidate) => selectedTuple(candidate.tuple));
|
|
2418
2434
|
let attempted = 0;
|
|
2419
2435
|
for (let index = 0; index < Math.min(candidates.length, 128) && attempted < 4; index += 1) {
|
|
2420
2436
|
const candidate = candidates[(offset + index) % candidates.length];
|
|
@@ -2430,30 +2446,40 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
2430
2446
|
await acceptCandidate(candidate.subscriber, candidate.tuple.subscription.objectHash, signal);
|
|
2431
2447
|
}
|
|
2432
2448
|
}
|
|
2449
|
+
async function loadScopedKeys(view, active) {
|
|
2450
|
+
const scopedKeys = active.flatMap((item) => item.tuples.filter((tuple) => tuple.subscription.objectHash === item.selectedSubscription).map((tuple) => ({
|
|
2451
|
+
encryptionKey: tuple.subscription.body.encryptionKey,
|
|
2452
|
+
keyId: tuple.subscription.body.keyId,
|
|
2453
|
+
publisher: options.publisher
|
|
2454
|
+
})));
|
|
2455
|
+
if (view.hasCurrentAdvertisement && (options.serviceHash === void 0 || view.service.objectHash === options.serviceHash) && (options.deliveryMode === void 0 || view.service.body.deliveryMode === options.deliveryMode)) {
|
|
2456
|
+
scopedKeys.push({
|
|
2457
|
+
encryptionKey: view.key.objectHash,
|
|
2458
|
+
keyId: view.key.body.keyId,
|
|
2459
|
+
publisher: options.publisher
|
|
2460
|
+
});
|
|
2461
|
+
}
|
|
2462
|
+
const loadedKeys = await Promise.all(scopedKeys.map(async (key) => await options.keys.getPrivateKey(key)));
|
|
2463
|
+
status = {
|
|
2464
|
+
...status,
|
|
2465
|
+
keyLoaded: scopedKeys.length > 0 && loadedKeys.every((key) => key !== void 0),
|
|
2466
|
+
activeKeyIds: [...new Set(scopedKeys.map((key) => key.keyId))]
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2433
2469
|
return {
|
|
2434
2470
|
async discover(signal) {
|
|
2435
2471
|
const view = await activeView(signal);
|
|
2436
|
-
const
|
|
2437
|
-
|
|
2438
|
-
encryptionKey: key.objectHash,
|
|
2439
|
-
keyId: key.body.keyId,
|
|
2440
|
-
publisher: options.publisher
|
|
2441
|
-
});
|
|
2442
|
-
status = {
|
|
2443
|
-
...status,
|
|
2444
|
-
keyLoaded: loaded !== void 0,
|
|
2445
|
-
activeKeyIds: key === void 0 ? [] : [key.body.keyId]
|
|
2446
|
-
};
|
|
2447
|
-
const subscriptions = [];
|
|
2448
|
-
const active = view.subscriptions.filter((item) => item.state === "active");
|
|
2472
|
+
const active = view.subscriptions.filter((item) => item.state === "active" && item.tuples.some((tuple) => tuple.subscription.objectHash === item.selectedSubscription && selectedTuple(tuple)));
|
|
2473
|
+
await loadScopedKeys(view, active);
|
|
2449
2474
|
if (active.length > (options.maxSubscriptions ?? 64))
|
|
2450
2475
|
throw new Error("Publisher active subscription limit exceeded");
|
|
2476
|
+
const subscriptions = [];
|
|
2451
2477
|
for (const lifecycle of active) {
|
|
2452
2478
|
if (lifecycle.state !== "active" || lifecycle.activeFromBlock === void 0)
|
|
2453
2479
|
continue;
|
|
2454
2480
|
try {
|
|
2455
2481
|
const tuple = lifecycle.tuples.find((item) => item.subscription.objectHash === lifecycle.selectedSubscription);
|
|
2456
|
-
if (tuple === void 0)
|
|
2482
|
+
if (tuple === void 0 || !selectedTuple(tuple))
|
|
2457
2483
|
throw new Error("Active publisher subscription has no selected tuple");
|
|
2458
2484
|
const encrypted = tuple.subscription.body;
|
|
2459
2485
|
if (encrypted.chainId !== options.authorizationChainId)
|
|
@@ -2473,6 +2499,9 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
2473
2499
|
options.transport.assertAllowed(plaintext.endpoint);
|
|
2474
2500
|
subscriptions.push({
|
|
2475
2501
|
subscriptionHash: tuple.subscription.objectHash,
|
|
2502
|
+
serviceHash: tuple.service.objectHash,
|
|
2503
|
+
deliveryMode: plaintext.filter.deliveryMode,
|
|
2504
|
+
...plaintext.filter.bestEffort === void 0 ? {} : { bestEffort: plaintext.filter.bestEffort },
|
|
2476
2505
|
authorizationChainId: options.authorizationChainId,
|
|
2477
2506
|
controller: tuple.subscriber,
|
|
2478
2507
|
audience: plaintext.audience,
|
|
@@ -2492,7 +2521,7 @@ function createPublisherHandshakeDiscovery(options) {
|
|
|
2492
2521
|
},
|
|
2493
2522
|
async isActive(subscription, signal) {
|
|
2494
2523
|
const view = await activeView(signal);
|
|
2495
|
-
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));
|
|
2524
|
+
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)));
|
|
2496
2525
|
},
|
|
2497
2526
|
async acceptPending(signal) {
|
|
2498
2527
|
if (accepting === void 0) {
|
|
@@ -2818,18 +2847,128 @@ function createPublisherHttpsTransport(input) {
|
|
|
2818
2847
|
}
|
|
2819
2848
|
|
|
2820
2849
|
// src/runtime/publisherRuntime.ts
|
|
2821
|
-
import {
|
|
2822
|
-
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
2850
|
+
import { isDeepStrictEqual as isDeepStrictEqual4 } from "node:util";
|
|
2823
2851
|
import { createMemoryLocator, PeriodicActor } from "@ariestools/actor";
|
|
2824
2852
|
import { creatable } from "@ariestools/sdk";
|
|
2825
|
-
import {
|
|
2853
|
+
import { BestEffortAdmissionAcknowledgmentZod } from "@xyo-network/event-kit-protocol";
|
|
2854
|
+
import { BestEffortDeliveryPolicyZod, WakeDeliveryModeZod } from "@xyo-network/event-kit-schemas";
|
|
2826
2855
|
import { z as z3 } from "zod";
|
|
2827
2856
|
|
|
2828
|
-
// src/runtime/
|
|
2857
|
+
// src/runtime/publisherDeliveryState.ts
|
|
2829
2858
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
2859
|
+
import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
|
|
2860
|
+
import { WakeEnvelopeSchema, WakeEnvelopeZod } from "@xyo-network/event-kit-schemas";
|
|
2861
|
+
function assertDeliveryState(record, subscription) {
|
|
2862
|
+
if (!isDeepStrictEqual3(record.subscription, subscription))
|
|
2863
|
+
throw new Error("Subscription revision changed without a new identity");
|
|
2864
|
+
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))
|
|
2865
|
+
throw new Error("Persisted publisher delivery state is invalid");
|
|
2866
|
+
if (record.pending === void 0) return;
|
|
2867
|
+
const pending = WakeEnvelopeZod.parse(record.pending);
|
|
2868
|
+
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 || !isDeepStrictEqual3(pending.source, subscription.source))
|
|
2869
|
+
throw new Error("Persisted publisher outbox binding is invalid");
|
|
2870
|
+
}
|
|
2871
|
+
function makeEvent(subscription, fromPosition, toPosition, now, wakeId = randomBytes5(16).toString("hex")) {
|
|
2872
|
+
return WakeEnvelopeZod.parse({
|
|
2873
|
+
schema: WakeEnvelopeSchema,
|
|
2874
|
+
authorizationChainId: subscription.authorizationChainId,
|
|
2875
|
+
deliveryMode: subscription.deliveryMode,
|
|
2876
|
+
deploymentId: subscription.deploymentId,
|
|
2877
|
+
filterHash: subscription.filterHash,
|
|
2878
|
+
fromPosition,
|
|
2879
|
+
issuedAt: now,
|
|
2880
|
+
source: subscription.source,
|
|
2881
|
+
toPosition,
|
|
2882
|
+
wakeId,
|
|
2883
|
+
...subscription.queueId === void 0 ? {} : { queueId: subscription.queueId }
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
var DeliveryResponseError = class extends Error {
|
|
2887
|
+
retryAfterMs;
|
|
2888
|
+
status;
|
|
2889
|
+
constructor(status, retryAfterMs) {
|
|
2890
|
+
super("Publisher admission failed");
|
|
2891
|
+
this.status = status;
|
|
2892
|
+
this.retryAfterMs = retryAfterMs;
|
|
2893
|
+
}
|
|
2894
|
+
get retryable() {
|
|
2895
|
+
return this.status === 408 || this.status === 429 || this.status >= 500;
|
|
2896
|
+
}
|
|
2897
|
+
};
|
|
2898
|
+
function prepareBestEffortDelivery(options) {
|
|
2899
|
+
const {
|
|
2900
|
+
bestEffort,
|
|
2901
|
+
first,
|
|
2902
|
+
heads,
|
|
2903
|
+
now,
|
|
2904
|
+
record,
|
|
2905
|
+
state
|
|
2906
|
+
} = options;
|
|
2907
|
+
const subscription = record.subscription;
|
|
2908
|
+
const head = heads.get(publisherSourceKey(subscription.source));
|
|
2909
|
+
const prior = record.pending;
|
|
2910
|
+
const expired = isHintExpired(record, now, bestEffort);
|
|
2911
|
+
if (head === void 0 || head instanceof Error || head.position < subscription.fromPosition) {
|
|
2912
|
+
if (prior !== void 0 && !expired) return { ...record, pending: prior };
|
|
2913
|
+
if (prior !== void 0) state.set("best-effort-deliveries", subscription.subscriptionHash, {
|
|
2914
|
+
...record,
|
|
2915
|
+
pending: void 0,
|
|
2916
|
+
abandoned: (record.abandoned ?? 0) + 1
|
|
2917
|
+
});
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
const refresh = refreshDue(record, now, bestEffort);
|
|
2921
|
+
if (freshHintRequired(record, head.position, first, refresh, expired)) {
|
|
2922
|
+
const pending = makeEvent(subscription, head.position, head.position, now);
|
|
2923
|
+
const prepared = {
|
|
2924
|
+
...record,
|
|
2925
|
+
pending,
|
|
2926
|
+
attempts: 0,
|
|
2927
|
+
lastHintAt: now,
|
|
2928
|
+
lastHintTarget: head.position,
|
|
2929
|
+
superseded: (record.superseded ?? 0) + (prior !== void 0 && !expired ? 1 : 0),
|
|
2930
|
+
abandoned: (record.abandoned ?? 0) + (expired ? 1 : 0),
|
|
2931
|
+
nextAttemptMs: 0,
|
|
2932
|
+
state: "active"
|
|
2933
|
+
};
|
|
2934
|
+
state.set("best-effort-deliveries", subscription.subscriptionHash, prepared);
|
|
2935
|
+
return prepared;
|
|
2936
|
+
}
|
|
2937
|
+
if (expired) state.set("best-effort-deliveries", subscription.subscriptionHash, {
|
|
2938
|
+
...record,
|
|
2939
|
+
pending: void 0,
|
|
2940
|
+
abandoned: (record.abandoned ?? 0) + 1
|
|
2941
|
+
});
|
|
2942
|
+
return prior === void 0 || expired ? void 0 : { ...record, pending: prior };
|
|
2943
|
+
}
|
|
2944
|
+
function isHintExpired(record, now, policy) {
|
|
2945
|
+
return record.pending !== void 0 && (now > record.pending.issuedAt + policy.maxHintAgeSeconds || (record.attempts ?? 0) >= policy.maxAttempts);
|
|
2946
|
+
}
|
|
2947
|
+
function refreshDue(record, now, policy) {
|
|
2948
|
+
return now - (record.lastHintAt ?? record.lastNotifiedAt ?? 0) >= policy.refreshIntervalSeconds;
|
|
2949
|
+
}
|
|
2950
|
+
function freshHintRequired(record, head, first, refresh, expired) {
|
|
2951
|
+
return first || head > (record.pending?.toPosition ?? record.lastHintTarget ?? record.cursor) || refresh && (record.pending === void 0 || expired);
|
|
2952
|
+
}
|
|
2953
|
+
function failedDelivery(record, current, mode, error, now) {
|
|
2954
|
+
const bestEffort = mode === "best-effort";
|
|
2955
|
+
const wait = error instanceof DeliveryResponseError && error.retryAfterMs !== void 0 ? error.retryAfterMs : Math.min(3e5, 1e3 * 2 ** Math.min(record.failures, 8));
|
|
2956
|
+
return {
|
|
2957
|
+
...record,
|
|
2958
|
+
failures: record.failures + 1,
|
|
2959
|
+
totalAttempts: bestEffort ? current?.totalAttempts ?? record.totalAttempts ?? 0 : record.totalAttempts,
|
|
2960
|
+
attempts: bestEffort ? current?.attempts ?? record.attempts ?? 0 : record.attempts,
|
|
2961
|
+
state: !bestEffort && error instanceof DeliveryResponseError && !error.retryable ? "blocked" : "active",
|
|
2962
|
+
nextAttemptMs: now * 1e3 + Math.min(36e5, wait),
|
|
2963
|
+
lastError: "delivery-failed"
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
// src/runtime/sourceSigner.ts
|
|
2968
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
2830
2969
|
import {
|
|
2831
2970
|
NormalizedAddressZod as NormalizedAddressZod4,
|
|
2832
|
-
WakeEnvelopeZod,
|
|
2971
|
+
WakeEnvelopeZod as WakeEnvelopeZod2,
|
|
2833
2972
|
WakeJwtSchema
|
|
2834
2973
|
} from "@xyo-network/event-kit-schemas";
|
|
2835
2974
|
import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
|
|
@@ -2839,14 +2978,14 @@ function createAccountSourceWakeSigner(options) {
|
|
|
2839
2978
|
throw new Error("Publisher signing account mismatch");
|
|
2840
2979
|
return {
|
|
2841
2980
|
async sign(input, audience) {
|
|
2842
|
-
const event =
|
|
2981
|
+
const event = WakeEnvelopeZod2.parse(input);
|
|
2843
2982
|
const eventHash = await PayloadBuilder2.dataHash(event);
|
|
2844
2983
|
const now = options.nowSeconds?.() ?? Math.floor(Date.now() / 1e3);
|
|
2845
2984
|
if (!Number.isSafeInteger(now) || now < 0)
|
|
2846
2985
|
throw new Error("Publisher signing clock is invalid");
|
|
2847
2986
|
const { token: jwt } = await options.account.signJwt({
|
|
2848
2987
|
audience,
|
|
2849
|
-
claims: { eventHash, jti:
|
|
2988
|
+
claims: { eventHash, jti: randomBytes6(16).toString("hex") },
|
|
2850
2989
|
now,
|
|
2851
2990
|
schema: WakeJwtSchema,
|
|
2852
2991
|
ttl: 60
|
|
@@ -2865,30 +3004,6 @@ function createAccountSourceWakeSigner(options) {
|
|
|
2865
3004
|
function activationReady(status) {
|
|
2866
3005
|
return (status?.keyLoaded ?? true) && (status?.pendingSubscriptions ?? 0) === 0;
|
|
2867
3006
|
}
|
|
2868
|
-
function assertDeliveryState(record, subscription) {
|
|
2869
|
-
if (!isDeepStrictEqual2(record.subscription, subscription))
|
|
2870
|
-
throw new Error("Subscription revision changed without a new identity");
|
|
2871
|
-
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))
|
|
2872
|
-
throw new Error("Persisted publisher delivery state is invalid");
|
|
2873
|
-
if (record.pending === void 0) return;
|
|
2874
|
-
const pending = WakeEnvelopeZod2.parse(record.pending);
|
|
2875
|
-
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 || !isDeepStrictEqual2(pending.source, subscription.source))
|
|
2876
|
-
throw new Error("Persisted publisher outbox binding is invalid");
|
|
2877
|
-
}
|
|
2878
|
-
function makeEvent(subscription, fromPosition, toPosition, now, wakeId = randomBytes6(16).toString("hex")) {
|
|
2879
|
-
return WakeEnvelopeZod2.parse({
|
|
2880
|
-
schema: WakeEnvelopeSchema,
|
|
2881
|
-
authorizationChainId: subscription.authorizationChainId,
|
|
2882
|
-
deploymentId: subscription.deploymentId,
|
|
2883
|
-
filterHash: subscription.filterHash,
|
|
2884
|
-
fromPosition,
|
|
2885
|
-
issuedAt: now,
|
|
2886
|
-
source: subscription.source,
|
|
2887
|
-
toPosition,
|
|
2888
|
-
wakeId,
|
|
2889
|
-
...subscription.queueId === void 0 ? {} : { queueId: subscription.queueId }
|
|
2890
|
-
});
|
|
2891
|
-
}
|
|
2892
3007
|
var PublisherRuntimeActor = class extends PeriodicActor {
|
|
2893
3008
|
async runPass(context) {
|
|
2894
3009
|
await this.params.tick(context.signal);
|
|
@@ -2903,20 +3018,24 @@ var TimingZod = z3.object({
|
|
|
2903
3018
|
maxSubscriptions: z3.number().int().min(1).max(1024).default(64),
|
|
2904
3019
|
requestTimeoutMs: z3.number().int().min(100).max(6e4).default(1e4)
|
|
2905
3020
|
});
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
};
|
|
3021
|
+
function runtimeSettings(options) {
|
|
3022
|
+
const deliveryMode = WakeDeliveryModeZod.parse(options.deliveryMode);
|
|
3023
|
+
const bestEffort = options.bestEffort === void 0 ? void 0 : BestEffortDeliveryPolicyZod.parse(options.bestEffort);
|
|
3024
|
+
if (deliveryMode === "best-effort" !== (bestEffort !== void 0)) throw new TypeError("Explicit delivery mode requires its exact policy");
|
|
3025
|
+
return {
|
|
3026
|
+
bestEffort,
|
|
3027
|
+
deliveryMode,
|
|
3028
|
+
deliveryNamespace: deliveryMode === "best-effort" ? "best-effort-deliveries" : "deliveries",
|
|
3029
|
+
timing: TimingZod.parse(options)
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
2918
3032
|
async function createPersistentEventPublisherRuntime(options) {
|
|
2919
|
-
const
|
|
3033
|
+
const {
|
|
3034
|
+
bestEffort,
|
|
3035
|
+
deliveryMode,
|
|
3036
|
+
deliveryNamespace,
|
|
3037
|
+
timing
|
|
3038
|
+
} = runtimeSettings(options);
|
|
2920
3039
|
if (options.sources.length < 1 || options.sources.length > 16)
|
|
2921
3040
|
throw new Error("Publisher requires between one and sixteen bounded sources");
|
|
2922
3041
|
const clock = options.nowSeconds ?? (() => Math.floor(Date.now() / 1e3));
|
|
@@ -2928,7 +3047,12 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
2928
3047
|
const sourceReaders = new Map(options.sources.map((reader) => [publisherSourceKey(reader.source), reader]));
|
|
2929
3048
|
if (sourceReaders.size !== options.sources.length)
|
|
2930
3049
|
throw new Error("Duplicate publisher source identity");
|
|
3050
|
+
const state = options.state;
|
|
3051
|
+
const existingMode = state.get("metadata", "runtime-delivery-mode");
|
|
3052
|
+
if (existingMode !== void 0 && existingMode !== deliveryMode) throw new Error("Publisher state delivery mode mismatch; preserve custody and use an explicitly qualified state binding");
|
|
3053
|
+
state.set("metadata", "runtime-delivery-mode", deliveryMode);
|
|
2931
3054
|
const abort = new AbortController();
|
|
3055
|
+
const seenThisProcess = /* @__PURE__ */ new Set();
|
|
2932
3056
|
let running = false;
|
|
2933
3057
|
let ready = false;
|
|
2934
3058
|
let clockHealthy = true;
|
|
@@ -2936,11 +3060,16 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
2936
3060
|
let lastError;
|
|
2937
3061
|
let sourcesHealthy = false;
|
|
2938
3062
|
let stop;
|
|
2939
|
-
const state = options.state;
|
|
2940
3063
|
const timed = (promise, signal) => boundedPublisherOperation(promise, signal, timing.requestTimeoutMs);
|
|
2941
3064
|
function loadDelivery(subscription, now) {
|
|
3065
|
+
if (subscription.deliveryMode !== deliveryMode || !isDeepStrictEqual4(subscription.bestEffort, bestEffort))
|
|
3066
|
+
throw new Error("Subscription delivery mode or policy disagrees with runtime");
|
|
3067
|
+
if (deliveryMode === "best-effort" && (subscription.maxPositionsPerWake !== 1 || subscription.serviceHash === void 0)) throw new Error("Best Effort requires point range");
|
|
3068
|
+
if (options.serviceHash !== void 0 && subscription.serviceHash !== options.serviceHash)
|
|
3069
|
+
throw new Error("Subscription does not match the configured exact service descriptor");
|
|
2942
3070
|
const key = subscription.subscriptionHash;
|
|
2943
|
-
|
|
3071
|
+
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");
|
|
3072
|
+
let record = state.get(deliveryNamespace, key);
|
|
2944
3073
|
if (record === void 0) {
|
|
2945
3074
|
makeEvent(subscription, subscription.fromPosition, subscription.fromPosition, now, "0".repeat(32));
|
|
2946
3075
|
if (!Number.isSafeInteger(subscription.maxPositionsPerWake) || subscription.maxPositionsPerWake < 1 || subscription.maxPositionsPerWake > 4096) {
|
|
@@ -2954,12 +3083,26 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
2954
3083
|
nextAttemptMs: 0,
|
|
2955
3084
|
state: "active"
|
|
2956
3085
|
};
|
|
2957
|
-
state.set(
|
|
3086
|
+
state.set(deliveryNamespace, key, record);
|
|
2958
3087
|
}
|
|
2959
3088
|
assertDeliveryState(record, subscription);
|
|
2960
3089
|
return record;
|
|
2961
3090
|
}
|
|
2962
3091
|
function prepareDelivery(record, now, heads) {
|
|
3092
|
+
if (deliveryMode === "best-effort" && bestEffort !== void 0) {
|
|
3093
|
+
const first = !seenThisProcess.has(record.subscription.subscriptionHash);
|
|
3094
|
+
const head2 = heads.get(publisherSourceKey(record.subscription.source));
|
|
3095
|
+
if (head2 !== void 0 && !(head2 instanceof Error) && head2.position >= record.subscription.fromPosition)
|
|
3096
|
+
seenThisProcess.add(record.subscription.subscriptionHash);
|
|
3097
|
+
return prepareBestEffortDelivery({
|
|
3098
|
+
bestEffort,
|
|
3099
|
+
first,
|
|
3100
|
+
heads,
|
|
3101
|
+
now,
|
|
3102
|
+
record,
|
|
3103
|
+
state
|
|
3104
|
+
});
|
|
3105
|
+
}
|
|
2963
3106
|
if (record.pending !== void 0) return { ...record, pending: record.pending };
|
|
2964
3107
|
const subscription = record.subscription;
|
|
2965
3108
|
const head = heads.get(publisherSourceKey(subscription.source));
|
|
@@ -2971,43 +3114,74 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
2971
3114
|
state.transaction(() => {
|
|
2972
3115
|
const logicalKey = JSON.stringify([subscription.subscriptionHash, pending.fromPosition, pending.toPosition]);
|
|
2973
3116
|
if (!state.insert("delivery-events", logicalKey, pending)) throw new Error("Publisher logical range already exists outside its contiguous cursor");
|
|
2974
|
-
state.set(
|
|
3117
|
+
state.set(deliveryNamespace, subscription.subscriptionHash, prepared);
|
|
2975
3118
|
});
|
|
2976
3119
|
return prepared;
|
|
2977
3120
|
}
|
|
2978
3121
|
async function stillActive(record, signal) {
|
|
2979
3122
|
if (await timed(options.discovery.isActive(record.subscription, signal), signal)) return true;
|
|
2980
|
-
state.set(
|
|
3123
|
+
state.set(deliveryNamespace, record.subscription.subscriptionHash, {
|
|
2981
3124
|
...record,
|
|
2982
3125
|
pending: void 0,
|
|
2983
3126
|
state: "cancelled"
|
|
2984
3127
|
});
|
|
2985
3128
|
return false;
|
|
2986
3129
|
}
|
|
3130
|
+
function acknowledgeBestEffort(record, body, eventHash, signal, now) {
|
|
3131
|
+
const { pending, subscription } = record;
|
|
3132
|
+
const acknowledgment = BestEffortAdmissionAcknowledgmentZod.safeParse(body);
|
|
3133
|
+
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)
|
|
3134
|
+
throw new Error("Publisher did not receive a matching finite acknowledgment");
|
|
3135
|
+
signal.throwIfAborted();
|
|
3136
|
+
state.set(deliveryNamespace, subscription.subscriptionHash, {
|
|
3137
|
+
subscription,
|
|
3138
|
+
cursor: pending.toPosition,
|
|
3139
|
+
delivered: record.delivered + 1,
|
|
3140
|
+
failures: 0,
|
|
3141
|
+
attempts: 0,
|
|
3142
|
+
superseded: record.superseded ?? 0,
|
|
3143
|
+
abandoned: record.abandoned ?? 0,
|
|
3144
|
+
totalAttempts: (record.totalAttempts ?? 0) + 1,
|
|
3145
|
+
lastNotifiedAt: now,
|
|
3146
|
+
lastHintAt: pending.issuedAt,
|
|
3147
|
+
lastHintTarget: pending.toPosition,
|
|
3148
|
+
nextAttemptMs: 0,
|
|
3149
|
+
state: "active"
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
2987
3152
|
async function sendDelivery(record, signal, now) {
|
|
2988
3153
|
const { pending, subscription } = record;
|
|
2989
3154
|
signal.throwIfAborted();
|
|
2990
3155
|
const signed = await signer.sign(pending, subscription.audience);
|
|
2991
3156
|
signal.throwIfAborted();
|
|
2992
|
-
const attempt = state.get("delivery-attempts", pending.wakeId)?.count ?? 0;
|
|
2993
|
-
state.set(
|
|
3157
|
+
const attempt = deliveryMode === "best-effort" ? record.attempts ?? 0 : state.get("delivery-attempts", pending.wakeId)?.count ?? 0;
|
|
3158
|
+
if (deliveryMode === "best-effort") state.set(deliveryNamespace, subscription.subscriptionHash, {
|
|
3159
|
+
...record,
|
|
3160
|
+
attempts: attempt + 1,
|
|
3161
|
+
totalAttempts: (record.totalAttempts ?? 0) + 1
|
|
3162
|
+
});
|
|
3163
|
+
else state.set("delivery-attempts", pending.wakeId, {
|
|
2994
3164
|
count: attempt + 1,
|
|
2995
3165
|
startedAt: now,
|
|
2996
3166
|
status: "in-flight"
|
|
2997
3167
|
});
|
|
2998
3168
|
const response = await options.transport.post(subscription.endpoint, signed.event, signal, signed.jwt);
|
|
2999
|
-
state.set("delivery-attempts", pending.wakeId, {
|
|
3169
|
+
if (deliveryMode === "durable") state.set("delivery-attempts", pending.wakeId, {
|
|
3000
3170
|
count: attempt + 1,
|
|
3001
3171
|
startedAt: now,
|
|
3002
3172
|
status: response.status
|
|
3003
3173
|
});
|
|
3004
3174
|
if (response.status !== 200 && response.status !== 202) throw new DeliveryResponseError(response.status, response.retryAfterMs);
|
|
3175
|
+
if (deliveryMode === "best-effort") {
|
|
3176
|
+
acknowledgeBestEffort(record, response.body, signed.eventHash, signal, now);
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3005
3179
|
const receipt = z3.object({ receiptId: z3.string().min(1), wakeId: z3.literal(pending.wakeId) }).safeParse(response.body);
|
|
3006
3180
|
if (!receipt.success) throw new Error("Publisher admission did not return a matching durable receipt");
|
|
3007
3181
|
signal.throwIfAborted();
|
|
3008
3182
|
state.transaction(() => {
|
|
3009
3183
|
state.set("delivery-receipts", pending.wakeId, { receiptId: receipt.data.receiptId, receivedAt: now });
|
|
3010
|
-
state.set(
|
|
3184
|
+
state.set(deliveryNamespace, subscription.subscriptionHash, {
|
|
3011
3185
|
subscription,
|
|
3012
3186
|
cursor: pending.toPosition,
|
|
3013
3187
|
delivered: record.delivered + 1,
|
|
@@ -3019,7 +3193,7 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
3019
3193
|
}
|
|
3020
3194
|
async function deliver(subscription, signal, now, heads) {
|
|
3021
3195
|
let record = loadDelivery(subscription, now);
|
|
3022
|
-
if (record.state !== "active" || record.nextAttemptMs > now * 1e3) return;
|
|
3196
|
+
if (deliveryMode === "durable" && (record.state !== "active" || record.nextAttemptMs > now * 1e3)) return;
|
|
3023
3197
|
try {
|
|
3024
3198
|
if (!await stillActive(record, signal)) return;
|
|
3025
3199
|
options.transport.assertAllowed(subscription.endpoint);
|
|
@@ -3027,17 +3201,16 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
3027
3201
|
const prepared = prepareDelivery(record, now, heads);
|
|
3028
3202
|
if (prepared === void 0) return;
|
|
3029
3203
|
record = prepared;
|
|
3204
|
+
if (record.nextAttemptMs > now * 1e3) return;
|
|
3030
3205
|
if (!await stillActive(record, signal)) return;
|
|
3031
3206
|
await sendDelivery(prepared, signal, now);
|
|
3032
3207
|
} catch (error) {
|
|
3033
3208
|
if (signal.aborted) return;
|
|
3034
|
-
state.set(
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
state
|
|
3038
|
-
|
|
3039
|
-
lastError: "delivery-failed"
|
|
3040
|
-
});
|
|
3209
|
+
state.set(
|
|
3210
|
+
deliveryNamespace,
|
|
3211
|
+
subscription.subscriptionHash,
|
|
3212
|
+
failedDelivery(record, state.get(deliveryNamespace, subscription.subscriptionHash), deliveryMode, error, now)
|
|
3213
|
+
);
|
|
3041
3214
|
}
|
|
3042
3215
|
}
|
|
3043
3216
|
async function readSources(signal) {
|
|
@@ -3067,13 +3240,13 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
3067
3240
|
if (subscriptions.length > timing.maxSubscriptions) throw new Error("Publisher subscription limit exceeded");
|
|
3068
3241
|
const active = new Set(subscriptions.map((subscription) => subscription.subscriptionHash));
|
|
3069
3242
|
if (active.size !== subscriptions.length) throw new Error("Duplicate subscription identity");
|
|
3070
|
-
const unavailable = state.entries(
|
|
3243
|
+
const unavailable = state.entries(deliveryNamespace).filter((item) => !active.has(item.key) && item.value.state !== "cancelled");
|
|
3071
3244
|
let available = true;
|
|
3072
3245
|
for (let offset = 0; offset < unavailable.length; offset += timing.concurrency) {
|
|
3073
3246
|
signal.throwIfAborted();
|
|
3074
3247
|
const results = await Promise.allSettled(unavailable.slice(offset, offset + timing.concurrency).map(async ({ key, value }) => {
|
|
3075
3248
|
const stillAuthorized = await timed(options.discovery.isActive(value.subscription, signal), signal);
|
|
3076
|
-
if (!stillAuthorized) state.set(
|
|
3249
|
+
if (!stillAuthorized) state.set(deliveryNamespace, key, {
|
|
3077
3250
|
...value,
|
|
3078
3251
|
pending: void 0,
|
|
3079
3252
|
state: "cancelled"
|
|
@@ -3167,9 +3340,14 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
3167
3340
|
throw new AggregateError(failures, "Publisher runtime shutdown failed");
|
|
3168
3341
|
})(),
|
|
3169
3342
|
status() {
|
|
3170
|
-
const deliveries = state.entries(
|
|
3343
|
+
const deliveries = state.entries(deliveryNamespace).map((item) => item.value);
|
|
3171
3344
|
return {
|
|
3172
3345
|
running,
|
|
3346
|
+
deliveryMode,
|
|
3347
|
+
...bestEffort === void 0 ? {} : { bestEffort: Object.freeze({ ...bestEffort }) },
|
|
3348
|
+
superseded: deliveries.reduce((sum, item) => sum + (item.superseded ?? 0), 0),
|
|
3349
|
+
abandoned: deliveries.reduce((sum, item) => sum + (item.abandoned ?? 0), 0),
|
|
3350
|
+
totalAttempts: deliveries.reduce((sum, item) => sum + (item.totalAttempts ?? 0), 0),
|
|
3173
3351
|
ready: running && ready,
|
|
3174
3352
|
clockHealthy,
|
|
3175
3353
|
degraded: !ready || deliveries.some((item) => item.state !== "cancelled" && item.failures > 0),
|
|
@@ -3181,10 +3359,15 @@ async function createPersistentEventPublisherRuntime(options) {
|
|
|
3181
3359
|
passes,
|
|
3182
3360
|
...lastError === void 0 ? {} : { lastError },
|
|
3183
3361
|
...options.discovery.status === void 0 ? {} : { handshake: options.discovery.status() },
|
|
3184
|
-
sources: state.entries("source-heads").map((item) => ({ id: item.key, ...item.value })),
|
|
3185
|
-
subscriptions: state.entries(
|
|
3362
|
+
sources: state.entries("source-heads").filter((item) => sourceReaders.has(item.key)).map((item) => ({ id: item.key, ...item.value })),
|
|
3363
|
+
subscriptions: state.entries(deliveryNamespace).map((item) => ({
|
|
3186
3364
|
id: item.key,
|
|
3187
3365
|
cursor: item.value.cursor,
|
|
3366
|
+
...deliveryMode === "best-effort" ? {
|
|
3367
|
+
...item.value.cursor < item.value.subscription.fromPosition ? {} : { lastAdmissionTarget: item.value.cursor },
|
|
3368
|
+
attempts: item.value.attempts ?? 0,
|
|
3369
|
+
lastHintAt: item.value.lastHintAt
|
|
3370
|
+
} : {},
|
|
3188
3371
|
state: item.value.state,
|
|
3189
3372
|
pending: item.value.pending !== void 0,
|
|
3190
3373
|
failures: item.value.failures
|
|
@@ -3275,7 +3458,7 @@ async function openPublisherDataLake(options) {
|
|
|
3275
3458
|
}
|
|
3276
3459
|
|
|
3277
3460
|
// src/runtime/sqliteHandshakeStores.ts
|
|
3278
|
-
import { isDeepStrictEqual as
|
|
3461
|
+
import { isDeepStrictEqual as isDeepStrictEqual5 } from "node:util";
|
|
3279
3462
|
import { endpointChallengeHash as endpointChallengeHash2 } from "@xyo-network/event-kit-protocol";
|
|
3280
3463
|
import {
|
|
3281
3464
|
Base64Url32BytesZod as Base64Url32BytesZod3,
|
|
@@ -3296,7 +3479,7 @@ function requireTime2(value) {
|
|
|
3296
3479
|
if (!Number.isSafeInteger(value) || value < 0) throw new Error("Publisher clock is invalid");
|
|
3297
3480
|
}
|
|
3298
3481
|
function samePublicationIntent(left, right) {
|
|
3299
|
-
return left.chainId === right.chainId && left.source === right.source && left.operationId === right.operationId &&
|
|
3482
|
+
return left.chainId === right.chainId && left.source === right.source && left.operationId === right.operationId && isDeepStrictEqual5(left.eventHashes, right.eventHashes) && isDeepStrictEqual5(left.attachmentHashes, right.attachmentHashes);
|
|
3300
3483
|
}
|
|
3301
3484
|
function publicationKey(intent) {
|
|
3302
3485
|
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) => !HashZod5.safeParse(hash).success)) {
|
|
@@ -3304,11 +3487,12 @@ function publicationKey(intent) {
|
|
|
3304
3487
|
}
|
|
3305
3488
|
return JSON.stringify([intent.chainId, intent.source, intent.operationId]);
|
|
3306
3489
|
}
|
|
3307
|
-
function createSqlitePublisherHandshakeStores(state) {
|
|
3490
|
+
function createSqlitePublisherHandshakeStores(state, options = {}) {
|
|
3491
|
+
const replayKey = options.replayNamespace === void 0 ? "checkpoint" : `checkpoint:${z4.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/u).parse(options.replayNamespace)}`;
|
|
3308
3492
|
const replay = {
|
|
3309
|
-
load: async () => state.get("replay",
|
|
3493
|
+
load: async () => state.get("replay", replayKey),
|
|
3310
3494
|
async save(checkpoint) {
|
|
3311
|
-
state.set("replay",
|
|
3495
|
+
state.set("replay", replayKey, checkpoint);
|
|
3312
3496
|
}
|
|
3313
3497
|
};
|
|
3314
3498
|
const intents = {
|
|
@@ -3346,7 +3530,7 @@ function createSqlitePublisherHandshakeStores(state) {
|
|
|
3346
3530
|
const key = `${record.publisher}:${record.keyId}`;
|
|
3347
3531
|
const prior = state.get("encryption-keys", key);
|
|
3348
3532
|
if (prior !== void 0) {
|
|
3349
|
-
if (!
|
|
3533
|
+
if (!isDeepStrictEqual5(prior, record)) throw new Error("Encryption key collision");
|
|
3350
3534
|
return "duplicate";
|
|
3351
3535
|
}
|
|
3352
3536
|
state.set("encryption-keys", key, record);
|
|
@@ -3366,7 +3550,7 @@ function createSqlitePublisherHandshakeStores(state) {
|
|
|
3366
3550
|
const priorNonce = state.get("challenge-nonces", challenge.nonce);
|
|
3367
3551
|
if (priorNonce !== void 0 && priorNonce !== hash) return { kind: "collision" };
|
|
3368
3552
|
const prior = current(hash);
|
|
3369
|
-
if (prior !== void 0) return
|
|
3553
|
+
if (prior !== void 0) return isDeepStrictEqual5(prior.challenge, challenge) ? { kind: "duplicate", record: prior } : { kind: "collision" };
|
|
3370
3554
|
const record = {
|
|
3371
3555
|
challenge,
|
|
3372
3556
|
challengeHash: hash,
|
|
@@ -3419,7 +3603,7 @@ function createSqlitePublisherHandshakeStores(state) {
|
|
|
3419
3603
|
for (const phase of phases) {
|
|
3420
3604
|
const record = state.get(`publication:${phase}`, key);
|
|
3421
3605
|
if (record === void 0) continue;
|
|
3422
|
-
if (record.phase !== phase || !samePublicationIntent(record, intent) || !
|
|
3606
|
+
if (record.phase !== phase || !samePublicationIntent(record, intent) || !isDeepStrictEqual5(record.evidence, signed.evidence)) {
|
|
3423
3607
|
throw new Error("Publication evidence collision");
|
|
3424
3608
|
}
|
|
3425
3609
|
latest = record;
|
|
@@ -3433,12 +3617,12 @@ function createSqlitePublisherHandshakeStores(state) {
|
|
|
3433
3617
|
state.transaction(() => {
|
|
3434
3618
|
const signed = state.get("publication:signed", key);
|
|
3435
3619
|
if (signed === void 0 && record.phase !== "signed") throw new Error("Publication must bind signed evidence first");
|
|
3436
|
-
if (signed !== void 0 && (!samePublicationIntent(signed, record) || !
|
|
3620
|
+
if (signed !== void 0 && (!samePublicationIntent(signed, record) || !isDeepStrictEqual5(signed.evidence, record.evidence))) {
|
|
3437
3621
|
throw new Error("Publication intent or evidence collision");
|
|
3438
3622
|
}
|
|
3439
3623
|
const namespace = `publication:${record.phase}`;
|
|
3440
3624
|
const prior = state.get(namespace, key);
|
|
3441
|
-
if (prior !== void 0 && !
|
|
3625
|
+
if (prior !== void 0 && !isDeepStrictEqual5(prior, record)) throw new Error("Publication phase collision");
|
|
3442
3626
|
if (prior === void 0) state.set(namespace, key, record);
|
|
3443
3627
|
});
|
|
3444
3628
|
}
|