@xyo-network/event-kit-node 0.1.1 → 0.1.2

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.
Files changed (36) hide show
  1. package/README.md +23 -3
  2. package/dist/node/handshake.mjs +122 -249
  3. package/dist/node/handshake.mjs.map +2 -2
  4. package/dist/node/handshakeClients.d.ts +7 -4
  5. package/dist/node/handshakeClients.d.ts.map +1 -1
  6. package/dist/node/handshakeMutationIntent.d.ts +118 -34
  7. package/dist/node/handshakeMutationIntent.d.ts.map +1 -1
  8. package/dist/node/index.d.ts +1 -0
  9. package/dist/node/index.d.ts.map +1 -1
  10. package/dist/node/index.mjs +1582 -291
  11. package/dist/node/index.mjs.map +4 -4
  12. package/dist/node/runtime/abort.d.ts +6 -0
  13. package/dist/node/runtime/abort.d.ts.map +1 -0
  14. package/dist/node/runtime/boundedFetch.d.ts +20 -0
  15. package/dist/node/runtime/boundedFetch.d.ts.map +1 -0
  16. package/dist/node/runtime/handshakeDiscovery.d.ts +16 -0
  17. package/dist/node/runtime/handshakeDiscovery.d.ts.map +1 -0
  18. package/dist/node/runtime/httpsTransport.d.ts +24 -0
  19. package/dist/node/runtime/httpsTransport.d.ts.map +1 -0
  20. package/dist/node/runtime/publisherRuntime.d.ts +84 -0
  21. package/dist/node/runtime/publisherRuntime.d.ts.map +1 -0
  22. package/dist/node/runtime/restGateway.d.ts +36 -0
  23. package/dist/node/runtime/restGateway.d.ts.map +1 -0
  24. package/dist/node/runtime/sourceSigner.d.ts +41 -0
  25. package/dist/node/runtime/sourceSigner.d.ts.map +1 -0
  26. package/dist/node/runtime/sources.d.ts +35 -0
  27. package/dist/node/runtime/sources.d.ts.map +1 -0
  28. package/dist/node/runtime/sqliteHandshakeStores.d.ts +15 -0
  29. package/dist/node/runtime/sqliteHandshakeStores.d.ts.map +1 -0
  30. package/dist/node/runtime/sqlitePublisherState.d.ts +21 -0
  31. package/dist/node/runtime/sqlitePublisherState.d.ts.map +1 -0
  32. package/dist/node/runtime.d.ts +10 -0
  33. package/dist/node/runtime.d.ts.map +1 -0
  34. package/dist/node/runtime.mjs +1640 -0
  35. package/dist/node/runtime.mjs.map +7 -0
  36. package/package.json +16 -11
@@ -0,0 +1,1640 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ // src/runtime/boundedFetch.ts
13
+ import { Buffer as Buffer2 } from "node:buffer";
14
+
15
+ // src/runtime/abort.ts
16
+ import { clearTimeout, setTimeout } from "node:timers";
17
+ function publisherAbortScope(signals, timeoutMs) {
18
+ const controller = new AbortController();
19
+ const abort = () => controller.abort();
20
+ const timer = timeoutMs === void 0 ? void 0 : setTimeout(abort, timeoutMs);
21
+ timer?.unref();
22
+ for (const signal of signals) {
23
+ if (signal.aborted) controller.abort();
24
+ else signal.addEventListener("abort", abort, { once: true });
25
+ }
26
+ return {
27
+ signal: controller.signal,
28
+ dispose() {
29
+ if (timer !== void 0) clearTimeout(timer);
30
+ for (const signal of signals) signal.removeEventListener("abort", abort);
31
+ }
32
+ };
33
+ }
34
+
35
+ // src/runtime/boundedFetch.ts
36
+ function createBoundedPublisherFetch(options = {}) {
37
+ const owner = new AbortController();
38
+ const timeout = options.requestTimeoutMs ?? 1e4;
39
+ const maximum = options.maxResponseBytes ?? 16 * 1024 * 1024;
40
+ const concurrency = options.maxConcurrency ?? 16;
41
+ if (!Number.isSafeInteger(timeout) || timeout < 100 || timeout > 6e4 || !Number.isSafeInteger(maximum) || maximum < 1 || maximum > 64 * 1024 * 1024 || !Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > 64)
42
+ throw new Error("Invalid publisher fetch limits");
43
+ const active = /* @__PURE__ */ new Set();
44
+ const nativeFetch = globalThis.fetch.bind(globalThis);
45
+ const fetchFn = (input, init) => {
46
+ if (active.size >= concurrency)
47
+ return Promise.reject(new Error("Publisher HTTP concurrency limit exceeded"));
48
+ const operation = (async () => {
49
+ const signals = [
50
+ owner.signal,
51
+ ...options.signal === void 0 ? [] : [options.signal],
52
+ ...init?.signal == null ? [] : [init.signal],
53
+ ...input instanceof Request ? [input.signal] : []
54
+ ];
55
+ const scope = publisherAbortScope(signals, timeout);
56
+ try {
57
+ scope.signal.throwIfAborted();
58
+ const response = await nativeFetch(input, {
59
+ ...init,
60
+ redirect: "error",
61
+ signal: scope.signal
62
+ });
63
+ if (response.body === null)
64
+ return response;
65
+ const reader = response.body.getReader();
66
+ const chunks = [];
67
+ let size = 0;
68
+ try {
69
+ for (; ; ) {
70
+ const chunk = await reader.read();
71
+ if (chunk.done)
72
+ break;
73
+ const bytes = chunk.value;
74
+ if (!(bytes instanceof Uint8Array)) throw new Error("Publisher HTTP body returned a non-byte chunk");
75
+ size += bytes.byteLength;
76
+ if (size > maximum) {
77
+ await reader.cancel();
78
+ throw new Error("Publisher HTTP body exceeds its bound");
79
+ }
80
+ chunks.push(bytes);
81
+ }
82
+ } finally {
83
+ reader.releaseLock();
84
+ }
85
+ const headers = new Headers(response.headers);
86
+ headers.delete("content-encoding");
87
+ headers.delete("transfer-encoding");
88
+ headers.set("content-length", String(size));
89
+ return new Response(Buffer2.concat(chunks), {
90
+ status: response.status,
91
+ statusText: response.statusText,
92
+ headers
93
+ });
94
+ } finally {
95
+ scope.dispose();
96
+ }
97
+ })();
98
+ active.add(operation);
99
+ void operation.then(() => active.delete(operation), () => active.delete(operation));
100
+ return operation;
101
+ };
102
+ return {
103
+ fetchFn,
104
+ async close() {
105
+ owner.abort();
106
+ await Promise.allSettled(active);
107
+ },
108
+ get activeRequests() {
109
+ return active.size;
110
+ }
111
+ };
112
+ }
113
+ function createBoundedPublisherEthersTransport(options = {}) {
114
+ const bounded = createBoundedPublisherFetch(options);
115
+ const getUrl = async (request2, cancellation) => {
116
+ const controller = new AbortController();
117
+ cancellation?.addListener(() => controller.abort());
118
+ cancellation?.checkSignal();
119
+ const response = await bounded.fetchFn(request2.url, {
120
+ body: request2.body === null ? void 0 : Buffer2.from(request2.body),
121
+ headers: request2.headers,
122
+ method: request2.method,
123
+ signal: controller.signal
124
+ });
125
+ return {
126
+ body: new Uint8Array(await response.arrayBuffer()),
127
+ headers: Object.fromEntries(response.headers),
128
+ statusCode: response.status,
129
+ statusMessage: response.statusText
130
+ };
131
+ };
132
+ return {
133
+ getUrl,
134
+ async close() {
135
+ await bounded.close();
136
+ },
137
+ get activeRequests() {
138
+ return bounded.activeRequests;
139
+ }
140
+ };
141
+ }
142
+
143
+ // src/runtime/handshakeDiscovery.ts
144
+ import { decryptWakeSubscription, sourceWakeFilterHash } from "@xyo-network/event-kit-protocol";
145
+ import { EndpointChallengeResponseZod } from "@xyo-network/event-kit-schemas";
146
+ function createPublisherHandshakeDiscovery(options) {
147
+ const clock = options.nowSeconds ?? (() => Math.floor(Date.now() / 1e3));
148
+ const owner = new AbortController();
149
+ let accepting;
150
+ let viewing;
151
+ let status = {
152
+ authorizationChainId: options.authorizationChainId,
153
+ keyLoaded: false,
154
+ activeKeyIds: [],
155
+ pendingSubscriptions: 0,
156
+ rejectedSubscriptions: 0,
157
+ challengeFailures: 0
158
+ };
159
+ async function activeView(signal) {
160
+ signal.throwIfAborted();
161
+ viewing ??= options.client.subscriptions().finally(() => {
162
+ viewing = void 0;
163
+ });
164
+ const view = await viewing;
165
+ signal.throwIfAborted();
166
+ if (!view.ok || view.indexedThrough !== view.finalizedHead)
167
+ throw new Error("Publisher subscription view is unavailable or stale");
168
+ status = {
169
+ ...status,
170
+ finalizedHead: view.finalizedHead,
171
+ indexedThrough: view.indexedThrough,
172
+ pendingSubscriptions: view.subscriptions.filter((item) => item.state === "pending").length,
173
+ rejectedSubscriptions: view.rejections.length
174
+ };
175
+ return view;
176
+ }
177
+ async function acceptCandidate(subscriber, subscription, signal) {
178
+ try {
179
+ let pending = options.state.get("outbound-challenges", subscription);
180
+ if (pending !== void 0 && pending.response === void 0 && clock() >= pending.expiresAt)
181
+ pending = void 0;
182
+ if (pending === void 0) {
183
+ const issued = await options.client.issueChallenge({
184
+ issuedAt: clock(),
185
+ lifetimeSeconds: 300,
186
+ subscriber,
187
+ subscription
188
+ });
189
+ pending = {
190
+ challengeHash: issued.challengeHash,
191
+ subscriber,
192
+ subscription,
193
+ expiresAt: issued.challenge.expiresAt
194
+ };
195
+ options.state.set("outbound-challenges", subscription, pending);
196
+ }
197
+ if (pending.response === void 0) {
198
+ const begun = await options.client.beginChallenge({ challengeHash: pending.challengeHash, nowSeconds: clock });
199
+ if (begun.kind !== "started")
200
+ return;
201
+ signal.throwIfAborted();
202
+ const response2 = await options.transport.post(begun.target.endpoint, begun.challenge, signal);
203
+ if (response2.status !== 200)
204
+ throw new Error("Endpoint challenge was rejected");
205
+ pending = { ...pending, response: EndpointChallengeResponseZod.parse(response2.body) };
206
+ options.state.set("outbound-challenges", subscription, pending);
207
+ }
208
+ signal.throwIfAborted();
209
+ const response = pending.response;
210
+ if (response === void 0)
211
+ throw new Error("Endpoint proof is unavailable");
212
+ await options.client.completeChallengeAndAccept({
213
+ ...pending,
214
+ response,
215
+ nowSeconds: clock
216
+ });
217
+ } catch {
218
+ if (signal.aborted)
219
+ return;
220
+ const failures = options.state.get("challenge-failures", subscription) ?? 0;
221
+ options.state.set("challenge-failures", subscription, failures + 1);
222
+ const pending = options.state.get("outbound-challenges", subscription);
223
+ if (pending !== void 0 && clock() >= pending.expiresAt)
224
+ options.state.remove("outbound-challenges", subscription);
225
+ }
226
+ }
227
+ async function accept(signal) {
228
+ signal.throwIfAborted();
229
+ const now = clock();
230
+ if (now < (options.state.get("challenge-rate", "next-pass") ?? 0))
231
+ return;
232
+ options.state.set("challenge-rate", "next-pass", now + 10);
233
+ const found = await options.client.acceptanceCandidates();
234
+ if (!found.ok)
235
+ throw new Error("Publisher acceptance candidates are unavailable");
236
+ const offset = options.state.get("challenge-rate", "offset") ?? 0;
237
+ const candidates = found.candidates;
238
+ let attempted = 0;
239
+ for (let index = 0; index < Math.min(candidates.length, 128) && attempted < 4; index += 1) {
240
+ const candidate = candidates[(offset + index) % candidates.length];
241
+ if (candidate === void 0)
242
+ break;
243
+ options.state.set("challenge-rate", "offset", (offset + index + 1) % candidates.length);
244
+ const next = options.state.get("challenge-controller-rate", candidate.subscriber) ?? 0;
245
+ if (now < next)
246
+ continue;
247
+ options.state.set("challenge-controller-rate", candidate.subscriber, now + 30);
248
+ attempted += 1;
249
+ signal.throwIfAborted();
250
+ await acceptCandidate(candidate.subscriber, candidate.tuple.subscription.objectHash, signal);
251
+ }
252
+ }
253
+ return {
254
+ async discover(signal) {
255
+ const view = await activeView(signal);
256
+ const key = view.hasCurrentAdvertisement ? view.key : void 0;
257
+ const loaded = key === void 0 ? void 0 : await options.keys.getPrivateKey({
258
+ encryptionKey: key.objectHash,
259
+ keyId: key.body.keyId,
260
+ publisher: options.publisher
261
+ });
262
+ status = {
263
+ ...status,
264
+ keyLoaded: loaded !== void 0,
265
+ activeKeyIds: key === void 0 ? [] : [key.body.keyId]
266
+ };
267
+ const subscriptions = [];
268
+ const active = view.subscriptions.filter((item) => item.state === "active");
269
+ if (active.length > (options.maxSubscriptions ?? 64))
270
+ throw new Error("Publisher active subscription limit exceeded");
271
+ for (const lifecycle of active) {
272
+ if (lifecycle.state !== "active" || lifecycle.activeFromBlock === void 0)
273
+ continue;
274
+ try {
275
+ const tuple = lifecycle.tuples.find((item) => item.subscription.objectHash === lifecycle.selectedSubscription);
276
+ if (tuple === void 0)
277
+ throw new Error("Active publisher subscription has no selected tuple");
278
+ const encrypted = tuple.subscription.body;
279
+ if (encrypted.chainId !== options.authorizationChainId)
280
+ throw new Error("Subscription authorization chain mismatch");
281
+ const publisherKey = await options.keys.getPrivateKey({
282
+ encryptionKey: encrypted.encryptionKey,
283
+ keyId: encrypted.keyId,
284
+ publisher: options.publisher
285
+ });
286
+ if (publisherKey === void 0)
287
+ throw new Error("Subscription encryption key is unavailable");
288
+ const plaintext = await decryptWakeSubscription({
289
+ publisherKey,
290
+ subscriber: tuple.subscriber,
291
+ subscription: encrypted
292
+ });
293
+ options.transport.assertAllowed(plaintext.endpoint);
294
+ subscriptions.push({
295
+ subscriptionHash: tuple.subscription.objectHash,
296
+ authorizationChainId: options.authorizationChainId,
297
+ controller: tuple.subscriber,
298
+ audience: plaintext.audience,
299
+ endpoint: plaintext.endpoint,
300
+ deploymentId: plaintext.deploymentId,
301
+ ...plaintext.queueId === null ? {} : { queueId: plaintext.queueId },
302
+ source: plaintext.filter.source,
303
+ fromPosition: plaintext.filter.fromPosition,
304
+ maxPositionsPerWake: plaintext.filter.maxPositionsPerWake,
305
+ filterHash: await sourceWakeFilterHash(plaintext.filter)
306
+ });
307
+ } catch {
308
+ status = { ...status, rejectedSubscriptions: status.rejectedSubscriptions + 1 };
309
+ }
310
+ }
311
+ return subscriptions;
312
+ },
313
+ async isActive(subscription, signal) {
314
+ const view = await activeView(signal);
315
+ 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));
316
+ },
317
+ async acceptPending(signal) {
318
+ if (accepting === void 0) {
319
+ const scope = publisherAbortScope([owner.signal, signal]);
320
+ accepting = accept(scope.signal).finally(() => {
321
+ scope.dispose();
322
+ accepting = void 0;
323
+ });
324
+ }
325
+ await accepting;
326
+ },
327
+ async close() {
328
+ await Promise.allSettled([accepting, viewing].filter((item) => item !== void 0));
329
+ },
330
+ status() {
331
+ return { ...status, challengeFailures: options.state.entries("challenge-failures").reduce((sum, item) => sum + item.value, 0) };
332
+ }
333
+ };
334
+ }
335
+
336
+ // src/runtime/httpsTransport.ts
337
+ import { Buffer as Buffer3 } from "node:buffer";
338
+ import { lookup } from "node:dns/promises";
339
+ import { request } from "node:https";
340
+ import { BlockList, isIP } from "node:net";
341
+ import { z } from "zod";
342
+
343
+ // src/runtime/sources.ts
344
+ import { createHash } from "node:crypto";
345
+ import { canonicalJson } from "@xyo-network/event-kit-protocol";
346
+ import { completedUtcIntervalPosition, WakeSourceZod } from "@xyo-network/event-kit-schemas";
347
+ import { PayloadBuilder } from "@xyo-network/sdk";
348
+ import { asXL1BlockNumber } from "@xyo-network/xl1-sdk";
349
+ import { FetchRequest, JsonRpcProvider } from "ethers";
350
+ function publisherSourceKey(source) {
351
+ return createHash("sha256").update(canonicalJson(WakeSourceZod.parse(source))).digest("hex");
352
+ }
353
+ async function boundedPublisherOperation(operation, signal, timeoutMs = 1e4) {
354
+ const scope = publisherAbortScope([signal], timeoutMs);
355
+ const combined = scope.signal;
356
+ let rejectAbort;
357
+ const aborted = new Promise((_resolve, reject) => {
358
+ rejectAbort = () => reject(new Error("Publisher operation aborted or timed out"));
359
+ combined.addEventListener("abort", rejectAbort, { once: true });
360
+ });
361
+ try {
362
+ combined.throwIfAborted();
363
+ return await Promise.race([operation, aborted]);
364
+ } finally {
365
+ if (rejectAbort !== void 0)
366
+ combined.removeEventListener("abort", rejectAbort);
367
+ scope.dispose();
368
+ }
369
+ }
370
+ function createXl1PublisherSource(options) {
371
+ const source = WakeSourceZod.parse(options.source);
372
+ if (source.kind !== "xl1-finalized")
373
+ throw new Error("Expected an XL1 finalized source");
374
+ return {
375
+ source,
376
+ async head(signal) {
377
+ const chainId = String(await boundedPublisherOperation(Promise.resolve(options.viewer.chainId()), signal));
378
+ if (chainId !== source.chainId)
379
+ throw new Error("XL1 source chain identity changed");
380
+ const position = Number(await boundedPublisherOperation(Promise.resolve(options.viewer.finalization.headNumber()), signal));
381
+ if (!Number.isSafeInteger(position) || position < 0)
382
+ throw new Error("XL1 finalized head is invalid");
383
+ const block = await boundedPublisherOperation(Promise.resolve(options.viewer.block.blockByNumber(asXL1BlockNumber(position, true))), signal);
384
+ if (block?.[0].block !== position || !/^[0-9a-f]{64}$/u.test(block[0]._hash)) {
385
+ throw new Error("XL1 finalized head block is unavailable or inconsistent");
386
+ }
387
+ if (String(block[0].chain) !== source.chainId || await PayloadBuilder.hash(block[0]) !== block[0]._hash) {
388
+ throw new Error("XL1 finalized head block chain or hash is inconsistent");
389
+ }
390
+ return { position, hash: block[0]._hash };
391
+ }
392
+ };
393
+ }
394
+ function createEthereumFinalizedPublisherSource(options) {
395
+ const source = WakeSourceZod.parse(options.source);
396
+ if (source.kind !== "ethereum-finalized")
397
+ throw new Error("Expected an Ethereum finalized source");
398
+ const { chainId, genesisHash } = source;
399
+ const url = new URL(options.rpcUrl);
400
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.hash !== "") {
401
+ throw new Error("Ethereum source RPC must use HTTPS without URL credentials or fragments");
402
+ }
403
+ const connection = new FetchRequest(url.href);
404
+ connection.timeout = options.requestTimeoutMs ?? 1e4;
405
+ const transport = createBoundedPublisherEthersTransport({
406
+ requestTimeoutMs: options.requestTimeoutMs,
407
+ maxResponseBytes: 2 * 1024 * 1024,
408
+ maxConcurrency: 8
409
+ });
410
+ connection.getUrlFunc = transport.getUrl;
411
+ connection.setThrottleParams({ maxAttempts: 1 });
412
+ const provider = new JsonRpcProvider(connection, void 0, { batchMaxCount: 1, cacheTimeout: -1 });
413
+ let reading;
414
+ let closed = false;
415
+ let closing;
416
+ const close = () => closing ??= (async () => {
417
+ closed = true;
418
+ provider.destroy();
419
+ await transport.close();
420
+ })();
421
+ const onAbort = () => {
422
+ void close();
423
+ };
424
+ async function read() {
425
+ const requests = [provider.getNetwork(), provider.getBlock(0), provider.getBlock("finalized")];
426
+ const values = await Promise.all(requests).catch(async (error) => {
427
+ await Promise.allSettled(requests);
428
+ throw error;
429
+ });
430
+ const [network, genesis, block] = values;
431
+ if (network.chainId.toString() !== chainId || genesis?.hash?.slice(2).toLowerCase() !== genesisHash) {
432
+ throw new Error("Ethereum source chain identity mismatch");
433
+ }
434
+ if (block?.hash === null || block === null || !/^0x[0-9a-fA-F]{64}$/u.test(block.hash) || !Number.isSafeInteger(block.number) || block.number < 0) throw new Error("Ethereum finalized head is unavailable");
435
+ return { position: block.number, hash: block.hash.slice(2).toLowerCase() };
436
+ }
437
+ return {
438
+ source,
439
+ close,
440
+ async head(signal) {
441
+ signal.throwIfAborted();
442
+ if (closed) throw new Error("Ethereum source is closed");
443
+ signal.addEventListener("abort", onAbort, { once: true });
444
+ reading ??= read().finally(() => {
445
+ reading = void 0;
446
+ });
447
+ try {
448
+ return await boundedPublisherOperation(reading, signal, options.requestTimeoutMs ?? 1e4);
449
+ } finally {
450
+ signal.removeEventListener("abort", onAbort);
451
+ }
452
+ }
453
+ };
454
+ }
455
+ function createUtcIntervalPublisherSource(options) {
456
+ const source = WakeSourceZod.parse(options.source);
457
+ if (source.kind !== "utc-interval")
458
+ throw new Error("Expected a UTC interval source");
459
+ const nowSeconds = options.nowSeconds ?? (() => Math.floor(Date.now() / 1e3));
460
+ return {
461
+ source,
462
+ async head(signal) {
463
+ signal.throwIfAborted();
464
+ const now = nowSeconds();
465
+ if (!Number.isSafeInteger(now) || now < 0)
466
+ throw new Error("UTC interval clock is invalid");
467
+ return { position: completedUtcIntervalPosition(source, now) ?? -1 };
468
+ }
469
+ };
470
+ }
471
+
472
+ // src/runtime/httpsTransport.ts
473
+ var OptionsZod = z.strictObject({
474
+ allowedEndpoints: z.array(z.url()).min(1).max(64),
475
+ endpointHeaders: z.array(z.strictObject({
476
+ endpoint: z.url(),
477
+ headers: z.strictObject({ "x-vercel-protection-bypass": z.string().min(1).max(4096).regex(/^[\x21-\x7E]+$/u) })
478
+ })).max(64).default([]),
479
+ maxResponseBytes: z.number().int().min(1).max(1048576).default(65536),
480
+ requestTimeoutMs: z.number().int().min(100).max(6e4).default(1e4)
481
+ });
482
+ var nonPublicIpv4 = new BlockList();
483
+ for (const [address, prefix] of [
484
+ ["0.0.0.0", 8],
485
+ ["10.0.0.0", 8],
486
+ ["100.64.0.0", 10],
487
+ ["127.0.0.0", 8],
488
+ ["169.254.0.0", 16],
489
+ ["172.16.0.0", 12],
490
+ ["192.0.0.0", 24],
491
+ ["192.0.2.0", 24],
492
+ ["192.88.99.0", 24],
493
+ ["192.168.0.0", 16],
494
+ ["198.18.0.0", 15],
495
+ ["198.51.100.0", 24],
496
+ ["203.0.113.0", 24],
497
+ ["224.0.0.0", 4],
498
+ ["240.0.0.0", 4]
499
+ ])
500
+ nonPublicIpv4.addSubnet(address, prefix, "ipv4");
501
+ var globalIpv6 = new BlockList();
502
+ globalIpv6.addSubnet("2000::", 3, "ipv6");
503
+ var nonPublicIpv6 = new BlockList();
504
+ for (const [address, prefix] of [
505
+ ["2001::", 23],
506
+ ["2001:db8::", 32],
507
+ ["2002::", 16],
508
+ ["3fff::", 20]
509
+ ])
510
+ nonPublicIpv6.addSubnet(address, prefix, "ipv6");
511
+ function isPublicPublisherAddress(address) {
512
+ if (isIP(address) === 4)
513
+ return !nonPublicIpv4.check(address, "ipv4");
514
+ if (isIP(address) !== 6)
515
+ return false;
516
+ return globalIpv6.check(address, "ipv6") && !nonPublicIpv6.check(address, "ipv6");
517
+ }
518
+ function createPublisherHttpsTransport(input) {
519
+ const parsed = OptionsZod.safeParse(input);
520
+ if (!parsed.success) throw new Error("Invalid publisher HTTPS transport configuration");
521
+ const options = parsed.data;
522
+ const allowed = new Set(options.allowedEndpoints.map((endpoint) => {
523
+ const url = new URL(endpoint);
524
+ if (url.href !== endpoint || url.protocol !== "https:" || url.port !== "" && url.port !== "443" || url.username !== "" || url.password !== "" || url.hash !== "")
525
+ throw new Error("Publisher destinations must be canonical public HTTPS URLs on port 443");
526
+ const literal = url.hostname.replaceAll(/^\[|\]$/gu, "");
527
+ if (isIP(literal) !== 0 && !isPublicPublisherAddress(literal))
528
+ throw new Error("Publisher destination is not public");
529
+ if (url.hostname === "localhost" || url.hostname.endsWith(".localhost") || url.hostname.endsWith(".local"))
530
+ throw new Error("Publisher destination is not public");
531
+ return endpoint;
532
+ }));
533
+ const assertAllowed = (endpoint) => {
534
+ if (!allowed.has(endpoint))
535
+ throw new Error("Publisher destination is not explicitly allowed");
536
+ };
537
+ const endpointHeaders = /* @__PURE__ */ new Map();
538
+ for (const entry of options.endpointHeaders) {
539
+ assertAllowed(entry.endpoint);
540
+ if (endpointHeaders.has(entry.endpoint)) throw new Error("Duplicate publisher endpoint header configuration");
541
+ endpointHeaders.set(entry.endpoint, entry.headers);
542
+ }
543
+ return {
544
+ assertAllowed,
545
+ async post(endpoint, body, signal, jwt) {
546
+ assertAllowed(endpoint);
547
+ signal.throwIfAborted();
548
+ const url = new URL(endpoint);
549
+ const scope = publisherAbortScope([signal], options.requestTimeoutMs);
550
+ const combined = scope.signal;
551
+ try {
552
+ const addresses = await boundedPublisherOperation(lookup(url.hostname.replaceAll(/^\[|\]$/gu, ""), { all: true, verbatim: true }), combined, options.requestTimeoutMs);
553
+ combined.throwIfAborted();
554
+ if (addresses.length === 0 || addresses.some((item) => !isPublicPublisherAddress(item.address)))
555
+ throw new Error("Publisher DNS answer includes a non-public address");
556
+ const selected = addresses[0];
557
+ if (selected === void 0)
558
+ throw new Error("Publisher DNS lookup was empty");
559
+ const encoded = Buffer3.from(JSON.stringify(body));
560
+ if (encoded.byteLength > 65536)
561
+ throw new Error("Publisher request body is too large");
562
+ return await new Promise((resolve, reject) => {
563
+ const call = request(url, {
564
+ agent: false,
565
+ headers: {
566
+ ...endpointHeaders.get(endpoint),
567
+ "content-length": encoded.byteLength,
568
+ "content-type": "application/json",
569
+ ...jwt === void 0 ? {} : { authorization: `Bearer ${jwt}` }
570
+ },
571
+ lookup(_hostname, lookupOptions, callback) {
572
+ if (lookupOptions.all)
573
+ callback(null, [{ address: selected.address, family: selected.family }]);
574
+ else
575
+ callback(null, selected.address, selected.family);
576
+ },
577
+ method: "POST",
578
+ rejectUnauthorized: true,
579
+ signal: combined
580
+ }, (response) => {
581
+ const status = response.statusCode ?? 500;
582
+ if (status >= 300 && status < 400) {
583
+ response.destroy();
584
+ reject(new Error("Publisher HTTPS redirects are forbidden"));
585
+ return;
586
+ }
587
+ let size = 0;
588
+ const chunks = [];
589
+ response.on("data", (chunk) => {
590
+ size += chunk.byteLength;
591
+ if (size > options.maxResponseBytes) {
592
+ response.destroy();
593
+ reject(new Error("Publisher HTTPS response is too large"));
594
+ } else
595
+ chunks.push(chunk);
596
+ });
597
+ response.on("error", () => reject(new Error("Publisher HTTPS response failed")));
598
+ response.on("end", () => {
599
+ try {
600
+ const responseBody = size === 0 ? null : JSON.parse(Buffer3.concat(chunks).toString("utf8"));
601
+ const retry = Number(response.headers["retry-after"]);
602
+ resolve({
603
+ status,
604
+ body: responseBody,
605
+ ...Number.isFinite(retry) && retry > 0 ? { retryAfterMs: Math.min(retry * 1e3, 36e5) } : {}
606
+ });
607
+ } catch {
608
+ reject(new Error("Publisher HTTPS response is not JSON"));
609
+ }
610
+ });
611
+ });
612
+ call.on("error", () => reject(new Error("Publisher HTTPS request failed")));
613
+ call.end(encoded);
614
+ });
615
+ } finally {
616
+ scope.dispose();
617
+ }
618
+ }
619
+ };
620
+ }
621
+
622
+ // src/runtime/publisherRuntime.ts
623
+ import { randomBytes as randomBytes2 } from "node:crypto";
624
+ import { isDeepStrictEqual } from "node:util";
625
+ import { createMemoryLocator, PeriodicActor } from "@ariestools/actor";
626
+ import { creatable } from "@ariestools/sdk";
627
+ import { WakeEnvelopeSchema, WakeEnvelopeZod as WakeEnvelopeZod2 } from "@xyo-network/event-kit-schemas";
628
+ import { z as z2 } from "zod";
629
+
630
+ // src/runtime/sourceSigner.ts
631
+ import { randomBytes } from "node:crypto";
632
+ import {
633
+ NormalizedAddressZod,
634
+ WakeEnvelopeZod,
635
+ WakeJwtSchema
636
+ } from "@xyo-network/event-kit-schemas";
637
+ import { PayloadBuilder as PayloadBuilder2 } from "@xyo-network/sdk";
638
+ function createAccountSourceWakeSigner(options) {
639
+ const publisher = NormalizedAddressZod.parse(options.publisher);
640
+ if (String(options.account.address).replace(/^0x/u, "").toLowerCase() !== publisher)
641
+ throw new Error("Publisher signing account mismatch");
642
+ return {
643
+ async sign(input, audience) {
644
+ const event = WakeEnvelopeZod.parse(input);
645
+ const eventHash = await PayloadBuilder2.dataHash(event);
646
+ const now = options.nowSeconds?.() ?? Math.floor(Date.now() / 1e3);
647
+ if (!Number.isSafeInteger(now) || now < 0)
648
+ throw new Error("Publisher signing clock is invalid");
649
+ const { token: jwt } = await options.account.signJwt({
650
+ audience,
651
+ claims: { eventHash, jti: randomBytes(16).toString("hex") },
652
+ now,
653
+ schema: WakeJwtSchema,
654
+ ttl: 60
655
+ });
656
+ return {
657
+ event,
658
+ eventHash,
659
+ jwt,
660
+ publisher
661
+ };
662
+ }
663
+ };
664
+ }
665
+
666
+ // src/runtime/publisherRuntime.ts
667
+ function activationReady(status) {
668
+ return (status?.keyLoaded ?? true) && (status?.pendingSubscriptions ?? 0) === 0;
669
+ }
670
+ function assertDeliveryState(record, subscription) {
671
+ if (!isDeepStrictEqual(record.subscription, subscription))
672
+ throw new Error("Subscription revision changed without a new identity");
673
+ if (!Number.isSafeInteger(record.cursor) || record.cursor < subscription.fromPosition - 1 || ![record.delivered, record.failures, record.nextAttemptMs].every((value) => Number.isSafeInteger(value) && value >= 0) || !["active", "blocked", "cancelled"].includes(record.state))
674
+ throw new Error("Persisted publisher delivery state is invalid");
675
+ if (record.pending !== void 0) {
676
+ const pending = WakeEnvelopeZod2.parse(record.pending);
677
+ 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))
678
+ throw new Error("Persisted publisher outbox binding is invalid");
679
+ }
680
+ }
681
+ function makeEvent(subscription, fromPosition, toPosition, now, wakeId = randomBytes2(16).toString("hex")) {
682
+ return WakeEnvelopeZod2.parse({
683
+ schema: WakeEnvelopeSchema,
684
+ authorizationChainId: subscription.authorizationChainId,
685
+ deploymentId: subscription.deploymentId,
686
+ filterHash: subscription.filterHash,
687
+ fromPosition,
688
+ issuedAt: now,
689
+ source: subscription.source,
690
+ toPosition,
691
+ wakeId,
692
+ ...subscription.queueId === void 0 ? {} : { queueId: subscription.queueId }
693
+ });
694
+ }
695
+ var PublisherRuntimeActor = class extends PeriodicActor {
696
+ async runPass(context) {
697
+ await this.params.tick(context.signal);
698
+ }
699
+ };
700
+ PublisherRuntimeActor = __decorateClass([
701
+ creatable()
702
+ ], PublisherRuntimeActor);
703
+ var TimingZod = z2.object({
704
+ concurrency: z2.number().int().min(1).max(32).default(4),
705
+ intervalMs: z2.number().int().min(100).max(6e4).default(1e3),
706
+ maxSubscriptions: z2.number().int().min(1).max(1024).default(64),
707
+ requestTimeoutMs: z2.number().int().min(100).max(6e4).default(1e4)
708
+ });
709
+ var DeliveryResponseError = class extends Error {
710
+ retryAfterMs;
711
+ status;
712
+ constructor(status, retryAfterMs) {
713
+ super("Publisher admission failed");
714
+ this.status = status;
715
+ this.retryAfterMs = retryAfterMs;
716
+ }
717
+ get retryable() {
718
+ return this.status === 408 || this.status === 429 || this.status >= 500;
719
+ }
720
+ };
721
+ async function createPersistentEventPublisherRuntime(options) {
722
+ const timing = TimingZod.parse(options);
723
+ if (options.sources.length < 1 || options.sources.length > 16)
724
+ throw new Error("Publisher requires between one and sixteen bounded sources");
725
+ const clock = options.nowSeconds ?? (() => Math.floor(Date.now() / 1e3));
726
+ const signer = createAccountSourceWakeSigner({
727
+ account: options.account,
728
+ publisher: options.publisher,
729
+ nowSeconds: clock
730
+ });
731
+ const sourceReaders = new Map(options.sources.map((reader) => [publisherSourceKey(reader.source), reader]));
732
+ if (sourceReaders.size !== options.sources.length)
733
+ throw new Error("Duplicate publisher source identity");
734
+ const abort = new AbortController();
735
+ let running = false;
736
+ let ready = false;
737
+ let clockHealthy = true;
738
+ let passes = 0;
739
+ let lastError;
740
+ let sourcesHealthy = false;
741
+ let stop;
742
+ const state = options.state;
743
+ const timed = (promise, signal) => boundedPublisherOperation(promise, signal, timing.requestTimeoutMs);
744
+ function loadDelivery(subscription, now) {
745
+ const key = subscription.subscriptionHash;
746
+ let record = state.get("deliveries", key);
747
+ if (record === void 0) {
748
+ makeEvent(subscription, subscription.fromPosition, subscription.fromPosition, now, "0".repeat(32));
749
+ if (!Number.isSafeInteger(subscription.maxPositionsPerWake) || subscription.maxPositionsPerWake < 1 || subscription.maxPositionsPerWake > 4096) {
750
+ throw new Error("Invalid source range budget");
751
+ }
752
+ record = {
753
+ subscription,
754
+ cursor: subscription.fromPosition - 1,
755
+ delivered: 0,
756
+ failures: 0,
757
+ nextAttemptMs: 0,
758
+ state: "active"
759
+ };
760
+ state.set("deliveries", key, record);
761
+ }
762
+ assertDeliveryState(record, subscription);
763
+ return record;
764
+ }
765
+ function prepareDelivery(record, now, heads) {
766
+ if (record.pending !== void 0) return { ...record, pending: record.pending };
767
+ const subscription = record.subscription;
768
+ const head = heads.get(publisherSourceKey(subscription.source));
769
+ if (head === void 0 || head instanceof Error) throw new Error("Configured source is unavailable");
770
+ if (head.position <= record.cursor) return;
771
+ const fromPosition = record.cursor + 1;
772
+ const pending = makeEvent(subscription, fromPosition, Math.min(head.position, fromPosition + subscription.maxPositionsPerWake - 1), now);
773
+ const prepared = { ...record, pending };
774
+ state.transaction(() => {
775
+ const logicalKey = JSON.stringify([subscription.subscriptionHash, pending.fromPosition, pending.toPosition]);
776
+ if (!state.insert("delivery-events", logicalKey, pending)) throw new Error("Publisher logical range already exists outside its contiguous cursor");
777
+ state.set("deliveries", subscription.subscriptionHash, prepared);
778
+ });
779
+ return prepared;
780
+ }
781
+ async function stillActive(record, signal) {
782
+ if (await timed(options.discovery.isActive(record.subscription, signal), signal)) return true;
783
+ state.set("deliveries", record.subscription.subscriptionHash, {
784
+ ...record,
785
+ pending: void 0,
786
+ state: "cancelled"
787
+ });
788
+ return false;
789
+ }
790
+ async function sendDelivery(record, signal, now) {
791
+ const { pending, subscription } = record;
792
+ signal.throwIfAborted();
793
+ const signed = await signer.sign(pending, subscription.audience);
794
+ signal.throwIfAborted();
795
+ const attempt = state.get("delivery-attempts", pending.wakeId)?.count ?? 0;
796
+ state.set("delivery-attempts", pending.wakeId, {
797
+ count: attempt + 1,
798
+ startedAt: now,
799
+ status: "in-flight"
800
+ });
801
+ const response = await options.transport.post(subscription.endpoint, signed.event, signal, signed.jwt);
802
+ state.set("delivery-attempts", pending.wakeId, {
803
+ count: attempt + 1,
804
+ startedAt: now,
805
+ status: response.status
806
+ });
807
+ if (response.status !== 200 && response.status !== 202) throw new DeliveryResponseError(response.status, response.retryAfterMs);
808
+ const receipt = z2.object({ receiptId: z2.string().min(1), wakeId: z2.literal(pending.wakeId) }).safeParse(response.body);
809
+ if (!receipt.success) throw new Error("Publisher admission did not return a matching durable receipt");
810
+ signal.throwIfAborted();
811
+ state.transaction(() => {
812
+ state.set("delivery-receipts", pending.wakeId, { receiptId: receipt.data.receiptId, receivedAt: now });
813
+ state.set("deliveries", subscription.subscriptionHash, {
814
+ subscription,
815
+ cursor: pending.toPosition,
816
+ delivered: record.delivered + 1,
817
+ failures: 0,
818
+ nextAttemptMs: 0,
819
+ state: "active"
820
+ });
821
+ });
822
+ }
823
+ async function deliver(subscription, signal, now, heads) {
824
+ let record = loadDelivery(subscription, now);
825
+ if (record.state !== "active" || record.nextAttemptMs > now * 1e3) return;
826
+ try {
827
+ if (!await stillActive(record, signal)) return;
828
+ options.transport.assertAllowed(subscription.endpoint);
829
+ if (!sourceReaders.has(publisherSourceKey(subscription.source))) throw new Error("Subscription source is not configured");
830
+ const prepared = prepareDelivery(record, now, heads);
831
+ if (prepared === void 0) return;
832
+ record = prepared;
833
+ if (!await stillActive(record, signal)) return;
834
+ await sendDelivery(prepared, signal, now);
835
+ } catch (error) {
836
+ if (signal.aborted) return;
837
+ state.set("deliveries", subscription.subscriptionHash, {
838
+ ...record,
839
+ failures: record.failures + 1,
840
+ state: error instanceof DeliveryResponseError && !error.retryable ? "blocked" : "active",
841
+ nextAttemptMs: now * 1e3 + Math.min(36e5, error instanceof DeliveryResponseError && error.retryAfterMs !== void 0 ? error.retryAfterMs : Math.min(3e5, 1e3 * 2 ** Math.min(record.failures, 8))),
842
+ lastError: "delivery-failed"
843
+ });
844
+ }
845
+ }
846
+ async function readSources(signal) {
847
+ const heads = /* @__PURE__ */ new Map();
848
+ const results = await Promise.allSettled([...sourceReaders].map(async ([key, reader]) => {
849
+ const head = await timed(reader.head(signal), signal);
850
+ if (!Number.isSafeInteger(head.position) || head.position < -1)
851
+ throw new Error("Source head is invalid");
852
+ const previous = state.get("source-heads", key);
853
+ if (previous !== void 0 && (head.position < previous.position || head.position === previous.position && head.hash !== previous.hash)) {
854
+ throw new Error("Finalized source regressed or changed hash");
855
+ }
856
+ signal.throwIfAborted();
857
+ state.set("source-heads", key, head);
858
+ heads.set(key, head);
859
+ }));
860
+ sourcesHealthy = results.every((item) => item.status === "fulfilled");
861
+ for (const [index, result] of results.entries())
862
+ if (result.status === "rejected") {
863
+ const key = [...sourceReaders.keys()][index];
864
+ if (key !== void 0)
865
+ heads.set(key, new Error("Source read failed"));
866
+ }
867
+ return heads;
868
+ }
869
+ async function reconcileDeliveries(subscriptions, signal) {
870
+ if (subscriptions.length > timing.maxSubscriptions) throw new Error("Publisher subscription limit exceeded");
871
+ const active = new Set(subscriptions.map((subscription) => subscription.subscriptionHash));
872
+ if (active.size !== subscriptions.length) throw new Error("Duplicate subscription identity");
873
+ const unavailable = state.entries("deliveries").filter((item) => !active.has(item.key) && item.value.state !== "cancelled");
874
+ let available = true;
875
+ for (let offset = 0; offset < unavailable.length; offset += timing.concurrency) {
876
+ signal.throwIfAborted();
877
+ const results = await Promise.allSettled(unavailable.slice(offset, offset + timing.concurrency).map(async ({ key, value }) => {
878
+ const stillAuthorized = await timed(options.discovery.isActive(value.subscription, signal), signal);
879
+ if (!stillAuthorized) state.set("deliveries", key, {
880
+ ...value,
881
+ pending: void 0,
882
+ state: "cancelled"
883
+ });
884
+ return stillAuthorized;
885
+ }));
886
+ available = available && results.every((result) => result.status === "fulfilled" && !result.value);
887
+ }
888
+ return available;
889
+ }
890
+ async function tick(actorSignal) {
891
+ const scope = publisherAbortScope([abort.signal, actorSignal]);
892
+ const signal = scope.signal;
893
+ try {
894
+ signal.throwIfAborted();
895
+ const now = clock();
896
+ const prior = state.get("runtime", "highest-wall-clock") ?? 0;
897
+ clockHealthy = Number.isSafeInteger(now) && now >= prior;
898
+ if (!clockHealthy)
899
+ throw new Error("Publisher clock moved backwards");
900
+ state.set("runtime", "highest-wall-clock", now);
901
+ const subscriptions = await timed(options.discovery.discover(signal), signal);
902
+ const subscriptionsAvailable = await reconcileDeliveries(subscriptions, signal);
903
+ const heads = await readSources(signal);
904
+ for (let offset = 0; offset < subscriptions.length; offset += timing.concurrency) {
905
+ signal.throwIfAborted();
906
+ const outcomes = await Promise.allSettled(subscriptions.slice(offset, offset + timing.concurrency).map((subscription) => deliver(subscription, signal, now, heads)));
907
+ const failed = outcomes.find((result) => result.status === "rejected");
908
+ if (failed?.status === "rejected")
909
+ throw failed.reason;
910
+ }
911
+ if (options.discovery.acceptPending !== void 0)
912
+ await timed(options.discovery.acceptPending(signal), signal);
913
+ const discoveryStatus = options.discovery.status?.();
914
+ ready = subscriptionsAvailable && sourcesHealthy && activationReady(discoveryStatus);
915
+ lastError = subscriptionsAvailable ? void 0 : "publisher-subscription-unavailable";
916
+ passes += 1;
917
+ } catch (error) {
918
+ ready = false;
919
+ lastError = clockHealthy ? "publisher-pass-failed" : "publisher-clock-unhealthy";
920
+ if (!signal.aborted)
921
+ throw error;
922
+ } finally {
923
+ scope.dispose();
924
+ }
925
+ }
926
+ const actor = await PublisherRuntimeActor.create({
927
+ locator: createMemoryLocator({}),
928
+ intervalMs: timing.intervalMs,
929
+ firstRun: "immediate",
930
+ readiness: "immediate",
931
+ tick
932
+ });
933
+ return {
934
+ async start() {
935
+ if (stop !== void 0)
936
+ throw new Error("Publisher is stopped");
937
+ if (!running) {
938
+ await actor.start();
939
+ running = true;
940
+ }
941
+ },
942
+ async runOnce() {
943
+ if (stop !== void 0)
944
+ throw new Error("Publisher is stopped");
945
+ await actor.runOnce();
946
+ },
947
+ stop() {
948
+ return stop ??= (async () => {
949
+ abort.abort();
950
+ const failures = [];
951
+ try {
952
+ await actor.stop();
953
+ } catch (error) {
954
+ failures.push(error);
955
+ }
956
+ try {
957
+ await options.discovery.close?.();
958
+ } catch (error) {
959
+ failures.push(error);
960
+ }
961
+ for (const source of options.sources)
962
+ try {
963
+ await source.close?.();
964
+ } catch (error) {
965
+ failures.push(error);
966
+ }
967
+ running = false;
968
+ ready = false;
969
+ if (failures.length > 0)
970
+ throw new AggregateError(failures, "Publisher runtime shutdown failed");
971
+ })();
972
+ },
973
+ status() {
974
+ const deliveries = state.entries("deliveries").map((item) => item.value);
975
+ return {
976
+ running,
977
+ ready: running && ready,
978
+ clockHealthy,
979
+ degraded: !ready || deliveries.some((item) => item.state !== "cancelled" && item.failures > 0),
980
+ publisher: options.publisher,
981
+ activeSubscriptions: deliveries.filter((item) => item.state === "active").length,
982
+ pendingDeliveries: deliveries.filter((item) => item.pending !== void 0).length,
983
+ delivered: deliveries.reduce((sum, item) => sum + item.delivered, 0),
984
+ failures: deliveries.reduce((sum, item) => sum + item.failures, 0),
985
+ passes,
986
+ ...lastError === void 0 ? {} : { lastError },
987
+ ...options.discovery.status === void 0 ? {} : { handshake: options.discovery.status() },
988
+ sources: state.entries("source-heads").map((item) => ({ id: item.key, ...item.value })),
989
+ subscriptions: state.entries("deliveries").map((item) => ({
990
+ id: item.key,
991
+ cursor: item.value.cursor,
992
+ state: item.value.state,
993
+ pending: item.value.pending !== void 0,
994
+ failures: item.value.failures
995
+ }))
996
+ };
997
+ }
998
+ };
999
+ }
1000
+
1001
+ // src/runtime/restGateway.ts
1002
+ import { FetchJsonClient } from "@ariestools/sdk";
1003
+ import {
1004
+ baseConfigFactoryLocator,
1005
+ basicRestRunnerLocator,
1006
+ basicRestViewerLocator,
1007
+ creatableProvider,
1008
+ releaseProviderFactoryLocator,
1009
+ RestDataLakeRunner,
1010
+ RestDataLakeViewer,
1011
+ XyoGatewayMoniker,
1012
+ XyoGatewayRunnerMoniker
1013
+ } from "@xyo-network/xl1-sdk";
1014
+ async function openPublisherRestGateway(options) {
1015
+ const rest = {
1016
+ finalized: { type: "rest", baseUrl: options.finalizedUrl },
1017
+ chainState: { type: "rest", baseUrl: options.chainStateUrl },
1018
+ index: { type: "rest", baseUrl: options.indexUrl ?? options.finalizedUrl }
1019
+ };
1020
+ const locator = options.account === void 0 ? await basicRestViewerLocator("event-kit-publisher-read", rest, void 0, { fetchFn: options.fetchFn, primeCaches: "off" }) : await basicRestRunnerLocator("event-kit-publisher-write", rest, void 0, {
1021
+ fetchFn: options.fetchFn,
1022
+ primeCaches: "off",
1023
+ rpcUrl: options.rpcUrl,
1024
+ signerAccount: options.account
1025
+ });
1026
+ try {
1027
+ const gateway = options.account === void 0 ? await locator.getInstance(XyoGatewayMoniker) : await locator.getInstance(XyoGatewayRunnerMoniker);
1028
+ let stopping;
1029
+ return Object.freeze({ gateway, stop: () => stopping ??= releaseProviderFactoryLocator(locator) });
1030
+ } catch (error) {
1031
+ await releaseProviderFactoryLocator(locator);
1032
+ throw error;
1033
+ }
1034
+ }
1035
+ var BoundedRestDataLakeViewer = class extends RestDataLakeViewer {
1036
+ get client() {
1037
+ if (this.context.fetchFn === void 0)
1038
+ throw new Error("Publisher datalake requires its bounded fetch transport");
1039
+ return new FetchJsonClient({ fetcher: this.context.fetchFn });
1040
+ }
1041
+ };
1042
+ BoundedRestDataLakeViewer = __decorateClass([
1043
+ creatableProvider()
1044
+ ], BoundedRestDataLakeViewer);
1045
+ var BoundedRestDataLakeRunner = class extends RestDataLakeRunner {
1046
+ get client() {
1047
+ if (this.context.fetchFn === void 0)
1048
+ throw new Error("Publisher datalake requires its bounded fetch transport");
1049
+ return new FetchJsonClient({ fetcher: this.context.fetchFn });
1050
+ }
1051
+ };
1052
+ BoundedRestDataLakeRunner = __decorateClass([
1053
+ creatableProvider()
1054
+ ], BoundedRestDataLakeRunner);
1055
+ async function openPublisherDataLake(options) {
1056
+ const locator = await baseConfigFactoryLocator({
1057
+ connections: { "event-kit-lake": { type: "rest", baseUrl: options.url } },
1058
+ providerBindings: { DataLakeViewer: { connection: "event-kit-lake" }, DataLakeRunner: { connection: "event-kit-lake" } }
1059
+ });
1060
+ locator.context.fetchFn = options.fetchFn;
1061
+ locator.register(BoundedRestDataLakeViewer.factory(BoundedRestDataLakeViewer.dependencies));
1062
+ if (options.writable)
1063
+ locator.register(BoundedRestDataLakeRunner.factory(BoundedRestDataLakeRunner.dependencies));
1064
+ locator.freeze();
1065
+ try {
1066
+ const reader = await locator.getInstance(BoundedRestDataLakeViewer.defaultMoniker);
1067
+ const writer = options.writable ? await locator.getInstance(BoundedRestDataLakeRunner.defaultMoniker) : void 0;
1068
+ return {
1069
+ reader,
1070
+ writer,
1071
+ async close() {
1072
+ await releaseProviderFactoryLocator(locator);
1073
+ }
1074
+ };
1075
+ } catch (error) {
1076
+ await releaseProviderFactoryLocator(locator);
1077
+ throw error;
1078
+ }
1079
+ }
1080
+
1081
+ // src/runtime/sqliteHandshakeStores.ts
1082
+ import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
1083
+ import { endpointChallengeHash } from "@xyo-network/event-kit-protocol";
1084
+ import {
1085
+ Base64Url32BytesZod as Base64Url32BytesZod2,
1086
+ EndpointChallengeZod as EndpointChallengeZod2,
1087
+ HandshakeIdZod as HandshakeIdZod2,
1088
+ HashZod as HashZod2,
1089
+ NormalizedAddressZod as NormalizedAddressZod3
1090
+ } from "@xyo-network/event-kit-schemas";
1091
+ import { z as z4 } from "zod";
1092
+
1093
+ // src/handshakeMutationIntent.ts
1094
+ import {
1095
+ canonicalJsonBytes,
1096
+ handshakeObjectHash
1097
+ } from "@xyo-network/event-kit-protocol";
1098
+ import {
1099
+ Base64Url32BytesZod,
1100
+ ChainIdZod,
1101
+ EncryptedWakeSubscriptionZod,
1102
+ EndpointChallengeResponseZod as EndpointChallengeResponseZod2,
1103
+ EndpointChallengeZod,
1104
+ HandshakeIdZod,
1105
+ HashZod,
1106
+ NormalizedAddressZod as NormalizedAddressZod2,
1107
+ PublisherEncryptionKeyRevocationZod,
1108
+ PublisherEncryptionKeyZod,
1109
+ PublisherServiceRevocationZod,
1110
+ PublisherServiceZod,
1111
+ WakeGrantObjectZod,
1112
+ WakeGrantRevocationZod,
1113
+ WakeSubscriptionAcceptanceZod,
1114
+ WakeSubscriptionRevocationZod
1115
+ } from "@xyo-network/event-kit-schemas";
1116
+ import { z as z3 } from "zod";
1117
+ var HandshakeMutationIntentKinds = [
1118
+ "publisher-encryption-key-retire",
1119
+ "publisher-service-publish",
1120
+ "publisher-service-retire",
1121
+ "publisher-service-rotate-key",
1122
+ "publisher-subscription-accept",
1123
+ "subscriber-grant-revoke",
1124
+ "subscriber-subscription-revoke",
1125
+ "subscriber-subscription-subscribe"
1126
+ ];
1127
+ var HandshakeMutationIntentKindZod = z3.enum(HandshakeMutationIntentKinds);
1128
+ var HandshakeMutationOperationIdZod = z3.union([HandshakeIdZod, HashZod]);
1129
+ var WakeSubscriptionPrivateKeyZod = z3.strictObject({
1130
+ encryptionKey: HashZod,
1131
+ keyId: HandshakeIdZod,
1132
+ privateKey: Base64Url32BytesZod
1133
+ });
1134
+ var IntentBaseFields = {
1135
+ chainId: ChainIdZod,
1136
+ operationId: HandshakeMutationOperationIdZod,
1137
+ publisher: NormalizedAddressZod2,
1138
+ requestFingerprint: HashZod,
1139
+ source: NormalizedAddressZod2,
1140
+ version: z3.literal(1)
1141
+ };
1142
+ var PublisherServicePublishIntentZod = z3.strictObject({
1143
+ ...IntentBaseFields,
1144
+ kind: z3.literal("publisher-service-publish"),
1145
+ operationId: HandshakeIdZod,
1146
+ encryptionKey: PublisherEncryptionKeyZod,
1147
+ privateKey: WakeSubscriptionPrivateKeyZod,
1148
+ service: PublisherServiceZod
1149
+ });
1150
+ var PublisherServiceRotateKeyIntentZod = z3.strictObject({
1151
+ ...IntentBaseFields,
1152
+ kind: z3.literal("publisher-service-rotate-key"),
1153
+ operationId: HandshakeIdZod,
1154
+ encryptionKey: PublisherEncryptionKeyZod,
1155
+ privateKey: WakeSubscriptionPrivateKeyZod,
1156
+ selectedKey: HashZod,
1157
+ selectedService: HashZod,
1158
+ service: PublisherServiceZod
1159
+ });
1160
+ var SubscriberSubscriptionIntentZod = z3.strictObject({
1161
+ ...IntentBaseFields,
1162
+ kind: z3.literal("subscriber-subscription-subscribe"),
1163
+ operationId: HandshakeIdZod,
1164
+ encryptionKey: PublisherEncryptionKeyZod,
1165
+ grant: WakeGrantObjectZod,
1166
+ selectedKey: HashZod,
1167
+ selectedService: HashZod,
1168
+ service: PublisherServiceZod,
1169
+ subscription: EncryptedWakeSubscriptionZod
1170
+ });
1171
+ var PublisherAcceptanceIntentZod = z3.strictObject({
1172
+ ...IntentBaseFields,
1173
+ acceptance: WakeSubscriptionAcceptanceZod,
1174
+ challenge: EndpointChallengeZod,
1175
+ kind: z3.literal("publisher-subscription-accept"),
1176
+ operationId: HashZod,
1177
+ response: EndpointChallengeResponseZod2
1178
+ });
1179
+ var PublisherServiceRetireIntentZod = z3.strictObject({
1180
+ ...IntentBaseFields,
1181
+ kind: z3.literal("publisher-service-retire"),
1182
+ operationId: HashZod,
1183
+ revocation: PublisherServiceRevocationZod
1184
+ });
1185
+ var PublisherEncryptionKeyRetireIntentZod = z3.strictObject({
1186
+ ...IntentBaseFields,
1187
+ kind: z3.literal("publisher-encryption-key-retire"),
1188
+ operationId: HashZod,
1189
+ revocation: PublisherEncryptionKeyRevocationZod
1190
+ });
1191
+ var SubscriberGrantRevokeIntentZod = z3.strictObject({
1192
+ ...IntentBaseFields,
1193
+ kind: z3.literal("subscriber-grant-revoke"),
1194
+ operationId: HashZod,
1195
+ revocation: WakeGrantRevocationZod
1196
+ });
1197
+ var SubscriberSubscriptionRevokeIntentZod = z3.strictObject({
1198
+ ...IntentBaseFields,
1199
+ kind: z3.literal("subscriber-subscription-revoke"),
1200
+ operationId: HashZod,
1201
+ revocation: WakeSubscriptionRevocationZod
1202
+ });
1203
+ var HandshakeMutationIntentZod = z3.discriminatedUnion("kind", [
1204
+ PublisherEncryptionKeyRetireIntentZod,
1205
+ PublisherServicePublishIntentZod,
1206
+ PublisherServiceRetireIntentZod,
1207
+ PublisherServiceRotateKeyIntentZod,
1208
+ PublisherAcceptanceIntentZod,
1209
+ SubscriberGrantRevokeIntentZod,
1210
+ SubscriberSubscriptionRevokeIntentZod,
1211
+ SubscriberSubscriptionIntentZod
1212
+ ]);
1213
+ var HandshakeMutationIntentConflictError = class extends Error {
1214
+ constructor(message = "Handshake mutation operation ID is already bound to different intent") {
1215
+ super(message);
1216
+ this.name = "HandshakeMutationIntentConflictError";
1217
+ }
1218
+ };
1219
+ function parseHandshakeMutationIntentLookup(value) {
1220
+ return Object.freeze({
1221
+ chainId: ChainIdZod.parse(value.chainId),
1222
+ kind: HandshakeMutationIntentKindZod.parse(value.kind),
1223
+ operationId: HandshakeMutationOperationIdZod.parse(value.operationId),
1224
+ publisher: NormalizedAddressZod2.parse(value.publisher),
1225
+ source: NormalizedAddressZod2.parse(value.source)
1226
+ });
1227
+ }
1228
+ async function parseHandshakeMutationIntent(value) {
1229
+ const intent = HandshakeMutationIntentZod.parse(value);
1230
+ await assertIntentIntegrity(intent);
1231
+ return Object.freeze(intent);
1232
+ }
1233
+ function intentLookupKey(lookup2) {
1234
+ const parsed = parseHandshakeMutationIntentLookup(lookup2);
1235
+ return [parsed.chainId, parsed.publisher, parsed.source, parsed.kind, parsed.operationId].join("/");
1236
+ }
1237
+ function assertSameIntentBinding(left, right) {
1238
+ const shared = left.version === right.version && left.chainId === right.chainId && left.kind === right.kind && left.operationId === right.operationId && left.publisher === right.publisher && left.source === right.source && left.requestFingerprint === right.requestFingerprint;
1239
+ const bothRotate = left.kind === "publisher-service-rotate-key" && right.kind === "publisher-service-rotate-key";
1240
+ const bothSubscribe = left.kind === "subscriber-subscription-subscribe" && right.kind === "subscriber-subscription-subscribe";
1241
+ const selected = bothRotate || bothSubscribe ? left.selectedService === right.selectedService && left.selectedKey === right.selectedKey : !("selectedService" in left) && !("selectedService" in right);
1242
+ if (!shared || !selected) throw new HandshakeMutationIntentConflictError();
1243
+ }
1244
+ async function assertIntentIntegrity(intent) {
1245
+ if (intent.kind.startsWith("publisher-") && intent.source !== intent.publisher) {
1246
+ throw new TypeError("Publisher mutation intent source must equal publisher");
1247
+ }
1248
+ if (intent.kind === "publisher-service-publish" || intent.kind === "publisher-service-rotate-key") {
1249
+ await assertPublisherServiceIntent(intent);
1250
+ return;
1251
+ }
1252
+ if (intent.kind === "subscriber-subscription-subscribe") {
1253
+ await assertSubscriberSubscriptionIntent(intent);
1254
+ return;
1255
+ }
1256
+ if (intent.kind === "publisher-subscription-accept") {
1257
+ await assertPublisherAcceptanceIntent(intent);
1258
+ return;
1259
+ }
1260
+ await assertRevocationIntent(intent);
1261
+ }
1262
+ async function assertPublisherServiceIntent(intent) {
1263
+ const keyHash = await handshakeObjectHash(intent.encryptionKey);
1264
+ if (intent.privateKey.encryptionKey !== keyHash || intent.privateKey.keyId !== intent.encryptionKey.keyId || intent.service.preferredEncryptionKey !== keyHash || intent.service.encryptionKeys.length !== 1 || intent.service.encryptionKeys[0] !== keyHash) {
1265
+ throw new TypeError("Publisher service mutation intent has inconsistent encryption-key artifacts");
1266
+ }
1267
+ if (intent.kind === "publisher-service-publish") {
1268
+ if (intent.service.revision !== 1 || intent.service.supersedes !== void 0) {
1269
+ throw new TypeError("Publisher service root intent must contain a root service revision");
1270
+ }
1271
+ } else if (intent.service.supersedes !== intent.selectedService || intent.service.revision <= 1) {
1272
+ throw new TypeError("Publisher service rotation intent does not extend its selected service");
1273
+ }
1274
+ }
1275
+ async function assertSubscriberSubscriptionIntent(intent) {
1276
+ const [keyHash, serviceHash, grantHash] = await Promise.all([
1277
+ handshakeObjectHash(intent.encryptionKey),
1278
+ handshakeObjectHash(intent.service),
1279
+ handshakeObjectHash(intent.grant)
1280
+ ]);
1281
+ if (keyHash !== intent.selectedKey || serviceHash !== intent.selectedService || intent.service.preferredEncryptionKey !== keyHash || !intent.service.encryptionKeys.includes(keyHash) || intent.grant.publisher !== intent.publisher || intent.subscription.publisher !== intent.publisher || intent.subscription.service !== serviceHash || intent.subscription.encryptionKey !== keyHash || intent.subscription.keyId !== intent.encryptionKey.keyId || intent.subscription.grant !== grantHash) {
1282
+ throw new TypeError("Subscriber subscription intent has inconsistent public artifacts");
1283
+ }
1284
+ }
1285
+ async function assertPublisherAcceptanceIntent(intent) {
1286
+ const [challengeHash, responseHash] = await Promise.all([
1287
+ handshakeObjectHash(intent.challenge),
1288
+ handshakeObjectHash(intent.response)
1289
+ ]);
1290
+ if (challengeHash !== intent.operationId || intent.challenge.publisher !== intent.publisher || intent.response.challenge !== challengeHash || intent.acceptance.challenge !== challengeHash || intent.acceptance.response !== responseHash || intent.acceptance.service !== intent.challenge.service || intent.acceptance.encryptionKey !== intent.challenge.encryptionKey || intent.acceptance.grant !== intent.challenge.grant || intent.acceptance.subscription !== intent.challenge.subscription) {
1291
+ throw new TypeError("Publisher acceptance intent has inconsistent public artifacts");
1292
+ }
1293
+ }
1294
+ async function assertRevocationIntent(intent) {
1295
+ const target = intent.kind === "publisher-encryption-key-retire" ? intent.revocation.key : intent.kind === "publisher-service-retire" ? intent.revocation.service : intent.kind === "subscriber-grant-revoke" ? intent.revocation.grant : intent.revocation.subscription;
1296
+ if (target !== intent.operationId) {
1297
+ throw new TypeError("Handshake revocation intent has inconsistent target binding");
1298
+ }
1299
+ }
1300
+
1301
+ // src/runtime/sqliteHandshakeStores.ts
1302
+ var KeyZod = z4.strictObject({
1303
+ encryptionKey: HashZod2,
1304
+ keyId: HandshakeIdZod2,
1305
+ privateKey: Base64Url32BytesZod2,
1306
+ publisher: NormalizedAddressZod3
1307
+ });
1308
+ var phases = ["signed", "content-written", "content-verified", "broadcast", "included", "finalized"];
1309
+ function requireTime(value) {
1310
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error("Publisher clock is invalid");
1311
+ }
1312
+ function samePublicationIntent(left, right) {
1313
+ return left.chainId === right.chainId && left.source === right.source && left.operationId === right.operationId && isDeepStrictEqual2(left.eventHashes, right.eventHashes) && isDeepStrictEqual2(left.attachmentHashes, right.attachmentHashes);
1314
+ }
1315
+ function publicationKey(intent) {
1316
+ 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].every((hash) => HashZod2.safeParse(hash).success)) {
1317
+ throw new Error("Invalid publication intent");
1318
+ }
1319
+ return JSON.stringify([intent.chainId, intent.source, intent.operationId]);
1320
+ }
1321
+ function createSqlitePublisherHandshakeStores(state) {
1322
+ const replay = {
1323
+ async load() {
1324
+ return state.get("replay", "checkpoint");
1325
+ },
1326
+ async save(checkpoint) {
1327
+ state.set("replay", "checkpoint", checkpoint);
1328
+ }
1329
+ };
1330
+ const intents = {
1331
+ async get(lookup2) {
1332
+ const value = state.get("intents", intentLookupKey(lookup2));
1333
+ return value === void 0 ? void 0 : await parseHandshakeMutationIntent(value);
1334
+ },
1335
+ async getOrCreate(raw) {
1336
+ const proposed = await parseHandshakeMutationIntent(raw);
1337
+ return state.transaction(() => {
1338
+ const key = intentLookupKey(proposed);
1339
+ const winner = state.get("intents", key);
1340
+ if (winner !== void 0) {
1341
+ assertSameIntentBinding(winner, proposed);
1342
+ return { disposition: "resumed", intent: winner };
1343
+ }
1344
+ state.set("intents", key, proposed);
1345
+ return { disposition: "created", intent: proposed };
1346
+ });
1347
+ }
1348
+ };
1349
+ const keys = {
1350
+ async getPrivateKey(input) {
1351
+ const key = `${NormalizedAddressZod3.parse(input.publisher)}:${HandshakeIdZod2.parse(input.keyId)}`;
1352
+ const raw = state.get("encryption-keys", key);
1353
+ if (raw === void 0) return;
1354
+ const record = KeyZod.parse(raw);
1355
+ if (record.encryptionKey !== HashZod2.parse(input.encryptionKey)) throw new Error("Encryption key identity mismatch");
1356
+ const { publisher: _publisher, ...privateKey } = record;
1357
+ return privateKey;
1358
+ },
1359
+ async putPrivateKey(input) {
1360
+ const record = KeyZod.parse({ ...input.key, publisher: input.publisher });
1361
+ return state.transaction(() => {
1362
+ const key = `${record.publisher}:${record.keyId}`;
1363
+ const prior = state.get("encryption-keys", key);
1364
+ if (prior !== void 0) {
1365
+ if (!isDeepStrictEqual2(prior, record)) throw new Error("Encryption key collision");
1366
+ return "duplicate";
1367
+ }
1368
+ state.set("encryption-keys", key, record);
1369
+ return "stored";
1370
+ });
1371
+ }
1372
+ };
1373
+ const current = (hash) => state.get("challenges", HashZod2.parse(hash));
1374
+ const live = (record, now) => record.challenge.issuedAt <= now && now < record.challenge.expiresAt;
1375
+ const challenges = {
1376
+ async get(hash) {
1377
+ return current(hash);
1378
+ },
1379
+ async issue(input) {
1380
+ const challenge = EndpointChallengeZod2.parse(input.challenge);
1381
+ const hash = HashZod2.parse(input.challengeHash);
1382
+ if (await endpointChallengeHash(challenge) !== hash) return { kind: "collision" };
1383
+ return state.transaction(() => {
1384
+ const priorNonce = state.get("challenge-nonces", challenge.nonce);
1385
+ if (priorNonce !== void 0 && priorNonce !== hash) return { kind: "collision" };
1386
+ const prior = current(hash);
1387
+ if (prior !== void 0) return isDeepStrictEqual2(prior.challenge, challenge) ? { kind: "duplicate", record: prior } : { kind: "collision" };
1388
+ const record = {
1389
+ challenge,
1390
+ challengeHash: hash,
1391
+ state: "issued"
1392
+ };
1393
+ state.set("challenge-nonces", challenge.nonce, hash);
1394
+ state.set("challenges", hash, record);
1395
+ return { kind: "issued", record };
1396
+ });
1397
+ },
1398
+ async beginAttempt(input) {
1399
+ requireTime(input.nowSeconds);
1400
+ return state.transaction(() => {
1401
+ const record = current(input.challengeHash);
1402
+ if (record === void 0) return { kind: "missing" };
1403
+ if (record.state === "verified") return { kind: "verified", record };
1404
+ if (record.state === "attempted") return { kind: "already-attempted", record };
1405
+ if (!live(record, input.nowSeconds)) return { kind: "expired", record };
1406
+ const attempted = { ...record, state: "attempted" };
1407
+ state.set("challenges", input.challengeHash, attempted);
1408
+ return { kind: "started", record: attempted };
1409
+ });
1410
+ },
1411
+ async completeVerified(input) {
1412
+ requireTime(input.nowSeconds);
1413
+ const responseHash = HashZod2.parse(input.responseHash);
1414
+ return state.transaction(() => {
1415
+ const record = current(input.challengeHash);
1416
+ if (record === void 0) return { kind: "missing" };
1417
+ if (record.state === "verified") return { kind: record.responseHash === responseHash ? "duplicate" : "collision", record };
1418
+ if (record.state !== "attempted") return { kind: "not-attempted", record };
1419
+ if (!live(record, input.nowSeconds)) return { kind: "expired", record };
1420
+ const verified = {
1421
+ ...record,
1422
+ responseHash,
1423
+ state: "verified"
1424
+ };
1425
+ state.set("challenges", input.challengeHash, verified);
1426
+ return { kind: "verified", record: verified };
1427
+ });
1428
+ }
1429
+ };
1430
+ const journal = {
1431
+ async load(intent) {
1432
+ const key = publicationKey(intent);
1433
+ const signed = state.get("publication:signed", key);
1434
+ if (signed === void 0) return;
1435
+ if (!samePublicationIntent(signed, intent)) throw new Error("Publication intent collision");
1436
+ let latest = signed;
1437
+ for (const phase of phases) {
1438
+ const record = state.get(`publication:${phase}`, key);
1439
+ if (record === void 0) continue;
1440
+ if (record.phase !== phase || !samePublicationIntent(record, intent) || !isDeepStrictEqual2(record.evidence, signed.evidence)) {
1441
+ throw new Error("Publication evidence collision");
1442
+ }
1443
+ latest = record;
1444
+ }
1445
+ return latest;
1446
+ },
1447
+ async save(record) {
1448
+ const key = publicationKey(record);
1449
+ HashZod2.parse(record.evidence.transactionHash);
1450
+ if (!phases.includes(record.phase)) throw new Error("Unknown publication phase");
1451
+ state.transaction(() => {
1452
+ const signed = state.get("publication:signed", key);
1453
+ if (signed === void 0 && record.phase !== "signed") throw new Error("Publication must bind signed evidence first");
1454
+ if (signed !== void 0 && (!samePublicationIntent(signed, record) || !isDeepStrictEqual2(signed.evidence, record.evidence))) {
1455
+ throw new Error("Publication intent or evidence collision");
1456
+ }
1457
+ const namespace = `publication:${record.phase}`;
1458
+ const prior = state.get(namespace, key);
1459
+ if (prior !== void 0 && !isDeepStrictEqual2(prior, record)) throw new Error("Publication phase collision");
1460
+ if (prior === void 0) state.set(namespace, key, record);
1461
+ });
1462
+ }
1463
+ };
1464
+ return {
1465
+ challenges,
1466
+ intents,
1467
+ journal,
1468
+ keys,
1469
+ replay
1470
+ };
1471
+ }
1472
+
1473
+ // src/runtime/sqlitePublisherState.ts
1474
+ import { randomBytes as randomBytes3 } from "node:crypto";
1475
+ import { constants } from "node:fs";
1476
+ import {
1477
+ lstat,
1478
+ mkdir,
1479
+ open
1480
+ } from "node:fs/promises";
1481
+ import { hostname } from "node:os";
1482
+ import PATH from "node:path";
1483
+
1484
+ // src/filesystemJson.ts
1485
+ function stringifyJson(value) {
1486
+ return JSON.stringify(value, replaceJsonValue);
1487
+ }
1488
+ function replaceJsonValue(_key, candidate) {
1489
+ return typeof candidate === "bigint" ? { $eventKitBigInt: candidate.toString() } : candidate;
1490
+ }
1491
+
1492
+ // src/runtime/sqlitePublisherState.ts
1493
+ function decode(value) {
1494
+ return JSON.parse(value, (_key, candidate) => {
1495
+ if (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))
1496
+ return BigInt(candidate.$eventKitBigInt);
1497
+ return candidate;
1498
+ });
1499
+ }
1500
+ function processExists(pid) {
1501
+ if (!Number.isSafeInteger(pid) || pid < 1)
1502
+ throw new Error("Invalid publisher owner record");
1503
+ try {
1504
+ process.kill(pid, 0);
1505
+ return true;
1506
+ } catch (error) {
1507
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH")
1508
+ return false;
1509
+ return true;
1510
+ }
1511
+ }
1512
+ async function openSqlitePublisherState(options) {
1513
+ if (options.path === ":memory:" || options.identity.length === 0)
1514
+ throw new Error("Persistent publisher needs a file and an identity");
1515
+ const path = PATH.resolve(options.path);
1516
+ const directory = PATH.dirname(path);
1517
+ await mkdir(directory, { recursive: true, mode: 448 });
1518
+ const metadata = await lstat(directory);
1519
+ if (!metadata.isDirectory() || (metadata.mode & 63) !== 0)
1520
+ throw new Error("Publisher state directory must be a real directory with 0700 permissions");
1521
+ const file = await open(path, constants.O_CREAT | constants.O_RDWR | constants.O_NOFOLLOW, 384);
1522
+ try {
1523
+ const metadata2 = await file.stat();
1524
+ if (!metadata2.isFile() || (metadata2.mode & 63) !== 0)
1525
+ throw new Error("Publisher database permissions must be 0600");
1526
+ await file.sync();
1527
+ } finally {
1528
+ await file.close();
1529
+ }
1530
+ const { DatabaseSync } = await import("node:sqlite");
1531
+ const database = new DatabaseSync(path);
1532
+ const token = randomBytes3(16).toString("hex");
1533
+ let closed = false;
1534
+ let inTransaction = false;
1535
+ const transaction = (work) => {
1536
+ if (closed)
1537
+ throw new Error("Publisher state is closed");
1538
+ if (inTransaction)
1539
+ return work();
1540
+ database.exec("BEGIN IMMEDIATE");
1541
+ inTransaction = true;
1542
+ try {
1543
+ const result = work();
1544
+ if (result instanceof Promise)
1545
+ throw new Error("Publisher state transactions must be synchronous");
1546
+ database.exec("COMMIT");
1547
+ return result;
1548
+ } catch (error) {
1549
+ database.exec("ROLLBACK");
1550
+ throw error;
1551
+ } finally {
1552
+ inTransaction = false;
1553
+ }
1554
+ };
1555
+ try {
1556
+ database.exec(`
1557
+ PRAGMA journal_mode = WAL;
1558
+ PRAGMA synchronous = FULL;
1559
+ PRAGMA busy_timeout = 5000;
1560
+ CREATE TABLE IF NOT EXISTS publisher_records (namespace TEXT NOT NULL, key TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY(namespace, key));
1561
+ CREATE TABLE IF NOT EXISTS publisher_owner (id INTEGER PRIMARY KEY CHECK(id = 1), host TEXT NOT NULL, pid INTEGER NOT NULL, token TEXT NOT NULL);
1562
+ `);
1563
+ transaction(() => {
1564
+ const prior = database.prepare("SELECT host, pid, token FROM publisher_owner WHERE id = 1").get();
1565
+ if (prior !== void 0 && (prior.host !== hostname() || processExists(prior.pid))) {
1566
+ throw new Error("Publisher database already has an active owner; a shared or multi-host database is not supported");
1567
+ }
1568
+ const binding = database.prepare("SELECT value FROM publisher_records WHERE namespace = ? AND key = ?").get("metadata", "identity");
1569
+ if (binding !== void 0 && decode(binding.value) !== options.identity)
1570
+ throw new Error("Publisher database identity mismatch");
1571
+ database.prepare("INSERT OR IGNORE INTO publisher_records(namespace,key,value) VALUES (?,?,?)").run("metadata", "identity", stringifyJson(options.identity));
1572
+ database.prepare("INSERT OR REPLACE INTO publisher_owner(id,host,pid,token) VALUES (1,?,?,?)").run(hostname(), process.pid, token);
1573
+ });
1574
+ } catch (error) {
1575
+ database.close();
1576
+ throw error;
1577
+ }
1578
+ const read = database.prepare("SELECT value FROM publisher_records WHERE namespace = ? AND key = ?");
1579
+ const list = database.prepare("SELECT key, value FROM publisher_records WHERE namespace = ? ORDER BY key");
1580
+ const write = database.prepare("INSERT INTO publisher_records(namespace,key,value) VALUES (?,?,?) ON CONFLICT(namespace,key) DO UPDATE SET value = excluded.value");
1581
+ const insert = database.prepare("INSERT OR IGNORE INTO publisher_records(namespace,key,value) VALUES (?,?,?)");
1582
+ const remove = database.prepare("DELETE FROM publisher_records WHERE namespace = ? AND key = ?");
1583
+ const assertOpen = () => {
1584
+ if (closed)
1585
+ throw new Error("Publisher state is closed");
1586
+ };
1587
+ return {
1588
+ path,
1589
+ get(namespace, key) {
1590
+ assertOpen();
1591
+ const row = read.get(namespace, key);
1592
+ return row === void 0 ? void 0 : decode(row.value);
1593
+ },
1594
+ entries(namespace) {
1595
+ assertOpen();
1596
+ return list.all(namespace).map((row) => ({ key: row.key, value: decode(row.value) }));
1597
+ },
1598
+ set(namespace, key, value) {
1599
+ assertOpen();
1600
+ write.run(namespace, key, stringifyJson(value));
1601
+ },
1602
+ insert(namespace, key, value) {
1603
+ assertOpen();
1604
+ return insert.run(namespace, key, stringifyJson(value)).changes === 1;
1605
+ },
1606
+ remove(namespace, key) {
1607
+ assertOpen();
1608
+ remove.run(namespace, key);
1609
+ },
1610
+ transaction,
1611
+ close() {
1612
+ if (closed)
1613
+ return;
1614
+ transaction(() => {
1615
+ database.prepare("DELETE FROM publisher_owner WHERE id = 1 AND token = ?").run(token);
1616
+ });
1617
+ database.close();
1618
+ closed = true;
1619
+ }
1620
+ };
1621
+ }
1622
+ export {
1623
+ boundedPublisherOperation,
1624
+ createAccountSourceWakeSigner,
1625
+ createBoundedPublisherEthersTransport,
1626
+ createBoundedPublisherFetch,
1627
+ createEthereumFinalizedPublisherSource,
1628
+ createPersistentEventPublisherRuntime,
1629
+ createPublisherHandshakeDiscovery,
1630
+ createPublisherHttpsTransport,
1631
+ createSqlitePublisherHandshakeStores,
1632
+ createUtcIntervalPublisherSource,
1633
+ createXl1PublisherSource,
1634
+ isPublicPublisherAddress,
1635
+ openPublisherDataLake,
1636
+ openPublisherRestGateway,
1637
+ openSqlitePublisherState,
1638
+ publisherSourceKey
1639
+ };
1640
+ //# sourceMappingURL=runtime.mjs.map