weida 0.0.0-stage → 0.1.0-alpha.14

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/lib/index.js ADDED
@@ -0,0 +1,3354 @@
1
+ // weida for JavaScript: the classes over the Node-API addon, one per C ABI
2
+ // handle (docs/libraries/weida-js.md). Every method that waits returns a
3
+ // Promise and takes an optional last `{ signal }`; `AbortSignal.timeout(ms)`
4
+ // is the timeout. A dropped or consumed object throws WEIDA_ECLOSED without
5
+ // calling C.
6
+ // A namespace import, so the module still loads on a runtime that lacks one
7
+ // of the exports; the conversions that need it check at use.
8
+ import * as nodeCrypto from "node:crypto";
9
+ import native from "./native.js";
10
+ import { WeidaError } from "./errors.js";
11
+ import { WAITS, call, cancelWaits, op } from "./wait.js";
12
+
13
+ export { WeidaError };
14
+
15
+ const HANDLE = Symbol("weida.handle");
16
+ const dispose = Symbol.dispose ?? Symbol.for("Symbol.dispose");
17
+ const encoder = new TextEncoder();
18
+
19
+ /** The library's version, e.g. "0.1.0-alpha.11-dev". */
20
+ export function version() {
21
+ return native.version();
22
+ }
23
+
24
+ /** The description of a status code, either sign; "unknown error" for one
25
+ * libweida does not define. */
26
+ export function strerror(code) {
27
+ return native.strerror(code);
28
+ }
29
+
30
+ /** A fresh root trace context as W3C `traceparent` text, for
31
+ * `TransferMeta.trace`: weida never starts a trace on its own. */
32
+ export function newTrace() {
33
+ return native.newTrace();
34
+ }
35
+
36
+ function bytes(data) {
37
+ if (typeof data === "string") {
38
+ return encoder.encode(data);
39
+ }
40
+ if (data instanceof Uint8Array) {
41
+ return data;
42
+ }
43
+ throw new TypeError("expected a Uint8Array or a string");
44
+ }
45
+
46
+ /** Whether `value` is an instance of `node:crypto`'s export `name`. */
47
+ function isCrypto(value, name) {
48
+ const Class = nodeCrypto[name];
49
+ return typeof Class === "function" && value instanceof Class;
50
+ }
51
+
52
+ /** An `X509Certificate` from PEM or DER. */
53
+ function x509(data) {
54
+ const X509 = nodeCrypto.X509Certificate;
55
+ if (typeof X509 !== "function") {
56
+ throw new TypeError("node:crypto has no X509Certificate on this runtime");
57
+ }
58
+ return new X509(data);
59
+ }
60
+
61
+ /** A certificate chain as PEM: X509Certificate(s), bytes or a string. */
62
+ function certPem(cert) {
63
+ if (isCrypto(cert, "X509Certificate")) {
64
+ return cert.toString();
65
+ }
66
+ if (Array.isArray(cert)) {
67
+ if (cert.length === 0) {
68
+ throw new TypeError("expected at least one certificate");
69
+ }
70
+ return cert
71
+ .map((c) => {
72
+ if (!isCrypto(c, "X509Certificate")) {
73
+ throw new TypeError("expected an array of X509Certificate");
74
+ }
75
+ return c.toString();
76
+ })
77
+ .join("\n");
78
+ }
79
+ return bytes(cert);
80
+ }
81
+
82
+ /** A private key as PEM: a private KeyObject (exported as PKCS#8), bytes or
83
+ * a string. */
84
+ function keyPem(key) {
85
+ if (isCrypto(key, "KeyObject")) {
86
+ if (key.type !== "private") {
87
+ throw new TypeError("expected a private KeyObject");
88
+ }
89
+ return key.export({ type: "pkcs8", format: "pem" });
90
+ }
91
+ return bytes(key);
92
+ }
93
+
94
+ /** The certificate blocks of a PEM text. */
95
+ function pemBlocks(text) {
96
+ return text.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----\n?/g) ?? [];
97
+ }
98
+
99
+ function handleOf(value, Class) {
100
+ if (!(value instanceof Class)) {
101
+ throw new TypeError(`expected a ${Class.name}`);
102
+ }
103
+ return value[HANDLE];
104
+ }
105
+
106
+ function init(object, handle) {
107
+ object[HANDLE] = handle;
108
+ object[WAITS] = new Set();
109
+ return object;
110
+ }
111
+
112
+ /** An object of a class without a public constructor, around `handle`. */
113
+ function adopt(Class, handle) {
114
+ return init(Object.create(Class.prototype), handle);
115
+ }
116
+
117
+ /** The handle of `object` for a consuming call (R6), its waits ended. */
118
+ function consume(object) {
119
+ cancelWaits(object);
120
+ return object[HANDLE];
121
+ }
122
+
123
+ function noConstructor(name, how) {
124
+ throw new TypeError(`${name} has no public constructor; ${how} makes one`);
125
+ }
126
+
127
+ class Handle {
128
+ /** Releases the handle now, ending its pending waits with WEIDA_ECLOSED. */
129
+ drop() {
130
+ cancelWaits(this);
131
+ native.drop(this[HANDLE]);
132
+ }
133
+
134
+ [dispose]() {
135
+ this.drop();
136
+ }
137
+ }
138
+
139
+ /** Freezes metadata, the presented chain mapped to `X509Certificate`s. */
140
+ function freezeMeta(meta) {
141
+ if (meta.peerChain != null) {
142
+ meta.peerChain = Object.freeze(meta.peerChain.map((der) => x509(der)));
143
+ }
144
+ if (meta.peerLocal != null) Object.freeze(meta.peerLocal);
145
+ if (meta.gap != null) Object.freeze(meta.gap);
146
+ Object.freeze(meta.report);
147
+ return Object.freeze(meta);
148
+ }
149
+
150
+ /** Freezes one connection record and the objects inside it. */
151
+ function freezeStats(record) {
152
+ if (record.transport !== null) {
153
+ Object.freeze(record.transport.path);
154
+ Object.freeze(record.transport.tx);
155
+ Object.freeze(record.transport.rx);
156
+ Object.freeze(record.transport);
157
+ }
158
+ if (record.remote !== null) {
159
+ Object.freeze(record.remote.tx);
160
+ Object.freeze(record.remote.rx);
161
+ Object.freeze(record.remote);
162
+ }
163
+ return Object.freeze(record);
164
+ }
165
+
166
+ /** The value of the weida.h constant `name`, which must start with
167
+ * `prefix`: enumerations travel as the constant names. */
168
+ function constant(prefix, name) {
169
+ const value =
170
+ typeof name === "string" && name.startsWith(prefix) ? native.CONSTANTS[name] : undefined;
171
+ if (value === undefined) {
172
+ const names = Object.keys(native.CONSTANTS).filter((k) => k.startsWith(prefix));
173
+ throw new TypeError(`expected one of ${names.join(", ")}`);
174
+ }
175
+ return value;
176
+ }
177
+
178
+ /** Throws for an own key of `object` that is not in `known`. */
179
+ function checkKeys(object, known, what) {
180
+ for (const key of Object.keys(object)) {
181
+ if (!known.includes(key)) {
182
+ throw new TypeError(`unknown ${what} field: ${key}`);
183
+ }
184
+ }
185
+ }
186
+
187
+ // Field tables: [jsKey, setter, kind], the kind "value" (passed as is) or
188
+ // the prefix of an enumeration's constants.
189
+ const CONFIG_FIELDS = [
190
+ ["workerThreads", native.runtimeConfigSetWorkerThreads, "value"],
191
+ ["shutdownTimeoutMs", native.runtimeConfigSetShutdownTimeoutMs, "value"],
192
+ ["maxConnections", native.runtimeConfigSetMaxConnections, "value"],
193
+ ["maxConnectionsPerPeer", native.runtimeConfigSetMaxConnectionsPerPeer, "value"],
194
+ ["endpointQueue", native.runtimeConfigSetEndpointQueue, "value"],
195
+ ["maxResolvedAddresses", native.runtimeConfigSetMaxResolvedAddresses, "value"],
196
+ ["connectAttemptTimeoutMs", native.runtimeConfigSetConnectAttemptTimeoutMs, "value"],
197
+ ["sendTimeoutMs", native.runtimeConfigSetSendTimeoutMs, "value"],
198
+ ["outboxMessages", native.runtimeConfigSetOutboxMessages, "value"],
199
+ ["outboxBytes", native.runtimeConfigSetOutboxBytes, "value"],
200
+ ["maxHandshakes", native.runtimeConfigSetMaxHandshakes, "value"],
201
+ ["retryAboveHandshakes", native.runtimeConfigSetRetryAboveHandshakes, "value"],
202
+ ["outboxFull", native.runtimeConfigSetOutboxFull, "WEIDA_OUTBOX_FULL_"],
203
+ ["discovery", native.runtimeConfigSetDiscovery, "WEIDA_DISCOVERY_"],
204
+ ];
205
+
206
+ const LIMITS_FIELDS = [
207
+ ["maxHeaderBytes", native.limitsSetMaxHeaderBytes, "value"],
208
+ ["maxConcurrentUniStreams", native.limitsSetMaxConcurrentUniStreams, "value"],
209
+ ["maxConcurrentBidiStreams", native.limitsSetMaxConcurrentBidiStreams, "value"],
210
+ ["streamReceiveWindow", native.limitsSetStreamReceiveWindow, "value"],
211
+ ["connectionReceiveWindow", native.limitsSetConnectionReceiveWindow, "value"],
212
+ ["keepAliveMs", native.limitsSetKeepAliveMs, "value"],
213
+ ["idleTimeoutMs", native.limitsSetIdleTimeoutMs, "value"],
214
+ ["helloTimeoutMs", native.limitsSetHelloTimeoutMs, "value"],
215
+ ["maxSubscriptions", native.limitsSetMaxSubscriptions, "value"],
216
+ ["subscriberBufferBytes", native.limitsSetSubscriberBufferBytes, "value"],
217
+ ["maxSequenceScopes", native.limitsSetMaxSequenceScopes, "value"],
218
+ ["maxReorderHold", native.limitsSetMaxReorderHold, "value"],
219
+ ["maxLocalStreams", native.limitsSetMaxLocalStreams, "value"],
220
+ ["maxParkedReverse", native.limitsSetMaxParkedReverse, "value"],
221
+ ["maxDedupEntries", native.limitsSetMaxDedupEntries, "value"],
222
+ ["datagramReceiveBytes", native.limitsSetDatagramReceiveBytes, "value"],
223
+ ["datagramSendBytes", native.limitsSetDatagramSendBytes, "value"],
224
+ ["pathReport", native.limitsSetPathReport, "value"],
225
+ ["maxFlows", native.limitsSetMaxFlows, "value"],
226
+ ["flowQueueBytes", native.limitsSetFlowQueueBytes, "value"],
227
+ ["flowEarlyBytes", native.limitsSetFlowEarlyBytes, "value"],
228
+ ["flowEarlyHoldMs", native.limitsSetFlowEarlyHoldMs, "value"],
229
+ ["congestion", native.limitsSetCongestion, "WEIDA_CONGESTION_"],
230
+ ["headerTimeoutMs", native.limitsSetHeaderTimeoutMs, "value"],
231
+ ["maxHeaderStreams", native.limitsSetMaxHeaderStreams, "value"],
232
+ ];
233
+
234
+ const GUARANTEE_FIELDS = [
235
+ ["delivery", native.guaranteeSetSetDelivery, "WEIDA_DELIVERY_LEVEL_"],
236
+ ["acknowledgement", native.guaranteeSetSetAcknowledgement, "WEIDA_ACKNOWLEDGEMENT_"],
237
+ ["durability", native.guaranteeSetSetDurability, "WEIDA_DURABILITY_"],
238
+ ["replicas", native.guaranteeSetSetReplicas, "value"],
239
+ ["ordering", native.guaranteeSetSetOrdering, "WEIDA_ORDERING_MODE_"],
240
+ ["deduplication", native.guaranteeSetSetDeduplication, "WEIDA_DEDUPLICATION_"],
241
+ ["dedupWindowMs", native.guaranteeSetSetDedupWindowMs, "value"],
242
+ ["backpressure", native.guaranteeSetSetBackpressure, "WEIDA_BACKPRESSURE_"],
243
+ ["producerNaming", native.guaranteeSetSetProducerNaming, "WEIDA_PRODUCER_NAMING_"],
244
+ ["controlIsolated", native.guaranteeSetSetControlIsolated, "value"],
245
+ ];
246
+
247
+ const RECONNECT_FIELDS = [
248
+ ["initialMs", native.reconnectPolicySetInitialMs, "value"],
249
+ ["maxMs", native.reconnectPolicySetMaxMs, "value"],
250
+ ["jitter", native.reconnectPolicySetJitter, "value"],
251
+ ["maxAttempts", native.reconnectPolicySetMaxAttempts, "value"],
252
+ ["stopOnPeerClosed", native.reconnectPolicySetStopOnPeerClosed, "value"],
253
+ ];
254
+
255
+ const NESTED = ["limits", "guarantees", "reconnect"];
256
+ /** The keys besides the fields: the nested objects and the resolver. */
257
+ const EXTRA = [...NESTED, "resolver"];
258
+ const CONFIG_KEYS = [...CONFIG_FIELDS.map(([key]) => key), ...EXTRA];
259
+
260
+ /** Checks `object`'s keys against `fields` (and `extra`), then calls the
261
+ * setter of each field that is neither null nor undefined. */
262
+ function applyFields(handle, object, fields, what, extra = []) {
263
+ checkKeys(object, [...fields.map(([key]) => key), ...extra], what);
264
+ for (const [key, setter, kind] of fields) {
265
+ const value = object[key];
266
+ if (value != null) {
267
+ setter(handle, kind === "value" ? value : constant(kind, value));
268
+ }
269
+ }
270
+ }
271
+
272
+ /** Builds a nested configuration handle from `object`, hands it to `use`
273
+ * (the parent's setter, which copies it) and drops it. */
274
+ function nested(object, make, fields, what, use, extra) {
275
+ const handle = make();
276
+ try {
277
+ applyFields(handle, object, fields, what, extra);
278
+ use(handle);
279
+ } finally {
280
+ native.drop(handle);
281
+ }
282
+ }
283
+
284
+ const META_FIELDS = [
285
+ ["contentType", native.transferMetaSetContentType, "value"],
286
+ ["contentLen", native.transferMetaSetContentLen, "value"],
287
+ ["trace", native.transferMetaSetTrace, "value"],
288
+ ["topic", native.transferMetaSetTopic, "value"],
289
+ ["achieved", native.transferMetaSetAchieved, "WEIDA_ACKNOWLEDGEMENT_"],
290
+ ["report", reportLevels, "value"],
291
+ ["reportMode", native.transferMetaSetReportMode, "WEIDA_REPORT_MODE_"],
292
+ ];
293
+
294
+ /** A terms object as a handle for one call: `make()` the handle, set
295
+ * `fields` of `object`, `use` it and drop it; null `object` is `use(null)`. */
296
+ function withFields(object, make, fields, what, use) {
297
+ if (object == null) {
298
+ return use(null);
299
+ }
300
+ const handle = make();
301
+ try {
302
+ applyFields(handle, object, fields, what);
303
+ return use(handle);
304
+ } finally {
305
+ native.drop(handle);
306
+ }
307
+ }
308
+
309
+ /** A transfer meta `{ contentType?, contentLen?, trace?, topic?,
310
+ * achieved?, report?, reportMode? }` as a handle for one call. */
311
+ function withTransferMeta(meta, use) {
312
+ return withFields(meta, native.transferMetaNew, META_FIELDS, "TransferMeta", use);
313
+ }
314
+
315
+ const JOIN_FIELDS = [
316
+ ["maxAgeMs", native.joinTermsSetMaxAgeMs, "value"],
317
+ ["maxLayer", native.joinTermsSetMaxLayer, "value"],
318
+ ];
319
+
320
+ const SEGMENT_FIELDS = [
321
+ ["maxAgeMs", native.segmentTermsSetMaxAgeMs, "value"],
322
+ ["priority", native.segmentTermsSetPriority, "value"],
323
+ ["followsUpstream", native.segmentTermsSetFollowsUpstream, "value"],
324
+ ];
325
+
326
+ const FETCH_FIELDS = [
327
+ ["list", (h, list) => native.fetchOptionsSetList(h, handleOf(list, PieceList)), "value"],
328
+ ["stallMs", native.fetchOptionsSetStallMs, "value"],
329
+ ["minRate", native.fetchOptionsSetMinRate, "value"],
330
+ ["perPeer", native.fetchOptionsSetPerPeer, "value"],
331
+ ["maxInflight", native.fetchOptionsSetMaxInflight, "value"],
332
+ ["maxPeers", native.fetchOptionsSetMaxPeers, "value"],
333
+ ["haveRefreshMs", native.fetchOptionsSetHaveRefreshMs, "value"],
334
+ ];
335
+
336
+ /** How many layers a segment carries: 0 to 15. */
337
+ export const MAX_SEGMENT_LAYERS = native.CONSTANTS.WEIDA_MAX_SEGMENT_LAYERS;
338
+
339
+ /** The first cursor level an application may name; the levels below it are
340
+ * weida's own, named "WEIDA_ACKNOWLEDGEMENT_*". */
341
+ export const CURSOR_LEVEL_APPLICATION_FLOOR =
342
+ native.CONSTANTS.WEIDA_CURSOR_LEVEL_APPLICATION_FLOOR;
343
+
344
+ /** The `datagramReceiveBytes` a profile with flows uses. */
345
+ export const DEFAULT_DATAGRAM_RECEIVE_BYTES =
346
+ native.CONSTANTS.WEIDA_DEFAULT_DATAGRAM_RECEIVE_BYTES;
347
+
348
+ /** The bytes of a blob id: a BLAKE3 hash. */
349
+ export const BLOB_ID_LEN = native.CONSTANTS.WEIDA_BLOB_ID_LEN;
350
+
351
+ /** The most pieces a blob has. */
352
+ export const MAX_PIECES = native.CONSTANTS.WEIDA_MAX_PIECES;
353
+
354
+ /** The smallest piece size as its base-2 logarithm: 1 KiB. */
355
+ export const PIECE_SIZE_MIN_LOG2 = native.CONSTANTS.WEIDA_PIECE_SIZE_MIN_LOG2;
356
+
357
+ /** The largest piece size as its base-2 logarithm: 16 MiB. */
358
+ export const PIECE_SIZE_MAX_LOG2 = native.CONSTANTS.WEIDA_PIECE_SIZE_MAX_LOG2;
359
+
360
+ /** The default piece size as its base-2 logarithm: 1 MiB. */
361
+ export const PIECE_SIZE_DEFAULT_LOG2 = native.CONSTANTS.WEIDA_PIECE_SIZE_DEFAULT_LOG2;
362
+
363
+ /** The id of `data` (a string is hashed as UTF-8): its BLAKE3 hash,
364
+ * `BLOB_ID_LEN` bytes. */
365
+ export function blobIdOf(data) {
366
+ return native.blobIdOf(bytes(data));
367
+ }
368
+
369
+ const FLOW_META_FIELDS = [
370
+ ["contentType", native.flowMetaSetContentType, "value"],
371
+ ["trace", native.flowMetaSetTrace, "value"],
372
+ ["topic", native.flowMetaSetTopic, "value"],
373
+ ];
374
+
375
+ /** A cursor level in: a "WEIDA_ACKNOWLEDGEMENT_*" name becomes its value,
376
+ * an application stage (a number or a BigInt) passes on; C refuses 6 to
377
+ * 15. */
378
+ function cursorLevel(level) {
379
+ return typeof level === "string" ? constant("WEIDA_ACKNOWLEDGEMENT_", level) : level;
380
+ }
381
+
382
+ /** `TransferMeta.report`: an array of cursor levels. */
383
+ function reportLevels(handle, levels) {
384
+ if (!Array.isArray(levels)) {
385
+ throw new TypeError("expected an array of cursor levels");
386
+ }
387
+ native.transferMetaSetReport(handle, levels.map(cursorLevel));
388
+ }
389
+
390
+ const FILES_FIELDS = [
391
+ ["names", native.filesOptionsSetNames, "value"],
392
+ ["pollMs", native.filesOptionsSetPollMs, "value"],
393
+ ];
394
+
395
+ /** The `WEIDA_ALPN_*` value of `alpn`, or null when it is not given. */
396
+ function tlsAlpn(alpn) {
397
+ return alpn == null ? null : constant("WEIDA_ALPN_", alpn);
398
+ }
399
+
400
+ /** Checks that `names` is an array of strings. */
401
+ function checkNames(names) {
402
+ if (!Array.isArray(names) || !names.every((n) => typeof n === "string")) {
403
+ throw new TypeError("expected an array of names");
404
+ }
405
+ return names;
406
+ }
407
+
408
+ /** `RuntimeConfig`: the fields set here, weida's defaults for the rest. */
409
+ export class RuntimeConfig {
410
+ constructor(fields = {}) {
411
+ checkKeys(fields, CONFIG_KEYS, "RuntimeConfig");
412
+ for (const key of CONFIG_KEYS) {
413
+ if (fields[key] !== undefined) {
414
+ this[key] = fields[key];
415
+ }
416
+ }
417
+ }
418
+ }
419
+
420
+ function configHandle(config, resolver) {
421
+ if (config == null) {
422
+ return null;
423
+ }
424
+ const handle = native.runtimeConfigNew();
425
+ try {
426
+ applyFields(handle, config, CONFIG_FIELDS, "RuntimeConfig", EXTRA);
427
+ if (config.limits != null) {
428
+ nested(config.limits, native.limitsNew, LIMITS_FIELDS, "limits", (h) =>
429
+ native.runtimeConfigSetLimits(handle, h),
430
+ );
431
+ }
432
+ if (config.guarantees != null) {
433
+ nested(config.guarantees, native.guaranteeSetNew, GUARANTEE_FIELDS, "guarantees", (h) =>
434
+ native.runtimeConfigSetGuarantees(handle, h),
435
+ );
436
+ }
437
+ if (config.reconnect != null) {
438
+ const make =
439
+ config.reconnect.never === true ? native.reconnectPolicyNever : native.reconnectPolicyNew;
440
+ nested(
441
+ config.reconnect,
442
+ make,
443
+ RECONNECT_FIELDS,
444
+ "reconnect",
445
+ (h) => native.runtimeConfigSetReconnect(handle, h),
446
+ ["never"],
447
+ );
448
+ }
449
+ if (resolver !== null) {
450
+ native.runtimeConfigSetResolver(handle, resolver);
451
+ }
452
+ } catch (error) {
453
+ native.drop(handle);
454
+ throw error;
455
+ }
456
+ return handle;
457
+ }
458
+
459
+ /** Answers one lookup through `resolve`; never rejects. A throw, a
460
+ * rejection or an answer that is not an array of strings refuses it. */
461
+ async function answerLookup(handle, resolve) {
462
+ try {
463
+ const lookup = Object.freeze({
464
+ name: native.resolvedName(handle),
465
+ port: native.resolvedPort(handle),
466
+ maxAddresses: native.resolvedMaxAddresses(handle),
467
+ });
468
+ let addresses;
469
+ try {
470
+ addresses = await resolve(lookup);
471
+ } catch (error) {
472
+ native.resolvedRefuse(handle, error instanceof Error ? error.message : String(error));
473
+ return;
474
+ }
475
+ if (!Array.isArray(addresses) || !addresses.every((a) => typeof a === "string")) {
476
+ native.resolvedRefuse(handle, "the resolver did not answer an array of address strings");
477
+ return;
478
+ }
479
+ native.resolvedAnswer(handle, addresses);
480
+ } catch {
481
+ // A spent lookup drops as a no-op; an unspent one fails its dial as
482
+ // dropped unanswered.
483
+ native.drop(handle);
484
+ }
485
+ }
486
+
487
+ /** Reads `resolver`'s lookups until no configuration or runtime holds it,
488
+ * answering them concurrently. Its watch is weak: it never keeps the
489
+ * process alive. */
490
+ async function serveLookups(resolver, resolve) {
491
+ const owner = { [WAITS]: new Set() };
492
+ try {
493
+ for (;;) {
494
+ const handle = await call(
495
+ owner,
496
+ () => native.resolverRecv(resolver),
497
+ () => native.resolverFd(resolver),
498
+ undefined,
499
+ true,
500
+ );
501
+ if (handle === null) {
502
+ break;
503
+ }
504
+ void answerLookup(handle, resolve);
505
+ }
506
+ } catch {
507
+ // A descriptor that cannot be made ends the service; the lookups after
508
+ // it fail their dial with WEIDA_EINVALID_ADDRESS.
509
+ } finally {
510
+ native.drop(resolver);
511
+ }
512
+ }
513
+
514
+ /** A weida runtime: the reactor it owns, and every endpoint made from it. */
515
+ export class Runtime extends Handle {
516
+ constructor() {
517
+ super();
518
+ noConstructor("Runtime", "Runtime.owned()");
519
+ }
520
+
521
+ /** A runtime on a reactor of its own.
522
+ *
523
+ * A `resolver` in `config` answers each lookup of this runtime's dials on
524
+ * the JS thread: it is called with a frozen `{ name, port, maxAddresses }`
525
+ * and answers `"ip:port"` strings in dial order; a throw refuses the
526
+ * lookup, and the dial rejects with `WEIDA_EINVALID_ADDRESS` carrying the
527
+ * message. It is served until the runtime and everything made from it are
528
+ * gone, and never keeps the process alive. */
529
+ static owned(config) {
530
+ const resolve = config?.resolver ?? null;
531
+ if (resolve !== null && typeof resolve !== "function") {
532
+ throw new TypeError("resolver is a function (lookup) => addresses");
533
+ }
534
+ const resolver = resolve === null ? null : native.resolverNew();
535
+ let cfg = null;
536
+ try {
537
+ cfg = configHandle(config, resolver);
538
+ const runtime = adopt(Runtime, native.runtimeOwned(cfg));
539
+ if (resolver !== null) {
540
+ void serveLookups(resolver, resolve);
541
+ }
542
+ return runtime;
543
+ } catch (error) {
544
+ if (resolver !== null) {
545
+ native.drop(resolver);
546
+ }
547
+ throw error;
548
+ } finally {
549
+ if (cfg !== null) {
550
+ native.drop(cfg);
551
+ }
552
+ }
553
+ }
554
+
555
+ listener() {
556
+ return adopt(Listener, native.runtimeListener(this[HANDLE]));
557
+ }
558
+
559
+ requester(clientTls) {
560
+ return adopt(Requester, native.runtimeRequester(this[HANDLE], handleOf(clientTls, ClientTls)));
561
+ }
562
+
563
+ pusher(clientTls) {
564
+ return adopt(Pusher, native.runtimePusher(this[HANDLE], handleOf(clientTls, ClientTls)));
565
+ }
566
+
567
+ /** A subscriber dialling on `clientTls`. Two subscribers on one runtime
568
+ * share its pooled connection and collide on the path
569
+ * (WEIDA_EALREADY_REGISTERED): use one runtime per subscriber of a path. */
570
+ subscriber(clientTls) {
571
+ return adopt(Subscriber, native.runtimeSubscriber(this[HANDLE], handleOf(clientTls, ClientTls)));
572
+ }
573
+
574
+ /** A dialling pair on `clientTls`: one peer, dialled with `connect`. */
575
+ pair(clientTls) {
576
+ return adopt(Paired, native.runtimePair(this[HANDLE], handleOf(clientTls, ClientTls)));
577
+ }
578
+
579
+ /** A surveyor dialling respondents on `clientTls`. */
580
+ surveyor(clientTls) {
581
+ return adopt(Surveyor, native.runtimeSurveyor(this[HANDLE], handleOf(clientTls, ClientTls)));
582
+ }
583
+
584
+ /** A dish dialling radios on `clientTls`. Two dishes on one runtime
585
+ * share its pooled connection and collide on the path
586
+ * (WEIDA_EALREADY_REGISTERED): use one runtime per dish. */
587
+ dish(clientTls) {
588
+ return adopt(Dish, native.runtimeDish(this[HANDLE], handleOf(clientTls, ClientTls)));
589
+ }
590
+
591
+ /** A raw peer dialling on `clientTls`, which opens datagram flows. */
592
+ peer(clientTls) {
593
+ return adopt(Peer, native.runtimePeer(this[HANDLE], handleOf(clientTls, ClientTls)));
594
+ }
595
+
596
+ /** A swarm dialling seeders on `clientTls`: it fetches one blob from
597
+ * every seeder it connected at once. */
598
+ swarm(clientTls) {
599
+ return adopt(Swarm, native.runtimeSwarm(this[HANDLE], handleOf(clientTls, ClientTls)));
600
+ }
601
+
602
+ /** Duplicates this runtime's connections discarded inside a negotiated
603
+ * deduplication window, a BigInt; 0n without deduplication. */
604
+ suppressedDuplicates() {
605
+ return native.runtimeSuppressedDuplicates(this[HANDLE]);
606
+ }
607
+
608
+ /** Outgoing transfers this runtime reset because their deadline passed,
609
+ * a BigInt. */
610
+ expiredTransfers() {
611
+ return native.runtimeExpiredTransfers(this[HANDLE]);
612
+ }
613
+
614
+ /** Closes every endpoint and waits for the connections to go idle.
615
+ * Consumes the runtime. */
616
+ async shutdown({ signal } = {}) {
617
+ await op(
618
+ this,
619
+ () => native.runtimeShutdown(consume(this)),
620
+ native.runtimeShutdownOpWait,
621
+ native.runtimeShutdownOpFd,
622
+ signal,
623
+ );
624
+ }
625
+
626
+ /** Stops admitting work, gives every finished transfer whose receipt
627
+ * nobody holds until `deadlineMs` to reach the peer's transport, then
628
+ * closes as `shutdown` does within what is left of it. Resolves to a
629
+ * frozen `{ delivered, outstanding }` of BigInts; a non-zero
630
+ * `outstanding` is not an error. Aborting cancels the drain where it is.
631
+ * Consumes the runtime. */
632
+ async drain(deadlineMs, { signal } = {}) {
633
+ const drained = await op(
634
+ this,
635
+ () => native.runtimeDrain(consume(this), deadlineMs),
636
+ native.runtimeDrainOpWait,
637
+ native.runtimeDrainOpFd,
638
+ signal,
639
+ );
640
+ return Object.freeze(drained);
641
+ }
642
+ }
643
+
644
+ /** Whom a dialler trusts: what each address names, plus pins. */
645
+ export class Trust extends Handle {
646
+ constructor() {
647
+ super();
648
+ init(this, native.trustNew());
649
+ }
650
+
651
+ /** Pins a `sha256:` fingerprint; returns this trust. */
652
+ addPin(fingerprint) {
653
+ native.trustAddPin(this[HANDLE], fingerprint);
654
+ return this;
655
+ }
656
+
657
+ /** Trusts the certificate authorities in `anchor`, an `X509Certificate`
658
+ * or PEM bytes, for a peer whose chain leads to one and names the dialled
659
+ * host; returns this trust. */
660
+ addAnchor(anchor) {
661
+ const pem = isCrypto(anchor, "X509Certificate") ? anchor.toString() : anchor;
662
+ native.trustAddAnchor(this[HANDLE], bytes(pem));
663
+ return this;
664
+ }
665
+
666
+ /** Trusts the certificate authorities in a PEM file, read at each dial;
667
+ * returns this trust. */
668
+ addAnchorFile(path) {
669
+ native.trustAddAnchorFile(this[HANDLE], path);
670
+ return this;
671
+ }
672
+
673
+ /** Whether the trust holds no pin and no anchor, so it accepts only what
674
+ * each dialled address names. */
675
+ isEmpty() {
676
+ return native.trustIsEmpty(this[HANDLE]);
677
+ }
678
+ }
679
+
680
+ /** A certificate chain and its private key. Conversions from Node's key
681
+ * types follow docs/decisions/0047. */
682
+ export class Identity extends Handle {
683
+ constructor() {
684
+ super();
685
+ noConstructor("Identity", "Identity.generate(), Identity.from() or Identity.fromPem()");
686
+ }
687
+
688
+ /** A self-signed identity with a fresh key, for pinning. */
689
+ static generate() {
690
+ return adopt(Identity, native.identityGenerate());
691
+ }
692
+
693
+ /** A self-signed identity whose certificate names `names` (DNS names or IP
694
+ * literals), so a peer anchoring it can check the host it dialled. */
695
+ static generateFor(names) {
696
+ return adopt(Identity, native.identityGenerateFor(checkNames(names)));
697
+ }
698
+
699
+ /** An identity from PEM: the chain, leaf first, and the key. */
700
+ static fromPem(cert, key) {
701
+ return adopt(Identity, native.identityFromPem(bytes(cert), bytes(key)));
702
+ }
703
+
704
+ /** An identity from Node's types or PEM: `cert` is an `X509Certificate`,
705
+ * an array of them (leaf first) or PEM bytes; `key` a private `KeyObject`
706
+ * or PEM bytes. */
707
+ static from({ cert, key } = {}) {
708
+ if (cert == null || key == null) {
709
+ throw new TypeError("Identity.from needs { cert, key }");
710
+ }
711
+ return Identity.fromPem(certPem(cert), keyPem(key));
712
+ }
713
+
714
+ /** An identity from one PEM file holding the chain and the key, the form
715
+ * `toPem()` writes. The file is read at first use. */
716
+ static fromPemFile(path) {
717
+ return adopt(Identity, native.identityFromPemFile(path));
718
+ }
719
+
720
+ /** An identity from a chain file and a key file, read at first use. */
721
+ static fromPemFiles(cert, key) {
722
+ return adopt(Identity, native.identityFromPemFiles(cert, key));
723
+ }
724
+
725
+ /** The public-key fingerprint, `sha256:` text. */
726
+ fingerprint() {
727
+ return native.identityFingerprint(this[HANDLE]);
728
+ }
729
+
730
+ /** The certificate chain as PEM, without the key. */
731
+ certificatePem() {
732
+ return native.identityCertificatePem(this[HANDLE]);
733
+ }
734
+
735
+ /** The chain and the private key as one PEM document. Holds the private
736
+ * key; wipe it with `fill(0)` when done. */
737
+ toPem() {
738
+ return native.identityToPem(this[HANDLE]);
739
+ }
740
+
741
+ /** The certificate chain, leaf first. */
742
+ certificates() {
743
+ return pemBlocks(this.certificatePem()).map((block) => x509(block));
744
+ }
745
+ }
746
+
747
+ /** The TLS terms a requester dials on, under `trust` (a `Trust`, copied, or
748
+ * a `TrustSource`, shared): anonymous, or presenting `identity` (an
749
+ * `Identity`, copied, or an `IdentitySource`, shared). `alpn` is the
750
+ * `WEIDA_ALPN_*` token to ask for, `WEIDA_ALPN_WEIDA` unless given. */
751
+ export class ClientTls extends Handle {
752
+ constructor(trust, { identity, alpn } = {}) {
753
+ super();
754
+ let make;
755
+ if (trust instanceof TrustSource) {
756
+ make = () => native.clientTlsNewSource(trust[HANDLE]);
757
+ } else if (trust instanceof Trust) {
758
+ make = () => native.clientTlsNew(trust[HANDLE]);
759
+ } else {
760
+ throw new TypeError("expected a Trust or a TrustSource");
761
+ }
762
+ if (!(identity == null || identity instanceof Identity || identity instanceof IdentitySource)) {
763
+ throw new TypeError("identity is an Identity or an IdentitySource");
764
+ }
765
+ const token = tlsAlpn(alpn);
766
+ const handle = make();
767
+ try {
768
+ if (identity instanceof IdentitySource) {
769
+ native.clientTlsSetIdentitySource(handle, identity[HANDLE]);
770
+ } else if (identity != null) {
771
+ native.clientTlsSetIdentity(handle, identity[HANDLE]);
772
+ }
773
+ if (token !== null) {
774
+ native.clientTlsSetAlpn(handle, token);
775
+ }
776
+ } catch (error) {
777
+ native.drop(handle);
778
+ throw error;
779
+ }
780
+ init(this, handle);
781
+ }
782
+ }
783
+
784
+ /** The TLS terms a QUIC binding serves on, presenting `identity` (an
785
+ * `Identity`, copied, or an `IdentitySource`, shared, whose renewals reach
786
+ * the next peer with no re-bind). With `requireClient` every client must
787
+ * present a key: one the `Trust` or `TrustSource` accepts, or with
788
+ * "any-key" any key it proves it holds. `alpn` is the `WEIDA_ALPN_*` token
789
+ * to offer, `WEIDA_ALPN_WEIDA` unless given. */
790
+ export class ServerTls extends Handle {
791
+ constructor(identity, { requireClient, alpn } = {}) {
792
+ super();
793
+ let make;
794
+ if (identity instanceof IdentitySource) {
795
+ make = () => native.serverTlsNewSource(identity[HANDLE]);
796
+ } else if (identity instanceof Identity) {
797
+ make = () => native.serverTlsNew(identity[HANDLE]);
798
+ } else {
799
+ throw new TypeError("expected an Identity or an IdentitySource");
800
+ }
801
+ if (
802
+ !(
803
+ requireClient == null ||
804
+ requireClient === "any-key" ||
805
+ requireClient instanceof Trust ||
806
+ requireClient instanceof TrustSource
807
+ )
808
+ ) {
809
+ throw new TypeError('requireClient is a Trust, a TrustSource or "any-key"');
810
+ }
811
+ const token = tlsAlpn(alpn);
812
+ const handle = make();
813
+ try {
814
+ if (requireClient === "any-key") {
815
+ native.serverTlsRequireAnyKey(handle);
816
+ } else if (requireClient instanceof TrustSource) {
817
+ native.serverTlsRequireClientSource(handle, requireClient[HANDLE]);
818
+ } else if (requireClient != null) {
819
+ native.serverTlsRequireClient(handle, requireClient[HANDLE]);
820
+ }
821
+ if (token !== null) {
822
+ native.serverTlsSetAlpn(handle, token);
823
+ }
824
+ } catch (error) {
825
+ native.drop(handle);
826
+ throw error;
827
+ }
828
+ init(this, handle);
829
+ }
830
+ }
831
+
832
+ /** Where an identity comes from: a value, an ephemeral identity, one driven
833
+ * from outside, or a directory an agent rewrites. A source is shared by the
834
+ * TLS terms made from it, so an update reaches their next handshake;
835
+ * dropping this handle leaves the source alive wherever it is held. */
836
+ export class IdentitySource extends Handle {
837
+ constructor() {
838
+ super();
839
+ noConstructor(
840
+ "IdentitySource",
841
+ "IdentitySource.fromIdentity(), .ephemeral(), .external() or .files()",
842
+ );
843
+ }
844
+
845
+ /** A source that holds `identity` and never changes; throws WEIDA_ETLS
846
+ * when its material does not read. */
847
+ static fromIdentity(identity) {
848
+ return adopt(IdentitySource, native.identitySourceFromIdentity(handleOf(identity, Identity)));
849
+ }
850
+
851
+ /** A self-signed identity generated in memory for `names`. */
852
+ static ephemeral(names = []) {
853
+ return adopt(IdentitySource, native.identitySourceEphemeral(checkNames(names)));
854
+ }
855
+
856
+ /** A source that starts as `identity` and changes on `update()`. */
857
+ static external(identity) {
858
+ return adopt(IdentitySource, native.identitySourceExternal(handleOf(identity, Identity)));
859
+ }
860
+
861
+ /** The directory `dir` holding `cert.pem` and `key.pem`, bootstrapped
862
+ * with a self-signed identity for `names` when empty and reloaded when
863
+ * the files change, checked at the next use after `pollMs`. */
864
+ static files(dir, options = {}) {
865
+ let handle;
866
+ nested(options ?? {}, native.filesOptionsNew, FILES_FIELDS, "FilesOptions", (h) => {
867
+ handle = native.identitySourceFiles(dir, h);
868
+ });
869
+ return adopt(IdentitySource, handle);
870
+ }
871
+
872
+ /** The identity of the moment. */
873
+ current() {
874
+ return adopt(Identity, native.identitySourceCurrent(this[HANDLE]));
875
+ }
876
+
877
+ /** The current key's fingerprint, `sha256:` text. */
878
+ fingerprint() {
879
+ return native.identitySourceFingerprint(this[HANDLE]);
880
+ }
881
+
882
+ /** Replaces the identity: RENEWED when the key is unchanged, KEY_CHANGED
883
+ * when it is not. */
884
+ update(identity) {
885
+ native.identitySourceUpdate(this[HANDLE], handleOf(identity, Identity));
886
+ }
887
+
888
+ /** Reports a renewal that failed without material (RENEWAL_FAILED). */
889
+ reportFailure(reason) {
890
+ native.identitySourceReportFailure(this[HANDLE], reason);
891
+ }
892
+
893
+ /** Reports that the hand-off credential had already been redeemed. */
894
+ reportHandoffStolen() {
895
+ native.identitySourceReportHandoffStolen(this[HANDLE]);
896
+ }
897
+
898
+ /** Reloads a file source now: true when the files changed and were
899
+ * loaded, false when nothing changed or this is not a file source. */
900
+ reload() {
901
+ return native.identitySourceReload(this[HANDLE]);
902
+ }
903
+
904
+ /** A reader of this source's events from now on. */
905
+ events() {
906
+ return adopt(IdentityEvents, native.identitySourceEvents(this[HANDLE]));
907
+ }
908
+
909
+ /** A handle that does not keep the source alive. */
910
+ downgrade() {
911
+ return adopt(WeakIdentitySource, native.identitySourceDowngrade(this[HANDLE]));
912
+ }
913
+ }
914
+
915
+ /** A handle to an `IdentitySource` that does not keep it alive. */
916
+ export class WeakIdentitySource extends Handle {
917
+ constructor() {
918
+ super();
919
+ noConstructor("WeakIdentitySource", "IdentitySource.downgrade()");
920
+ }
921
+
922
+ /** The source while anything holds it, else null. */
923
+ upgrade() {
924
+ const handle = native.weakIdentitySourceUpgrade(this[HANDLE]);
925
+ return handle === null ? null : adopt(IdentitySource, handle);
926
+ }
927
+ }
928
+
929
+ /** Whom a peer is accepted as, as a source: a `Trust` that never changes,
930
+ * or one driven from outside. Shared by the TLS terms made from it, so an
931
+ * update reaches their next handshake. */
932
+ export class TrustSource extends Handle {
933
+ constructor() {
934
+ super();
935
+ noConstructor("TrustSource", "TrustSource.fromTrust() or .external()");
936
+ }
937
+
938
+ /** A source that holds a copy of `trust` and never changes. */
939
+ static fromTrust(trust) {
940
+ return adopt(TrustSource, native.trustSourceFromTrust(handleOf(trust, Trust)));
941
+ }
942
+
943
+ /** A source that starts as a copy of `trust` and changes on `update()`. */
944
+ static external(trust) {
945
+ return adopt(TrustSource, native.trustSourceExternal(handleOf(trust, Trust)));
946
+ }
947
+
948
+ /** A copy of the trust of the moment. */
949
+ current() {
950
+ return adopt(Trust, native.trustSourceCurrent(this[HANDLE]));
951
+ }
952
+
953
+ /** Replaces the trust (TRUST_REFRESHED). */
954
+ update(trust) {
955
+ native.trustSourceUpdate(this[HANDLE], handleOf(trust, Trust));
956
+ }
957
+
958
+ /** Reports a refresh that failed (RENEWAL_FAILED). */
959
+ reportFailure(reason) {
960
+ native.trustSourceReportFailure(this[HANDLE], reason);
961
+ }
962
+
963
+ /** Whether the trust of the moment holds no pin and no anchor. */
964
+ isEmpty() {
965
+ return native.trustSourceIsEmpty(this[HANDLE]);
966
+ }
967
+
968
+ /** A reader of this source's events from now on. */
969
+ events() {
970
+ return adopt(IdentityEvents, native.trustSourceEvents(this[HANDLE]));
971
+ }
972
+
973
+ /** A handle that does not keep the source alive. */
974
+ downgrade() {
975
+ return adopt(WeakTrustSource, native.trustSourceDowngrade(this[HANDLE]));
976
+ }
977
+ }
978
+
979
+ /** A handle to a `TrustSource` that does not keep it alive. */
980
+ export class WeakTrustSource extends Handle {
981
+ constructor() {
982
+ super();
983
+ noConstructor("WeakTrustSource", "TrustSource.downgrade()");
984
+ }
985
+
986
+ /** The source while anything holds it, else null. */
987
+ upgrade() {
988
+ const handle = native.weakTrustSourceUpgrade(this[HANDLE]);
989
+ return handle === null ? null : adopt(TrustSource, handle);
990
+ }
991
+ }
992
+
993
+ /** Where a runtime serves: bindings and the endpoints registered on them. */
994
+ export class Listener extends Handle {
995
+ constructor() {
996
+ super();
997
+ noConstructor("Listener", "Runtime.listener()");
998
+ }
999
+
1000
+ /** Serves QUIC on `addr`, e.g. "127.0.0.1:0". */
1001
+ async bindQuic(addr, serverTls, { signal } = {}) {
1002
+ const tls = handleOf(serverTls, ServerTls);
1003
+ const binding = await op(
1004
+ this,
1005
+ () => native.listenerBindQuic(this[HANDLE], addr, tls),
1006
+ native.listenerBindQuicOpWait,
1007
+ native.listenerBindQuicOpFd,
1008
+ signal,
1009
+ );
1010
+ return adopt(Binding, binding);
1011
+ }
1012
+
1013
+ /** Serves QUIC on a socket that is already bound: its descriptor number
1014
+ * (on Windows its SOCKET), e.g. a predecessor's `Handoff.takeSocket()`
1015
+ * passed on through `child_process` `stdio`. The descriptor is libweida's
1016
+ * once the call reaches it; weida neither binds it nor sets options on
1017
+ * it. */
1018
+ async bindQuicSocket(socket, serverTls, { signal } = {}) {
1019
+ const tls = handleOf(serverTls, ServerTls);
1020
+ const binding = await op(
1021
+ this,
1022
+ () => native.listenerBindQuicSocket(this[HANDLE], socket, tls),
1023
+ native.listenerBindQuicSocketOpWait,
1024
+ native.listenerBindQuicSocketOpFd,
1025
+ signal,
1026
+ );
1027
+ return adopt(Binding, binding);
1028
+ }
1029
+
1030
+ /** Serves this listener's endpoints through the relay at `relay`
1031
+ * (`weida://[relay-key@]host:port/.weida/relay`), registered under the
1032
+ * key `via` presents; diallers reach them as
1033
+ * `weida+relay://<key>@[relay-key@]host:port/path`. Rejects with
1034
+ * WEIDA_EREJECTED where the relay refused the registration. */
1035
+ async bindRelay(relay, via, serverTls, { signal } = {}) {
1036
+ const client = handleOf(via, ClientTls);
1037
+ const tls = handleOf(serverTls, ServerTls);
1038
+ const binding = await op(
1039
+ this,
1040
+ () => native.listenerBindRelay(this[HANDLE], relay, client, tls),
1041
+ native.listenerBindRelayOpWait,
1042
+ native.listenerBindRelayOpFd,
1043
+ signal,
1044
+ );
1045
+ return adopt(Binding, binding);
1046
+ }
1047
+
1048
+ /** Serves a relay on this listener's bindings: registrations and tunnels
1049
+ * arrive at `Relay.accept()`. Throws WEIDA_EALREADY_REGISTERED on a
1050
+ * second call. */
1051
+ relay() {
1052
+ return adopt(Relay, native.listenerRelay(this[HANDLE]));
1053
+ }
1054
+
1055
+ /** Answers on the in-process bus `bus`. */
1056
+ bindInproc(bus) {
1057
+ return adopt(LocalBinding, native.listenerBindInproc(this[HANDLE], bus));
1058
+ }
1059
+
1060
+ /** Serves this listener's endpoints on the `AF_UNIX` socket `path`, reached
1061
+ * at `weida+unix://<encodeURIComponent(path)>/<endpoint>`. No TLS: the kernel
1062
+ * proves the peer. `path` MUST be in a directory this process owns and no
1063
+ * other user may write. Throws WEIDA_EUNSUPPORTED on Windows. */
1064
+ bindUnix(path) {
1065
+ return adopt(UnixBinding, native.listenerBindUnix(this[HANDLE], path));
1066
+ }
1067
+
1068
+ /** Serves this listener's endpoints on the named pipe `\\.\pipe\<name>`,
1069
+ * owner-only and local-only, reached at `weida+pipe://<name>/<endpoint>`.
1070
+ * Throws WEIDA_EUNSUPPORTED off Windows. */
1071
+ bindPipe(name) {
1072
+ return adopt(PipeBinding, native.listenerBindPipe(this[HANDLE], name));
1073
+ }
1074
+
1075
+ /** The replier serving `path` on every binding of this listener. */
1076
+ replier(path) {
1077
+ return adopt(Replier, native.listenerReplier(this[HANDLE], path));
1078
+ }
1079
+
1080
+ /** The puller serving `path` on every binding of this listener. */
1081
+ puller(path) {
1082
+ return adopt(Puller, native.listenerPuller(this[HANDLE], path));
1083
+ }
1084
+
1085
+ /** The publisher serving `path` on every binding of this listener. */
1086
+ publisher(path) {
1087
+ return adopt(Publisher, native.listenerPublisher(this[HANDLE], path));
1088
+ }
1089
+
1090
+ /** A bound pair serving `path` on every binding of this listener. */
1091
+ pair(path) {
1092
+ return adopt(Paired, native.listenerPair(this[HANDLE], path));
1093
+ }
1094
+
1095
+ /** The respondent serving `path` on every binding of this listener. */
1096
+ respondent(path) {
1097
+ return adopt(Respondent, native.listenerRespondent(this[HANDLE], path));
1098
+ }
1099
+
1100
+ /** A bus member accepting on `path` and dialling the other members on
1101
+ * `clientTls`. */
1102
+ bus(path, clientTls) {
1103
+ return adopt(BusMember, native.listenerBus(this[HANDLE], path, handleOf(clientTls, ClientTls)));
1104
+ }
1105
+
1106
+ /** The radio serving `path` on every binding of this listener; dropping
1107
+ * it releases the path. */
1108
+ radio(path) {
1109
+ return adopt(Radio, native.listenerRadio(this[HANDLE], path));
1110
+ }
1111
+
1112
+ /** A raw acceptor serving `path`: it takes both stream kinds and flows on
1113
+ * the path, tagged. */
1114
+ acceptor(path) {
1115
+ return adopt(Acceptor, native.listenerAcceptor(this[HANDLE], path));
1116
+ }
1117
+
1118
+ /** A seeder serving `path`: it hands each fetch's session to the
1119
+ * application, which answers its orders from its own storage. */
1120
+ seeder(path) {
1121
+ return adopt(Seeder, native.listenerSeeder(this[HANDLE], path));
1122
+ }
1123
+ }
1124
+
1125
+ /** A QUIC binding: the socket a listener serves on, directly or through a
1126
+ * relay. Dropping it releases the handle only: the binding serves until
1127
+ * `close()`, a hand-over or the runtime's shutdown. */
1128
+ export class Binding extends Handle {
1129
+ constructor() {
1130
+ super();
1131
+ noConstructor(
1132
+ "Binding",
1133
+ "Listener.bindQuic(), Listener.bindQuicSocket() or Listener.bindRelay()",
1134
+ );
1135
+ }
1136
+
1137
+ localAddr() {
1138
+ return native.bindingLocalAddr(this[HANDLE]);
1139
+ }
1140
+
1141
+ /** Stops accepting and closes the binding's connections with SHUTDOWN;
1142
+ * resolves once the port is free to bind again. A relayed binding ends
1143
+ * its registration at the relay. */
1144
+ async close({ signal } = {}) {
1145
+ await op(
1146
+ this,
1147
+ () => native.bindingClose(this[HANDLE]),
1148
+ native.bindingCloseOpWait,
1149
+ native.bindingCloseOpFd,
1150
+ signal,
1151
+ );
1152
+ }
1153
+
1154
+ /** Hands the socket over to a successor process: every connection is
1155
+ * closed with RESTARTING, its close packet held in the `Handoff` and not
1156
+ * sent. Consumes the binding. Rejects with WEIDA_EUNSUPPORTED for a
1157
+ * relayed binding and WEIDA_ECLOSED for a closed one. */
1158
+ async handOver({ signal } = {}) {
1159
+ const handoff = await op(
1160
+ this,
1161
+ () => native.bindingHandOver(consume(this)),
1162
+ native.bindingHandOverOpWait,
1163
+ native.bindingHandOverOpFd,
1164
+ signal,
1165
+ );
1166
+ return adopt(Handoff, handoff);
1167
+ }
1168
+
1169
+ /** Sends the close packets a predecessor held (`Handoff.bytes()`), so its
1170
+ * clients redial into this binding; call it once their endpoints are
1171
+ * registered. Resolves with how many were sent. The bytes need the
1172
+ * integrity of the channel that carries the socket. */
1173
+ async takeOver(held, { signal } = {}) {
1174
+ return await op(
1175
+ this,
1176
+ () => native.bindingTakeOver(this[HANDLE], held),
1177
+ native.bindingTakeOverOpWait,
1178
+ native.bindingTakeOverOpFd,
1179
+ signal,
1180
+ );
1181
+ }
1182
+
1183
+ /** Closes every connection from the peer with this key (`sha256:` text)
1184
+ * with REJECTED; how many it closed. Not a ban: the peer redials under
1185
+ * its own policy. */
1186
+ disconnect(fingerprint) {
1187
+ return native.bindingDisconnect(this[HANDLE], fingerprint);
1188
+ }
1189
+
1190
+ /** A new reader of a relayed binding's registration events, or null for
1191
+ * a binding that is not relayed. */
1192
+ relayEvents() {
1193
+ const handle = native.bindingRelayEvents(this[HANDLE]);
1194
+ return handle === null ? null : adopt(PeerEvents, handle);
1195
+ }
1196
+ }
1197
+
1198
+ /** What `Binding.handOver` leaves for a successor: the socket and the close
1199
+ * packets held for its clients. Dropping it closes a socket not taken. */
1200
+ export class Handoff extends Handle {
1201
+ constructor() {
1202
+ super();
1203
+ noConstructor("Handoff", "Binding.handOver()");
1204
+ }
1205
+
1206
+ /** How many close packets are held. */
1207
+ held() {
1208
+ return native.handoffHeld(this[HANDLE]);
1209
+ }
1210
+
1211
+ /** The address the socket is bound to. */
1212
+ localAddr() {
1213
+ return native.handoffLocalAddr(this[HANDLE]);
1214
+ }
1215
+
1216
+ /** A copy of the held packets, for the successor's `Binding.takeOver`. */
1217
+ bytes() {
1218
+ return native.handoffBytes(this[HANDLE]);
1219
+ }
1220
+
1221
+ /** The socket's descriptor (on Windows its SOCKET), the caller's from
1222
+ * then on; once, then WEIDA_ECLOSED. */
1223
+ takeSocket() {
1224
+ return native.handoffTakeSocket(this[HANDLE]);
1225
+ }
1226
+ }
1227
+
1228
+ /** An in-process binding: the bus a listener answers on. */
1229
+ export class LocalBinding extends Handle {
1230
+ constructor() {
1231
+ super();
1232
+ noConstructor("LocalBinding", "Listener.bindInproc()");
1233
+ }
1234
+
1235
+ bus() {
1236
+ return native.localBindingBus(this[HANDLE]);
1237
+ }
1238
+ }
1239
+
1240
+ /** An `AF_UNIX` binding; dropping it ends the binding and removes the socket
1241
+ * file. */
1242
+ export class UnixBinding extends Handle {
1243
+ constructor() {
1244
+ super();
1245
+ noConstructor("UnixBinding", "Listener.bindUnix()");
1246
+ }
1247
+
1248
+ /** The socket path bound. */
1249
+ path() {
1250
+ return native.unixBindingPath(this[HANDLE]);
1251
+ }
1252
+ }
1253
+
1254
+ /** A named-pipe binding; dropping it ends the binding and releases the
1255
+ * name. */
1256
+ export class PipeBinding extends Handle {
1257
+ constructor() {
1258
+ super();
1259
+ noConstructor("PipeBinding", "Listener.bindPipe()");
1260
+ }
1261
+
1262
+ /** The pipe name bound, without `\\.\pipe\`. */
1263
+ name() {
1264
+ return native.pipeBindingName(this[HANDLE]);
1265
+ }
1266
+ }
1267
+
1268
+ /** The requesting half of Req/Rep: dials repliers and sends them requests. */
1269
+ export class Requester extends Handle {
1270
+ constructor() {
1271
+ super();
1272
+ noConstructor("Requester", "Runtime.requester()");
1273
+ }
1274
+
1275
+ /** Dials `url`; the runtime redials it when the connection is lost. */
1276
+ async connect(url, { signal } = {}) {
1277
+ await op(
1278
+ this,
1279
+ () => native.requesterConnect(this[HANDLE], url),
1280
+ native.requesterConnectOpWait,
1281
+ native.requesterConnectOpFd,
1282
+ signal,
1283
+ );
1284
+ }
1285
+
1286
+ /** Sends `body` (a string is sent as UTF-8) and resolves with the reply as
1287
+ * soon as its header arrives. Aborting abandons the exchange. */
1288
+ async request(body, { signal } = {}) {
1289
+ const data = bytes(body);
1290
+ const transfer = await op(
1291
+ this,
1292
+ () => native.requesterRequest(this[HANDLE], data),
1293
+ native.requesterRequestOpWait,
1294
+ native.requesterRequestOpFd,
1295
+ signal,
1296
+ );
1297
+ return adopt(IncomingTransfer, transfer);
1298
+ }
1299
+
1300
+ /** `request` with metadata (`{ contentType?, contentLen?, trace?, topic?,
1301
+ * achieved? }`, or null), copied when the request starts. */
1302
+ async requestWith(meta, body, { signal } = {}) {
1303
+ const data = bytes(body);
1304
+ const transfer = await op(
1305
+ this,
1306
+ () => withTransferMeta(meta, (m) => native.requesterRequestWith(this[HANDLE], m, data)),
1307
+ native.requesterRequestOpWait,
1308
+ native.requesterRequestOpFd,
1309
+ signal,
1310
+ );
1311
+ return adopt(IncomingTransfer, transfer);
1312
+ }
1313
+
1314
+ /** Opens one exchange with `meta` (a `TransferMeta` object, or null),
1315
+ * copied when it starts: `body`, the request written like a socket and
1316
+ * ended with `finish()`, and `reply`, the stream the reply is awaited on.
1317
+ * The halves are independent, so the reply can be read while the body is
1318
+ * still open. Aborting abandons the exchange. */
1319
+ async open(meta, { signal } = {}) {
1320
+ const opened = await op(
1321
+ this,
1322
+ () => withTransferMeta(meta, (m) => native.requesterOpen(this[HANDLE], m)),
1323
+ native.requesterOpenOpWait,
1324
+ native.requesterOpenOpFd,
1325
+ signal,
1326
+ );
1327
+ return Object.freeze({
1328
+ body: adopt(OutgoingTransfer, opened.body),
1329
+ reply: adopt(ReplyStream, opened.reply),
1330
+ });
1331
+ }
1332
+
1333
+ /** A frozen array with one frozen record per live connection, in dial
1334
+ * order: `{ url, ageUs, redials, transport, remote }`, every number a
1335
+ * BigInt, `transport` ({ path, tx, rx }) and `remote` null when absent. */
1336
+ connectionStats() {
1337
+ return Object.freeze(native.requesterConnectionStats(this[HANDLE]).map(freezeStats));
1338
+ }
1339
+
1340
+ /** A new reader of this requester's peer events, from now on. */
1341
+ events() {
1342
+ return adopt(PeerEvents, native.requesterEvents(this[HANDLE]));
1343
+ }
1344
+ }
1345
+
1346
+ /** The reply half of an exchange `Requester.open` started. Dropping it
1347
+ * before `recv` tells the replier nobody is listening. */
1348
+ export class ReplyStream extends Handle {
1349
+ constructor() {
1350
+ super();
1351
+ noConstructor("ReplyStream", "Requester.open()");
1352
+ }
1353
+
1354
+ /** The reply, as soon as its header arrives; a refused request rejects
1355
+ * with the refusal's WeidaError. Consumes the stream. */
1356
+ async recv({ signal } = {}) {
1357
+ const transfer = await op(
1358
+ this,
1359
+ () => native.replyStreamRecv(consume(this)),
1360
+ native.replyStreamRecvOpWait,
1361
+ native.replyStreamRecvOpFd,
1362
+ signal,
1363
+ );
1364
+ return adopt(IncomingTransfer, transfer);
1365
+ }
1366
+ }
1367
+
1368
+ /** The peer events of one requester, or of a relayed binding's
1369
+ * registration, read like a queue or iterated with `for await`; the loop
1370
+ * ends once the requester or the registration is gone, and leaving it
1371
+ * early drops the reader. */
1372
+ export class PeerEvents extends Handle {
1373
+ constructor() {
1374
+ super();
1375
+ noConstructor("PeerEvents", "Requester.events() or Binding.relayEvents()");
1376
+ }
1377
+
1378
+ /** The next event as a frozen object, or null once the requester, or the
1379
+ * relayed binding's registration, is gone. */
1380
+ async recv({ signal } = {}) {
1381
+ const handle = this[HANDLE];
1382
+ const event = await call(
1383
+ this,
1384
+ () => native.peerEventsRecv(handle),
1385
+ () => native.peerEventsFd(handle),
1386
+ signal,
1387
+ );
1388
+ return event === null ? null : Object.freeze(event);
1389
+ }
1390
+
1391
+ async *[Symbol.asyncIterator]() {
1392
+ try {
1393
+ for (let event = await this.recv(); event !== null; event = await this.recv()) {
1394
+ yield event;
1395
+ }
1396
+ } finally {
1397
+ this.drop();
1398
+ }
1399
+ }
1400
+ }
1401
+
1402
+ /** The events of one identity or trust source, read like a queue or
1403
+ * iterated with `for await`; the loop ends once the source is gone, and
1404
+ * leaving it early drops the reader. */
1405
+ export class IdentityEvents extends Handle {
1406
+ constructor() {
1407
+ super();
1408
+ noConstructor("IdentityEvents", "IdentitySource.events() or TrustSource.events()");
1409
+ }
1410
+
1411
+ /** The next event as a frozen object, or null once the source is gone. */
1412
+ async recv({ signal } = {}) {
1413
+ const handle = this[HANDLE];
1414
+ const event = await call(
1415
+ this,
1416
+ () => native.identityEventsRecv(handle),
1417
+ () => native.identityEventsFd(handle),
1418
+ signal,
1419
+ );
1420
+ return event === null ? null : Object.freeze(event);
1421
+ }
1422
+
1423
+ async *[Symbol.asyncIterator]() {
1424
+ try {
1425
+ for (let event = await this.recv(); event !== null; event = await this.recv()) {
1426
+ yield event;
1427
+ }
1428
+ } finally {
1429
+ this.drop();
1430
+ }
1431
+ }
1432
+ }
1433
+
1434
+ /** The cursor sets one transfer's receiver reports, read like a queue or
1435
+ * iterated with `for await`; each set is the newest offset per reported
1436
+ * level, changes since the last read coalesced. The loop ends once no more
1437
+ * cursors come, and leaving it early drops the reader. */
1438
+ export class Cursors extends Handle {
1439
+ constructor() {
1440
+ super();
1441
+ noConstructor("Cursors", "OutgoingTransfer.cursors()");
1442
+ }
1443
+
1444
+ /** The newest set after a change, a frozen array of frozen
1445
+ * `{ level, offset }` in ascending level order, or null once no more
1446
+ * cursors come (the reporter finished, the stream was reset or the
1447
+ * connection is gone). */
1448
+ async recv({ signal } = {}) {
1449
+ const handle = this[HANDLE];
1450
+ const set = await call(
1451
+ this,
1452
+ () => native.cursorsRecv(handle),
1453
+ () => native.cursorsFd(handle),
1454
+ signal,
1455
+ );
1456
+ return set === null ? null : Object.freeze(set.map((entry) => Object.freeze(entry)));
1457
+ }
1458
+
1459
+ async *[Symbol.asyncIterator]() {
1460
+ try {
1461
+ for (let set = await this.recv(); set !== null; set = await this.recv()) {
1462
+ yield set;
1463
+ }
1464
+ } finally {
1465
+ this.drop();
1466
+ }
1467
+ }
1468
+ }
1469
+
1470
+ /** The receiver's end of a report: how far each ordered level got. Dropping
1471
+ * it without `finish()` flushes nothing. */
1472
+ export class Reporter extends Handle {
1473
+ constructor() {
1474
+ super();
1475
+ noConstructor("Reporter", "IncomingTransfer.reporter()");
1476
+ }
1477
+
1478
+ /** The levels the sender ordered, ascending, frozen. */
1479
+ levels() {
1480
+ return Object.freeze(native.reporterLevels(this[HANDLE]));
1481
+ }
1482
+
1483
+ /** The `WEIDA_REPORT_MODE_*` the sender asked for. */
1484
+ mode() {
1485
+ return native.reporterMode(this[HANDLE]);
1486
+ }
1487
+
1488
+ /** Progress records are written when a level advanced `bytes` or
1489
+ * `intervalMs` passed, whichever comes first; 0 and 0 write every
1490
+ * report. No effect in final-only mode. */
1491
+ setGranularity(bytes, intervalMs) {
1492
+ native.reporterSetGranularity(this[HANDLE], bytes, intervalMs);
1493
+ }
1494
+
1495
+ /** Reports that `level` has reached `offset`; a level the sender did not
1496
+ * order is ignored. Never rejects for a transport reason. */
1497
+ report(level, offset, { signal } = {}) {
1498
+ return op(
1499
+ this,
1500
+ () => native.reporterReport(this[HANDLE], cursorLevel(level), offset),
1501
+ native.reporterReportOpWait,
1502
+ native.reporterReportOpFd,
1503
+ signal,
1504
+ );
1505
+ }
1506
+
1507
+ /** Flushes the newest offset per reported level and ends the report.
1508
+ * Consumes the reporter. */
1509
+ finish({ signal } = {}) {
1510
+ return op(
1511
+ this,
1512
+ () => native.reporterFinish(consume(this)),
1513
+ native.reporterFinishOpWait,
1514
+ native.reporterFinishOpFd,
1515
+ signal,
1516
+ );
1517
+ }
1518
+ }
1519
+
1520
+ /** The dialling side of the stream core: it opens one-way streams,
1521
+ * exchanges, segments and datagram flows to the peers it dialled,
1522
+ * round-robin. */
1523
+ export class Peer extends Handle {
1524
+ constructor() {
1525
+ super();
1526
+ noConstructor("Peer", "Runtime.peer()");
1527
+ }
1528
+
1529
+ /** Dials `url`; the runtime redials it when the connection is lost. */
1530
+ async connect(url, { signal } = {}) {
1531
+ await op(
1532
+ this,
1533
+ () => native.peerConnect(this[HANDLE], url),
1534
+ native.peerConnectOpWait,
1535
+ native.peerConnectOpFd,
1536
+ signal,
1537
+ );
1538
+ }
1539
+
1540
+ /** Forgets a dialled address: whether it was dialled. */
1541
+ disconnect(url) {
1542
+ return native.peerDisconnect(this[HANDLE], url);
1543
+ }
1544
+
1545
+ /** The number of connected peers. */
1546
+ peerCount() {
1547
+ return native.peerPeerCount(this[HANDLE]);
1548
+ }
1549
+
1550
+ /** A frozen array with one frozen record per live connection, as
1551
+ * `Requester.connectionStats`. */
1552
+ connectionStats() {
1553
+ return Object.freeze(native.peerConnectionStats(this[HANDLE]).map(freezeStats));
1554
+ }
1555
+
1556
+ /** A new reader of this peer's events, from now on. */
1557
+ events() {
1558
+ return adopt(PeerEvents, native.peerEvents(this[HANDLE]));
1559
+ }
1560
+
1561
+ /** The bodies the outbox discarded, a BigInt. */
1562
+ dropped() {
1563
+ return native.peerDropped(this[HANDLE]);
1564
+ }
1565
+
1566
+ /** Opens a one-way stream to the next peer with `meta` (a `TransferMeta`
1567
+ * object, or null), written like a socket and ended with `finish()`. With
1568
+ * every peer down it waits for the redial, bounded by `sendTimeout`;
1569
+ * a peer never connected rejects with WEIDA_ENOT_CONNECTED. Aborting
1570
+ * abandons it. */
1571
+ async open(meta, { signal } = {}) {
1572
+ const transfer = await op(
1573
+ this,
1574
+ () => withTransferMeta(meta, (m) => native.peerOpen(this[HANDLE], m)),
1575
+ native.peerOpenOpWait,
1576
+ native.peerOpenOpFd,
1577
+ signal,
1578
+ );
1579
+ return adopt(OutgoingTransfer, transfer);
1580
+ }
1581
+
1582
+ /** Opens one exchange with the next peer, as `Requester.open`: a frozen
1583
+ * `{ body, reply }`. */
1584
+ async openBi(meta, { signal } = {}) {
1585
+ const opened = await op(
1586
+ this,
1587
+ () => withTransferMeta(meta, (m) => native.peerOpenBi(this[HANDLE], m)),
1588
+ native.peerOpenBiOpWait,
1589
+ native.peerOpenBiOpFd,
1590
+ signal,
1591
+ );
1592
+ return Object.freeze({
1593
+ body: adopt(OutgoingTransfer, opened.body),
1594
+ reply: adopt(ReplyStream, opened.reply),
1595
+ });
1596
+ }
1597
+
1598
+ /** Sends `body` (a string is sent as UTF-8) as one message with `meta` (a
1599
+ * `TransferMeta` object, or null); resolves once it is written or held in
1600
+ * the outbox. */
1601
+ async send(meta, body, { signal } = {}) {
1602
+ const data = bytes(body);
1603
+ await op(
1604
+ this,
1605
+ () => withTransferMeta(meta, (m) => native.peerSend(this[HANDLE], m, data)),
1606
+ native.peerSendOpWait,
1607
+ native.peerSendOpFd,
1608
+ signal,
1609
+ );
1610
+ }
1611
+
1612
+ /** Opens segment n+1 on `topic` to the next peer, on `terms`
1613
+ * `{ maxAgeMs?, priority?, followsUpstream? }`: it supersedes this peer's
1614
+ * copy of segment n there, as a radio's does. */
1615
+ async segment(topic, terms = null, { signal } = {}) {
1616
+ const segment = await op(
1617
+ this,
1618
+ () =>
1619
+ withFields(terms, native.segmentTermsNew, SEGMENT_FIELDS, "SegmentTerms", (t) =>
1620
+ native.peerSegment(this[HANDLE], topic, t),
1621
+ ),
1622
+ native.peerSegmentOpWait,
1623
+ native.peerSegmentOpFd,
1624
+ signal,
1625
+ );
1626
+ return adopt(Segment, segment);
1627
+ }
1628
+
1629
+ /** What this peer's segments lost on `topic`, a frozen record as
1630
+ * `Publisher.droppedOn`, or null when nothing was. */
1631
+ segmentDrops(topic) {
1632
+ const [record] = native.peerSegmentDrops(this[HANDLE], topic);
1633
+ return record === undefined ? null : Object.freeze(record);
1634
+ }
1635
+
1636
+ /** Registers a datagram flow to the next peer with `meta` (a
1637
+ * `{ contentType?, trace?, topic? }` object, or null), resolving once its
1638
+ * header is written. A refusal arrives later, as a `send` that throws.
1639
+ * Rejects with WEIDA_EDATAGRAMS_UNAVAILABLE unless both runtimes carry
1640
+ * datagrams (`limits.datagramReceiveBytes` above 0). */
1641
+ async openFlow(meta = null, { signal } = {}) {
1642
+ const flow = await op(
1643
+ this,
1644
+ () =>
1645
+ withFields(meta, native.flowMetaNew, FLOW_META_FIELDS, "FlowMeta", (m) =>
1646
+ native.peerOpenFlow(this[HANDLE], m),
1647
+ ),
1648
+ native.peerOpenFlowOpWait,
1649
+ native.peerOpenFlowOpFd,
1650
+ signal,
1651
+ );
1652
+ return adopt(Flow, flow);
1653
+ }
1654
+ }
1655
+
1656
+ /** The bound side of the stream core, serving one endpoint path. */
1657
+ export class Acceptor extends Handle {
1658
+ constructor() {
1659
+ super();
1660
+ noConstructor("Acceptor", "Listener.acceptor()");
1661
+ }
1662
+
1663
+ path() {
1664
+ return native.acceptorPath(this[HANDLE]);
1665
+ }
1666
+
1667
+ /** The next thing that arrived on the path, a frozen object by `kind`:
1668
+ * `{ kind: "WEIDA_INCOMING_STREAM", transfer }`,
1669
+ * `{ kind: "WEIDA_INCOMING_EXCHANGE", request }`,
1670
+ * `{ kind: "WEIDA_INCOMING_SUBSCRIBED", consumer }`,
1671
+ * `{ kind: "WEIDA_INCOMING_CREDIT", id, filter, limit }` (BigInt id and
1672
+ * limit), `{ kind: "WEIDA_INCOMING_UNSUBSCRIBED", id, filter }` (`filter`
1673
+ * null when the whole connection went away), or
1674
+ * `{ kind: "WEIDA_INCOMING_FLOW", flow }`. */
1675
+ async accept({ signal } = {}) {
1676
+ const handle = this[HANDLE];
1677
+ const r = await call(
1678
+ this,
1679
+ () => native.acceptorAccept(handle),
1680
+ () => native.acceptorFd(handle),
1681
+ signal,
1682
+ );
1683
+ switch (r.kind) {
1684
+ case "WEIDA_INCOMING_STREAM":
1685
+ return Object.freeze({ kind: r.kind, transfer: adopt(IncomingTransfer, r.transfer) });
1686
+ case "WEIDA_INCOMING_EXCHANGE":
1687
+ return Object.freeze({ kind: r.kind, request: adopt(IncomingRequest, r.request) });
1688
+ case "WEIDA_INCOMING_SUBSCRIBED":
1689
+ return Object.freeze({ kind: r.kind, consumer: adopt(Consumer, r.consumer) });
1690
+ case "WEIDA_INCOMING_FLOW":
1691
+ return Object.freeze({ kind: r.kind, flow: adopt(IncomingFlow, r.flow) });
1692
+ default:
1693
+ return Object.freeze(r);
1694
+ }
1695
+ }
1696
+ }
1697
+
1698
+ /** One consumer that subscribed on an acceptor's path, and the way to
1699
+ * deliver to it. A queue honours its credit; nothing is delivered by
1700
+ * itself. */
1701
+ export class Consumer extends Handle {
1702
+ constructor() {
1703
+ super();
1704
+ noConstructor("Consumer", "Acceptor.accept()");
1705
+ }
1706
+
1707
+ /** The number of the connection it subscribed on, a BigInt shared by
1708
+ * every subscription of that connection and never reused. */
1709
+ id() {
1710
+ return native.consumerId(this[HANDLE]);
1711
+ }
1712
+
1713
+ /** The filter the subscription was created with. */
1714
+ filter() {
1715
+ return native.consumerFilter(this[HANDLE]);
1716
+ }
1717
+
1718
+ /** The endpoint path it subscribed on. */
1719
+ path() {
1720
+ return native.consumerPath(this[HANDLE]);
1721
+ }
1722
+
1723
+ /** Opens one delivery with `meta` (a `TransferMeta` object, or null; the
1724
+ * topic the consumer filters on travels in it), addressed to the
1725
+ * subscribed path and written like a socket. Rejects with
1726
+ * WEIDA_ECONNECTION_LOST once the consumer's connection is gone. */
1727
+ async open(meta, { signal } = {}) {
1728
+ const transfer = await op(
1729
+ this,
1730
+ () => withTransferMeta(meta, (m) => native.consumerOpen(this[HANDLE], m)),
1731
+ native.consumerOpenOpWait,
1732
+ native.consumerOpenOpFd,
1733
+ signal,
1734
+ );
1735
+ return adopt(OutgoingTransfer, transfer);
1736
+ }
1737
+
1738
+ /** Delivers `body` (a string is sent as UTF-8) as one whole transfer with
1739
+ * `meta`; resolves once it is written and finished. */
1740
+ async deliver(meta, body, { signal } = {}) {
1741
+ const data = bytes(body);
1742
+ await op(
1743
+ this,
1744
+ () => withTransferMeta(meta, (m) => native.consumerDeliver(this[HANDLE], m, data)),
1745
+ native.consumerDeliverOpWait,
1746
+ native.consumerDeliverOpFd,
1747
+ signal,
1748
+ );
1749
+ }
1750
+
1751
+ /** Closes the connection the consumer subscribed on with `code` (one of
1752
+ * PROTOCOL.md §7's, e.g. 6 `SHUTDOWN`; above 2^62-1 sent as `INTERNAL`)
1753
+ * and `reason`, ending every subscription it holds on every path. The
1754
+ * acceptor then reports an unsubscription with a null filter. */
1755
+ close(code, reason) {
1756
+ native.consumerClose(this[HANDLE], code, reason);
1757
+ }
1758
+ }
1759
+
1760
+ /** The sending half of one datagram flow. `drop()` closes it with FIN: the
1761
+ * receiver sees its end after the datagrams already delivered. */
1762
+ export class Flow extends Handle {
1763
+ constructor() {
1764
+ super();
1765
+ noConstructor("Flow", "Peer.openFlow()");
1766
+ }
1767
+
1768
+ /** Sends one datagram (a string is sent as UTF-8). Never waits: it is
1769
+ * handed to the connection, not known to arrive. Throws WEIDA_ETOO_LARGE
1770
+ * (its `max` names the limit) above `maxPayload()`, and on an ended flow
1771
+ * the error that ended it, e.g. WEIDA_EREJECTED. */
1772
+ send(payload) {
1773
+ native.flowSend(this[HANDLE], bytes(payload));
1774
+ }
1775
+
1776
+ /** The largest payload `send` accepts now, or null when the connection
1777
+ * carries no datagrams. */
1778
+ maxPayload() {
1779
+ return native.flowMaxPayload(this[HANDLE]);
1780
+ }
1781
+
1782
+ /** A frozen `{ sent, tooLarge, discarded, notLive }` of BigInts. */
1783
+ stats() {
1784
+ const h = this[HANDLE];
1785
+ return Object.freeze({
1786
+ sent: native.flowSent(h),
1787
+ tooLarge: native.flowTooLarge(h),
1788
+ discarded: native.flowDiscarded(h),
1789
+ notLive: native.flowNotLive(h),
1790
+ });
1791
+ }
1792
+
1793
+ /** The path under the flow, frozen, or null on a local transport. */
1794
+ pathStats() {
1795
+ const p = native.flowPathStats(this[HANDLE]);
1796
+ return p === null ? null : Object.freeze(p);
1797
+ }
1798
+
1799
+ /** Resets the flow `CANCELED`. Consumes it. */
1800
+ abort() {
1801
+ native.flowAbort(consume(this));
1802
+ }
1803
+ }
1804
+
1805
+ /** The receiving half of one datagram flow, read like a queue or iterated
1806
+ * with `for await`; the loop ends with the flow, and leaving it early drops
1807
+ * the flow. `drop()` stops it `CANCELED`. */
1808
+ export class IncomingFlow extends Handle {
1809
+ constructor() {
1810
+ super();
1811
+ noConstructor("IncomingFlow", "Acceptor.accept()");
1812
+ }
1813
+
1814
+ /** What the sender attached, frozen: `{ endpoint, topic, contentType,
1815
+ * trace, peerFingerprint, peerChain, peerLocal }`. */
1816
+ info() {
1817
+ const m = native.incomingFlowInfo(this[HANDLE]);
1818
+ return freezeMeta({
1819
+ endpoint: m.endpoint,
1820
+ topic: m.topic,
1821
+ contentType: m.contentType,
1822
+ trace: m.trace,
1823
+ peerFingerprint: m.peerFingerprint,
1824
+ peerChain: m.peerChain,
1825
+ peerLocal: m.peerLocal,
1826
+ });
1827
+ }
1828
+
1829
+ /** The next datagram, oldest first, or null once the flow ended and every
1830
+ * datagram that arrived before its end was taken. */
1831
+ async recv({ signal } = {}) {
1832
+ const handle = this[HANDLE];
1833
+ return await call(
1834
+ this,
1835
+ () => native.incomingFlowRecv(handle),
1836
+ () => native.incomingFlowFd(handle),
1837
+ signal,
1838
+ );
1839
+ }
1840
+
1841
+ async *[Symbol.asyncIterator]() {
1842
+ try {
1843
+ for (let d = await this.recv(); d !== null; d = await this.recv()) {
1844
+ yield d;
1845
+ }
1846
+ } finally {
1847
+ this.drop();
1848
+ }
1849
+ }
1850
+
1851
+ /** A frozen `{ received, overflow }` of BigInts. */
1852
+ stats() {
1853
+ const h = this[HANDLE];
1854
+ return Object.freeze({
1855
+ received: native.incomingFlowReceived(h),
1856
+ overflow: native.incomingFlowOverflow(h),
1857
+ });
1858
+ }
1859
+
1860
+ /** The path under the flow, frozen, or null on a local transport. */
1861
+ pathStats() {
1862
+ const p = native.incomingFlowPathStats(this[HANDLE]);
1863
+ return p === null ? null : Object.freeze(p);
1864
+ }
1865
+
1866
+ /** Stops the flow `REJECTED`, which the sender's next `send` throws as
1867
+ * WEIDA_EREJECTED. Consumes it. */
1868
+ refuse() {
1869
+ native.incomingFlowRefuse(consume(this));
1870
+ }
1871
+ }
1872
+
1873
+ /** The replying half of Req/Rep, serving one endpoint path. */
1874
+ export class Replier extends Handle {
1875
+ constructor() {
1876
+ super();
1877
+ noConstructor("Replier", "Listener.replier()");
1878
+ }
1879
+
1880
+ path() {
1881
+ return native.replierPath(this[HANDLE]);
1882
+ }
1883
+
1884
+ /** The next request, as soon as its header arrives. */
1885
+ async accept({ signal } = {}) {
1886
+ const handle = this[HANDLE];
1887
+ const request = await call(
1888
+ this,
1889
+ () => native.replierAccept(handle),
1890
+ () => native.replierFd(handle),
1891
+ signal,
1892
+ );
1893
+ return adopt(IncomingRequest, request);
1894
+ }
1895
+ }
1896
+
1897
+ /** The sending half of Push/Pull: dials pullers and pushes to them
1898
+ * round-robin. */
1899
+ export class Pusher extends Handle {
1900
+ constructor() {
1901
+ super();
1902
+ noConstructor("Pusher", "Runtime.pusher()");
1903
+ }
1904
+
1905
+ /** Dials `url`; the runtime redials it when the connection is lost. */
1906
+ async connect(url, { signal } = {}) {
1907
+ await op(
1908
+ this,
1909
+ () => native.pusherConnect(this[HANDLE], url),
1910
+ native.pusherConnectOpWait,
1911
+ native.pusherConnectOpFd,
1912
+ signal,
1913
+ );
1914
+ }
1915
+
1916
+ /** Forgets a dialled address: whether it was dialled. */
1917
+ disconnect(url) {
1918
+ return native.pusherDisconnect(this[HANDLE], url);
1919
+ }
1920
+
1921
+ /** The number of connected peers. */
1922
+ peerCount() {
1923
+ return native.pusherPeerCount(this[HANDLE]);
1924
+ }
1925
+
1926
+ /** A frozen array with one frozen record per live connection, as
1927
+ * `Requester.connectionStats`. */
1928
+ connectionStats() {
1929
+ return Object.freeze(native.pusherConnectionStats(this[HANDLE]).map(freezeStats));
1930
+ }
1931
+
1932
+ /** A new reader of this pusher's peer events, from now on. */
1933
+ events() {
1934
+ return adopt(PeerEvents, native.pusherEvents(this[HANDLE]));
1935
+ }
1936
+
1937
+ /** The bodies the outbox discarded, a BigInt. */
1938
+ dropped() {
1939
+ return native.pusherDropped(this[HANDLE]);
1940
+ }
1941
+
1942
+ /** Opens a one-way transfer to the next peer with `meta` (a
1943
+ * `TransferMeta` object, or null): written like a socket, and its
1944
+ * `finish()` hands back the delivery receipt `send` discards. Aborting
1945
+ * abandons it. */
1946
+ async open(meta, { signal } = {}) {
1947
+ const transfer = await op(
1948
+ this,
1949
+ () => withTransferMeta(meta, (m) => native.pusherOpen(this[HANDLE], m)),
1950
+ native.pusherOpenOpWait,
1951
+ native.pusherOpenOpFd,
1952
+ signal,
1953
+ );
1954
+ return adopt(OutgoingTransfer, transfer);
1955
+ }
1956
+
1957
+ /** Sends `body` (a string is sent as UTF-8) as one message; resolves once
1958
+ * it is written or held in the outbox. */
1959
+ async send(body, { signal } = {}) {
1960
+ const data = bytes(body);
1961
+ await op(
1962
+ this,
1963
+ () => native.pusherSend(this[HANDLE], data),
1964
+ native.pusherSendOpWait,
1965
+ native.pusherSendOpFd,
1966
+ signal,
1967
+ );
1968
+ }
1969
+
1970
+ /** `send` with metadata (a `TransferMeta` object, or null), copied when
1971
+ * the send starts. */
1972
+ async sendWith(meta, body, { signal } = {}) {
1973
+ const data = bytes(body);
1974
+ await op(
1975
+ this,
1976
+ () => withTransferMeta(meta, (m) => native.pusherSendWith(this[HANDLE], m, data)),
1977
+ native.pusherSendOpWait,
1978
+ native.pusherSendOpFd,
1979
+ signal,
1980
+ );
1981
+ }
1982
+ }
1983
+
1984
+ /** The receiving half of Push/Pull, serving one endpoint path. */
1985
+ export class Puller extends Handle {
1986
+ constructor() {
1987
+ super();
1988
+ noConstructor("Puller", "Listener.puller()");
1989
+ }
1990
+
1991
+ path() {
1992
+ return native.pullerPath(this[HANDLE]);
1993
+ }
1994
+
1995
+ /** The next pushed transfer, as soon as its header arrives. */
1996
+ async recv({ signal } = {}) {
1997
+ const handle = this[HANDLE];
1998
+ const transfer = await call(
1999
+ this,
2000
+ () => native.pullerRecv(handle),
2001
+ () => native.pullerFd(handle),
2002
+ signal,
2003
+ );
2004
+ return adopt(IncomingTransfer, transfer);
2005
+ }
2006
+ }
2007
+
2008
+ /** The publishing half of Pub/Sub, serving one endpoint path. */
2009
+ export class Publisher extends Handle {
2010
+ constructor() {
2011
+ super();
2012
+ noConstructor("Publisher", "Listener.publisher()");
2013
+ }
2014
+
2015
+ path() {
2016
+ return native.publisherPath(this[HANDLE]);
2017
+ }
2018
+
2019
+ /** Fans `payload` (a string is sent as UTF-8) out to every subscriber
2020
+ * whose filter matches `topic`, without waiting on any: how many it was
2021
+ * enqueued to. One whose budget is exhausted loses it, counted by
2022
+ * `dropped()`. */
2023
+ publish(topic, payload) {
2024
+ return native.publisherPublish(this[HANDLE], topic, bytes(payload));
2025
+ }
2026
+
2027
+ /** `publish` propagating `trace`, a `traceparent` text. */
2028
+ publishWithTrace(topic, payload, trace) {
2029
+ return native.publisherPublishWithTrace(this[HANDLE], topic, bytes(payload), trace);
2030
+ }
2031
+
2032
+ /** A streamed publish on `topic` to the subscribers matched now. */
2033
+ open(topic) {
2034
+ return adopt(FanOut, native.publisherOpen(this[HANDLE], topic));
2035
+ }
2036
+
2037
+ /** `open` propagating `trace`, a `traceparent` text. */
2038
+ openWithTrace(topic, trace) {
2039
+ return adopt(FanOut, native.publisherOpenWithTrace(this[HANDLE], topic, trace));
2040
+ }
2041
+
2042
+ /** The connections subscribed to this publisher. */
2043
+ subscriberCount() {
2044
+ return native.publisherSubscriberCount(this[HANDLE]);
2045
+ }
2046
+
2047
+ /** The filters registered on this publisher, summed over subscribers. */
2048
+ filterCount() {
2049
+ return native.publisherFilterCount(this[HANDLE]);
2050
+ }
2051
+
2052
+ /** The copies dropped because a subscriber could not take them, a BigInt. */
2053
+ dropped() {
2054
+ return native.publisherDropped(this[HANDLE]);
2055
+ }
2056
+
2057
+ /** What was dropped on `topic`, a frozen `{ topic, subscriberBudget,
2058
+ * subscriberQueue, noParkedConnection, superseded, expired, tooLarge,
2059
+ * noDatagrams, layersCut }` of BigInts, or null when nothing was. */
2060
+ droppedOn(topic) {
2061
+ const [record] = native.publisherDroppedOn(this[HANDLE], topic);
2062
+ return record === undefined ? null : Object.freeze(record);
2063
+ }
2064
+
2065
+ /** A frozen array with one frozen record per topic that lost a copy. */
2066
+ drops() {
2067
+ return Object.freeze(native.publisherDrops(this[HANDLE]).map((r) => Object.freeze(r)));
2068
+ }
2069
+ }
2070
+
2071
+ /** A publish in progress: one payload written chunk by chunk, a stream per
2072
+ * subscriber. Dropping it without `finish()` aborts every copy. */
2073
+ export class FanOut extends Handle {
2074
+ constructor() {
2075
+ super();
2076
+ noConstructor("FanOut", "Publisher.open()");
2077
+ }
2078
+
2079
+ topic() {
2080
+ return native.fanOutTopic(this[HANDLE]);
2081
+ }
2082
+
2083
+ /** The subscribers still receiving this transfer. */
2084
+ subscribers() {
2085
+ return native.fanOutSubscribers(this[HANDLE]);
2086
+ }
2087
+
2088
+ /** Writes `chunk` to every subscriber still receiving, waiting up to
2089
+ * `limitMs` for all of those with no room together: the subscribers left.
2090
+ * Until it settles every other call on the fan-out throws WEIDA_EINVAL.
2091
+ * Aborting abandons the transfer: later writes throw WEIDA_ECANCELED and
2092
+ * `finish()` aborts every copy. */
2093
+ async writeWithin(chunk, limitMs, { signal } = {}) {
2094
+ const data = bytes(chunk);
2095
+ return op(
2096
+ this,
2097
+ () => native.fanOutWriteWithin(this[HANDLE], data, limitMs),
2098
+ native.fanOutWriteWithinOpWait,
2099
+ native.fanOutWriteWithinOpFd,
2100
+ signal,
2101
+ );
2102
+ }
2103
+
2104
+ /** Writes `chunk` without waiting: one with no room loses the transfer.
2105
+ * The subscribers left. */
2106
+ writeNow(chunk) {
2107
+ return native.fanOutWriteNow(this[HANDLE], bytes(chunk));
2108
+ }
2109
+
2110
+ /** Ends the transfer: the subscribers that received all of it. Consumes
2111
+ * the fan-out. */
2112
+ finish() {
2113
+ return native.fanOutFinish(consume(this));
2114
+ }
2115
+ }
2116
+
2117
+ /** The subscribing half of Pub/Sub. */
2118
+ export class Subscriber extends Handle {
2119
+ constructor() {
2120
+ super();
2121
+ noConstructor("Subscriber", "Runtime.subscriber()");
2122
+ }
2123
+
2124
+ /** Dials `url` and subscribes there to every filter registered so far;
2125
+ * the runtime redials it, filters included, when the connection is lost. */
2126
+ async connect(url, { signal } = {}) {
2127
+ await op(
2128
+ this,
2129
+ () => native.subscriberConnect(this[HANDLE], url),
2130
+ native.subscriberConnectOpWait,
2131
+ native.subscriberConnectOpFd,
2132
+ signal,
2133
+ );
2134
+ }
2135
+
2136
+ /** Forgets a dialled address: whether it was dialled. */
2137
+ disconnect(url) {
2138
+ return native.subscriberDisconnect(this[HANDLE], url);
2139
+ }
2140
+
2141
+ /** The number of connected peers. */
2142
+ peerCount() {
2143
+ return native.subscriberPeerCount(this[HANDLE]);
2144
+ }
2145
+
2146
+ /** A frozen array with one frozen record per live connection, as
2147
+ * `Requester.connectionStats`. */
2148
+ connectionStats() {
2149
+ return Object.freeze(native.subscriberConnectionStats(this[HANDLE]).map(freezeStats));
2150
+ }
2151
+
2152
+ /** A new reader of this subscriber's peer events, from now on. */
2153
+ events() {
2154
+ return adopt(PeerEvents, native.subscriberEvents(this[HANDLE]));
2155
+ }
2156
+
2157
+ /** Registers interest in every topic `filter` matches (PROTOCOL.md §6.4:
2158
+ * `*` one segment, a trailing `#` any number). A filter outside the
2159
+ * grammar rejects with WEIDA_EPROTOCOL. */
2160
+ async subscribe(filter, { signal } = {}) {
2161
+ await op(
2162
+ this,
2163
+ () => native.subscriberSubscribe(this[HANDLE], filter),
2164
+ native.subscriberSubscribeOpWait,
2165
+ native.subscriberSubscribeOpFd,
2166
+ signal,
2167
+ );
2168
+ }
2169
+
2170
+ /** Withdraws `filter`; an unknown one is ignored. */
2171
+ async unsubscribe(filter, { signal } = {}) {
2172
+ await op(
2173
+ this,
2174
+ () => native.subscriberUnsubscribe(this[HANDLE], filter),
2175
+ native.subscriberUnsubscribeOpWait,
2176
+ native.subscriberUnsubscribeOpFd,
2177
+ signal,
2178
+ );
2179
+ }
2180
+
2181
+ /** States how many messages this subscriber accepts in total on `filter`:
2182
+ * the credit a queue serving the path consults, absolute and cumulative. */
2183
+ async grant(filter, limit, { signal } = {}) {
2184
+ await op(
2185
+ this,
2186
+ () => native.subscriberGrant(this[HANDLE], filter, limit),
2187
+ native.subscriberGrantOpWait,
2188
+ native.subscriberGrantOpFd,
2189
+ signal,
2190
+ );
2191
+ }
2192
+
2193
+ /** The filters this subscriber registered. */
2194
+ filterCount() {
2195
+ return native.subscriberFilterCount(this[HANDLE]);
2196
+ }
2197
+
2198
+ /** The next published copy, as soon as its header arrives; its topic is
2199
+ * `meta().topic`. */
2200
+ async recv({ signal } = {}) {
2201
+ const handle = this[HANDLE];
2202
+ const transfer = await call(
2203
+ this,
2204
+ () => native.subscriberRecv(handle),
2205
+ () => native.subscriberFd(handle),
2206
+ signal,
2207
+ );
2208
+ return adopt(IncomingTransfer, transfer);
2209
+ }
2210
+ }
2211
+
2212
+ /** Either half of PAIR: one peer, one-way transfers both ways. A dialling
2213
+ * pair (`Runtime.pair`) dials its peer; a bound one (`Listener.pair`) is
2214
+ * dialled, and its first send or open waits until the peer has sent. */
2215
+ export class Paired extends Handle {
2216
+ constructor() {
2217
+ super();
2218
+ noConstructor("Paired", "Runtime.pair() or Listener.pair()");
2219
+ }
2220
+
2221
+ /** The bound pair's path; "" on a dialling pair. */
2222
+ path() {
2223
+ return native.pairedPath(this[HANDLE]);
2224
+ }
2225
+
2226
+ /** Dials `url`, once: a second address rejects with
2227
+ * WEIDA_ELIMIT_EXCEEDED, a bound pair with WEIDA_EUNSUPPORTED. */
2228
+ async connect(url, { signal } = {}) {
2229
+ await op(
2230
+ this,
2231
+ () => native.pairedConnect(this[HANDLE], url),
2232
+ native.pairedConnectOpWait,
2233
+ native.pairedConnectOpFd,
2234
+ signal,
2235
+ );
2236
+ }
2237
+
2238
+ /** The number of connected peers, 0 or 1; 0 on a bound pair. */
2239
+ peerCount() {
2240
+ return native.pairedPeerCount(this[HANDLE]);
2241
+ }
2242
+
2243
+ /** A frozen array with one frozen record per live connection, as
2244
+ * `Requester.connectionStats`; empty on a bound pair. */
2245
+ connectionStats() {
2246
+ return Object.freeze(native.pairedConnectionStats(this[HANDLE]).map(freezeStats));
2247
+ }
2248
+
2249
+ /** A new reader of a dialling pair's peer events, from now on; null on a
2250
+ * bound pair. */
2251
+ events() {
2252
+ const handle = native.pairedEvents(this[HANDLE]);
2253
+ return handle === null ? null : adopt(PeerEvents, handle);
2254
+ }
2255
+
2256
+ /** The bodies a dialling pair's outbox discarded, a BigInt. */
2257
+ dropped() {
2258
+ return native.pairedDropped(this[HANDLE]);
2259
+ }
2260
+
2261
+ /** Opens a one-way transfer to the peer with `meta` (a `TransferMeta`
2262
+ * object, or null): written like a socket, and its `finish()` hands back
2263
+ * the delivery receipt `send` discards. Aborting abandons it. */
2264
+ async open(meta, { signal } = {}) {
2265
+ const transfer = await op(
2266
+ this,
2267
+ () => withTransferMeta(meta, (m) => native.pairedOpen(this[HANDLE], m)),
2268
+ native.pairedOpenOpWait,
2269
+ native.pairedOpenOpFd,
2270
+ signal,
2271
+ );
2272
+ return adopt(OutgoingTransfer, transfer);
2273
+ }
2274
+
2275
+ /** Sends `body` (a string is sent as UTF-8) as one message; resolves once
2276
+ * it is written or held in the outbox. */
2277
+ async send(body, { signal } = {}) {
2278
+ const data = bytes(body);
2279
+ await op(
2280
+ this,
2281
+ () => native.pairedSend(this[HANDLE], data),
2282
+ native.pairedSendOpWait,
2283
+ native.pairedSendOpFd,
2284
+ signal,
2285
+ );
2286
+ }
2287
+
2288
+ /** `send` with metadata (a `TransferMeta` object, or null), copied when
2289
+ * the send starts. */
2290
+ async sendWith(meta, body, { signal } = {}) {
2291
+ const data = bytes(body);
2292
+ await op(
2293
+ this,
2294
+ () => withTransferMeta(meta, (m) => native.pairedSendWith(this[HANDLE], m, data)),
2295
+ native.pairedSendOpWait,
2296
+ native.pairedSendOpFd,
2297
+ signal,
2298
+ );
2299
+ }
2300
+
2301
+ /** The next transfer from the peer, as soon as its header arrives. */
2302
+ async recv({ signal } = {}) {
2303
+ const handle = this[HANDLE];
2304
+ const transfer = await call(
2305
+ this,
2306
+ () => native.pairedRecv(handle),
2307
+ () => native.pairedFd(handle),
2308
+ signal,
2309
+ );
2310
+ return adopt(IncomingTransfer, transfer);
2311
+ }
2312
+ }
2313
+
2314
+ /** The asking half of SURVEY: dials respondents and asks every one of
2315
+ * them. */
2316
+ export class Surveyor extends Handle {
2317
+ constructor() {
2318
+ super();
2319
+ noConstructor("Surveyor", "Runtime.surveyor()");
2320
+ }
2321
+
2322
+ /** Dials the respondent at `url`, adding it to the set every survey
2323
+ * asks; the runtime redials it when the connection is lost. */
2324
+ async connect(url, { signal } = {}) {
2325
+ await op(
2326
+ this,
2327
+ () => native.surveyorConnect(this[HANDLE], url),
2328
+ native.surveyorConnectOpWait,
2329
+ native.surveyorConnectOpFd,
2330
+ signal,
2331
+ );
2332
+ }
2333
+
2334
+ /** The number of connected respondents. */
2335
+ peerCount() {
2336
+ return native.surveyorPeerCount(this[HANDLE]);
2337
+ }
2338
+
2339
+ /** A frozen array with one frozen record per live connection, as
2340
+ * `Requester.connectionStats`. */
2341
+ connectionStats() {
2342
+ return Object.freeze(native.surveyorConnectionStats(this[HANDLE]).map(freezeStats));
2343
+ }
2344
+
2345
+ /** Asks every connected respondent `body` (a string is sent as UTF-8)
2346
+ * and collects their answers for `deadlineMs`, which bounds the asking
2347
+ * too: resolves with the run once every respondent was asked. */
2348
+ async survey(body, deadlineMs, { signal } = {}) {
2349
+ const data = bytes(body);
2350
+ const run = await op(
2351
+ this,
2352
+ () => native.surveyorSurvey(this[HANDLE], data, deadlineMs),
2353
+ native.surveyorSurveyOpWait,
2354
+ native.surveyorSurveyOpFd,
2355
+ signal,
2356
+ );
2357
+ return adopt(SurveyRun, run);
2358
+ }
2359
+
2360
+ /** `survey` with metadata (a `TransferMeta` object, or null), copied
2361
+ * when the survey starts. */
2362
+ async surveyWith(meta, body, deadlineMs, { signal } = {}) {
2363
+ const data = bytes(body);
2364
+ const run = await op(
2365
+ this,
2366
+ () =>
2367
+ withTransferMeta(meta, (m) =>
2368
+ native.surveyorSurveyWith(this[HANDLE], m, data, deadlineMs),
2369
+ ),
2370
+ native.surveyorSurveyOpWait,
2371
+ native.surveyorSurveyOpFd,
2372
+ signal,
2373
+ );
2374
+ return adopt(SurveyRun, run);
2375
+ }
2376
+ }
2377
+
2378
+ /** One survey in progress: its answers, and what the deadline cost.
2379
+ * Dropping it resets every exchange not yet answered. */
2380
+ export class SurveyRun extends Handle {
2381
+ constructor() {
2382
+ super();
2383
+ noConstructor("SurveyRun", "Surveyor.survey()");
2384
+ }
2385
+
2386
+ /** The next answer's bytes, at most `maxBytes` of them, or null once the
2387
+ * deadline passed or every respondent answered. An answer that failed
2388
+ * rejects and the run goes on. An aborted call leaves its read in flight
2389
+ * for the next one, which keeps the first call's `maxBytes`. */
2390
+ async next(maxBytes, { signal } = {}) {
2391
+ const handle = this[HANDLE];
2392
+ return call(
2393
+ this,
2394
+ () => native.surveyRunNext(handle, maxBytes),
2395
+ () => native.surveyRunFd(handle),
2396
+ signal,
2397
+ );
2398
+ }
2399
+
2400
+ /** The answers that arrived after the deadline, a BigInt. */
2401
+ late() {
2402
+ return native.surveyRunLate(this[HANDLE]);
2403
+ }
2404
+
2405
+ /** How many respondents were asked. */
2406
+ respondents() {
2407
+ return native.surveyRunRespondents(this[HANDLE]);
2408
+ }
2409
+ }
2410
+
2411
+ /** The answering half of SURVEY, serving one endpoint path. */
2412
+ export class Respondent extends Handle {
2413
+ constructor() {
2414
+ super();
2415
+ noConstructor("Respondent", "Listener.respondent()");
2416
+ }
2417
+
2418
+ path() {
2419
+ return native.respondentPath(this[HANDLE]);
2420
+ }
2421
+
2422
+ /** The next question, as soon as its header arrives: an
2423
+ * `IncomingRequest` answered as a replier's is. */
2424
+ async accept({ signal } = {}) {
2425
+ const handle = this[HANDLE];
2426
+ const request = await call(
2427
+ this,
2428
+ () => native.respondentAccept(handle),
2429
+ () => native.respondentFd(handle),
2430
+ signal,
2431
+ );
2432
+ return adopt(IncomingRequest, request);
2433
+ }
2434
+ }
2435
+
2436
+ /** A blob: its root (`BLOB_ID_LEN` bytes, the BLAKE3 hash of its bytes),
2437
+ * its size in bytes and its piece size, `2 ** pieceLog2` bytes. More than
2438
+ * `MAX_PIECES` pieces, a piece size outside `PIECE_SIZE_MIN_LOG2` to
2439
+ * `PIECE_SIZE_MAX_LOG2`, or an empty blob whose root is not the hash of no
2440
+ * bytes throws WEIDA_EINVAL. */
2441
+ export class Blob extends Handle {
2442
+ constructor(root, size, pieceLog2 = PIECE_SIZE_DEFAULT_LOG2) {
2443
+ super();
2444
+ init(this, native.blobNew(root, size, pieceLog2));
2445
+ }
2446
+
2447
+ /** The hash of the whole blob, a fresh Uint8Array. */
2448
+ root() {
2449
+ return native.blobRoot(this[HANDLE]);
2450
+ }
2451
+
2452
+ /** The length in bytes, a BigInt. */
2453
+ size() {
2454
+ return native.blobSize(this[HANDLE]);
2455
+ }
2456
+
2457
+ /** The piece size's base-2 logarithm. */
2458
+ pieceLog2() {
2459
+ return native.blobPieceLog2(this[HANDLE]);
2460
+ }
2461
+
2462
+ /** The number of pieces: the size over the piece size, rounded up. */
2463
+ count() {
2464
+ return native.blobCount(this[HANDLE]);
2465
+ }
2466
+
2467
+ /** The byte range of piece `index`, a frozen `{ start, end }` of BigInts,
2468
+ * end exclusive; null past the last piece. */
2469
+ span(index) {
2470
+ const span = native.blobSpan(this[HANDLE], index);
2471
+ return span === null ? null : Object.freeze(span);
2472
+ }
2473
+ }
2474
+
2475
+ /** The piece hashes of a blob, checked against its root. */
2476
+ export class PieceList extends Handle {
2477
+ constructor() {
2478
+ super();
2479
+ noConstructor("PieceList", "PieceList.of(), .parse(), TreeHasher.finish() or Fetch.list()");
2480
+ }
2481
+
2482
+ /** The piece list of `data` (a string is hashed as UTF-8) cut into pieces
2483
+ * of `2 ** pieceLog2` bytes; its `blob()` holds the root. */
2484
+ static of(data, pieceLog2 = PIECE_SIZE_DEFAULT_LOG2) {
2485
+ return adopt(PieceList, native.pieceListOf(bytes(data), pieceLog2));
2486
+ }
2487
+
2488
+ /** The piece list of `blob` from its bytes, as `toBytes()` gives them:
2489
+ * WEIDA_EINVAL unless they hash to the blob's root. */
2490
+ static parse(blob, data) {
2491
+ return adopt(PieceList, native.pieceListParse(handleOf(blob, Blob), data));
2492
+ }
2493
+
2494
+ /** The blob this list belongs to. */
2495
+ blob() {
2496
+ return adopt(Blob, native.pieceListBlob(this[HANDLE]));
2497
+ }
2498
+
2499
+ /** The chaining values one after the other, 32 bytes each, a copy; empty
2500
+ * for a blob of at most one piece. */
2501
+ toBytes() {
2502
+ return native.pieceListToBytes(this[HANDLE]);
2503
+ }
2504
+
2505
+ /** Whether `piece` (a string is checked as UTF-8) is piece `index` of
2506
+ * the blob; false past the last piece. */
2507
+ check(index, piece) {
2508
+ return native.pieceListCheck(this[HANDLE], index, bytes(piece));
2509
+ }
2510
+ }
2511
+
2512
+ /** Builds a piece list in one pass over a blob's bytes, fed in order. */
2513
+ export class TreeHasher extends Handle {
2514
+ constructor(pieceLog2 = PIECE_SIZE_DEFAULT_LOG2) {
2515
+ super();
2516
+ init(this, native.treeHasherNew(pieceLog2));
2517
+ }
2518
+
2519
+ /** Feeds the next bytes of the blob (a string as UTF-8); returns this
2520
+ * hasher. */
2521
+ update(data) {
2522
+ native.treeHasherUpdate(this[HANDLE], bytes(data));
2523
+ return this;
2524
+ }
2525
+
2526
+ /** Ends the bytes: their piece list, whose `blob()` holds the root.
2527
+ * Consumes the hasher. */
2528
+ finish() {
2529
+ return adopt(PieceList, native.treeHasherFinish(consume(this)));
2530
+ }
2531
+ }
2532
+
2533
+ /** A map of held pieces, one bit per piece. */
2534
+ export class Bits extends Handle {
2535
+ /** `len` bits, all clear. */
2536
+ constructor(len) {
2537
+ super();
2538
+ init(this, native.bitsNew(len));
2539
+ }
2540
+
2541
+ /** `len` bits, all set. */
2542
+ static full(len) {
2543
+ return adopt(Bits, native.bitsFull(len));
2544
+ }
2545
+
2546
+ /** `len` bits from the bytes they travel as: bit `i` is bit `i % 8`,
2547
+ * least significant first, of byte `i / 8`, padding bits clear. */
2548
+ static fromBytes(len, data) {
2549
+ return adopt(Bits, native.bitsFromBytes(len, data));
2550
+ }
2551
+
2552
+ /** The bits as they travel, a copy. */
2553
+ asBytes() {
2554
+ return native.bitsAsBytes(this[HANDLE]);
2555
+ }
2556
+
2557
+ len() {
2558
+ return native.bitsLen(this[HANDLE]);
2559
+ }
2560
+
2561
+ /** Whether bit `index` is set; false past the end. */
2562
+ get(index) {
2563
+ return native.bitsGet(this[HANDLE], index);
2564
+ }
2565
+
2566
+ /** Sets or clears bit `index`; past the end it does nothing. Returns
2567
+ * this map. */
2568
+ set(index, on) {
2569
+ native.bitsSet(this[HANDLE], index, on);
2570
+ return this;
2571
+ }
2572
+
2573
+ /** How many bits are set. */
2574
+ ones() {
2575
+ return native.bitsOnes(this[HANDLE]);
2576
+ }
2577
+
2578
+ /** Whether every bit is set; true for no bits at all. */
2579
+ all() {
2580
+ return native.bitsAll(this[HANDLE]);
2581
+ }
2582
+ }
2583
+
2584
+ /** The dialling half of SWARM: fetches one blob from every connected seeder
2585
+ * at once, each piece verified against the root. */
2586
+ export class Swarm extends Handle {
2587
+ constructor() {
2588
+ super();
2589
+ noConstructor("Swarm", "Runtime.swarm()");
2590
+ }
2591
+
2592
+ /** Dials the seeder at `url`; the runtime redials it when the connection
2593
+ * is lost. A seeder connected during a fetch joins it at its next read. */
2594
+ async connect(url, { signal } = {}) {
2595
+ await op(
2596
+ this,
2597
+ () => native.swarmConnect(this[HANDLE], url),
2598
+ native.swarmConnectOpWait,
2599
+ native.swarmConnectOpFd,
2600
+ signal,
2601
+ );
2602
+ }
2603
+
2604
+ /** Forgets a dialled address: whether it was dialled. */
2605
+ disconnect(url) {
2606
+ return native.swarmDisconnect(this[HANDLE], url);
2607
+ }
2608
+
2609
+ /** The number of connected seeders. */
2610
+ peerCount() {
2611
+ return native.swarmPeerCount(this[HANDLE]);
2612
+ }
2613
+
2614
+ /** A frozen array with one frozen record per live connection, as
2615
+ * `Requester.connectionStats`. */
2616
+ connectionStats() {
2617
+ return Object.freeze(native.swarmConnectionStats(this[HANDLE]).map(freezeStats));
2618
+ }
2619
+
2620
+ /** A new reader of this swarm's events, from now on. */
2621
+ events() {
2622
+ return adopt(PeerEvents, native.swarmEvents(this[HANDLE]));
2623
+ }
2624
+
2625
+ /** Starts fetching the pieces of `blob` that `have` (a `Bits` of one bit
2626
+ * per piece) lacks, ordered as `options` says: `{ list?, stallMs?,
2627
+ * minRate?, perPeer?, maxInflight?, maxPeers?, haveRefreshMs? }`, or the
2628
+ * defaults. `have` and the options are copied. Nothing is ordered before
2629
+ * the first `next`. */
2630
+ fetch(blob, have, options = null) {
2631
+ return withFields(options, native.fetchOptionsNew, FETCH_FIELDS, "FetchOptions", (o) =>
2632
+ adopt(
2633
+ Fetch,
2634
+ native.swarmFetch(this[HANDLE], handleOf(blob, Blob), handleOf(have, Bits), o),
2635
+ ),
2636
+ );
2637
+ }
2638
+ }
2639
+
2640
+ /** One blob being fetched: its verified pieces, in any order. Dropping it
2641
+ * aborts every order in flight. */
2642
+ export class Fetch extends Handle {
2643
+ constructor() {
2644
+ super();
2645
+ noConstructor("Fetch", "Swarm.fetch()");
2646
+ }
2647
+
2648
+ /** The blob being fetched. */
2649
+ blob() {
2650
+ return adopt(Blob, native.fetchBlob(this[HANDLE]));
2651
+ }
2652
+
2653
+ /** The piece list once it is known and verified, or null: keep it to
2654
+ * resume without asking for it (`{ list }`). WEIDA_EINVAL while a `next`
2655
+ * is pending. */
2656
+ list() {
2657
+ const list = native.fetchList(this[HANDLE]);
2658
+ return list === null ? null : adopt(PieceList, list);
2659
+ }
2660
+
2661
+ /** The pieces the caller holds: those it started with and those this
2662
+ * fetch yielded, a copy. WEIDA_EINVAL while a `next` is pending. */
2663
+ delivered() {
2664
+ return adopt(Bits, native.fetchDelivered(this[HANDLE]));
2665
+ }
2666
+
2667
+ /** The pieces ordered and not yet taken, a copy. WEIDA_EINVAL while a
2668
+ * `next` is pending. */
2669
+ inFlight() {
2670
+ return adopt(Bits, native.fetchInFlight(this[HANDLE]));
2671
+ }
2672
+
2673
+ /** The next verified piece, a frozen `{ index, bytes }` copied out at
2674
+ * once, or null once every piece is held. Rejects with
2675
+ * WEIDA_ENOT_CONNECTED when no seeder is left that has not failed this
2676
+ * fetch; connecting one and calling again resumes. Taking a piece frees
2677
+ * its order's credit, so a caller that takes pieces only as fast as it
2678
+ * stores them is the fetch's backpressure. An aborted call leaves its
2679
+ * read in flight for the next one. */
2680
+ async next({ signal } = {}) {
2681
+ const handle = this[HANDLE];
2682
+ const piece = await call(
2683
+ this,
2684
+ () => native.fetchNext(handle),
2685
+ () => native.fetchFd(handle),
2686
+ signal,
2687
+ );
2688
+ return piece === null ? null : Object.freeze(piece);
2689
+ }
2690
+
2691
+ /** Every piece until the end; the fetch stays alive, so its list and
2692
+ * maps can be read after the loop. */
2693
+ async *[Symbol.asyncIterator]() {
2694
+ for (let piece = await this.next(); piece !== null; piece = await this.next()) {
2695
+ yield piece;
2696
+ }
2697
+ }
2698
+ }
2699
+
2700
+ /** The bound half of SWARM, serving one endpoint path. */
2701
+ export class Seeder extends Handle {
2702
+ constructor() {
2703
+ super();
2704
+ noConstructor("Seeder", "Listener.seeder()");
2705
+ }
2706
+
2707
+ path() {
2708
+ return native.seederPath(this[HANDLE]);
2709
+ }
2710
+
2711
+ /** The next fetch's session, always with a well-formed open: admit it
2712
+ * from its blob and metadata, or decline it. */
2713
+ async accept({ signal } = {}) {
2714
+ const handle = this[HANDLE];
2715
+ const session = await call(
2716
+ this,
2717
+ () => native.seederAccept(handle),
2718
+ () => native.seederFd(handle),
2719
+ signal,
2720
+ );
2721
+ return adopt(SeedSession, session);
2722
+ }
2723
+ }
2724
+
2725
+ /** One fetch's session with this seeder, about one blob. Its calls are one
2726
+ * at a time (WEIDA_EINVAL otherwise), and each order is answered before
2727
+ * the next `next`. Dropping it ends the session. */
2728
+ export class SeedSession extends Handle {
2729
+ constructor() {
2730
+ super();
2731
+ noConstructor("SeedSession", "Seeder.accept()");
2732
+ }
2733
+
2734
+ /** The blob this session is about. */
2735
+ blob() {
2736
+ return adopt(Blob, native.seedSessionBlob(this[HANDLE]));
2737
+ }
2738
+
2739
+ /** The exchange's metadata, frozen, as `IncomingRequest.meta`. */
2740
+ meta() {
2741
+ return freezeMeta(native.seedSessionMeta(this[HANDLE]));
2742
+ }
2743
+
2744
+ /** The number of the connection this session arrived on, a BigInt. */
2745
+ connection() {
2746
+ return native.seedSessionConnection(this[HANDLE]);
2747
+ }
2748
+
2749
+ /** The next order, a frozen `{ kind, index }` (`kind` one of
2750
+ * "WEIDA_ASK_LIST", "WEIDA_ASK_HAVE", "WEIDA_ASK_PIECE", `index` the piece
2751
+ * of a PIECE and 0 otherwise), or null once the fetch finished its
2752
+ * orders. Waits as long as the fetch sends none. Rejects with
2753
+ * WEIDA_EPROTOCOL while the order before is unanswered. An aborted call
2754
+ * leaves its read in flight for the next one. */
2755
+ async next({ signal } = {}) {
2756
+ const handle = this[HANDLE];
2757
+ const ask = await call(
2758
+ this,
2759
+ () => native.seedSessionNext(handle),
2760
+ () => native.seedSessionFd(handle),
2761
+ signal,
2762
+ );
2763
+ return ask === null ? null : Object.freeze(ask);
2764
+ }
2765
+
2766
+ /** Answers LIST with the blob's piece list. */
2767
+ async answerList(list, { signal } = {}) {
2768
+ await op(
2769
+ this,
2770
+ () => native.seedSessionAnswerList(this[HANDLE], handleOf(list, PieceList)),
2771
+ native.seedSessionAnswerListOpWait,
2772
+ native.seedSessionAnswerListOpFd,
2773
+ signal,
2774
+ );
2775
+ }
2776
+
2777
+ /** Answers HAVE with the pieces this seeder can serve now, a `Bits`. */
2778
+ async answerHave(have, { signal } = {}) {
2779
+ await op(
2780
+ this,
2781
+ () => native.seedSessionAnswerHave(this[HANDLE], handleOf(have, Bits)),
2782
+ native.seedSessionAnswerHaveOpWait,
2783
+ native.seedSessionAnswerHaveOpFd,
2784
+ signal,
2785
+ );
2786
+ }
2787
+
2788
+ /** Answers PIECE with the piece's bytes, copied; the fetch checks them
2789
+ * against the root. */
2790
+ async answerPiece(data, { signal } = {}) {
2791
+ const piece = bytes(data);
2792
+ await op(
2793
+ this,
2794
+ () => native.seedSessionAnswerPiece(this[HANDLE], piece),
2795
+ native.seedSessionAnswerPieceOpWait,
2796
+ native.seedSessionAnswerPieceOpFd,
2797
+ signal,
2798
+ );
2799
+ }
2800
+
2801
+ /** Ends the session: a blob this seeder does not serve or a piece it
2802
+ * does not hold; the fetch drops this seeder for the blob. Consumes the
2803
+ * session. */
2804
+ async decline({ signal } = {}) {
2805
+ await op(
2806
+ this,
2807
+ () => native.seedSessionDecline(consume(this)),
2808
+ native.seedSessionDeclineOpWait,
2809
+ native.seedSessionDeclineOpFd,
2810
+ signal,
2811
+ );
2812
+ }
2813
+ }
2814
+
2815
+ /** One member of a BUS: accepts on its path and dials the other members;
2816
+ * a message goes to every member it dialled, never to itself, and nothing
2817
+ * is relayed. */
2818
+ export class BusMember extends Handle {
2819
+ constructor() {
2820
+ super();
2821
+ noConstructor("BusMember", "Listener.bus()");
2822
+ }
2823
+
2824
+ path() {
2825
+ return native.busMemberPath(this[HANDLE]);
2826
+ }
2827
+
2828
+ /** Joins the member at `url`; the runtime redials it when the connection
2829
+ * is lost. */
2830
+ async connect(url, { signal } = {}) {
2831
+ await op(
2832
+ this,
2833
+ () => native.busMemberConnect(this[HANDLE], url),
2834
+ native.busMemberConnectOpWait,
2835
+ native.busMemberConnectOpFd,
2836
+ signal,
2837
+ );
2838
+ }
2839
+
2840
+ /** The number of members this one joined. */
2841
+ peerCount() {
2842
+ return native.busMemberPeerCount(this[HANDLE]);
2843
+ }
2844
+
2845
+ /** A frozen array with one frozen record per live connection, as
2846
+ * `Requester.connectionStats`. */
2847
+ connectionStats() {
2848
+ return Object.freeze(native.busMemberConnectionStats(this[HANDLE]).map(freezeStats));
2849
+ }
2850
+
2851
+ /** Sends `body` (a string is sent as UTF-8) to every member this one
2852
+ * dialled: how many it reached. A member that cannot take a copy loses
2853
+ * it, counted by `dropped()`. */
2854
+ async send(body, { signal } = {}) {
2855
+ const data = bytes(body);
2856
+ return op(
2857
+ this,
2858
+ () => native.busMemberSend(this[HANDLE], data),
2859
+ native.busMemberSendOpWait,
2860
+ native.busMemberSendOpFd,
2861
+ signal,
2862
+ );
2863
+ }
2864
+
2865
+ /** `send` with metadata (a `TransferMeta` object, or null), copied when
2866
+ * the send starts. */
2867
+ async sendWith(meta, body, { signal } = {}) {
2868
+ const data = bytes(body);
2869
+ return op(
2870
+ this,
2871
+ () => withTransferMeta(meta, (m) => native.busMemberSendWith(this[HANDLE], m, data)),
2872
+ native.busMemberSendOpWait,
2873
+ native.busMemberSendOpFd,
2874
+ signal,
2875
+ );
2876
+ }
2877
+
2878
+ /** The next message from another member, as soon as its header
2879
+ * arrives. */
2880
+ async recv({ signal } = {}) {
2881
+ const handle = this[HANDLE];
2882
+ const transfer = await call(
2883
+ this,
2884
+ () => native.busMemberRecv(handle),
2885
+ () => native.busMemberFd(handle),
2886
+ signal,
2887
+ );
2888
+ return adopt(IncomingTransfer, transfer);
2889
+ }
2890
+
2891
+ /** The copies that never reached a member, a BigInt. */
2892
+ dropped() {
2893
+ return native.busMemberDropped(this[HANDLE]);
2894
+ }
2895
+ }
2896
+
2897
+ /** The sending half of RADIO/DISH, serving one endpoint path. Dropping it
2898
+ * releases the path. */
2899
+ export class Radio extends Handle {
2900
+ constructor() {
2901
+ super();
2902
+ noConstructor("Radio", "Listener.radio()");
2903
+ }
2904
+
2905
+ path() {
2906
+ return native.radioPath(this[HANDLE]);
2907
+ }
2908
+
2909
+ /** Opens the next segment on `topic` to the dishes joined now, on
2910
+ * `terms` `{ maxAgeMs?, priority?, followsUpstream? }`. Opening it resets
2911
+ * every unfinished layer of the topic's previous segment. */
2912
+ segment(topic, terms = null) {
2913
+ const handle = withFields(terms, native.segmentTermsNew, SEGMENT_FIELDS, "SegmentTerms", (t) =>
2914
+ native.radioSegment(this[HANDLE], topic, t),
2915
+ );
2916
+ return adopt(Segment, handle);
2917
+ }
2918
+
2919
+ /** Sends `payload` (a string is sent as UTF-8) as one datagram segment to
2920
+ * every dish joined to `topic`: how many it was handed to. A dish that
2921
+ * carries no datagrams counts a `noDatagrams` drop. */
2922
+ datagram(topic, payload) {
2923
+ return native.radioDatagram(this[HANDLE], topic, bytes(payload));
2924
+ }
2925
+
2926
+ /** The dishes joined. */
2927
+ dishCount() {
2928
+ return native.radioDishCount(this[HANDLE]);
2929
+ }
2930
+
2931
+ /** The copies dropped, over topics and causes, a BigInt. */
2932
+ dropped() {
2933
+ return native.radioDropped(this[HANDLE]);
2934
+ }
2935
+
2936
+ /** What was dropped on `topic`, a frozen record as
2937
+ * `Publisher.droppedOn`, or null when nothing was. */
2938
+ droppedOn(topic) {
2939
+ const [record] = native.radioDroppedOn(this[HANDLE], topic);
2940
+ return record === undefined ? null : Object.freeze(record);
2941
+ }
2942
+
2943
+ /** A frozen array with one frozen record per topic that lost a copy. */
2944
+ drops() {
2945
+ return Object.freeze(native.radioDrops(this[HANDLE]).map((r) => Object.freeze(r)));
2946
+ }
2947
+
2948
+ /** Withdraws the joins on `filter` of the dishes whose key is
2949
+ * `fingerprint` (`sha256:` text): how many went. */
2950
+ evict(fingerprint, filter) {
2951
+ return native.radioEvict(this[HANDLE], fingerprint, filter);
2952
+ }
2953
+
2954
+ /** A frozen array with one frozen `{ peerFingerprint, subscriberBudget,
2955
+ * subscriberQueue, superseded, expired, tooLarge, noDatagrams, layersCut }`
2956
+ * per joined dish connection, the counts BigInts. */
2957
+ dishDrops() {
2958
+ return Object.freeze(native.radioDishDrops(this[HANDLE]).map((r) => Object.freeze(r)));
2959
+ }
2960
+ }
2961
+
2962
+ /** One open segment of a radio: layers written without waiting. Dropping it
2963
+ * without `finish()` resets every unfinished layer. */
2964
+ export class Segment extends Handle {
2965
+ constructor() {
2966
+ super();
2967
+ noConstructor("Segment", "Radio.segment()");
2968
+ }
2969
+
2970
+ /** Its number on its topic, a BigInt; the first is 0n. */
2971
+ number() {
2972
+ return native.segmentNumber(this[HANDLE]);
2973
+ }
2974
+
2975
+ topic() {
2976
+ return native.segmentTopic(this[HANDLE]);
2977
+ }
2978
+
2979
+ /** Writes `chunk` to layer 0: how many copies took it. */
2980
+ write(chunk) {
2981
+ return native.segmentWrite(this[HANDLE], bytes(chunk));
2982
+ }
2983
+
2984
+ /** Writes `chunk` to `layer` (0 to 15) without waiting: how many copies
2985
+ * took it. A copy without room loses this layer and every one above it;
2986
+ * a copy capped below `layer` is skipped, which is not a drop. */
2987
+ writeLayer(layer, chunk) {
2988
+ return native.segmentWriteLayer(this[HANDLE], layer, bytes(chunk));
2989
+ }
2990
+
2991
+ /** Finishes `layer`; a later write to it throws WEIDA_ERUNTIME. */
2992
+ finishLayer(layer) {
2993
+ native.segmentFinishLayer(this[HANDLE], layer);
2994
+ }
2995
+
2996
+ /** Finishes every open layer: how many copies still hold layer 0.
2997
+ * Consumes the segment. */
2998
+ finish() {
2999
+ return native.segmentFinish(consume(this));
3000
+ }
3001
+ }
3002
+
3003
+ /** The receiving half of RADIO/DISH. */
3004
+ export class Dish extends Handle {
3005
+ constructor() {
3006
+ super();
3007
+ noConstructor("Dish", "Runtime.dish()");
3008
+ }
3009
+
3010
+ /** Dials a radio at `url` and joins there every filter joined so far; a
3011
+ * redial joins again, and what was sent in between is gone. */
3012
+ async connect(url, { signal } = {}) {
3013
+ await op(
3014
+ this,
3015
+ () => native.dishConnect(this[HANDLE], url),
3016
+ native.dishConnectOpWait,
3017
+ native.dishConnectOpFd,
3018
+ signal,
3019
+ );
3020
+ }
3021
+
3022
+ /** The number of connected radios. */
3023
+ peerCount() {
3024
+ return native.dishPeerCount(this[HANDLE]);
3025
+ }
3026
+
3027
+ /** A frozen array with one frozen record per live connection, as
3028
+ * `Requester.connectionStats`. */
3029
+ connectionStats() {
3030
+ return Object.freeze(native.dishConnectionStats(this[HANDLE]).map(freezeStats));
3031
+ }
3032
+
3033
+ /** A new reader of this dish's peer events, from now on. */
3034
+ events() {
3035
+ return adopt(PeerEvents, native.dishEvents(this[HANDLE]));
3036
+ }
3037
+
3038
+ /** Joins every topic `filter` matches on `terms` `{ maxAgeMs?, maxLayer? }`.
3039
+ * A `maxLayer` of 16 or above rejects with WEIDA_ELIMIT_EXCEEDED, a
3040
+ * filter outside the grammar with WEIDA_EPROTOCOL. */
3041
+ async join(filter, terms = null, { signal } = {}) {
3042
+ await op(
3043
+ this,
3044
+ () =>
3045
+ withFields(terms, native.joinTermsNew, JOIN_FIELDS, "JoinTerms", (t) =>
3046
+ native.dishJoin(this[HANDLE], filter, t),
3047
+ ),
3048
+ native.dishJoinOpWait,
3049
+ native.dishJoinOpFd,
3050
+ signal,
3051
+ );
3052
+ }
3053
+
3054
+ /** Leaves `filter`; an unknown one is ignored. */
3055
+ async leave(filter, { signal } = {}) {
3056
+ await op(
3057
+ this,
3058
+ () => native.dishLeave(this[HANDLE], filter),
3059
+ native.dishLeaveOpWait,
3060
+ native.dishLeaveOpFd,
3061
+ signal,
3062
+ );
3063
+ }
3064
+
3065
+ /** The next segment, a frozen `{ kind: "WEIDA_RECEIVED_SEGMENT", transfer }`
3066
+ * whose topic, segment and layer are on `transfer.meta()`, or a frozen
3067
+ * `{ kind: "WEIDA_RECEIVED_DATAGRAM", topic, segment, payload }`. */
3068
+ async recv({ signal } = {}) {
3069
+ const handle = this[HANDLE];
3070
+ const r = await call(
3071
+ this,
3072
+ () => native.dishRecv(handle),
3073
+ () => native.dishFd(handle),
3074
+ signal,
3075
+ );
3076
+ if (r.kind === "WEIDA_RECEIVED_SEGMENT") {
3077
+ return Object.freeze({ kind: r.kind, transfer: adopt(IncomingTransfer, r.transfer) });
3078
+ }
3079
+ return Object.freeze(r);
3080
+ }
3081
+
3082
+ /** Segments discarded because a newer one on their topic was already
3083
+ * delivered, a BigInt. */
3084
+ stale() {
3085
+ return native.dishStale(this[HANDLE]);
3086
+ }
3087
+
3088
+ /** Segments discarded because the receive queue was full, a BigInt. */
3089
+ overflow() {
3090
+ return native.dishOverflow(this[HANDLE]);
3091
+ }
3092
+ }
3093
+
3094
+ /** The relay a listener serves on `/.weida/relay`. Dropping it closes every
3095
+ * registration's connection, so the hidden peers redial. */
3096
+ export class Relay extends Handle {
3097
+ constructor() {
3098
+ super();
3099
+ noConstructor("Relay", "Listener.relay()");
3100
+ }
3101
+
3102
+ /** The next registration or tunnel asking to be served. An anonymous
3103
+ * registration and a tunnel to a key nobody registered are refused before
3104
+ * they get here. */
3105
+ async accept({ signal } = {}) {
3106
+ const handle = this[HANDLE];
3107
+ const request = await call(
3108
+ this,
3109
+ () => native.relayAccept(handle),
3110
+ () => native.relayFd(handle),
3111
+ signal,
3112
+ );
3113
+ return adopt(RelayRequest, request);
3114
+ }
3115
+ }
3116
+
3117
+ /** A registration or a tunnel asking the relay to be served. Dropped
3118
+ * unanswered, it answers NO_REPLY. */
3119
+ export class RelayRequest extends Handle {
3120
+ constructor() {
3121
+ super();
3122
+ noConstructor("RelayRequest", "Relay.accept()");
3123
+ }
3124
+
3125
+ /** The key (`sha256:` text) the asker proved, null for an anonymous
3126
+ * dialler; always a key for a registration. */
3127
+ peerFingerprint() {
3128
+ return native.relayRequestPeerFingerprint(this[HANDLE]);
3129
+ }
3130
+
3131
+ /** null for a registration; for a tunnel, the key of the peer it reaches. */
3132
+ target() {
3133
+ return native.relayRequestTarget(this[HANDLE]);
3134
+ }
3135
+
3136
+ /** Serves it. A registration replaces one of the same key on another
3137
+ * connection. Consumes the request. */
3138
+ async admit({ signal } = {}) {
3139
+ await op(
3140
+ this,
3141
+ () => native.relayRequestAdmit(consume(this)),
3142
+ native.relayRequestAdmitOpWait,
3143
+ native.relayRequestAdmitOpFd,
3144
+ signal,
3145
+ );
3146
+ }
3147
+
3148
+ /** Refuses it: the asker fails with WEIDA_EREJECTED. Consumes the
3149
+ * request. */
3150
+ async refuse({ signal } = {}) {
3151
+ await op(
3152
+ this,
3153
+ () => native.relayRequestRefuse(consume(this)),
3154
+ native.relayRequestRefuseOpWait,
3155
+ native.relayRequestRefuseOpFd,
3156
+ signal,
3157
+ );
3158
+ }
3159
+ }
3160
+
3161
+ /** One accepted request: its body and the right to reply. */
3162
+ export class IncomingRequest extends Handle {
3163
+ constructor() {
3164
+ super();
3165
+ noConstructor("IncomingRequest", "Replier.accept() or Respondent.accept()");
3166
+ }
3167
+
3168
+ meta() {
3169
+ return freezeMeta(native.incomingRequestMeta(this[HANDLE]));
3170
+ }
3171
+
3172
+ /** The number of the connection this request arrived on, a BigInt. */
3173
+ connection() {
3174
+ return native.incomingRequestConnection(this[HANDLE]);
3175
+ }
3176
+
3177
+ /** Detaches the body, to be read like a socket; once. */
3178
+ takeBody() {
3179
+ return adopt(IncomingTransfer, native.incomingRequestTakeBody(this[HANDLE]));
3180
+ }
3181
+
3182
+ /** Opens the reply stream with `meta` (a `TransferMeta` object, or null).
3183
+ * Consumes the request. */
3184
+ async reply(meta, { signal } = {}) {
3185
+ const transfer = await op(
3186
+ this,
3187
+ () => withTransferMeta(meta, (m) => native.incomingRequestReply(consume(this), m)),
3188
+ native.incomingRequestReplyOpWait,
3189
+ native.incomingRequestReplyOpFd,
3190
+ signal,
3191
+ );
3192
+ return adopt(OutgoingTransfer, transfer);
3193
+ }
3194
+
3195
+ /** Refuses the exchange with `code`, a `WEIDA_ERROR_CODE_*` name, instead of
3196
+ * a reply: the requester's request rejects with the matching WeidaError.
3197
+ * Consumes the request; an unknown name is a TypeError and consumes
3198
+ * nothing. */
3199
+ async refuse(code, { signal } = {}) {
3200
+ const value = constant("WEIDA_ERROR_CODE_", code);
3201
+ await op(
3202
+ this,
3203
+ () => native.incomingRequestRefuse(consume(this), value),
3204
+ native.incomingRequestRefuseOpWait,
3205
+ native.incomingRequestRefuseOpFd,
3206
+ signal,
3207
+ );
3208
+ }
3209
+
3210
+ /** Resolves when the requester stops wanting a reply, a finished reply was
3211
+ * read whole, or the connection is gone. Started at the call, so take it
3212
+ * before `reply` or `refuse`; it outlives the request. A pending one keeps
3213
+ * the process alive: abort it through `signal` once it no longer matters. */
3214
+ canceled({ signal } = {}) {
3215
+ return op(
3216
+ { [WAITS]: new Set() },
3217
+ () => native.incomingRequestCanceled(this[HANDLE]),
3218
+ native.incomingRequestCanceledOpWait,
3219
+ native.incomingRequestCanceledOpFd,
3220
+ signal,
3221
+ );
3222
+ }
3223
+ }
3224
+
3225
+ /** An inbound transfer: a payload read like a socket. */
3226
+ export class IncomingTransfer extends Handle {
3227
+ constructor() {
3228
+ super();
3229
+ noConstructor("IncomingTransfer", "Requester.request(), ReplyStream.recv() or IncomingRequest.takeBody()");
3230
+ }
3231
+
3232
+ meta() {
3233
+ return freezeMeta(native.incomingTransferMeta(this[HANDLE]));
3234
+ }
3235
+
3236
+ /** The number of the connection this transfer arrived on, a BigInt. */
3237
+ connection() {
3238
+ return native.incomingTransferConnection(this[HANDLE]);
3239
+ }
3240
+
3241
+ /** A new reporter for the cursor levels the sender ordered, on every
3242
+ * call, so take one; null when it ordered none. Take it before
3243
+ * `collect()`: it outlives the transfer. */
3244
+ reporter() {
3245
+ const r = native.incomingTransferReporter(this[HANDLE]);
3246
+ return r === null ? null : adopt(Reporter, r);
3247
+ }
3248
+
3249
+ /** Up to `cap` bytes; an empty array at the end of the payload. */
3250
+ async read(cap = 65536, { signal } = {}) {
3251
+ const handle = this[HANDLE];
3252
+ return call(
3253
+ this,
3254
+ () => native.incomingTransferRead(handle, cap),
3255
+ () => native.incomingTransferFd(handle),
3256
+ signal,
3257
+ );
3258
+ }
3259
+
3260
+ /** The whole remaining payload, refusing more than `maxBytes`. Consumes
3261
+ * the transfer. */
3262
+ async collect(maxBytes, { signal } = {}) {
3263
+ const message = await op(
3264
+ this,
3265
+ () => native.incomingTransferCollect(consume(this), maxBytes),
3266
+ native.incomingTransferCollectOpWait,
3267
+ native.incomingTransferCollectOpFd,
3268
+ signal,
3269
+ );
3270
+ return Object.freeze({ payload: message.payload, meta: freezeMeta(message.meta) });
3271
+ }
3272
+ }
3273
+
3274
+ /** An outbound transfer: a payload written like a socket. */
3275
+ export class OutgoingTransfer extends Handle {
3276
+ constructor() {
3277
+ super();
3278
+ noConstructor("OutgoingTransfer", "IncomingRequest.reply() or Requester.open()");
3279
+ }
3280
+
3281
+ /** Writes some of `data`; resolves with how many bytes flow control took. */
3282
+ async write(data, { signal } = {}) {
3283
+ const buffer = bytes(data);
3284
+ const handle = this[HANDLE];
3285
+ return call(
3286
+ this,
3287
+ () => native.outgoingTransferWrite(handle, buffer),
3288
+ () => native.outgoingTransferFd(handle),
3289
+ signal,
3290
+ );
3291
+ }
3292
+
3293
+ /** Writes all of `data`: the loop the C ABI leaves to the caller (R3c). */
3294
+ async writeAll(data, options) {
3295
+ let rest = bytes(data);
3296
+ while (rest.length > 0) {
3297
+ rest = rest.subarray(await this.write(rest, options));
3298
+ }
3299
+ }
3300
+
3301
+ /** The trace context propagated with this transfer, as `traceparent`
3302
+ * text, or null; a reply inherits its request's. */
3303
+ trace() {
3304
+ return native.outgoingTransferTrace(this[HANDLE]);
3305
+ }
3306
+
3307
+ /** Orders this transfer against the other streams of its connection:
3308
+ * higher is sent first, the default is 0. A no-op on local transports. */
3309
+ setPriority(priority) {
3310
+ native.outgoingTransferSetPriority(this[HANDLE], priority);
3311
+ }
3312
+
3313
+ /** Resets the transfer with WEIDA_ECANCELED for the reader unless its
3314
+ * bytes are all acknowledged within `ms`; a write or finish past it
3315
+ * throws WEIDA_EEXPIRED. */
3316
+ expireAfterMs(ms) {
3317
+ native.outgoingTransferExpireAfterMs(this[HANDLE], ms);
3318
+ }
3319
+
3320
+ /** The queue of cursor sets the receiver reports for this transfer, the
3321
+ * first time; null after that and when its metadata ordered no levels.
3322
+ * Take it before `finish()`: it outlives the transfer, since Accepted
3323
+ * arrives after the payload's end. */
3324
+ cursors() {
3325
+ const c = native.outgoingTransferCursors(this[HANDLE]);
3326
+ return c === null ? null : adopt(Cursors, c);
3327
+ }
3328
+
3329
+ /** Ends the payload and returns its delivery receipt. Consumes the
3330
+ * transfer. */
3331
+ finish() {
3332
+ return adopt(Delivery, native.outgoingTransferFinish(consume(this)));
3333
+ }
3334
+ }
3335
+
3336
+ /** The receipt of a finished outbound transfer. */
3337
+ export class Delivery extends Handle {
3338
+ constructor() {
3339
+ super();
3340
+ noConstructor("Delivery", "OutgoingTransfer.finish()");
3341
+ }
3342
+
3343
+ /** Waits for the peer's transport to acknowledge the whole payload.
3344
+ * Consumes the receipt. */
3345
+ async delivered({ signal } = {}) {
3346
+ await op(
3347
+ this,
3348
+ () => native.deliveryDelivered(consume(this)),
3349
+ native.deliveryDeliveredOpWait,
3350
+ native.deliveryDeliveredOpFd,
3351
+ signal,
3352
+ );
3353
+ }
3354
+ }