Skip to content

Commit 55aba82

Browse files
committed
fix(p3-shim): adapt shim to newest bindings
1 parent 8a335e9 commit 55aba82

14 files changed

Lines changed: 166 additions & 18 deletions

File tree

packages/preview3-shim/lib/nodejs/cli.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { ResourceWorker } from "./workers/resource-worker.js";
55
import { StreamReader, readableStreamFromIterator } from "./stream.js";
66
import { future } from "./future.js";
77

8+
import { environment as environmentV2 } from "@bytecodealliance/preview2-shim/cli";
9+
810
export {
911
_appendEnv,
1012
_setEnv,
@@ -13,7 +15,6 @@ export {
1315
_setTerminalStdin,
1416
_setTerminalStdout,
1517
_setTerminalStderr,
16-
environment,
1718
exit,
1819
terminalInput,
1920
terminalOutput,
@@ -22,6 +23,15 @@ export {
2223
terminalStderr,
2324
} from "@bytecodealliance/preview2-shim/cli";
2425

26+
// `wasi:cli/environment` renamed `initial-cwd` to `get-initial-cwd` between
27+
// p2 and p3. Adapt the p2-shim shape to the p3 WIT member name while
28+
// re-exporting the unchanged members.
29+
export const environment = {
30+
getEnvironment: environmentV2.getEnvironment,
31+
getArguments: environmentV2.getArguments,
32+
getInitialCwd: environmentV2.initialCwd,
33+
};
34+
2535
let WORKER = null;
2636
function worker() {
2737
return (WORKER ??= new ResourceWorker(new URL("./workers/cli-worker.js", import.meta.url)));
@@ -88,7 +98,7 @@ export const stdout = {
8898
* @returns {Promise<{tag: string, val?: string}>} Result of the write operation.
8999
*/
90100
async writeViaStream(streamReader) {
91-
const readableStream = readableStreamFromIterator(streamReader.intoAsyncIterator());
101+
const readableStream = readableStreamFromIterator(streamReader[Symbol.asyncIterator]());
92102
try {
93103
await worker().run({ op: "stdout", stream: readableStream }, [readableStream]);
94104
return { tag: "ok", val: undefined };
@@ -109,7 +119,7 @@ export const stderr = {
109119
* @returns {Promise<{tag: string, val?: string}>} Result of the write operation.
110120
*/
111121
async writeViaStream(streamReader) {
112-
const readableStream = readableStreamFromIterator(streamReader.intoAsyncIterator());
122+
const readableStream = readableStreamFromIterator(streamReader[Symbol.asyncIterator]());
113123
try {
114124
await worker().run({ op: "stderr", stream: readableStream }, [readableStream]);
115125
return { tag: "ok", val: undefined };

packages/preview3-shim/lib/nodejs/clocks.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const monotonicClock = {
5858
* @throws {TypeError} If targetNs is not a bigint
5959
*/
6060
async waitUntil(targetNs) {
61-
const nowNs = this.now();
61+
const nowNs = monotonicClock.now();
6262
const diffNs = targetNs - nowNs;
6363

6464
if (diffNs <= 0n) {

packages/preview3-shim/lib/nodejs/filesystem/descriptor.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,14 @@ class Descriptor {
125125
* ```
126126
*
127127
* @async
128-
* @param {object} data A data source implementing `intoAsyncIterator()`.
128+
* @param {object} data A data source implementing `[Symbol.asyncIterator]()`.
129129
* @param {bigint} offset The offset within the file.
130130
* @returns {Promise<void>}
131131
* @throws {FSError} `payload.tag` contains mapped WASI error code.
132132
*/
133133
async writeViaStream(data, offset) {
134134
this.#ensureHandle();
135-
const stream = readableStreamFromIterator(data.intoAsyncIterator());
135+
const stream = readableStreamFromIterator(data[Symbol.asyncIterator]());
136136

137137
try {
138138
await worker().run({ op: "write", fd: this.#handle.fd, offset, stream }, [stream]);
@@ -149,7 +149,7 @@ class Descriptor {
149149
* ```
150150
*
151151
* @async
152-
* @param {object} data A data source implementing `intoAsyncIterator()`.
152+
* @param {object} data A data source implementing `[Symbol.asyncIterator]()`.
153153
* @returns {Promise<void>}
154154
* @throws {FSError} `payload.tag` contains mapped WASI error code.
155155
*/

packages/preview3-shim/lib/nodejs/http/client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const client = {
5151
const { port1: tx, port2: rx } = new MessageChannel();
5252

5353
const transfer = [rx];
54-
const stream = body ? readableStreamFromIterator(body.intoAsyncIterator()) : undefined;
54+
const stream = body ? readableStreamFromIterator(body[Symbol.asyncIterator]()) : undefined;
5555
if (stream) {
5656
transfer.unshift(stream);
5757
}

packages/preview3-shim/lib/nodejs/http/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class HttpServer extends EventEmitter {
9292
const [body, trailers] = Response.consumeBody(res, resRx);
9393
const { port1: tx, port2: rx } = new MessageChannel();
9494

95-
const stream = readableStreamFromIterator(body.intoAsyncIterator());
95+
const stream = readableStreamFromIterator(body[Symbol.asyncIterator]());
9696

9797
// Send trailers when ready
9898
trailers
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
1-
export { insecure, insecureSeed, random } from "@bytecodealliance/preview2-shim/random";
1+
import {
2+
insecure,
3+
insecureSeed as insecureSeedV2,
4+
random,
5+
} from "@bytecodealliance/preview2-shim/random";
6+
7+
export { insecure, random };
8+
9+
// `wasi:random/insecure-seed` was renamed between p2 (`insecure-seed`)
10+
// and p3 (`get-insecure-seed`). Rename the member to match the p3 WIT
11+
// while keeping the original implementation.
12+
export const insecureSeed = {
13+
getInsecureSeed: insecureSeedV2.insecureSeed,
14+
};

packages/preview3-shim/lib/nodejs/sockets/tcp.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,21 +239,22 @@ export class TcpSocket {
239239
* ```
240240
*
241241
* @async
242-
* @param {StreamReader} data - The data stream to send
242+
* @param {AsyncIterable<Uint8Array>} data - The data stream to send. Any
243+
* async-iterable yielding byte chunks (e.g. a `StreamReader`) is accepted.
243244
* @returns {Promise<void>}
244245
* @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 {StreamReader}
246+
* @throws {SocketError} With payload.tag 'invalid-argument' if `data` does not implement [Symbol.asyncIterator]
246247
* @throws {SocketError} for other errors, payload.tag maps the system error
247248
*/
248249
async send(data) {
249250
if (this.#state !== STATE.CONNECTED) {
250251
throw new SocketError("invalid-state");
251252
}
252-
if (!(data instanceof StreamReader)) {
253+
if (data == null || typeof data[Symbol.asyncIterator] !== "function") {
253254
throw new SocketError("invalid-argument");
254255
}
255256

256-
const stream = readableStreamFromIterator(data.intoAsyncIterator());
257+
const stream = readableStreamFromIterator(data[Symbol.asyncIterator]());
257258

258259
try {
259260
// Transfer the stream to the worker

packages/preview3-shim/lib/nodejs/stream.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,18 @@ export class StreamReader {
201201
return iterator;
202202
}
203203

204+
/**
205+
* Implements the async-iterable protocol so a StreamReader can be used
206+
* directly with `for await…of` and any helper that consumes an
207+
* async iterator (e.g. `readableStreamFromIterator`). Like
208+
* `intoAsyncIterator()` above, this consumes the reader.
209+
*
210+
* @returns {AsyncIterator}
211+
*/
212+
[Symbol.asyncIterator]() {
213+
return this.intoAsyncIterator();
214+
}
215+
204216
#ensureIterator() {
205217
if (this.#iterator === null) {
206218
throw new Error("StreamReader is closed");

packages/preview3-shim/lib/nodejs/workers/cli-worker.js

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,39 @@ Router().op("stdout", handleOp).op("stderr", handleOp);
99
async function handleOp(msg) {
1010
const { op, stream } = msg;
1111

12-
const readable = Readable.fromWeb(stream);
12+
const readable = Readable.from(byteChunks(stream), { objectMode: false });
1313
const writable = op === "stdout" ? process.stdout : process.stderr;
1414

1515
await pipeline(readable, writable, { end: false });
1616
return { ok: true };
1717
}
18+
19+
async function* byteChunks(stream) {
20+
for await (const value of stream) {
21+
yield nodeByteChunk(value);
22+
}
23+
}
24+
25+
function assertByte(value) {
26+
if (!Number.isInteger(value) || value < 0 || value > 255) {
27+
throw new RangeError(`Invalid byte stream value: ${value}`);
28+
}
29+
return value;
30+
}
31+
32+
function nodeByteChunk(value) {
33+
if (value instanceof Uint8Array) {
34+
return value; // also matches Buffer
35+
}
36+
if (value instanceof ArrayBuffer) {
37+
return new Uint8Array(value);
38+
}
39+
if (Array.isArray(value)) {
40+
for (const byte of value) {assertByte(byte);}
41+
return Uint8Array.from(value);
42+
}
43+
if (typeof value === "number") {
44+
return Uint8Array.of(assertByte(value));
45+
}
46+
return value;
47+
}

packages/preview3-shim/test/cli.test.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, test, expect } from "vitest";
22

3+
import { Buffer } from "node:buffer";
34
import process from "node:process";
45
import { Readable } from "node:stream";
56

@@ -37,6 +38,40 @@ describe("Node.js Preview3 wasi-cli", () => {
3738
expect(result.tag).toBe("ok");
3839
});
3940

41+
test("writeViaStream writes numeric byte chunks to stdout", async () => {
42+
const { cli } = await import("@bytecodealliance/preview3-shim");
43+
const { stream } = await import("@bytecodealliance/preview3-shim/stream");
44+
45+
const restore = process.stdout.write.bind(process.stdout);
46+
let output = "";
47+
48+
const message = "Hello world!";
49+
const finished = new Promise((resolve) => {
50+
process.stdout.write = (chunk, _enc, cb) => {
51+
output += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
52+
cb?.();
53+
if (output === message) {
54+
resolve();
55+
}
56+
};
57+
});
58+
59+
const { tx, rx } = stream();
60+
const resultPromise = cli.stdout.writeViaStream(rx);
61+
62+
for (const byte of Buffer.from(message)) {
63+
await tx.write(byte);
64+
}
65+
await tx.close();
66+
67+
await finished;
68+
const result = await resultPromise;
69+
70+
process.stdout.write = restore;
71+
expect(output).toBe(message);
72+
expect(result.tag).toBe("ok");
73+
});
74+
4075
test("readViaStream returns [stream, future] tuple", async () => {
4176
const { cli } = await import("@bytecodealliance/preview3-shim");
4277

0 commit comments

Comments
 (0)