@bytecodealliance/preview3-shim 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/nodejs/cli.d.ts +51 -0
  2. package/dist/nodejs/cli.js +106 -0
  3. package/dist/nodejs/clocks.d.ts +55 -0
  4. package/dist/nodejs/clocks.js +83 -0
  5. package/dist/nodejs/filesystem/descriptor.d.ts +443 -0
  6. package/dist/nodejs/filesystem/descriptor.js +1010 -0
  7. package/dist/nodejs/filesystem/error.d.ts +61 -0
  8. package/dist/nodejs/filesystem/error.js +86 -0
  9. package/dist/nodejs/filesystem/utils.d.ts +8 -0
  10. package/dist/nodejs/filesystem/utils.js +25 -0
  11. package/dist/nodejs/filesystem.js +2 -0
  12. package/dist/nodejs/finalization.d.ts +2 -0
  13. package/{lib → dist}/nodejs/finalization.js +15 -21
  14. package/dist/nodejs/future.d.ts +98 -0
  15. package/dist/nodejs/future.js +176 -0
  16. package/dist/nodejs/http/client.d.ts +22 -0
  17. package/dist/nodejs/http/client.js +138 -0
  18. package/dist/nodejs/http/error.d.ts +18 -0
  19. package/dist/nodejs/http/error.js +62 -0
  20. package/dist/nodejs/http/fields.d.ts +144 -0
  21. package/dist/nodejs/http/fields.js +327 -0
  22. package/dist/nodejs/http/request.d.ts +256 -0
  23. package/dist/nodejs/http/request.js +606 -0
  24. package/dist/nodejs/http/response.d.ts +82 -0
  25. package/dist/nodejs/http/response.js +202 -0
  26. package/dist/nodejs/http/server.d.ts +13 -0
  27. package/dist/nodejs/http/server.js +127 -0
  28. package/dist/nodejs/http.d.ts +19 -0
  29. package/{lib → dist}/nodejs/http.js +13 -18
  30. package/dist/nodejs/index.js +8 -0
  31. package/dist/nodejs/random.d.ts +5 -0
  32. package/{lib → dist}/nodejs/random.js +2 -8
  33. package/dist/nodejs/sockets/address.d.ts +78 -0
  34. package/dist/nodejs/sockets/address.js +120 -0
  35. package/dist/nodejs/sockets/error.d.ts +83 -0
  36. package/dist/nodejs/sockets/error.js +120 -0
  37. package/dist/nodejs/sockets/ip-name-lookup.d.ts +18 -0
  38. package/dist/nodejs/sockets/ip-name-lookup.js +84 -0
  39. package/dist/nodejs/sockets/tcp.d.ts +333 -0
  40. package/dist/nodejs/sockets/tcp.js +723 -0
  41. package/dist/nodejs/sockets/udp.d.ts +224 -0
  42. package/dist/nodejs/sockets/udp.js +475 -0
  43. package/dist/nodejs/sockets.d.ts +11 -0
  44. package/{lib → dist}/nodejs/sockets.js +2 -5
  45. package/dist/nodejs/stream.d.ts +161 -0
  46. package/dist/nodejs/stream.js +388 -0
  47. package/dist/nodejs/workers/cli-worker.d.ts +1 -0
  48. package/dist/nodejs/workers/cli-worker.js +41 -0
  49. package/dist/nodejs/workers/filesystem-worker.d.ts +1 -0
  50. package/dist/nodejs/workers/filesystem-worker.js +102 -0
  51. package/dist/nodejs/workers/http-worker.d.ts +1 -0
  52. package/dist/nodejs/workers/http-worker.js +355 -0
  53. package/dist/nodejs/workers/resource-worker.d.ts +13 -0
  54. package/dist/nodejs/workers/resource-worker.js +152 -0
  55. package/dist/nodejs/workers/tcp-worker.d.ts +2 -0
  56. package/dist/nodejs/workers/tcp-worker.js +318 -0
  57. package/dist/nodejs/workers/udp-worker.d.ts +1 -0
  58. package/dist/nodejs/workers/udp-worker.js +156 -0
  59. package/package.json +20 -12
  60. package/types/interfaces/wasi-cli-environment.d.ts +3 -3
  61. package/types/interfaces/wasi-cli-exit.d.ts +12 -1
  62. package/types/interfaces/wasi-cli-run.d.ts +1 -1
  63. package/types/interfaces/wasi-cli-stderr.d.ts +3 -3
  64. package/types/interfaces/wasi-cli-stdin.d.ts +5 -5
  65. package/types/interfaces/wasi-cli-stdout.d.ts +3 -3
  66. package/types/interfaces/wasi-cli-terminal-input.d.ts +1 -1
  67. package/types/interfaces/wasi-cli-terminal-output.d.ts +1 -1
  68. package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
  69. package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
  70. package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
  71. package/types/interfaces/wasi-cli-types.d.ts +5 -5
  72. package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +3 -3
  73. package/types/interfaces/wasi-clocks-system-clock.d.ts +6 -6
  74. package/types/interfaces/wasi-clocks-types.d.ts +1 -1
  75. package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
  76. package/types/interfaces/wasi-filesystem-types.d.ts +74 -74
  77. package/types/interfaces/wasi-http-client.d.ts +1 -1
  78. package/types/interfaces/wasi-http-handler.d.ts +1 -1
  79. package/types/interfaces/wasi-http-types.d.ts +34 -34
  80. package/types/interfaces/wasi-random-insecure-seed.d.ts +5 -5
  81. package/types/interfaces/wasi-random-insecure.d.ts +5 -5
  82. package/types/interfaces/wasi-random-random.d.ts +5 -5
  83. package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +11 -11
  84. package/types/interfaces/wasi-sockets-types.d.ts +121 -121
  85. package/types/wasi-cli-command.d.ts +23 -23
  86. package/types/wasi-http-service.d.ts +14 -14
  87. package/lib/nodejs/cli.js +0 -127
  88. package/lib/nodejs/clocks.js +0 -100
  89. package/lib/nodejs/filesystem/descriptor.js +0 -1093
  90. package/lib/nodejs/filesystem/error.js +0 -92
  91. package/lib/nodejs/filesystem/utils.js +0 -19
  92. package/lib/nodejs/future.js +0 -190
  93. package/lib/nodejs/http/client.js +0 -161
  94. package/lib/nodejs/http/error.js +0 -71
  95. package/lib/nodejs/http/fields.js +0 -373
  96. package/lib/nodejs/http/request.js +0 -691
  97. package/lib/nodejs/http/response.js +0 -227
  98. package/lib/nodejs/http/server.js +0 -157
  99. package/lib/nodejs/sockets/address.js +0 -146
  100. package/lib/nodejs/sockets/error.js +0 -123
  101. package/lib/nodejs/sockets/ip-name-lookup.js +0 -93
  102. package/lib/nodejs/sockets/tcp.js +0 -801
  103. package/lib/nodejs/sockets/udp.js +0 -516
  104. package/lib/nodejs/stream.js +0 -437
  105. package/lib/nodejs/workers/cli-worker.js +0 -49
  106. package/lib/nodejs/workers/filesystem-worker.js +0 -119
  107. package/lib/nodejs/workers/http-worker.js +0 -417
  108. package/lib/nodejs/workers/resource-worker.js +0 -195
  109. package/lib/nodejs/workers/tcp-worker.js +0 -370
  110. package/lib/nodejs/workers/udp-worker.js +0 -194
  111. /package/{lib/nodejs/filesystem.js → dist/nodejs/filesystem.d.ts} +0 -0
  112. /package/{lib/nodejs/index.js → dist/nodejs/index.d.ts} +0 -0
@@ -0,0 +1,723 @@
1
+ import { StreamReader, readableByteStreamFromReader } from "../stream.js";
2
+ import { FutureReader } from "../future.js";
3
+ import { ResourceWorker } from "../workers/resource-worker.js";
4
+ import { SocketError } from "./error.js";
5
+ import { isWildcardIpAddress, isUnicastIpAddress, isMulticastIpAddress, isIpv4MappedAddress, isBindableIpAddress, IP_ADDRESS_FAMILY, } from "./address.js";
6
+ let WORKER = null;
7
+ const symbolDispose = Symbol.dispose || Symbol.for("dispose");
8
+ function worker() {
9
+ return (WORKER ??= new ResourceWorker(new URL("../workers/tcp-worker.js", import.meta.url)));
10
+ }
11
+ let TCP_CREATE_TOKEN = null;
12
+ function token() {
13
+ return (TCP_CREATE_TOKEN ??= Symbol("TcpCreateToken"));
14
+ }
15
+ function invalidRecv() {
16
+ const stream = new ReadableStream({
17
+ start(controller) {
18
+ controller.close();
19
+ },
20
+ });
21
+ const promise = Promise.reject(new SocketError("invalid-state"));
22
+ return [new StreamReader(stream, { preventCancel: false }), new FutureReader(promise)];
23
+ }
24
+ const STATE = {
25
+ UNBOUND: "unbound",
26
+ BOUND: "bound",
27
+ LISTENING: "listening",
28
+ CONNECTING: "connecting",
29
+ CONNECTED: "connected",
30
+ CLOSED: "closed",
31
+ };
32
+ /**
33
+ * Creates a new TCP socket
34
+ * WIT: createTcpSocket: func(address-family: ip-address-family) -> result<tcp-socket, error-code>
35
+ *
36
+ * @param {IP_ADDRESS_FAMILY} addressFamily - The IP address family (IPv4 or IPv6)
37
+ * @returns {TcpSocket>} A new TCP socket instance
38
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `addressFamily` is not IPV4 or IPV6
39
+ * @throws {SocketError} for other errors, payload.tag maps the low-level system error_
40
+ */
41
+ export function createTcpSocket(addressFamily) {
42
+ if (addressFamily !== IP_ADDRESS_FAMILY.IPV4 && addressFamily !== IP_ADDRESS_FAMILY.IPV6) {
43
+ throw new SocketError("invalid-argument");
44
+ }
45
+ try {
46
+ const result = worker().runSync({
47
+ op: "tcp-create",
48
+ family: addressFamily,
49
+ });
50
+ return TcpSocket._create(token(), addressFamily, result.socketId);
51
+ }
52
+ catch (error) {
53
+ throw SocketError.from(error);
54
+ }
55
+ }
56
+ export class TcpSocket {
57
+ #socketId = null;
58
+ #family = null;
59
+ #state = "unbound";
60
+ #sendStarted = false;
61
+ #receiveStarted = false;
62
+ #options = {
63
+ // defaults per https://nodejs.org/docs/latest/api/net.html#socketsetkeepaliveenable-initialdelay
64
+ keepAliveEnabled: false,
65
+ // Node.js doesn't give us the ability to detect the OS default,
66
+ // therefore we hardcode the default value instead of using the OS default,
67
+ // since we would never be able to report it as a return value otherwise.
68
+ // We could make this configurable as a global JCO implementation configuration
69
+ // instead.
70
+ keepAliveIdleTime: 7200000000000n,
71
+ // The following options are NOT configurable in Node.js!
72
+ // Any configurations set will respond correctly, but underneath retain
73
+ // system / Node.js defaults.
74
+ keepAliveInterval: 1000000000n,
75
+ keepAliveCount: 10,
76
+ hopLimit: 1,
77
+ receiveBufferSize: 65536n,
78
+ sendBufferSize: 65536n,
79
+ listenBacklogSize: 128n,
80
+ };
81
+ /**
82
+ * WIT static constructor for `wasi:sockets/types.tcp-socket`.
83
+ *
84
+ * WIT:
85
+ * ```
86
+ * create: static func(address-family: ip-address-family) -> result<tcp-socket, error-code>
87
+ * ```
88
+ *
89
+ * @static
90
+ * @param {IP_ADDRESS_FAMILY} addressFamily - The IP address family (IPv4 or IPv6)
91
+ * @returns {TcpSocket} A new TcpSocket instance
92
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `addressFamily` is not IPV4 or IPV6
93
+ * @throws {SocketError} for other socket creation errors
94
+ */
95
+ static create(addressFamily) {
96
+ return createTcpSocket(addressFamily);
97
+ }
98
+ /**
99
+ * Creates a new TCP socket
100
+ * WIT:
101
+ * ```
102
+ * constructor(address-family: ip-address-family)
103
+ * ```
104
+ *
105
+ * @static
106
+ * @param {IP_ADDRESS_FAMILY} addressFamily - The IP address family (IPv4 or IPv6)
107
+ * @param {number} socketId - The internal socket identifier
108
+ * @returns {TcpSocket} A new TcpSocket instance
109
+ * @private
110
+ */
111
+ static _create(t, addressFamily, socketId) {
112
+ if (t !== token()) {
113
+ throw new Error("Use createTcpSocket to create a Socket");
114
+ }
115
+ if (addressFamily !== IP_ADDRESS_FAMILY.IPV4 && addressFamily !== IP_ADDRESS_FAMILY.IPV6) {
116
+ throw new SocketError("invalid-argument");
117
+ }
118
+ const socket = new TcpSocket();
119
+ socket.#family = addressFamily;
120
+ socket.#socketId = socketId;
121
+ return socket;
122
+ }
123
+ /**
124
+ * Binds the socket to a local address
125
+ * WIT:
126
+ * ```
127
+ * bind: func(local-address: ip-socket-address) -> result<_, error-code>
128
+ * ```
129
+ *
130
+ * @param {Object} localAddress - The local address to bind to
131
+ * @returns {void}
132
+ * @throws {SocketError} with payload.tag 'invalid-state' if the socket is not in ~STATE.UNBOUND~
133
+ * @throws {SocketError} with payload.tag 'invalid-argument' if ~localAddress~ is invalid
134
+ * @throws {SocketError} for other errors, payload.tag maps the system error
135
+ */
136
+ bind(localAddress) {
137
+ if (this.#state !== STATE.UNBOUND) {
138
+ throw new SocketError("invalid-state");
139
+ }
140
+ if (!this.#isValidLocalAddress(localAddress)) {
141
+ throw new SocketError("invalid-argument");
142
+ }
143
+ if (!isBindableIpAddress(localAddress)) {
144
+ throw new SocketError("address-not-bindable");
145
+ }
146
+ try {
147
+ worker().runSync({
148
+ op: "tcp-bind",
149
+ localAddress,
150
+ socketId: this.#socketId,
151
+ });
152
+ this.#state = STATE.BOUND;
153
+ }
154
+ catch (error) {
155
+ throw SocketError.from(error);
156
+ }
157
+ }
158
+ /**
159
+ * Connects to a remote endpoint
160
+ * WIT:
161
+ * ```
162
+ * connect: async func(remote-address: ip-socket-address) -> result<_, error-code>
163
+ * ```
164
+ *
165
+ * @async
166
+ * @param {Object} remoteAddress - The remote address to connect to
167
+ * @returns {Promise<void>}
168
+ * @throws {SocketError} with payload.tag 'invalid-state' if socket is CONNECTING, CONNECTED, or LISTENING
169
+ * @throws {SocketError} with payload.tag 'invalid-argument' if ~remoteAddress~ is invalid or port == 0
170
+ * @throws {SocketError} for other errors, payload.tag maps the system error
171
+ */
172
+ async connect(remoteAddress) {
173
+ if (this.#state === STATE.CONNECTING ||
174
+ this.#state === STATE.CONNECTED ||
175
+ this.#state === STATE.LISTENING) {
176
+ throw new SocketError("invalid-state");
177
+ }
178
+ if (!this.#isValidRemoteAddress(remoteAddress)) {
179
+ throw new SocketError("invalid-argument");
180
+ }
181
+ this.#state = STATE.CONNECTING;
182
+ try {
183
+ await worker().run({
184
+ op: "tcp-connect",
185
+ remoteAddress,
186
+ socketId: this.#socketId,
187
+ });
188
+ this.#state = STATE.CONNECTED;
189
+ }
190
+ catch (error) {
191
+ this.#state = STATE.CLOSED;
192
+ throw SocketError.from(error);
193
+ }
194
+ }
195
+ /**
196
+ * Starts listening for connections
197
+ * WIT:
198
+ * ```
199
+ * listen: func() -> result<stream<tcp-socket>, error-code>
200
+ * ```
201
+ *
202
+ * @returns {StreamReader} A stream of incoming TCP socket connections
203
+ * @throws {SocketError} with payload.tag 'invalid-state' if socket is LISTENING or CONNECTED
204
+ * @throws {SocketError} for other errors, payload.tag maps the system error
205
+ */
206
+ listen() {
207
+ if (this.#state === STATE.LISTENING || this.#state === STATE.CONNECTED) {
208
+ throw new SocketError("invalid-state");
209
+ }
210
+ try {
211
+ // Convert incoming connections from worker to TcpSocket instances
212
+ const listener = this;
213
+ const transform = new TransformStream({
214
+ transform({ family, socketId }, controller) {
215
+ const socket = TcpSocket._create(token(), family, socketId);
216
+ socket.#state = STATE.CONNECTED;
217
+ socket.#options = { ...listener.#options };
218
+ controller.enqueue(socket);
219
+ },
220
+ });
221
+ worker().runSync({
222
+ op: "tcp-listen",
223
+ socketId: this.#socketId,
224
+ stream: transform.writable,
225
+ }, [transform.writable]);
226
+ this.#state = STATE.LISTENING;
227
+ return new StreamReader(transform.readable);
228
+ }
229
+ catch (error) {
230
+ this.#state = STATE.CLOSED;
231
+ throw SocketError.from(error);
232
+ }
233
+ }
234
+ /**
235
+ * Sends data to the connected peer
236
+ * WIT:
237
+ * ```
238
+ * send: async func(data: stream<u8>) -> result<_, error-code>
239
+ * ```
240
+ *
241
+ * @async
242
+ * @param {object} data - The byte stream to send.
243
+ * @returns {Promise<void>}
244
+ * @throws {SocketError} With payload.tag 'invalid-state' if socket is not CONNECTED
245
+ * @throws {SocketError} With payload.tag 'invalid-argument' if `data` is not a readable byte stream
246
+ * @throws {SocketError} for other errors, payload.tag maps the system error
247
+ */
248
+ async send(data) {
249
+ if (this.#state !== STATE.CONNECTED) {
250
+ throw new SocketError("invalid-state");
251
+ }
252
+ if (this.#sendStarted) {
253
+ data?.[symbolDispose]?.();
254
+ throw new SocketError("invalid-state");
255
+ }
256
+ let stream;
257
+ try {
258
+ stream = readableByteStreamFromReader(data, { name: "tcp send data" });
259
+ }
260
+ catch (error) {
261
+ if (error instanceof TypeError) {
262
+ throw new SocketError("invalid-argument", error.message, undefined, { cause: error });
263
+ }
264
+ throw error;
265
+ }
266
+ this.#sendStarted = true;
267
+ try {
268
+ // Transfer the stream to the worker
269
+ await worker().run({
270
+ op: "tcp-send",
271
+ socketId: this.#socketId,
272
+ stream,
273
+ }, [stream]);
274
+ }
275
+ catch (error) {
276
+ throw SocketError.from(error);
277
+ }
278
+ }
279
+ /**
280
+ * Receives data from the connected peer
281
+ * WIT:
282
+ * ```
283
+ * receive: func() -> tuple<stream<u8>, future<result<_, error-code>>>
284
+ * ```
285
+ *
286
+ * @throws {SocketError} with payload.tag 'invalid-state' if socket is not CONNECTED
287
+ * @throws {SocketError} for other errors, payload.tag maps the system error
288
+ * @returns {{ stream: StreamReader, future: FutureReader }}
289
+ */
290
+ receive() {
291
+ if (this.#state !== STATE.CONNECTED) {
292
+ return invalidRecv();
293
+ }
294
+ if (this.#receiveStarted) {
295
+ return invalidRecv();
296
+ }
297
+ this.#receiveStarted = true;
298
+ const transform = new TransformStream();
299
+ const promise = worker()
300
+ .run({
301
+ op: "tcp-receive",
302
+ socketId: this.#socketId,
303
+ stream: transform.writable,
304
+ }, [transform.writable])
305
+ .catch((err) => {
306
+ throw SocketError.from(err);
307
+ });
308
+ return [
309
+ new StreamReader(transform.readable, { preventCancel: false }),
310
+ new FutureReader(promise),
311
+ ];
312
+ }
313
+ /**
314
+ * Gets the bound local address
315
+ * WIT:
316
+ * ```
317
+ * local-address: func() -> result<ip-socket-address, error-code>
318
+ * ```
319
+ *
320
+ * @returns {Object} The local socket address
321
+ * @throws {SocketError} with payload.tag 'invalid-state' if socket is not BOUND
322
+ * @throws {SocketError} for other errors, payload.tag maps the system error
323
+ */
324
+ getLocalAddress() {
325
+ if (this.#state === STATE.UNBOUND) {
326
+ throw new SocketError("invalid-state");
327
+ }
328
+ try {
329
+ return worker().runSync({
330
+ op: "tcp-get-local-address",
331
+ socketId: this.#socketId,
332
+ });
333
+ }
334
+ catch (err) {
335
+ throw SocketError.from(err);
336
+ }
337
+ }
338
+ /**
339
+ * Gets the remote address
340
+ * WIT:
341
+ * ```
342
+ * remote-address: func() -> result<ip-socket-address, error-code>
343
+ * ```
344
+ *
345
+ * @returns {Object} The remote socket address
346
+ * @throws {SocketError} with payload.tag 'invalid-state' if socket is not CONNECTED
347
+ * @throws {SocketError} for other errors, payload.tag maps the system error
348
+ */
349
+ getRemoteAddress() {
350
+ if (this.#state !== STATE.CONNECTED) {
351
+ throw new SocketError("invalid-state");
352
+ }
353
+ try {
354
+ return worker().runSync({
355
+ op: "tcp-get-remote-address",
356
+ socketId: this.#socketId,
357
+ });
358
+ }
359
+ catch (err) {
360
+ throw SocketError.from(err);
361
+ }
362
+ }
363
+ /**
364
+ * Checks if the socket is in listening state
365
+ * WIT:
366
+ * ```
367
+ * is-listening: func() -> bool
368
+ * ```
369
+ *
370
+ * @returns {boolean} True if socket is listening, false otherwise
371
+ */
372
+ getIsListening() {
373
+ return this.#state === STATE.LISTENING;
374
+ }
375
+ /**
376
+ * Gets the address family
377
+ * WIT:
378
+ * ```
379
+ * address-family: func() -> ip-address-family
380
+ * ```
381
+ *
382
+ * @returns {IP_ADDRESS_FAMILY} The IP address family (IPv4 or IPv6)
383
+ */
384
+ getAddressFamily() {
385
+ return this.#family;
386
+ }
387
+ /**
388
+ * Sets the listen backlog size
389
+ * WIT:
390
+ * ```
391
+ * set-listen-backlog-size: func(value: u64) -> result<_, error-code>
392
+ * ```
393
+ *
394
+ * @param {bigint} value - The backlog size
395
+ * @returns {void}
396
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `value` is 0
397
+ * @throws {SocketError} with payload.tag 'not-supported' if socket is CONNECTING or CONNECTED
398
+ * @throws {SocketError} with payload.tag 'invalid-state' if socket is not UNBOUND or BOUND
399
+ * @throws {SocketError} for other errors, payload.tag maps the system error
400
+ */
401
+ setListenBacklogSize(value) {
402
+ if (value <= 0n) {
403
+ throw new SocketError("invalid-argument");
404
+ }
405
+ if (this.#state === STATE.CONNECTING ||
406
+ this.#state === STATE.CONNECTED ||
407
+ this.#state === STATE.LISTENING) {
408
+ throw new SocketError("not-supported");
409
+ }
410
+ if (this.#state !== STATE.UNBOUND && this.#state !== STATE.BOUND) {
411
+ throw new SocketError("invalid-state");
412
+ }
413
+ this.#options.listenBacklogSize = value;
414
+ try {
415
+ worker().runSync({
416
+ op: "tcp-set-listen-backlog-size",
417
+ socketId: this.#socketId,
418
+ value,
419
+ });
420
+ }
421
+ catch (err) {
422
+ throw SocketError.from(err);
423
+ }
424
+ }
425
+ /**
426
+ * Gets whether keep-alive is enabled
427
+ * WIT:
428
+ * ```
429
+ * keep-alive-enabled: func() -> result<bool, error-code>
430
+ * ```
431
+ *
432
+ * @returns {boolean} True if keep-alive is enabled, false otherwise
433
+ */
434
+ getKeepAliveEnabled() {
435
+ return this.#options.keepAliveEnabled;
436
+ }
437
+ /**
438
+ * Sets keep-alive enabled state
439
+ * WIT:
440
+ * ```
441
+ * set-keep-alive-enabled: func(value: bool) -> result<_, error-code>
442
+ * ```
443
+ *
444
+ * @async
445
+ * @param {boolean} value - Whether to enable keep-alive
446
+ * @returns {void}
447
+ * @throws {SocketError} for other errors, payload.tag maps the system error
448
+ */
449
+ setKeepAliveEnabled(value) {
450
+ try {
451
+ this.#options.keepAliveEnabled = value;
452
+ worker().runSync({
453
+ op: "tcp-set-keep-alive",
454
+ socketId: this.#socketId,
455
+ keepAliveEnabled: this.#options.keepAliveEnabled,
456
+ keepAliveIdleTime: this.#options.keepAliveIdleTime,
457
+ });
458
+ }
459
+ catch (err) {
460
+ throw SocketError.from(err);
461
+ }
462
+ }
463
+ /**
464
+ * Gets the keep-alive idle time
465
+ * WIT:
466
+ * ```
467
+ * keep-alive-idle-time: func() -> result<duration, error-code>
468
+ * ```
469
+ *
470
+ * @returns {bigint} The keep-alive idle time in nanoseconds
471
+ */
472
+ getKeepAliveIdleTime() {
473
+ return this.#options.keepAliveIdleTime;
474
+ }
475
+ /**
476
+ * Sets the keep-alive idle time
477
+ * WIT:
478
+ * ```
479
+ * set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>
480
+ * ```
481
+ *
482
+ * @param {bigint} value - The idle time in nanoseconds
483
+ * @returns {void}
484
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `value` is less than 1
485
+ * @throws {SocketError} for other errors, payload.tag maps the system error
486
+ */
487
+ setKeepAliveIdleTime(value) {
488
+ if (value < 1n) {
489
+ throw new SocketError("invalid-argument");
490
+ }
491
+ // Ensure that the value passed to the worker never gets rounded down to 0.
492
+ if (value < 1000000000n) {
493
+ value = 1000000000n;
494
+ }
495
+ if (value === this.#options.keepAliveIdleTime || !this.#options.keepAliveEnabled) {
496
+ this.#options.keepAliveIdleTime = value;
497
+ return;
498
+ }
499
+ this.#options.keepAliveIdleTime = value;
500
+ try {
501
+ worker().runSync({
502
+ op: "tcp-set-keep-alive",
503
+ socketId: this.#socketId,
504
+ keepAliveEnabled: this.#options.keepAliveEnabled,
505
+ keepAliveIdleTime: this.#options.keepAliveIdleTime,
506
+ });
507
+ }
508
+ catch (err) {
509
+ throw SocketError.from(err);
510
+ }
511
+ }
512
+ /**
513
+ * Gets the keep-alive interval
514
+ * WIT:
515
+ * ```
516
+ * keep-alive-interval: func() -> result<duration, error-code>
517
+ * ```
518
+ *
519
+ * @returns {bigint} The keep-alive interval in nanoseconds
520
+ */
521
+ getKeepAliveInterval() {
522
+ return this.#options.keepAliveInterval;
523
+ }
524
+ /**
525
+ * Sets the keep-alive interval
526
+ * WIT:
527
+ * ```
528
+ * set-keep-alive-interval: func(value: duration) -> result<_, error-code>
529
+ * ```
530
+ *
531
+ * @param {bigint} value - The interval in nanoseconds
532
+ * @returns {void}
533
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `value` is less than 1
534
+ */
535
+ setKeepAliveInterval(value) {
536
+ if (value < 1n) {
537
+ throw new SocketError("invalid-argument");
538
+ }
539
+ this.#options.keepAliveInterval = value;
540
+ }
541
+ /**
542
+ * Gets the keep-alive count
543
+ * WIT:
544
+ * ```
545
+ * keep-alive-count: func() -> result<u32, error-code>
546
+ * ```
547
+ *
548
+ * @returns {number} The keep-alive count
549
+ */
550
+ getKeepAliveCount() {
551
+ return this.#options.keepAliveCount;
552
+ }
553
+ /**
554
+ * Sets the keep-alive count
555
+ * WIT:
556
+ * ```
557
+ * set-keep-alive-count: func(value: u32) -> result<_, error-code>
558
+ * ```
559
+ *
560
+ * @param {number} value - The keep-alive count
561
+ * @returns {void}
562
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `value` is less than 1
563
+ */
564
+ setKeepAliveCount(value) {
565
+ if (value < 1) {
566
+ throw new SocketError("invalid-argument");
567
+ }
568
+ this.#options.keepAliveCount = value;
569
+ }
570
+ /**
571
+ * Gets the hop limit
572
+ * WIT:
573
+ * ```
574
+ * hop-limit: func() -> result<u8, error-code>
575
+ * ```
576
+ *
577
+ * @returns {number} The hop limit
578
+ */
579
+ getHopLimit() {
580
+ return this.#options.hopLimit;
581
+ }
582
+ /**
583
+ * Sets the hop limit
584
+ * WIT:
585
+ * ```
586
+ * set-hop-limit: func(value: u8) -> result<_, error-code>
587
+ * ```
588
+ *
589
+ * @param {number} value - The hop limit
590
+ * @returns {void}
591
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `value` is less than 1
592
+ */
593
+ setHopLimit(value) {
594
+ if (value < 1) {
595
+ throw new SocketError("invalid-argument");
596
+ }
597
+ this.#options.hopLimit = value;
598
+ }
599
+ /**
600
+ * Gets the receive buffer size
601
+ * WIT:
602
+ * ```
603
+ * receive-buffer-size: func() -> result<u64, error-code>
604
+ * ```
605
+ *
606
+ * @returns {bigint} The receive buffer size in bytes
607
+ * @throws {SocketError} for other errors, payload.tag maps the system error
608
+ */
609
+ getReceiveBufferSize() {
610
+ if (this.#options.receiveBufferSize) {
611
+ return this.#options.receiveBufferSize;
612
+ }
613
+ try {
614
+ const result = worker().runSync({
615
+ op: "tcp-recv-buffer-size",
616
+ socketId: this.#socketId,
617
+ });
618
+ this.#options.receiveBufferSize = result;
619
+ return result;
620
+ }
621
+ catch (err) {
622
+ throw SocketError.from(err);
623
+ }
624
+ }
625
+ /**
626
+ * Sets the receive buffer size
627
+ * WIT:
628
+ * ```
629
+ * set-receive-buffer-size: func(value: u64) -> result<_, error-code>
630
+ * ```
631
+ *
632
+ * @param {bigint} value - The receive buffer size in bytes
633
+ * @returns {void}
634
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `value` is 0
635
+ */
636
+ setReceiveBufferSize(value) {
637
+ if (value <= 0n) {
638
+ throw new SocketError("invalid-argument");
639
+ }
640
+ this.#options.receiveBufferSize = value;
641
+ }
642
+ /**
643
+ * Gets the send buffer size
644
+ * WIT:
645
+ * ```
646
+ * send-buffer-size: func() -> result<u64, error-code>
647
+ * ```
648
+ *
649
+ * @returns {bigint} The send buffer size in bytes
650
+ * @throws {SocketError} for other errors, payload.tag maps the system error
651
+ */
652
+ getSendBufferSize() {
653
+ if (this.#options.sendBufferSize) {
654
+ return this.#options.sendBufferSize;
655
+ }
656
+ try {
657
+ const result = worker().runSync({
658
+ op: "tcp-send-buffer-size",
659
+ socketId: this.#socketId,
660
+ });
661
+ this.#options.sendBufferSize = result;
662
+ return result;
663
+ }
664
+ catch (err) {
665
+ throw SocketError.from(err);
666
+ }
667
+ }
668
+ /**
669
+ * Sets the send buffer size
670
+ * WIT:
671
+ * ```
672
+ * set-send-buffer-size: func(value: u64) -> result<_, error-code>
673
+ * ```
674
+ *
675
+ * @param {bigint} value - The send buffer size in bytes
676
+ * @returns {void}
677
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `value` is 0
678
+ */
679
+ setSendBufferSize(value) {
680
+ if (value <= 0n) {
681
+ throw new SocketError("invalid-argument");
682
+ }
683
+ this.#options.sendBufferSize = value;
684
+ }
685
+ /**
686
+ * Disposes the socket
687
+ */
688
+ [Symbol.dispose]() {
689
+ if (this.#socketId) {
690
+ worker().runSync({
691
+ op: "tcp-dispose",
692
+ socketId: this.#socketId,
693
+ });
694
+ }
695
+ this.#socketId = null;
696
+ this.#state = STATE.CLOSED;
697
+ }
698
+ /**
699
+ * Validates if a local address is valid for this socket
700
+ * @param {Object} localAddress - The local address to validate
701
+ * @returns {boolean} True if the address is valid, false otherwise
702
+ * @private
703
+ */
704
+ #isValidLocalAddress(localAddress) {
705
+ return (this.#family === localAddress.tag &&
706
+ (isUnicastIpAddress(localAddress) || isWildcardIpAddress(localAddress)) &&
707
+ !isIpv4MappedAddress(localAddress));
708
+ }
709
+ /**
710
+ * Validates if a remote address is valid for this socket
711
+ * @param {Object} remoteAddress - The remote address to validate
712
+ * @returns {boolean} True if the address is valid, false otherwise
713
+ * @private
714
+ */
715
+ #isValidRemoteAddress(remoteAddress) {
716
+ return (this.#family === remoteAddress.tag &&
717
+ remoteAddress.val.port !== 0 &&
718
+ isUnicastIpAddress(remoteAddress) &&
719
+ !isWildcardIpAddress(remoteAddress) &&
720
+ !isMulticastIpAddress(remoteAddress) &&
721
+ !isIpv4MappedAddress(remoteAddress));
722
+ }
723
+ }