@bytecodealliance/preview3-shim 0.4.0 → 0.5.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.
@@ -60,7 +60,7 @@ declare class Descriptor {
60
60
  * @returns {Promise<void>}
61
61
  * @throws {FSError} `payload.tag` contains mapped WASI error code.
62
62
  */
63
- advise(_offset: any, _length: any, _advice: any): Promise<void>;
63
+ advise(offset: any, length: any, advice: any): Promise<void>;
64
64
  /**
65
65
  * Synchronize file data to disk.
66
66
  * WIT:
@@ -158,11 +158,14 @@ class Descriptor {
158
158
  * @returns {Promise<void>}
159
159
  * @throws {FSError} `payload.tag` contains mapped WASI error code.
160
160
  */
161
- async advise(_offset, _length, _advice) {
162
- // TODO: This not directly suported on Node.js:
163
- // https://github.com/bytecodealliance/jco/issues/718
164
- if ((await this.getType()).tag === "directory") {
165
- throw new FSError("bad-descriptor");
161
+ async advise(offset, length, advice) {
162
+ this.#ensureHandle();
163
+ try {
164
+ const { fadvise } = await import("@bytecodealliance/jco-node-fs");
165
+ fadvise(this.#handle.fd, offset, length, advice);
166
+ }
167
+ catch (error) {
168
+ throw FSError.from(error);
166
169
  }
167
170
  }
168
171
  /**
@@ -1,6 +1,4 @@
1
1
  import { Server, Socket } from "node:net";
2
- import { Readable } from "stream";
3
- import { pipeline } from "stream/promises";
4
2
  import { once } from "node:events";
5
3
  import { isMainThread, parentPort } from "worker_threads";
6
4
  //#region dist/nodejs/workers/resource-worker.js
@@ -382,10 +380,29 @@ async function handleTcpSend({ socketId, stream }) {
382
380
  const socket = sockets.get(socketId);
383
381
  socket.activeStreams++;
384
382
  const { tcp } = socket;
385
- const readable = Readable.fromWeb(stream);
383
+ const reader = stream.getReader();
384
+ let rejectOnSocketError;
385
+ const socketError = new Promise((_resolve, reject) => {
386
+ rejectOnSocketError = reject;
387
+ });
388
+ tcp.once("error", rejectOnSocketError);
386
389
  try {
387
- await pipeline(readable, tcp);
390
+ while (true) {
391
+ const { done, value } = await Promise.race([reader.read(), socketError]);
392
+ if (done) break;
393
+ await Promise.race([new Promise((resolve, reject) => {
394
+ tcp.write(value, (err) => err ? reject(err) : resolve());
395
+ }), socketError]);
396
+ }
397
+ await new Promise((resolve, reject) => {
398
+ tcp.end((err) => err ? reject(err) : resolve());
399
+ });
400
+ } catch (err) {
401
+ reader.cancel(err).catch(() => {});
402
+ throw err;
388
403
  } finally {
404
+ tcp.off("error", rejectOnSocketError);
405
+ reader.releaseLock();
389
406
  socket.activeStreams--;
390
407
  cleanupDisposedSocket(socketId, socket);
391
408
  }
@@ -1,6 +1,4 @@
1
1
  import { Socket, Server } from "node:net";
2
- import { Readable } from "stream";
3
- import { pipeline } from "stream/promises";
4
2
  import { once } from "node:events";
5
3
  import { Router } from "../workers/resource-worker.js";
6
4
  import { serializeIpAddress, makeIpAddress, ipAddressConflict } from "../sockets/address.js";
@@ -170,11 +168,44 @@ async function handleTcpSend({ socketId, stream }) {
170
168
  const socket = sockets.get(socketId);
171
169
  socket.activeStreams++;
172
170
  const { tcp } = socket;
173
- const readable = Readable.fromWeb(stream);
171
+ const reader = stream.getReader();
172
+ let rejectOnSocketError;
173
+ const socketError = new Promise((_resolve, reject) => {
174
+ rejectOnSocketError = reject;
175
+ });
176
+ tcp.once("error", rejectOnSocketError);
174
177
  try {
175
- await pipeline(readable, tcp);
178
+ // Do not replace this pump with stream.pipeline(). Node 26 changed async
179
+ // iteration to preserve allowHalfOpen duplex streams. For WASI TCP send,
180
+ // however, a write failure must promptly cancel the transferred input
181
+ // stream so the guest's stream writer is dropped. Explicitly awaiting each
182
+ // write keeps socket backpressure and cancellation coupled across workers.
183
+ while (true) {
184
+ const { done, value } = await Promise.race([reader.read(), socketError]);
185
+ if (done) {
186
+ break;
187
+ }
188
+ await Promise.race([
189
+ new Promise((resolve, reject) => {
190
+ tcp.write(value, (err) => (err ? reject(err) : resolve()));
191
+ }),
192
+ socketError,
193
+ ]);
194
+ }
195
+ await new Promise((resolve, reject) => {
196
+ tcp.end((err) => (err ? reject(err) : resolve()));
197
+ });
198
+ }
199
+ catch (err) {
200
+ // Cancellation crosses the worker boundary. Do not wait for its completion:
201
+ // the producer can itself be waiting for this operation's result, which
202
+ // would turn error propagation into a cycle.
203
+ void reader.cancel(err).catch(() => { });
204
+ throw err;
176
205
  }
177
206
  finally {
207
+ tcp.off("error", rejectOnSocketError);
208
+ reader.releaseLock();
178
209
  socket.activeStreams--;
179
210
  cleanupDisposedSocket(socketId, socket);
180
211
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/preview3-shim",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "WASI Preview3 shim for JS environments",
5
5
  "homepage": "https://github.com/bytecodealliance/jco/blob/main/packages/preview3-shim/#readme",
6
6
  "bugs": {
@@ -48,7 +48,8 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@bytecodealliance/preview2-shim": "^0.21.0"
51
+ "@bytecodealliance/jco-node-fs": "0.2.0",
52
+ "@bytecodealliance/preview2-shim": "^0.22.0"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/node": "^24.12.4",