@bytecodealliance/preview2-shim 0.19.0 → 0.20.1

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/README.md CHANGED
@@ -6,6 +6,10 @@ Node.js support is fully tested and conformant against the Wasmtime test suite.
6
6
 
7
7
  Browser support is considered experimental, and not currently suitable for production applications.
8
8
 
9
+ The Node.js implementation owns its worker artifact. Direct package use and supported downstream
10
+ bundlers should resolve it through the public shim imports; applications do not need to import or
11
+ copy files from `dist/io`.
12
+
9
13
  # Features
10
14
 
11
15
  ## WASI Shim object for easy instantiation
@@ -15,8 +19,8 @@ An default instantiation object can be used via the `WASIShim` class in `@byteco
15
19
  ```typescript
16
20
  import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation";
17
21
  import type {
18
- VersionedWASIImportObject,
19
- WASIImportObject,
22
+ VersionedWASIImportObject,
23
+ WASIImportObject,
20
24
  } from "@bytecodealliance/preview2-shim/instantiation";
21
25
 
22
26
  const shim = new WASIShim();
@@ -27,7 +31,7 @@ unversioned satisfies WASIImportObject;
27
31
  unversioned satisfies VersionedWASIImportObject<"">;
28
32
 
29
33
  const versioned: VersionedWASIImportObject<"0.2.3"> = shim.getImportObject({
30
- asVersion: "0.2.3",
34
+ asVersion: "0.2.3",
31
35
  });
32
36
  //console.log('versioned', versioned);
33
37
  versioned satisfies VersionedWASIImportObject<"0.2.3">;
@@ -66,23 +70,23 @@ import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation";
66
70
 
67
71
  // Fully sandboxed - no filesystem, network, or env access
68
72
  const sandboxedShim = new WASIShim({
69
- sandbox: {
70
- preopens: {}, // No filesystem access
71
- env: {}, // No environment variables
72
- args: ["arg1"], // Custom arguments
73
- enableNetwork: false, // Disable network access
74
- },
73
+ sandbox: {
74
+ preopens: {}, // No filesystem access
75
+ env: {}, // No environment variables
76
+ args: ["arg1"], // Custom arguments
77
+ enableNetwork: false, // Disable network access
78
+ },
75
79
  });
76
80
 
77
81
  // Limited filesystem access - map virtual paths to host paths
78
82
  const limitedShim = new WASIShim({
79
- sandbox: {
80
- preopens: {
81
- "/data": "/tmp/guest-data", // Guest sees /data, maps to /tmp/guest-data
82
- "/config": "/etc/app", // Guest sees /config, maps to /etc/app
83
+ sandbox: {
84
+ preopens: {
85
+ "/data": "/tmp/guest-data", // Guest sees /data, maps to /tmp/guest-data
86
+ "/config": "/etc/app", // Guest sees /config, maps to /etc/app
87
+ },
88
+ env: { ENV1: "42" }, // Only expose specific env vars
83
89
  },
84
- env: { ENV1: "42" }, // Only expose specific env vars
85
- },
86
90
  });
87
91
 
88
92
  const component = await instantiate(loader, sandboxedShim.getImportObject());
@@ -74,6 +74,21 @@ function getSource(fileEntry) {
74
74
  }
75
75
  return fileEntry.source;
76
76
  }
77
+ // Keep spare capacity separate so FileDataEntry.source always reflects the logical file size.
78
+ const fileWriteBuffers = new WeakMap();
79
+ function getFileWriteBuffer(entry, source, requiredLength) {
80
+ let buffer = fileWriteBuffers.get(entry);
81
+ if (!buffer || buffer.buffer !== source.buffer || buffer.byteOffset !== source.byteOffset) {
82
+ buffer = source;
83
+ }
84
+ if (requiredLength <= buffer.byteLength) {
85
+ return buffer;
86
+ }
87
+ const newBuffer = new Uint8Array(Math.max(requiredLength, source.byteLength * 2));
88
+ newBuffer.set(source);
89
+ fileWriteBuffers.set(entry, newBuffer);
90
+ return newBuffer;
91
+ }
77
92
  class DirectoryEntryStream {
78
93
  idx = 0;
79
94
  entries = [];
@@ -130,15 +145,24 @@ class Descriptor {
130
145
  }
131
146
  writeViaStream(_offset) {
132
147
  const entry = this.#entry;
133
- let offset = Number(_offset);
148
+ let offset = coerceToSafeIntegerNumber(_offset);
134
149
  return outputStreamCreate({
135
150
  write(buf) {
136
- const src = entry.source;
137
- const newSource = new Uint8Array(buf.byteLength + src.byteLength);
138
- newSource.set(src, 0);
139
- newSource.set(buf, offset);
140
- offset += buf.byteLength;
141
- entry.source = newSource;
151
+ if (buf.byteLength === 0) {
152
+ return;
153
+ }
154
+ const source = getSource(entry);
155
+ const end = offset + buf.byteLength;
156
+ if (!Number.isSafeInteger(end)) {
157
+ throw new TypeError(`excessively large number: ${end}`);
158
+ }
159
+ const buffer = getFileWriteBuffer(entry, source, end);
160
+ if (offset > source.byteLength) {
161
+ buffer.fill(0, source.byteLength, offset);
162
+ }
163
+ buffer.set(buf, offset);
164
+ entry.source = buffer.subarray(0, Math.max(source.byteLength, end));
165
+ offset = end;
142
166
  },
143
167
  });
144
168
  }
@@ -509,7 +509,7 @@ class FutureTrailers {
509
509
  tag: "ok",
510
510
  val: {
511
511
  tag: "ok",
512
- val: undefined,
512
+ val: fieldsLock(fieldsFromEntriesChecked([])),
513
513
  },
514
514
  };
515
515
  }
@@ -164,7 +164,8 @@ export class WASIShim {
164
164
  obj[`wasi:sockets/tcp-create-socket${versionSuffix}`] = this.#sockets.tcpCreateSocket;
165
165
  obj[`wasi:sockets/udp${versionSuffix}`] = this.#sockets.udp;
166
166
  obj[`wasi:sockets/udp-create-socket${versionSuffix}`] = this.#sockets.udpCreateSocket;
167
- obj[`wasi:filesystem/preopens${versionSuffix}`] = this.#preopens ?? this.#filesystem.preopens;
167
+ obj[`wasi:filesystem/preopens${versionSuffix}`] =
168
+ this.#preopens ?? this.#filesystem.preopens;
168
169
  obj[`wasi:filesystem/types${versionSuffix}`] = this.#filesystem.types;
169
170
  obj[`wasi:io/error${versionSuffix}`] = this.#io.error;
170
171
  obj[`wasi:io/poll${versionSuffix}`] = this.#io.poll;
@@ -1,4 +1,4 @@
1
- import { fileURLToPath } from "node:url";
1
+ import { Worker } from "node:worker_threads";
2
2
  import { createSyncFn } from "../synckit/index.js";
3
3
  import { CALL_MASK, CALL_TYPE_MASK, FILE, HTTP_SERVER_INCOMING_HANDLER, HTTP, INPUT_STREAM_BLOCKING_READ, INPUT_STREAM_BLOCKING_SKIP, INPUT_STREAM_DISPOSE, INPUT_STREAM_READ, INPUT_STREAM_SKIP, INPUT_STREAM_SUBSCRIBE, OUTPUT_STREAM_BLOCKING_FLUSH, OUTPUT_STREAM_BLOCKING_SPLICE, OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH, OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH, OUTPUT_STREAM_CHECK_WRITE, OUTPUT_STREAM_DISPOSE, OUTPUT_STREAM_FLUSH, OUTPUT_STREAM_SPLICE, OUTPUT_STREAM_SUBSCRIBE, OUTPUT_STREAM_WRITE_ZEROES, OUTPUT_STREAM_WRITE, POLL_POLL_LIST, POLL_POLLABLE_BLOCK, POLL_POLLABLE_DISPOSE, POLL_POLLABLE_READY, SOCKET_TCP, STDERR, STDIN, STDOUT, reverseMap, } from "./calls.js";
4
4
  import nodeProcess, { exit, stderr, stdout, env } from "node:process";
@@ -6,18 +6,21 @@ const symbolDispose = Symbol.dispose || Symbol.for("dispose");
6
6
  const _rawDebug = "_rawDebug" in nodeProcess
7
7
  ? nodeProcess._rawDebug
8
8
  : console.error.bind(console);
9
- const workerPath = fileURLToPath(new URL("./worker-thread.js", import.meta.url));
10
9
  const httpIncomingHandlers = new Map();
11
10
  export function registerIncomingHttpHandler(id, handler) {
12
11
  httpIncomingHandlers.set(id, handler);
13
12
  }
14
13
  const instanceId = Math.round(Math.random() * 1000).toString();
15
14
  const DEBUG_DEFAULT = false;
16
- const DEBUG = env.PREVIEW2_SHIM_DEBUG === "0" ? false : env.PREVIEW2_SHIM_DEBUG === "1" ? true : DEBUG_DEFAULT;
15
+ const DEBUG = env.PREVIEW2_SHIM_DEBUG === "0"
16
+ ? false
17
+ : env.PREVIEW2_SHIM_DEBUG === "1"
18
+ ? true
19
+ : DEBUG_DEFAULT;
17
20
  /**
18
21
  * @type {(call: number, id: number | null, payload: any) -> any}
19
22
  */
20
- export let ioCall = createSyncFn(workerPath, DEBUG, (type, id, payload) => {
23
+ export let ioCall = createSyncFn((options) => new Worker(new URL("./worker-thread.bundle.js", import.meta.url), options), DEBUG, (type, id, payload) => {
21
24
  // 'callbacks' from the worker
22
25
  // ONLY happens for an http server incoming handler, and NOTHING else (not even sockets, since accept is sync!)
23
26
  if (type !== HTTP_SERVER_INCOMING_HANDLER) {
@@ -148,7 +151,8 @@ class InputStream {
148
151
  disposeFn = httpInputStreamDispose;
149
152
  break;
150
153
  default:
151
- throw new Error("wasi-io trap: Dispose function not created for stream type " + reverseMap[streamType]);
154
+ throw new Error("wasi-io trap: Dispose function not created for stream type " +
155
+ reverseMap[streamType]);
152
156
  }
153
157
  stream.#finalizer = registerDispose(stream, null, id, disposeFn);
154
158
  return stream;
@@ -253,7 +257,8 @@ class OutputStream {
253
257
  case HTTP:
254
258
  return stream;
255
259
  default:
256
- throw new Error("wasi-io trap: Dispose function not created for stream type " + reverseMap[streamType]);
260
+ throw new Error("wasi-io trap: Dispose function not created for stream type " +
261
+ reverseMap[streamType]);
257
262
  }
258
263
  stream.#finalizer = registerDispose(stream, null, id, disposeFn);
259
264
  return stream;
@@ -406,7 +406,8 @@ export async function socketOutgoingDatagramStreamSend(id, datagrams) {
406
406
  function doSendBatch() {
407
407
  return new Promise((resolve, reject) => {
408
408
  if (socket.remoteAddress) {
409
- if (sendQueueAddress !== socket.remoteAddress || sendQueuePort !== socket.remotePort) {
409
+ if (sendQueueAddress !== socket.remoteAddress ||
410
+ sendQueuePort !== socket.remotePort) {
410
411
  return void reject("invalid-argument");
411
412
  }
412
413
  udpSocket.send(sendQueue, handler);