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/errors.js +32 -0
- package/lib/index.d.ts +1211 -0
- package/lib/index.js +3354 -0
- package/lib/native.js +18 -0
- package/lib/wait.js +82 -0
- package/package.json +30 -4
- package/prebuilds/darwin-arm64/libweida.dylib +0 -0
- package/prebuilds/darwin-arm64/weida.node +0 -0
- package/prebuilds/linux-arm64/libweida.so +0 -0
- package/prebuilds/linux-arm64/weida.node +0 -0
- package/prebuilds/linux-x64/libweida.so +0 -0
- package/prebuilds/linux-x64/weida.node +0 -0
- package/prebuilds/win32-x64/weida.dll +0 -0
- package/prebuilds/win32-x64/weida.node +0 -0
- package/README.md +0 -3
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
|
+
}
|