@bytecodealliance/preview3-shim 0.1.0-rc.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 (67) hide show
  1. package/LICENSE +220 -0
  2. package/README.md +14 -0
  3. package/lib/nodejs/cli.js +127 -0
  4. package/lib/nodejs/clocks.js +100 -0
  5. package/lib/nodejs/filesystem/descriptor.js +997 -0
  6. package/lib/nodejs/filesystem/error.js +92 -0
  7. package/lib/nodejs/filesystem/utils.js +19 -0
  8. package/lib/nodejs/filesystem.js +2 -0
  9. package/lib/nodejs/finalization.js +33 -0
  10. package/lib/nodejs/future.js +187 -0
  11. package/lib/nodejs/http/client.js +146 -0
  12. package/lib/nodejs/http/error.js +71 -0
  13. package/lib/nodejs/http/fields.js +359 -0
  14. package/lib/nodejs/http/request.js +589 -0
  15. package/lib/nodejs/http/response.js +212 -0
  16. package/lib/nodejs/http/server.js +157 -0
  17. package/lib/nodejs/http.js +34 -0
  18. package/lib/nodejs/index.js +8 -0
  19. package/lib/nodejs/random.js +14 -0
  20. package/lib/nodejs/sockets/address.js +146 -0
  21. package/lib/nodejs/sockets/error.js +123 -0
  22. package/lib/nodejs/sockets/ip-name-lookup.js +93 -0
  23. package/lib/nodejs/sockets/tcp.js +776 -0
  24. package/lib/nodejs/sockets/udp.js +516 -0
  25. package/lib/nodejs/sockets.js +14 -0
  26. package/lib/nodejs/stream.js +423 -0
  27. package/lib/nodejs/workers/cli-worker.js +49 -0
  28. package/lib/nodejs/workers/filesystem-worker.js +119 -0
  29. package/lib/nodejs/workers/http-worker.js +387 -0
  30. package/lib/nodejs/workers/resource-worker.js +151 -0
  31. package/lib/nodejs/workers/tcp-worker.js +283 -0
  32. package/lib/nodejs/workers/udp-worker.js +194 -0
  33. package/package.json +57 -0
  34. package/types/cli.d.ts +12 -0
  35. package/types/clocks.d.ts +2 -0
  36. package/types/filesystem.d.ts +2 -0
  37. package/types/http.d.ts +4 -0
  38. package/types/index.d.ts +6 -0
  39. package/types/interfaces/wasi-cli-environment.d.ts +21 -0
  40. package/types/interfaces/wasi-cli-exit.d.ts +6 -0
  41. package/types/interfaces/wasi-cli-run.d.ts +5 -0
  42. package/types/interfaces/wasi-cli-stderr.d.ts +14 -0
  43. package/types/interfaces/wasi-cli-stdin.d.ts +19 -0
  44. package/types/interfaces/wasi-cli-stdout.d.ts +14 -0
  45. package/types/interfaces/wasi-cli-terminal-input.d.ts +8 -0
  46. package/types/interfaces/wasi-cli-terminal-output.d.ts +8 -0
  47. package/types/interfaces/wasi-cli-terminal-stderr.d.ts +7 -0
  48. package/types/interfaces/wasi-cli-terminal-stdin.d.ts +7 -0
  49. package/types/interfaces/wasi-cli-terminal-stdout.d.ts +7 -0
  50. package/types/interfaces/wasi-cli-types.d.ts +15 -0
  51. package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +33 -0
  52. package/types/interfaces/wasi-clocks-system-clock.d.ts +38 -0
  53. package/types/interfaces/wasi-clocks-types.d.ts +5 -0
  54. package/types/interfaces/wasi-filesystem-preopens.d.ts +6 -0
  55. package/types/interfaces/wasi-filesystem-types.d.ts +780 -0
  56. package/types/interfaces/wasi-http-client.d.ts +9 -0
  57. package/types/interfaces/wasi-http-handler.d.ts +9 -0
  58. package/types/interfaces/wasi-http-types.d.ts +619 -0
  59. package/types/interfaces/wasi-random-insecure-seed.d.ts +21 -0
  60. package/types/interfaces/wasi-random-insecure.d.ts +26 -0
  61. package/types/interfaces/wasi-random-random.d.ts +31 -0
  62. package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +77 -0
  63. package/types/interfaces/wasi-sockets-types.d.ts +830 -0
  64. package/types/random.d.ts +3 -0
  65. package/types/sockets.d.ts +2 -0
  66. package/types/wasi-cli-command.d.ts +23 -0
  67. package/types/wasi-http-service.d.ts +14 -0
@@ -0,0 +1,516 @@
1
+ import { ResourceWorker } from "../workers/resource-worker.js";
2
+ import { SocketError } from "./error.js";
3
+ import {
4
+ isWildcardIpAddress,
5
+ isUnicastIpAddress,
6
+ isIpv4MappedAddress,
7
+ isBindableIpAddress,
8
+ IP_ADDRESS_FAMILY,
9
+ } from "./address.js";
10
+
11
+ let WORKER = null;
12
+ function worker() {
13
+ return (WORKER ??= new ResourceWorker(new URL("../workers/udp-worker.js", import.meta.url)));
14
+ }
15
+
16
+ let UDP_CREATE_TOKEN = null;
17
+ function token() {
18
+ return (UDP_CREATE_TOKEN ??= Symbol("UdpCreateToken"));
19
+ }
20
+
21
+ const STATE = {
22
+ UNBOUND: "unbound",
23
+ BOUND: "bound",
24
+ CONNECTED: "connected",
25
+ CLOSED: "closed",
26
+ };
27
+
28
+ /**
29
+ * Create a new UDP socket.
30
+ * WIT:
31
+ * ```
32
+ * createUdpSocket: func(address-family: ip-address-family) -> result<udp-socket, error-code>
33
+ * ```
34
+ *
35
+ * @async
36
+ * @param {IP_ADDRESS_FAMILY} addressFamily - The IP address family
37
+ * @returns {Promise<UdpSocket>}
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 system error
40
+ */
41
+ export function createUdpSocket(addressFamily) {
42
+ if (addressFamily !== IP_ADDRESS_FAMILY.IPV4 && addressFamily !== IP_ADDRESS_FAMILY.IPV6) {
43
+ throw new SocketError("invalid-argument");
44
+ }
45
+
46
+ try {
47
+ const { socketId } = worker().runSync({
48
+ op: "udp-create",
49
+ family: addressFamily,
50
+ });
51
+ return UdpSocket._create(token(), addressFamily, socketId);
52
+ } catch (e) {
53
+ throw SocketError.from(e);
54
+ }
55
+ }
56
+
57
+ export class UdpSocket {
58
+ #socketId = null;
59
+ #family = null;
60
+ #state = STATE.UNBOUND;
61
+ #remote = null;
62
+ #options = {
63
+ hopLimit: 1,
64
+ receiveBufferSize: 65_536n,
65
+ sendBufferSize: 65_536n,
66
+ };
67
+
68
+ /**
69
+ * WIT static constructor for `wasi:sockets/types.udp-socket`.
70
+ *
71
+ * WIT:
72
+ * ```
73
+ * create: static func(address-family: ip-address-family) -> result<udp-socket, error-code>
74
+ * ```
75
+ *
76
+ * @static
77
+ * @param {IP_ADDRESS_FAMILY} addressFamily - The IP address family (IPv4 or IPv6)
78
+ * @returns {UdpSocket} A new UdpSocket instance
79
+ * @throws {SocketError} with payload.tag 'invalid-argument' if `addressFamily` is not IPV4 or IPV6
80
+ * @throws {SocketError} for other socket creation errors
81
+ */
82
+ static create(addressFamily) {
83
+ return createUdpSocket(addressFamily);
84
+ }
85
+
86
+ /**
87
+ * Creates a new UDP socket
88
+ * WIT:
89
+ * ```
90
+ * constructor(address-family: ip-address-family)
91
+ * ```
92
+ *
93
+ * @static
94
+ * @param {IP_ADDRESS_FAMILY} addressFamily - The IP address family (IPv4 or IPv6)
95
+ * @param {string} socketId - The internal socket identifier
96
+ * @returns {UdpSocket} A new UdpSocket instance
97
+ * @private
98
+ */
99
+ static _create(t, addressFamily, socketId) {
100
+ if (t !== token()) {
101
+ throw new Error("Use createUdpSocket to create a Socket");
102
+ }
103
+
104
+ if (addressFamily !== IP_ADDRESS_FAMILY.IPV4 && addressFamily !== IP_ADDRESS_FAMILY.IPV6) {
105
+ throw new SocketError("invalid-argument");
106
+ }
107
+
108
+ const sock = new UdpSocket();
109
+ sock.#family = addressFamily;
110
+ sock.#socketId = socketId;
111
+ return sock;
112
+ }
113
+
114
+ /**
115
+ * Bind the socket to the provided IP address and port.
116
+ * WIT:
117
+ * ```
118
+ * bind: func(local-address: ip-socket-address) -> result<_, error-code>
119
+ * ```
120
+ *
121
+ * @param {Object} localAddress - The local address to bind to
122
+ * @returns {void}
123
+ * @throws {SocketError} with payload.tag 'invalid-state' if the socket is not in ~STATE.UNBOUND~
124
+ * @throws {SocketError} with payload.tag 'invalid-argument' if ~localAddress~ is invalid
125
+ * @throws {SocketError} for other errors, payload.tag maps the system error
126
+ *
127
+ */
128
+ bind(localAddress) {
129
+ if (this.#state !== STATE.UNBOUND) {
130
+ throw new SocketError("invalid-state");
131
+ }
132
+ if (
133
+ localAddress.tag !== this.#family ||
134
+ (!isUnicastIpAddress(localAddress) && !isWildcardIpAddress(localAddress)) ||
135
+ isIpv4MappedAddress(localAddress)
136
+ ) {
137
+ throw new SocketError("invalid-argument");
138
+ }
139
+ if (!isBindableIpAddress(localAddress)) {
140
+ throw new SocketError("address-not-bindable");
141
+ }
142
+
143
+ try {
144
+ worker().runSync({
145
+ op: "udp-bind",
146
+ socketId: this.#socketId,
147
+ localAddress,
148
+ });
149
+ this.#state = STATE.BOUND;
150
+ } catch (e) {
151
+ throw SocketError.from(e);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Associate this socket with a specific peer address.
157
+ * WIT:
158
+ * ```
159
+ * connect: func(remote-address: ip-socket-address) -> result<_, error-code>
160
+ * ```
161
+ *
162
+ * @param {Object} remoteAddress - The remote address to connect to
163
+ * @returns {void}
164
+ * @throws {SocketError} with payload.tag 'invalid-state' if the socket is already connected
165
+ * @throws {SocketError} with payload.tag 'invalid-argument' if ~remoteAddress~ is invalid or wildcard
166
+ * @throws {SocketError} for other errors, payload.tag maps the system error
167
+ */
168
+ connect(remoteAddress) {
169
+ if (
170
+ remoteAddress.tag !== this.#family ||
171
+ remoteAddress.val.port === 0 ||
172
+ isWildcardIpAddress(remoteAddress) ||
173
+ !isUnicastIpAddress(remoteAddress) ||
174
+ isIpv4MappedAddress(remoteAddress)
175
+ ) {
176
+ throw new SocketError("invalid-argument");
177
+ }
178
+
179
+ try {
180
+ worker().runSync({
181
+ op: "udp-connect",
182
+ socketId: this.#socketId,
183
+ remoteAddress,
184
+ });
185
+ this.#remote = remoteAddress;
186
+ this.#state = STATE.CONNECTED;
187
+ } catch (e) {
188
+ throw SocketError.from(e);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Dissociate this socket from its peer address.
194
+ * WIT:
195
+ * ```
196
+ * disconnect: func() -> result<_, error-code>
197
+ * ```
198
+ *
199
+ * @returns {void}
200
+ * @throws {SocketError} with payload.tag 'invalid-state' if the socket is not connected
201
+ * @throws {SocketError} for other errors, payload.tag maps the system error
202
+ */
203
+ disconnect() {
204
+ if (this.#state !== STATE.CONNECTED) {
205
+ throw new SocketError("invalid-state");
206
+ }
207
+ try {
208
+ worker().runSync({
209
+ op: "udp-disconnect",
210
+ socketId: this.#socketId,
211
+ });
212
+ this.#remote = null;
213
+ this.#state = STATE.BOUND;
214
+ } catch (e) {
215
+ throw SocketError.from(e);
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Send a message on the socket to a particular peer.
221
+ * WIT:
222
+ * ```
223
+ * send: func(data: list<u8>, remote-address: option<ip-socket-address>) -> result<_, error-code>
224
+ * ```
225
+ *
226
+ * @async
227
+ * @param {Uint8Array} data - The datagram payload
228
+ * @param {?Object} remoteAddress Optional peer address if not connected.
229
+ * @returns {Promise<void>}
230
+ * @throws {SocketError} with payload.tag 'invalid-state' if socket is not bound
231
+ * @throws {SocketError} with payload.tag 'invalid-argument' if ~data~ is not a Uint8Array or address invalid
232
+ * @throws {SocketError} for other errors, payload.tag maps the system error
233
+ */
234
+ async send(data, remoteAddress = null) {
235
+ if (this.#state === STATE.CLOSED) {
236
+ throw new SocketError("invalid-state");
237
+ }
238
+
239
+ if (!(data instanceof Uint8Array)) {
240
+ throw new SocketError("invalid-argument");
241
+ }
242
+
243
+ const addr = remoteAddress ?? this.#remote;
244
+ if (
245
+ !addr ||
246
+ addr.val.port === 0 ||
247
+ addr.tag !== this.#family ||
248
+ isWildcardIpAddress(addr) ||
249
+ isIpv4MappedAddress(addr) ||
250
+ !isUnicastIpAddress(addr)
251
+ ) {
252
+ throw new SocketError("invalid-argument");
253
+ }
254
+
255
+ if (
256
+ this.#state === STATE.CONNECTED &&
257
+ remoteAddress != null &&
258
+ (addr.tag !== this.#remote.tag ||
259
+ addr.val.port !== this.#remote.val.port ||
260
+ addr.val.address.some((v, i) => v !== this.#remote.val.address[i]))
261
+ ) {
262
+ throw new SocketError("invalid-argument");
263
+ }
264
+
265
+ const wasUnbound = this.#state === STATE.UNBOUND;
266
+ try {
267
+ await worker().run(
268
+ {
269
+ op: "udp-send",
270
+ socketId: this.#socketId,
271
+ data,
272
+ remoteAddress: addr,
273
+ },
274
+ [data.buffer],
275
+ );
276
+ if (wasUnbound) {
277
+ this.#state = STATE.BOUND;
278
+ }
279
+ } catch (e) {
280
+ throw SocketError.from(e);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Receive a message on the socket.
286
+ * WIT:
287
+ * ```
288
+ * receive: func() -> result<tuple<list<u8>, ip-socket-address>, error-code>
289
+ * ```
290
+ *
291
+ * @async
292
+ * @returns {Promise<[Uint8Array, Object]>} Received data and sender address
293
+ * @throws {SocketError} with payload.tag 'invalid-state' if socket has not been bound
294
+ * @throws {SocketError} for other errors, payload.tag maps the system error
295
+ */
296
+ async receive() {
297
+ if (this.#state === STATE.UNBOUND) {
298
+ throw new SocketError("invalid-state");
299
+ }
300
+ try {
301
+ const { data, remoteAddress } = await worker().run({
302
+ op: "udp-receive",
303
+ socketId: this.#socketId,
304
+ });
305
+ return [new Uint8Array(data), remoteAddress];
306
+ } catch (e) {
307
+ throw SocketError.from(e);
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Get the current bound address.
313
+ * WIT:
314
+ * ```
315
+ * local-address: func() -> result<ip-socket-address, error-code>
316
+ * ```
317
+ *
318
+ * @returns {Object} The local socket address
319
+ * @throws {SocketError} with payload.tag 'invalid-state' if the socket hasn't been bound yet
320
+ * @throws {SocketError} for other errors, payload.tag maps the system error
321
+ */
322
+ getLocalAddress() {
323
+ if (this.#state === STATE.UNBOUND) {
324
+ throw new SocketError("invalid-state");
325
+ }
326
+ try {
327
+ return worker().runSync({
328
+ op: "udp-get-local-address",
329
+ socketId: this.#socketId,
330
+ });
331
+ } catch (e) {
332
+ throw SocketError.from(e);
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Get the address the socket is currently connected to.
338
+ * WIT:
339
+ * ```
340
+ * remote-address: func() -> result<ip-socket-address, error-code>
341
+ * ```
342
+ *
343
+ * @returns {Object} The connected peer address
344
+ * @throws {SocketError} with payload.tag 'invalid-state' if the socket is not connected
345
+ */
346
+ getRemoteAddress() {
347
+ if (this.#state !== STATE.CONNECTED) {
348
+ throw new SocketError("invalid-state");
349
+ }
350
+ return this.#remote;
351
+ }
352
+
353
+ /**
354
+ * Whether this is an IPv4 or IPv6 socket.
355
+ * WIT:
356
+ * ```
357
+ * address-family: func() -> ip-address-family
358
+ * ```
359
+ *
360
+ * @returns {IP_ADDRESS_FAMILY}
361
+ */
362
+ getAddressFamily() {
363
+ return this.#family;
364
+ }
365
+
366
+ /**
367
+ * Get the unicast hop limit (TTL).
368
+ * WIT:
369
+ * ```
370
+ * unicast-hop-limit: func() -> result<u8, error-code>
371
+ * ```
372
+ *
373
+ * @returns {number}
374
+ */
375
+ getUnicastHopLimit() {
376
+ return this.#options.hopLimit;
377
+ }
378
+
379
+ /**
380
+ * Set the unicast hop limit (TTL).
381
+ * WIT:
382
+ * ```
383
+ * set-unicast-hop-limit: func(value: u8) -> result<_, error-code>
384
+ * ```
385
+ *
386
+ * @param {number} value - The hop limit (must be ≥1)
387
+ * @returns {void}
388
+ * @throws {SocketError} with payload.tag 'invalid-argument' if value < 1
389
+ * @throws {SocketError} for other errors, payload.tag maps the system error
390
+ */
391
+ setUnicastHopLimit(value) {
392
+ if (value < 1) {
393
+ throw new SocketError("invalid-argument");
394
+ }
395
+
396
+ this.#options.hopLimit = value;
397
+ if (this.#state === STATE.UNBOUND) {
398
+ return;
399
+ }
400
+
401
+ try {
402
+ worker().runSync({
403
+ op: "udp-set-unicast-hop-limit",
404
+ socketId: this.#socketId,
405
+ value,
406
+ });
407
+ } catch (e) {
408
+ throw SocketError.from(e);
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Get the receive buffer size.
414
+ * WIT:
415
+ * ```
416
+ * receive-buffer-size: func() -> result<u64, error-code>
417
+ * ```
418
+ *
419
+ * @returns {bigint}
420
+ * @throws {SocketError} payload.tag maps the system error
421
+ */
422
+ getReceiveBufferSize() {
423
+ if (this.#options.receiveBufferSize) {
424
+ return this.#options.receiveBufferSize;
425
+ }
426
+ try {
427
+ const size = worker().runSync({
428
+ op: "udp-recv-buffer-size",
429
+ socketId: this.#socketId,
430
+ });
431
+ this.#options.receiveBufferSize = size;
432
+ return size;
433
+ } catch (e) {
434
+ throw SocketError.from(e);
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Set the receive buffer size.
440
+ * WIT:
441
+ * ```
442
+ * set-receive-buffer-size: func(value: u64) -> result<_, error-code>
443
+ * ```
444
+ *
445
+ * @param {bigint} value - The buffer size in bytes (must be >0)
446
+ * @throws {SocketError} payload.tag maps the system error
447
+ */
448
+ setReceiveBufferSize(value) {
449
+ if (value === 0n) {
450
+ throw new SocketError("invalid-argument");
451
+ }
452
+ this.#options.receiveBufferSize = value;
453
+ }
454
+
455
+ /**
456
+ * Get the send buffer size.
457
+ * WIT:
458
+ * ```
459
+ * send-buffer-size: func() -> result<u64, error-code>
460
+ * ```
461
+ *
462
+ * @async
463
+ * @returns {bigint}
464
+ * @throws {SocketError} payload.tag maps the system error
465
+ */
466
+ getSendBufferSize() {
467
+ if (this.#options.sendBufferSize) {
468
+ return this.#options.sendBufferSize;
469
+ }
470
+ try {
471
+ const size = worker().runSync({
472
+ op: "udp-send-buffer-size",
473
+ socketId: this.#socketId,
474
+ });
475
+ this.#options.sendBufferSize = size;
476
+ return size;
477
+ } catch (e) {
478
+ throw SocketError.from(e);
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Set the send buffer size.
484
+ * WIT:
485
+ * ```
486
+ * set-send-buffer-size: func(value: u64) -> result<_, error-code>
487
+ * ```
488
+ *
489
+ * @param {bigint} value - The buffer size in bytes (must be >0)
490
+ * @throws {SocketError} with payload.tag 'invalid-argument' if value == 0
491
+ */
492
+ setSendBufferSize(value) {
493
+ if (value === 0n) {
494
+ throw new SocketError("invalid-argument");
495
+ }
496
+ this.#options.sendBufferSize = value;
497
+ }
498
+
499
+ /**
500
+ * Dispose the socket.
501
+ *
502
+ * Releases native resources and marks the socket as closed.
503
+ */
504
+ [Symbol.dispose]() {
505
+ if (this.#socketId) {
506
+ worker().runSync({
507
+ op: "udp-dispose",
508
+ socketId: this.#socketId,
509
+ });
510
+ }
511
+ this.#socketId = null;
512
+ this.#state = STATE.CLOSED;
513
+ }
514
+ }
515
+
516
+ export const udpCreateSocket = {};
@@ -0,0 +1,14 @@
1
+ import { TcpSocket } from "./sockets/tcp.js";
2
+ import { UdpSocket } from "./sockets/udp.js";
3
+
4
+ export { ipNameLookup } from "./sockets/ip-name-lookup.js";
5
+ export * from "./sockets/tcp.js";
6
+ export * from "./sockets/udp.js";
7
+
8
+ export { IP_ADDRESS_FAMILY } from "./sockets/address.js";
9
+ export { makeIpAddress, serializeIpAddress } from "./sockets/address.js";
10
+
11
+ export const types = {
12
+ TcpSocket,
13
+ UdpSocket,
14
+ };