@bytecodealliance/preview2-shim 0.22.0 → 0.24.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.
@@ -10,7 +10,7 @@ import { Server, Socket, isIP } from "node:net";
10
10
  import { lookup } from "node:dns/promises";
11
11
  import { createSocket } from "node:dgram";
12
12
  import { ALL, BADFAMILY, CANCELLED, CONNREFUSED, NODATA, NOMEM, NONAME, NOTFOUND, REFUSED, SERVFAIL, TIMEOUT, V4MAPPED } from "node:dns";
13
- import { EACCES, EADDRINUSE, EADDRNOTAVAIL, EALREADY, EBADF, ECONNABORTED, ECONNREFUSED, ECONNRESET, EINVAL, ENOBUFS, ENOMEM, ENOTCONN, ENOTSUP, EPERM, EWOULDBLOCK } from "node:constants";
13
+ import { EACCES, EADDRINUSE, EADDRNOTAVAIL, EALREADY, EBADF, ECONNABORTED, ECONNREFUSED, ECONNRESET, EHOSTUNREACH, EINVAL, EMFILE, EMSGSIZE, ENETDOWN, ENETUNREACH, ENFILE, ENOBUFS, ENOMEM, ENOTCONN, ENOTSUP, EPERM, EWOULDBLOCK } from "node:constants";
14
14
  //#region \0rolldown/runtime.js
15
15
  var __defProp = Object.defineProperty;
16
16
  var __exportAll = (all, no_symbols) => {
@@ -611,11 +611,18 @@ function convertSocketError(err) {
611
611
  case "ENOBUFS": return "out-of-memory";
612
612
  case "EALREADY": return "concurrency-conflict";
613
613
  case "EWOULDBLOCK": return "would-block";
614
+ case "EMFILE":
615
+ case "ENFILE": return "new-socket-limit";
614
616
  case "EADDRNOTAVAIL": return "address-not-bindable";
615
617
  case "EADDRINUSE": return "address-in-use";
618
+ case "EHOSTUNREACH":
619
+ case "EHOSTDOWN":
620
+ case "ENETUNREACH":
621
+ case "ENETDOWN": return "remote-unreachable";
616
622
  case "ECONNREFUSED": return "connection-refused";
617
623
  case "ECONNRESET": return "connection-reset";
618
624
  case "ECONNABORTED": return "connection-aborted";
625
+ case "EMSGSIZE": return "datagram-too-large";
619
626
  default: return "unknown";
620
627
  }
621
628
  }
@@ -633,13 +640,19 @@ function convertSocketErrorCode(code) {
633
640
  case ENOBUFS: return "out-of-memory";
634
641
  case EALREADY: return "concurrency-conflict";
635
642
  case EWOULDBLOCK: return "would-block";
643
+ case EMFILE:
644
+ case ENFILE: return "new-socket-limit";
636
645
  case 4090:
637
646
  case EADDRNOTAVAIL: return "address-not-bindable";
638
647
  case 4091:
639
648
  case EADDRINUSE: return "address-in-use";
649
+ case EHOSTUNREACH:
650
+ case ENETUNREACH:
651
+ case ENETDOWN: return "remote-unreachable";
640
652
  case ECONNREFUSED: return "connection-refused";
641
653
  case ECONNRESET: return "connection-reset";
642
654
  case ECONNABORTED: return "connection-aborted";
655
+ case EMSGSIZE: return "datagram-too-large";
643
656
  default: return "unknown";
644
657
  }
645
658
  }
@@ -687,13 +700,14 @@ function ipv4ToTuple(ipv4) {
687
700
  return ipv4.split(".").map((segment) => parseInt(segment, 10));
688
701
  }
689
702
  function ipSocketAddress(family, addr, port) {
690
- if (family === "ipv4") return {
703
+ if (family === "ipv4" || family === "IPv4" || family === 4) return {
691
704
  tag: "ipv4",
692
705
  val: {
693
706
  port,
694
707
  address: ipv4ToTuple(addr)
695
708
  }
696
709
  };
710
+ if (family !== "ipv6" && family !== "IPv6" && family !== 6) throw "invalid-argument";
697
711
  return {
698
712
  tag: "ipv6",
699
713
  val: {
@@ -727,7 +741,6 @@ async function getDefaultReceiveBufferSize() {
727
741
  }
728
742
  //#endregion
729
743
  //#region dist/io/worker-socket-tcp.js
730
- const win = process.platform === "win32";
731
744
  const tcpSockets = /* @__PURE__ */ new Map();
732
745
  let tcpSocketCnt = 0;
733
746
  function createTcpSocket() {
@@ -758,7 +771,11 @@ function socketTcpFinish(id, fromState, toState) {
758
771
  futureDispose(socket.future, false);
759
772
  socket.future = null;
760
773
  if (tag === "err") {
761
- socket.state = SOCKET_STATE_CLOSED;
774
+ if (fromState === SOCKET_STATE_BIND) {
775
+ socket.server = null;
776
+ socket.localAddress = null;
777
+ socket.state = SOCKET_STATE_INIT;
778
+ } else socket.state = SOCKET_STATE_CLOSED;
762
779
  throw val;
763
780
  } else {
764
781
  socket.state = toState;
@@ -786,7 +803,7 @@ function socketTcpBindStart(id, localAddress, family) {
786
803
  reject("invalid-state");
787
804
  return;
788
805
  }
789
- socket.localAddress = ipSocketAddress(boundAddress.family.toLowerCase(), boundAddress.address, boundAddress.port);
806
+ socket.localAddress = ipSocketAddress(boundAddress.family, boundAddress.address, boundAddress.port);
790
807
  resolve();
791
808
  }
792
809
  server.once("listening", handleListen);
@@ -912,20 +929,19 @@ function socketTcpSetListenBacklogSize(id, backlogSize) {
912
929
  function socketTcpGetLocalAddress(id) {
913
930
  const socket = tcpSockets.get(id);
914
931
  const address = socket.tcpSocket?.address();
915
- if (address && typeof address !== "string" && "family" in address) return ipSocketAddress(address.family.toLowerCase(), address.address, address.port);
932
+ if (address && typeof address !== "string" && "family" in address) return ipSocketAddress(address.family, address.address, address.port);
916
933
  if (socket.localAddress) return socket.localAddress;
917
934
  throw "invalid-state";
918
935
  }
919
936
  function socketTcpGetRemoteAddress(id) {
920
937
  const { tcpSocket } = tcpSockets.get(id);
921
938
  if (!tcpSocket?.remoteFamily || !tcpSocket.remoteAddress || !tcpSocket.remotePort) throw "invalid-state";
922
- return ipSocketAddress(tcpSocket.remoteFamily.toLowerCase(), tcpSocket.remoteAddress, tcpSocket.remotePort);
939
+ return ipSocketAddress(tcpSocket.remoteFamily, tcpSocket.remoteAddress, tcpSocket.remotePort);
923
940
  }
924
941
  function socketTcpShutdown(id, _shutdownType) {
925
942
  const socket = tcpSockets.get(id);
926
943
  if (socket.state !== SOCKET_STATE_CONNECTION) throw "invalid-state";
927
- if (win && socket.tcpSocket?.destroySoon) socket.tcpSocket.destroySoon();
928
- else socket.tcpSocket?.destroy();
944
+ socket.tcpSocket?.destroy();
929
945
  }
930
946
  function socketTcpSetKeepAlive(id, { keepAlive, keepAliveIdleTime }) {
931
947
  const socket = tcpSockets.get(id);
@@ -998,10 +1014,9 @@ function createIncomingDatagramStream(socket) {
998
1014
  udpSocket.off("error", onError);
999
1015
  }
1000
1016
  function onMessage(data, rinfo) {
1001
- const family = rinfo.family.toLowerCase();
1002
1017
  datagramStream.queue?.push({
1003
1018
  data,
1004
- remoteAddress: ipSocketAddress(family, rinfo.address, rinfo.port)
1019
+ remoteAddress: ipSocketAddress(rinfo.family, rinfo.address, rinfo.port)
1005
1020
  });
1006
1021
  if (!pollState.ready) pollStateReady(pollState);
1007
1022
  }
@@ -1087,7 +1102,7 @@ function socketUdpGetLocalAddress(id) {
1087
1102
  } catch (err) {
1088
1103
  throw convertSocketError(err);
1089
1104
  }
1090
- return ipSocketAddress(family.toLowerCase(), address, port);
1105
+ return ipSocketAddress(family, address, port);
1091
1106
  }
1092
1107
  /**
1093
1108
  * @param {number} id
@@ -1101,7 +1116,7 @@ function socketUdpGetRemoteAddress(id) {
1101
1116
  } catch (err) {
1102
1117
  throw convertSocketError(err);
1103
1118
  }
1104
- return ipSocketAddress(family.toLowerCase(), address, port);
1119
+ return ipSocketAddress(family, address, port);
1105
1120
  }
1106
1121
  function socketUdpStream(id, remoteAddress) {
1107
1122
  const socket = udpSockets.get(id);
@@ -75,6 +75,13 @@ declare class Descriptor implements IDescriptor {
75
75
  }
76
76
  export declare const preopens: typeof PreopensNamespace;
77
77
  export declare const types: typeof TypesNamespace;
78
+ /** Create isolated filesystem namespaces from Node.js host-path preopens. */
79
+ export declare function createFilesystem({ preopens }: {
80
+ preopens: Record<string, string>;
81
+ }): {
82
+ types: typeof TypesNamespace;
83
+ preopens: typeof PreopensNamespace;
84
+ };
78
85
  /**
79
86
  * Replace all preopens with the given set.
80
87
  * @param {Record<string, string>} preopens - Map of virtual paths to host paths
@@ -1,6 +1,6 @@
1
1
  import { earlyDispose, inputStreamCreate, ioCall, outputStreamCreate, registerDispose, } from "../io/worker-io.js";
2
2
  import { FILE, FILESYSTEM_DESCRIPTOR_ADVISE, FILESYSTEM_DESCRIPTOR_CLOSE, FILESYSTEM_DESCRIPTOR_GET_TYPE, FILESYSTEM_DESCRIPTOR_METADATA_HASH, FILESYSTEM_DESCRIPTOR_OPEN, FILESYSTEM_DESCRIPTOR_READ, FILESYSTEM_DESCRIPTOR_SET_SIZE, FILESYSTEM_DESCRIPTOR_SET_TIMES, FILESYSTEM_DESCRIPTOR_STAT, FILESYSTEM_DESCRIPTOR_SYNC, FILESYSTEM_DESCRIPTOR_SYNC_DATA, FILESYSTEM_DESCRIPTOR_WRITE, INPUT_STREAM_CREATE, OUTPUT_STREAM_CREATE, } from "../io/calls.js";
3
- import { constants, linkSync, lstatSync, lutimesSync, mkdirSync, opendirSync, readlinkSync, renameSync, rmdirSync, statSync, symlinkSync, unlinkSync, utimesSync, } from "node:fs";
3
+ import { constants, linkSync, lstatSync, lutimesSync, mkdirSync, opendirSync, readlinkSync, realpathSync, renameSync, rmdirSync, statSync, symlinkSync, unlinkSync, utimesSync, } from "node:fs";
4
4
  import { platform } from "node:process";
5
5
  const symbolDispose = Symbol.dispose || Symbol.for("dispose");
6
6
  const isWindows = platform === "win32";
@@ -226,7 +226,7 @@ class Descriptor {
226
226
  }
227
227
  }
228
228
  createDirectoryAt(path) {
229
- const fullPath = this.#getFullPath(path, undefined);
229
+ const fullPath = this.#getFullPath(path);
230
230
  try {
231
231
  mkdirSync(fullPath);
232
232
  }
@@ -256,7 +256,7 @@ class Descriptor {
256
256
  };
257
257
  }
258
258
  statAt(pathFlags, path) {
259
- const fullPath = this.#getFullPath(path, false);
259
+ const fullPath = this.#getFullPath(path);
260
260
  let stats;
261
261
  try {
262
262
  stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(fullPath, {
@@ -277,7 +277,7 @@ class Descriptor {
277
277
  };
278
278
  }
279
279
  setTimesAt(pathFlags, path, dataAccessTimestamp, dataModificationTimestamp) {
280
- const fullPath = this.#getFullPath(path, false);
280
+ const fullPath = this.#getFullPath(path);
281
281
  let stats;
282
282
  if (dataAccessTimestamp.tag === "no-change" ||
283
283
  dataModificationTimestamp.tag === "no-change") {
@@ -297,14 +297,32 @@ class Descriptor {
297
297
  }
298
298
  }
299
299
  linkAt(oldPathFlags, oldPath, newDescriptor, newPath) {
300
- const oldFullPath = this.#getFullPath(oldPath, oldPathFlags.symlinkFollow);
301
- const newFullPath = newDescriptor.#getFullPath(newPath, false);
300
+ const oldFullPath = this.#getFullPath(oldPath);
301
+ const newFullPath = newDescriptor.#getFullPath(newPath);
302
302
  // Windows doesn't automatically fail on trailing slashes
303
303
  if (isWindows && newFullPath.endsWith("/")) {
304
304
  throw "no-entry";
305
305
  }
306
306
  try {
307
- linkSync(oldFullPath, newFullPath);
307
+ // Unlike open/stat, Node's link does not follow the final symlink.
308
+ // Resolve only this operation's source, propagating resolution errors
309
+ // instead of silently hard-linking a dangling or cyclic symlink.
310
+ let source = oldFullPath;
311
+ if (oldPathFlags.symlinkFollow) {
312
+ source = realpathSync(source);
313
+ // realpath strips trailing slashes; keep the caller's directory
314
+ // requirement so a regular-file source ending in '/' still fails.
315
+ if (oldFullPath.endsWith("/")) {
316
+ // Windows may ignore a trailing slash even in linkSync.
317
+ if (!statSync(source).isDirectory()) {
318
+ throw "not-directory";
319
+ }
320
+ if (!source.endsWith("/")) {
321
+ source += "/";
322
+ }
323
+ }
324
+ }
325
+ linkSync(source, newFullPath);
308
326
  }
309
327
  catch (e) {
310
328
  throw convertFsError(e);
@@ -314,7 +332,7 @@ class Descriptor {
314
332
  if (preopenEntries.length === 0) {
315
333
  throw "access";
316
334
  }
317
- const fullPath = this.#getFullPath(path, pathFlags.symlinkFollow);
335
+ const fullPath = this.#getFullPath(path);
318
336
  let fsOpenFlags = 0x0;
319
337
  if (openFlags.create) {
320
338
  fsOpenFlags |= constants.O_CREAT;
@@ -384,6 +402,15 @@ class Descriptor {
384
402
  path: fullPath.endsWith("/") ? fullPath.slice(0, -1) : fullPath,
385
403
  flags: fsOpenFlags,
386
404
  });
405
+ // Wasmtime's updated path_open_preopen test requires ISDIR for a
406
+ // writable directory on every platform.
407
+ //
408
+ // Windows may still open it, so check the returned descriptor type.
409
+ if (type === "directory" && descriptorFlags.write) {
410
+ // Release the opened handle before reporting the error.
411
+ closeFileResource(id);
412
+ throw "is-directory";
413
+ }
387
414
  const descriptor = descriptorCreate(id, descriptorFlags, fullPath);
388
415
  if (fullPath.endsWith("/") && type !== "directory") {
389
416
  descriptor[symbolDispose]();
@@ -399,7 +426,7 @@ class Descriptor {
399
426
  }
400
427
  }
401
428
  readlinkAt(path) {
402
- const fullPath = this.#getFullPath(path, false);
429
+ const fullPath = this.#getFullPath(path);
403
430
  try {
404
431
  return readlinkSync(fullPath);
405
432
  }
@@ -408,7 +435,7 @@ class Descriptor {
408
435
  }
409
436
  }
410
437
  removeDirectoryAt(path) {
411
- const fullPath = this.#getFullPath(path, false);
438
+ const fullPath = this.#getFullPath(path);
412
439
  try {
413
440
  rmdirSync(fullPath);
414
441
  }
@@ -420,8 +447,8 @@ class Descriptor {
420
447
  }
421
448
  }
422
449
  renameAt(oldPath, newDescriptor, newPath) {
423
- const oldFullPath = this.#getFullPath(oldPath, false);
424
- const newFullPath = newDescriptor.#getFullPath(newPath, false);
450
+ const oldFullPath = this.#getFullPath(oldPath);
451
+ const newFullPath = newDescriptor.#getFullPath(newPath);
425
452
  try {
426
453
  renameSync(oldFullPath, newFullPath);
427
454
  }
@@ -433,7 +460,7 @@ class Descriptor {
433
460
  }
434
461
  }
435
462
  symlinkAt(target, path) {
436
- const fullPath = this.#getFullPath(path, false);
463
+ const fullPath = this.#getFullPath(path);
437
464
  if (target.startsWith("/")) {
438
465
  throw "not-permitted";
439
466
  }
@@ -462,7 +489,7 @@ class Descriptor {
462
489
  }
463
490
  }
464
491
  unlinkFileAt(path) {
465
- const fullPath = this.#getFullPath(path, false);
492
+ const fullPath = this.#getFullPath(path);
466
493
  try {
467
494
  if (fullPath.endsWith("/")) {
468
495
  let isDir = false;
@@ -504,7 +531,7 @@ class Descriptor {
504
531
  }
505
532
  }
506
533
  metadataHashAt(pathFlags, path) {
507
- const fullPath = this.#getFullPath(path, false);
534
+ const fullPath = this.#getFullPath(path);
508
535
  try {
509
536
  const stats = (pathFlags.symlinkFollow ? statSync : lstatSync)(fullPath, {
510
537
  bigint: true,
@@ -515,8 +542,8 @@ class Descriptor {
515
542
  throw convertFsError(e);
516
543
  }
517
544
  }
518
- // TODO: support followSymlinks
519
- #getFullPath(subpath, _followSymlinks) {
545
+ // Join paths lexically; each operation supplies its own symlink semantics.
546
+ #getFullPath(subpath) {
520
547
  let descriptor = this;
521
548
  if (subpath.indexOf("\\") !== -1) {
522
549
  subpath = subpath.replace(/\\/g, "/");
@@ -567,12 +594,18 @@ class Descriptor {
567
594
  segments.push(subpath.slice(segmentIndex));
568
595
  }
569
596
  subpath = segments.join("");
570
- if (descriptor.#hostPreopen) {
571
- return (descriptor.#hostPreopen +
597
+ // The default Windows preopen represents all drives.
598
+ //
599
+ // Convert drive paths to namespaced paths so joining them to the preopen
600
+ // does not produce an invalid UNC server name such as //C:/Users/...
601
+ if (isWindows && descriptor.#hostPreopen === "//" && /^[a-zA-Z]:\//.test(subpath)) {
602
+ return "//?/" + subpath;
603
+ }
604
+ return descriptor.#hostPreopen
605
+ ? descriptor.#hostPreopen +
572
606
  (descriptor.#hostPreopen.endsWith("/") ? "" : subpath.length > 0 ? "/" : "") +
573
- subpath);
574
- }
575
- return descriptor.#fullPath + (subpath.length > 0 ? "/" : "") + subpath;
607
+ subpath
608
+ : descriptor.#fullPath + (subpath.length > 0 ? "/" : "") + subpath;
576
609
  }
577
610
  }
578
611
  const descriptorCreatePreopen = Descriptor._createPreopen;
@@ -629,6 +662,16 @@ export const types = {
629
662
  return convertFsError(err.payload);
630
663
  },
631
664
  };
665
+ /** Create isolated filesystem namespaces from Node.js host-path preopens. */
666
+ export function createFilesystem({ preopens }) {
667
+ const entries = Object.entries(preopens).map(([virtualPath, hostPath]) => [descriptorCreatePreopen(hostPath), virtualPath]);
668
+ return {
669
+ types,
670
+ preopens: {
671
+ getDirectories: () => [...entries],
672
+ },
673
+ };
674
+ }
632
675
  /**
633
676
  * Replace all preopens with the given set.
634
677
  * @param {Record<string, string>} preopens - Map of virtual paths to host paths
@@ -18,4 +18,17 @@ export declare const tcpCreateSocket: typeof TcpCreateSocketNamespace;
18
18
  export declare const tcp: typeof TcpNamespace;
19
19
  export declare const udpCreateSocket: typeof UdpCreateSocketNamespace;
20
20
  export declare const udp: typeof UdpNamespace;
21
+ export interface SocketsConfig {
22
+ enableNetwork?: boolean;
23
+ }
24
+ /** Create socket namespaces with an instance-local network capability. */
25
+ export declare function createSockets(config?: SocketsConfig): {
26
+ instanceNetwork: typeof InstanceNetworkNamespace;
27
+ network: typeof NetworkNamespace;
28
+ ipNameLookup: typeof IpNameLookupNamespace;
29
+ tcpCreateSocket: typeof TcpCreateSocketNamespace;
30
+ tcp: typeof TcpNamespace;
31
+ udpCreateSocket: typeof UdpCreateSocketNamespace;
32
+ udp: typeof UdpNamespace;
33
+ };
21
34
  export {};
@@ -480,3 +480,23 @@ export const udp = {
480
480
  OutgoingDatagramStream,
481
481
  IncomingDatagramStream,
482
482
  };
483
+ /** Create socket namespaces with an instance-local network capability. */
484
+ export function createSockets(config = {}) {
485
+ const localNetwork = new Network();
486
+ if (config.enableNetwork === false) {
487
+ _denyDnsLookup(localNetwork);
488
+ _denyTcp(localNetwork);
489
+ _denyUdp(localNetwork);
490
+ }
491
+ return {
492
+ instanceNetwork: {
493
+ instanceNetwork: () => localNetwork,
494
+ },
495
+ network,
496
+ ipNameLookup,
497
+ tcpCreateSocket,
498
+ tcp,
499
+ udpCreateSocket,
500
+ udp,
501
+ };
502
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/preview2-shim",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "WASI Preview2 shim for JS environments",
5
5
  "homepage": "https://github.com/bytecodealliance/jco#readme",
6
6
  "bugs": {
@@ -43,6 +43,7 @@ type _WASIImportObject = {
43
43
  'wasi:clocks/wall-clock': typeof import('./interfaces/wasi-clocks-wall-clock.d.ts');
44
44
 
45
45
  'wasi:http/types': typeof import('./interfaces/wasi-http-types.d.ts');
46
+ 'wasi:http/incoming-handler': typeof import('./interfaces/wasi-http-incoming-handler.d.ts');
46
47
  'wasi:http/outgoing-handler': typeof import('./interfaces/wasi-http-outgoing-handler.d.ts');
47
48
  };
48
49
 
@@ -53,10 +54,7 @@ type VersionedWASIImportObject<V extends string> = {
53
54
  };
54
55
 
55
56
  /** Used to append versions to generated WASI import objects */
56
- type AppendVersion<
57
- Key extends string | number | symbol,
58
- Version extends string,
59
- > = Version extends ''
57
+ type AppendVersion<Key extends string | number | symbol, Version extends string> = Version extends ''
60
58
  ? Key
61
59
  : Version extends `${infer V}`
62
60
  ? Key extends `${infer K}`
@@ -68,8 +66,8 @@ type AppendVersion<
68
66
  * Sandbox configuration options for WASIShim
69
67
  */
70
68
  interface SandboxConfig {
71
- /** Filesystem preopens mapping (virtual path -> host path) */
72
- preopens?: Record<string, string>;
69
+ /** Filesystem-specific preopens mapping (virtual path -> host path or capability). */
70
+ preopens?: Record<string, unknown>;
73
71
  /** Environment variables visible to the guest */
74
72
  env?: Record<string, string>;
75
73
  /** Command-line arguments */
@@ -78,14 +76,49 @@ interface SandboxConfig {
78
76
  enableNetwork?: boolean;
79
77
  }
80
78
 
79
+ /**
80
+ * Application-provided implementation of the two `wasi:filesystem` namespaces.
81
+ * Browser applications can use this boundary to select their own storage and
82
+ * permission model without the shim choosing a filesystem implementation.
83
+ */
84
+ export interface FilesystemShim {
85
+ preopens: typeof import('./interfaces/wasi-filesystem-preopens.d.ts');
86
+ types: typeof import('./interfaces/wasi-filesystem-types.d.ts');
87
+ /** Interpret sandbox preopen properties and return this filesystem's preopens namespace. */
88
+ createPreopens?(
89
+ preopens: Record<string, unknown>,
90
+ ): typeof import('./interfaces/wasi-filesystem-preopens.d.ts');
91
+ dispose?(): void;
92
+ }
93
+
94
+ /** Application-provided implementation of the WASI TCP namespaces. */
95
+ export interface TcpSocketsShim {
96
+ tcp: typeof import('./interfaces/wasi-sockets-tcp.d.ts');
97
+ tcpCreateSocket: typeof import('./interfaces/wasi-sockets-tcp-create-socket.d.ts');
98
+ }
99
+
100
+ /** Application-provided implementation of the WASI UDP namespaces. */
101
+ export interface UdpSocketsShim {
102
+ udp: typeof import('./interfaces/wasi-sockets-udp.d.ts');
103
+ udpCreateSocket: typeof import('./interfaces/wasi-sockets-udp-create-socket.d.ts');
104
+ }
105
+
106
+ /** Browser HTTP handler using standard Web Request and Response objects. */
107
+ export type WebIncomingHandler = (request: Request) => Response | Promise<Response>;
108
+
81
109
  /**
82
110
  * Configuration options for WASIShim
83
111
  */
84
112
  interface WASIShimConfig {
85
113
  /** Custom CLI shim */
86
114
  cli?: object;
87
- /** Custom filesystem shim */
88
- filesystem?: object;
115
+ /** Application-provided filesystem namespaces. */
116
+ filesystem?: FilesystemShim;
117
+ /** Explicit ephemeral browser file-data adapter and guest-path mappings. */
118
+ browserFilesystem?: {
119
+ adapter: object;
120
+ preopens: Record<string, unknown>;
121
+ };
89
122
  /** Custom I/O shim */
90
123
  io?: object;
91
124
  /** Custom random shim */
@@ -94,8 +127,26 @@ interface WASIShimConfig {
94
127
  clocks?: object;
95
128
  /** Custom sockets shim */
96
129
  sockets?: object;
130
+ /** Custom TCP socket namespaces. */
131
+ tcpSockets?: TcpSocketsShim;
132
+ /** Custom UDP socket namespaces. */
133
+ udpSockets?: UdpSocketsShim;
97
134
  /** Custom HTTP shim */
98
135
  http?: object;
136
+ /** Browser incoming HTTP handler. */
137
+ incomingHandler?: WebIncomingHandler;
138
+ /** Browser-only isolated environment variables. */
139
+ environment?: Record<string, string>;
140
+ /** Browser-only isolated command-line arguments. */
141
+ arguments?: string[];
142
+ /** Browser-only initial working directory. */
143
+ initialCwd?: string;
144
+ /** Browser input stream handler. */
145
+ stdin?: object;
146
+ /** Browser output stream handler. */
147
+ stdout?: object;
148
+ /** Browser error stream handler. */
149
+ stderr?: object;
99
150
  /** Sandbox configuration for restricting guest capabilities */
100
151
  sandbox?: SandboxConfig;
101
152
  }
@@ -160,7 +211,7 @@ interface WASIShimConfig {
160
211
  * }
161
212
  * });
162
213
  *
163
- * // Limited filesystem access
214
+ * // Node.js only: limited host filesystem access
164
215
  * const limitedShim = new WASIShim({
165
216
  * sandbox: {
166
217
  * preopens: {
@@ -171,6 +222,9 @@ interface WASIShimConfig {
171
222
  * });
172
223
  * ```
173
224
  *
225
+ * Browser applications should inject `filesystem` namespaces or explicitly
226
+ * configure `browserFilesystem`; browser preopens never interpret host paths.
227
+ *
174
228
  * Note that this object is similar but not identical to the Node `WASI` object --
175
229
  * it is solely concerned with shimming of preview2 when dealing with a WebAssembly
176
230
  * component transpiled by Jco. While this object *does* work with Node (and the browser)
@@ -189,9 +243,7 @@ export class WASIShim {
189
243
  * @param {options} [opt]
190
244
  * @returns {object}
191
245
  */
192
- getImportObject<V extends string = ''>(
193
- opts?: GetImportObjectArgs
194
- ): VersionedWASIImportObject<V>;
246
+ getImportObject<V extends string = ''>(opts?: GetImportObjectArgs): VersionedWASIImportObject<V>;
195
247
  }
196
248
 
197
249
  interface GetImportObjectArgs {