@bytecodealliance/preview3-shim 0.1.2 → 0.2.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/dist/nodejs/cli.d.ts +51 -0
- package/dist/nodejs/cli.js +106 -0
- package/dist/nodejs/clocks.d.ts +55 -0
- package/dist/nodejs/clocks.js +83 -0
- package/dist/nodejs/filesystem/descriptor.d.ts +443 -0
- package/dist/nodejs/filesystem/descriptor.js +1010 -0
- package/dist/nodejs/filesystem/error.d.ts +61 -0
- package/dist/nodejs/filesystem/error.js +86 -0
- package/dist/nodejs/filesystem/utils.d.ts +8 -0
- package/dist/nodejs/filesystem/utils.js +25 -0
- package/dist/nodejs/filesystem.js +2 -0
- package/dist/nodejs/finalization.d.ts +2 -0
- package/{lib → dist}/nodejs/finalization.js +15 -21
- package/dist/nodejs/future.d.ts +98 -0
- package/dist/nodejs/future.js +176 -0
- package/dist/nodejs/http/client.d.ts +22 -0
- package/dist/nodejs/http/client.js +138 -0
- package/dist/nodejs/http/error.d.ts +18 -0
- package/dist/nodejs/http/error.js +62 -0
- package/dist/nodejs/http/fields.d.ts +144 -0
- package/dist/nodejs/http/fields.js +332 -0
- package/dist/nodejs/http/request.d.ts +256 -0
- package/dist/nodejs/http/request.js +606 -0
- package/dist/nodejs/http/response.d.ts +82 -0
- package/dist/nodejs/http/response.js +202 -0
- package/dist/nodejs/http/server.d.ts +13 -0
- package/dist/nodejs/http/server.js +127 -0
- package/dist/nodejs/http.d.ts +19 -0
- package/{lib → dist}/nodejs/http.js +13 -18
- package/dist/nodejs/index.js +8 -0
- package/dist/nodejs/random.d.ts +5 -0
- package/{lib → dist}/nodejs/random.js +2 -8
- package/dist/nodejs/sockets/address.d.ts +78 -0
- package/dist/nodejs/sockets/address.js +120 -0
- package/dist/nodejs/sockets/error.d.ts +83 -0
- package/dist/nodejs/sockets/error.js +120 -0
- package/dist/nodejs/sockets/ip-name-lookup.d.ts +18 -0
- package/dist/nodejs/sockets/ip-name-lookup.js +84 -0
- package/dist/nodejs/sockets/tcp.d.ts +333 -0
- package/dist/nodejs/sockets/tcp.js +723 -0
- package/dist/nodejs/sockets/udp.d.ts +224 -0
- package/dist/nodejs/sockets/udp.js +475 -0
- package/dist/nodejs/sockets.d.ts +11 -0
- package/{lib → dist}/nodejs/sockets.js +2 -5
- package/dist/nodejs/stream.d.ts +161 -0
- package/dist/nodejs/stream.js +388 -0
- package/dist/nodejs/workers/cli-worker.d.ts +1 -0
- package/dist/nodejs/workers/cli-worker.js +41 -0
- package/dist/nodejs/workers/filesystem-worker.d.ts +1 -0
- package/dist/nodejs/workers/filesystem-worker.js +109 -0
- package/dist/nodejs/workers/http-worker.d.ts +1 -0
- package/dist/nodejs/workers/http-worker.js +355 -0
- package/dist/nodejs/workers/resource-worker.d.ts +13 -0
- package/dist/nodejs/workers/resource-worker.js +152 -0
- package/dist/nodejs/workers/tcp-worker.d.ts +2 -0
- package/dist/nodejs/workers/tcp-worker.js +318 -0
- package/dist/nodejs/workers/udp-worker.d.ts +1 -0
- package/dist/nodejs/workers/udp-worker.js +156 -0
- package/package.json +20 -12
- package/types/interfaces/wasi-cli-environment.d.ts +3 -3
- package/types/interfaces/wasi-cli-exit.d.ts +12 -1
- package/types/interfaces/wasi-cli-run.d.ts +1 -1
- package/types/interfaces/wasi-cli-stderr.d.ts +3 -3
- package/types/interfaces/wasi-cli-stdin.d.ts +5 -5
- package/types/interfaces/wasi-cli-stdout.d.ts +3 -3
- package/types/interfaces/wasi-cli-terminal-input.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-output.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
- package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
- package/types/interfaces/wasi-cli-types.d.ts +5 -5
- package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +3 -3
- package/types/interfaces/wasi-clocks-system-clock.d.ts +6 -6
- package/types/interfaces/wasi-clocks-types.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
- package/types/interfaces/wasi-filesystem-types.d.ts +74 -74
- package/types/interfaces/wasi-http-client.d.ts +1 -1
- package/types/interfaces/wasi-http-handler.d.ts +1 -1
- package/types/interfaces/wasi-http-types.d.ts +34 -34
- package/types/interfaces/wasi-random-insecure-seed.d.ts +5 -5
- package/types/interfaces/wasi-random-insecure.d.ts +5 -5
- package/types/interfaces/wasi-random-random.d.ts +5 -5
- package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +11 -11
- package/types/interfaces/wasi-sockets-types.d.ts +121 -121
- package/types/wasi-cli-command.d.ts +23 -23
- package/types/wasi-http-service.d.ts +14 -14
- package/lib/nodejs/cli.js +0 -127
- package/lib/nodejs/clocks.js +0 -98
- package/lib/nodejs/filesystem/descriptor.js +0 -1093
- package/lib/nodejs/filesystem/error.js +0 -92
- package/lib/nodejs/filesystem/utils.js +0 -19
- package/lib/nodejs/future.js +0 -190
- package/lib/nodejs/http/client.js +0 -161
- package/lib/nodejs/http/error.js +0 -71
- package/lib/nodejs/http/fields.js +0 -373
- package/lib/nodejs/http/request.js +0 -691
- package/lib/nodejs/http/response.js +0 -227
- package/lib/nodejs/http/server.js +0 -157
- package/lib/nodejs/sockets/address.js +0 -146
- package/lib/nodejs/sockets/error.js +0 -123
- package/lib/nodejs/sockets/ip-name-lookup.js +0 -93
- package/lib/nodejs/sockets/tcp.js +0 -801
- package/lib/nodejs/sockets/udp.js +0 -516
- package/lib/nodejs/stream.js +0 -437
- package/lib/nodejs/workers/cli-worker.js +0 -49
- package/lib/nodejs/workers/filesystem-worker.js +0 -119
- package/lib/nodejs/workers/http-worker.js +0 -417
- package/lib/nodejs/workers/resource-worker.js +0 -195
- package/lib/nodejs/workers/tcp-worker.js +0 -370
- package/lib/nodejs/workers/udp-worker.js +0 -194
- /package/{lib/nodejs/filesystem.js → dist/nodejs/filesystem.d.ts} +0 -0
- /package/{lib/nodejs/index.js → dist/nodejs/index.d.ts} +0 -0
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { TcpSocket } from "./sockets/tcp.js";
|
|
2
2
|
import { UdpSocket } from "./sockets/udp.js";
|
|
3
|
-
|
|
4
3
|
export { ipNameLookup } from "./sockets/ip-name-lookup.js";
|
|
5
4
|
export * from "./sockets/tcp.js";
|
|
6
5
|
export * from "./sockets/udp.js";
|
|
7
|
-
|
|
8
6
|
export { IP_ADDRESS_FAMILY } from "./sockets/address.js";
|
|
9
7
|
export { makeIpAddress, serializeIpAddress } from "./sockets/address.js";
|
|
10
|
-
|
|
11
8
|
export const types = {
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
TcpSocket,
|
|
10
|
+
UdpSocket,
|
|
14
11
|
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
export declare const DEFAULT_BYTE_STREAM_CHUNK_SIZE: number;
|
|
2
|
+
declare const symbolDispose: symbol;
|
|
3
|
+
/**
|
|
4
|
+
* Creates a transferable ReadableStream from an async iterator.
|
|
5
|
+
*
|
|
6
|
+
* Use this when a StreamReader's contents need to be sent to a worker thread,
|
|
7
|
+
* since async iterators are not transferable.
|
|
8
|
+
*
|
|
9
|
+
* @param {AsyncIterator} iterator - The async iterator to wrap.
|
|
10
|
+
* @param {string} [name="iterator"] - Optional name for error messages.
|
|
11
|
+
* @returns {ReadableStream} A transferable ReadableStream that pulls from the iterator.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readableStreamFromIterator(iterator: any, name?: string): ReadableStream<any>;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a transferable ReadableStream from a byte-stream reader.
|
|
16
|
+
*
|
|
17
|
+
* Generated p3 stream readers expose `read({ count })`, which performs one
|
|
18
|
+
* bounded Canonical ABI stream read for up to `count` bytes. Prefer that path so
|
|
19
|
+
* host byte sinks consume practical chunks instead of one byte at a time.
|
|
20
|
+
*
|
|
21
|
+
* @param {object} reader - Reader that provides `read()`, or `[Symbol.asyncIterator]()`.
|
|
22
|
+
* @param {object} [opts={}] - Optional adapter settings.
|
|
23
|
+
* @param {number} [opts.chunkSize=DEFAULT_BYTE_STREAM_CHUNK_SIZE] - Maximum bytes to request per generated `read()` call.
|
|
24
|
+
* @param {string} [opts.name="stream reader"] - Reader name used in error messages.
|
|
25
|
+
* @returns {ReadableStream<Uint8Array>} A transferable stream of byte chunks.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readableByteStreamFromReader(reader: any, opts?: {}): ReadableStream<any>;
|
|
28
|
+
export declare function _byteChunk(value: any): any;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a bidirectional stream with separate reader and writer interfaces.
|
|
31
|
+
*
|
|
32
|
+
* Uses a TransformStream internally.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} [opts={}] - Configuration options for the stream.
|
|
35
|
+
* @param {number} [opts.readableHWM=65536] - High water mark for the readable side (bytes).
|
|
36
|
+
* @param {number} [opts.writableHWM=65536] - High water mark for the writable side (bytes).
|
|
37
|
+
*
|
|
38
|
+
* @returns {Object} An object containing:
|
|
39
|
+
* - `tx` {StreamWriter}: The writer for sending data to the stream.
|
|
40
|
+
* - `rx` {StreamReader}: The reader for receiving data from the stream.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
*
|
|
44
|
+
* const { tx, rx } = stream();
|
|
45
|
+
* const payload = Buffer.from('roundtrip');
|
|
46
|
+
*
|
|
47
|
+
* await tx.write(payload);
|
|
48
|
+
* await tx.close();
|
|
49
|
+
*
|
|
50
|
+
* const chunk1 = await rx.read();
|
|
51
|
+
* expect(chunk1).toEqual(payload);
|
|
52
|
+
*
|
|
53
|
+
* const done = await rx.read();
|
|
54
|
+
* expect(done).toBeNull();
|
|
55
|
+
*
|
|
56
|
+
*/
|
|
57
|
+
export declare function stream(opts?: {
|
|
58
|
+
readableHWM?: number;
|
|
59
|
+
writableHWM?: number;
|
|
60
|
+
}): {
|
|
61
|
+
tx: StreamWriter;
|
|
62
|
+
rx: StreamReader;
|
|
63
|
+
};
|
|
64
|
+
export declare class StreamReader {
|
|
65
|
+
#private;
|
|
66
|
+
[symbolDispose]: () => void;
|
|
67
|
+
/**
|
|
68
|
+
* Constructs a StreamReader.
|
|
69
|
+
*
|
|
70
|
+
* @param {AsyncIterable|Iterable} source - An async or sync iterable to consume e.g. ReadableStream, async generator, array.
|
|
71
|
+
* @throws {Error} If the provided source does not implement `[Symbol.asyncIterator]` or `[Symbol.iterator]`.
|
|
72
|
+
*/
|
|
73
|
+
constructor(source: any, opts?: {
|
|
74
|
+
preventCancel?: boolean;
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Reads the next chunk from the stream.
|
|
78
|
+
*
|
|
79
|
+
* @returns {Promise<any>} Resolves with the next chunk or null if the stream is done.
|
|
80
|
+
*/
|
|
81
|
+
read(): Promise<any>;
|
|
82
|
+
/**
|
|
83
|
+
* Reads all chunks from the stream and concatenates them into a buffer.
|
|
84
|
+
*
|
|
85
|
+
* @returns {Promise<Buffer>} Resolves with the concatenated buffer.
|
|
86
|
+
*/
|
|
87
|
+
readAll(): Promise<Buffer<ArrayBuffer>>;
|
|
88
|
+
/**
|
|
89
|
+
* Cancels the stream, signalling a premature exit to the iterator.
|
|
90
|
+
*
|
|
91
|
+
* @returns {Promise<void>}
|
|
92
|
+
*/
|
|
93
|
+
cancel(_reason: any): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Closes the reader and releases the iterator.
|
|
96
|
+
* Only calls return() if the iterator hasn't completed naturally,
|
|
97
|
+
* since return() is meant for premature exits only.
|
|
98
|
+
*/
|
|
99
|
+
close(): void;
|
|
100
|
+
/**
|
|
101
|
+
* Consumes the reader and returns the underlying async iterator.
|
|
102
|
+
*
|
|
103
|
+
* After calling this method the StreamReader is closed and must not be used.
|
|
104
|
+
*
|
|
105
|
+
* @returns {AsyncIterator} The remaining async iterator.
|
|
106
|
+
*/
|
|
107
|
+
intoAsyncIterator(): any;
|
|
108
|
+
/**
|
|
109
|
+
* Implements the async-iterable protocol so a StreamReader can be used
|
|
110
|
+
* directly with `for await…of` and any helper that consumes an
|
|
111
|
+
* async iterator (e.g. `readableStreamFromIterator`). Like
|
|
112
|
+
* `intoAsyncIterator()` above, this consumes the reader.
|
|
113
|
+
*
|
|
114
|
+
* @returns {AsyncIterator}
|
|
115
|
+
*/
|
|
116
|
+
[Symbol.asyncIterator](): any;
|
|
117
|
+
}
|
|
118
|
+
export declare class StreamWriter {
|
|
119
|
+
#private;
|
|
120
|
+
/**
|
|
121
|
+
* Constructs a StreamWriter.
|
|
122
|
+
*
|
|
123
|
+
* @param {WritableStream} writable - The writable stream to write to.
|
|
124
|
+
* @throws {Error} If the provided stream does not implement `getWriter`.
|
|
125
|
+
*/
|
|
126
|
+
constructor(writable: any);
|
|
127
|
+
/**
|
|
128
|
+
* Writes a chunk to the stream.
|
|
129
|
+
*
|
|
130
|
+
* @param {*} chunk - The chunk to write.
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
write(chunk: any): Promise<any>;
|
|
134
|
+
/**
|
|
135
|
+
* Aborts the stream with the given reason.
|
|
136
|
+
*
|
|
137
|
+
* @param {*} reason - The reason for aborting the stream.
|
|
138
|
+
* @returns {Promise<void>}
|
|
139
|
+
*/
|
|
140
|
+
abort(reason: any): Promise<any>;
|
|
141
|
+
/**
|
|
142
|
+
* Closes the writer and the stream.
|
|
143
|
+
*
|
|
144
|
+
* @returns {Promise<void>}
|
|
145
|
+
*/
|
|
146
|
+
close(): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Closes the writer with an error.
|
|
149
|
+
*
|
|
150
|
+
* @param {Error} error - The error to close the writer with.
|
|
151
|
+
* @returns {Promise<void>}
|
|
152
|
+
*/
|
|
153
|
+
closeWithError(error: any): Promise<void>;
|
|
154
|
+
/**
|
|
155
|
+
* Converts the writer back into a writable stream.
|
|
156
|
+
*
|
|
157
|
+
* @returns {WritableStream} The original writable stream that implements `getWriter()`.
|
|
158
|
+
*/
|
|
159
|
+
intoWritableStream(): Promise<any>;
|
|
160
|
+
}
|
|
161
|
+
export {};
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
export const DEFAULT_BYTE_STREAM_CHUNK_SIZE = 64 * 1024;
|
|
2
|
+
const symbolDispose = Symbol.dispose || Symbol.for("dispose");
|
|
3
|
+
let BYTE_STREAM_ENCODER = null;
|
|
4
|
+
function encoder() {
|
|
5
|
+
return (BYTE_STREAM_ENCODER ??= new TextEncoder());
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Creates a transferable ReadableStream from an async iterator.
|
|
9
|
+
*
|
|
10
|
+
* Use this when a StreamReader's contents need to be sent to a worker thread,
|
|
11
|
+
* since async iterators are not transferable.
|
|
12
|
+
*
|
|
13
|
+
* @param {AsyncIterator} iterator - The async iterator to wrap.
|
|
14
|
+
* @param {string} [name="iterator"] - Optional name for error messages.
|
|
15
|
+
* @returns {ReadableStream} A transferable ReadableStream that pulls from the iterator.
|
|
16
|
+
*/
|
|
17
|
+
export function readableStreamFromIterator(iterator, name = "iterator") {
|
|
18
|
+
if (iterator == null ||
|
|
19
|
+
iterator == undefined ||
|
|
20
|
+
typeof iterator[Symbol.asyncIterator] !== "function") {
|
|
21
|
+
throw new TypeError(`${name} must implement [Symbol.asyncIterator]()`);
|
|
22
|
+
}
|
|
23
|
+
return new ReadableStream({
|
|
24
|
+
async pull(controller) {
|
|
25
|
+
const { done, value } = await iterator.next();
|
|
26
|
+
if (done) {
|
|
27
|
+
controller.close();
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
controller.enqueue(value);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
cancel() {
|
|
34
|
+
return iterator.return?.();
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a transferable ReadableStream from a byte-stream reader.
|
|
40
|
+
*
|
|
41
|
+
* Generated p3 stream readers expose `read({ count })`, which performs one
|
|
42
|
+
* bounded Canonical ABI stream read for up to `count` bytes. Prefer that path so
|
|
43
|
+
* host byte sinks consume practical chunks instead of one byte at a time.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} reader - Reader that provides `read()`, or `[Symbol.asyncIterator]()`.
|
|
46
|
+
* @param {object} [opts={}] - Optional adapter settings.
|
|
47
|
+
* @param {number} [opts.chunkSize=DEFAULT_BYTE_STREAM_CHUNK_SIZE] - Maximum bytes to request per generated `read()` call.
|
|
48
|
+
* @param {string} [opts.name="stream reader"] - Reader name used in error messages.
|
|
49
|
+
* @returns {ReadableStream<Uint8Array>} A transferable stream of byte chunks.
|
|
50
|
+
*/
|
|
51
|
+
export function readableByteStreamFromReader(reader, opts = {}) {
|
|
52
|
+
const source = byteStreamSource(reader, opts);
|
|
53
|
+
return new ReadableStream({
|
|
54
|
+
async pull(controller) {
|
|
55
|
+
const { done, value } = await source.read();
|
|
56
|
+
if (done) {
|
|
57
|
+
controller.close();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
controller.enqueue(_byteChunk(value));
|
|
61
|
+
},
|
|
62
|
+
cancel(reason) {
|
|
63
|
+
return source.cancel?.(reason);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function byteStreamSource(reader, opts) {
|
|
68
|
+
const name = opts.name ?? "stream reader";
|
|
69
|
+
const chunkSize = opts.chunkSize ?? DEFAULT_BYTE_STREAM_CHUNK_SIZE;
|
|
70
|
+
if (!Number.isInteger(chunkSize) || chunkSize < 1) {
|
|
71
|
+
throw new TypeError(`invalid byte stream chunk size [${chunkSize}]`);
|
|
72
|
+
}
|
|
73
|
+
if (typeof reader?.read === "function") {
|
|
74
|
+
return {
|
|
75
|
+
async read() {
|
|
76
|
+
const result = await reader.read({ count: chunkSize });
|
|
77
|
+
if (isIteratorResult(result)) {
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
return { value: result, done: result === null };
|
|
81
|
+
},
|
|
82
|
+
cancel(reason) {
|
|
83
|
+
if (typeof reader.cancel === "function") {
|
|
84
|
+
return reader.cancel(reason);
|
|
85
|
+
}
|
|
86
|
+
if (typeof reader.close === "function") {
|
|
87
|
+
return reader.close();
|
|
88
|
+
}
|
|
89
|
+
return reader[symbolDispose]?.();
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (typeof reader?.[Symbol.asyncIterator] === "function") {
|
|
94
|
+
const iterator = reader[Symbol.asyncIterator]();
|
|
95
|
+
return {
|
|
96
|
+
read() {
|
|
97
|
+
return iterator.next();
|
|
98
|
+
},
|
|
99
|
+
cancel(reason) {
|
|
100
|
+
return iterator.return?.(reason);
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
throw new TypeError(`${name} must provide read() or [Symbol.asyncIterator]()`);
|
|
105
|
+
}
|
|
106
|
+
function isIteratorResult(value) {
|
|
107
|
+
return value != null && typeof value === "object" && typeof value.done === "boolean";
|
|
108
|
+
}
|
|
109
|
+
export function _byteChunk(value) {
|
|
110
|
+
if (value instanceof Uint8Array) {
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
if (value instanceof ArrayBuffer) {
|
|
114
|
+
return new Uint8Array(value);
|
|
115
|
+
}
|
|
116
|
+
if (Array.isArray(value)) {
|
|
117
|
+
for (const byte of value) {
|
|
118
|
+
assertByte(byte);
|
|
119
|
+
}
|
|
120
|
+
return Uint8Array.from(value);
|
|
121
|
+
}
|
|
122
|
+
if (typeof value === "number") {
|
|
123
|
+
return Uint8Array.of(assertByte(value));
|
|
124
|
+
}
|
|
125
|
+
if (typeof value === "string") {
|
|
126
|
+
return encoder().encode(value);
|
|
127
|
+
}
|
|
128
|
+
throw new TypeError("byte stream chunk must be a byte, byte array, ArrayBuffer, Uint8Array, or string");
|
|
129
|
+
}
|
|
130
|
+
function assertByte(value) {
|
|
131
|
+
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
132
|
+
throw new RangeError(`Invalid byte stream value: ${value}`);
|
|
133
|
+
}
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Creates a bidirectional stream with separate reader and writer interfaces.
|
|
138
|
+
*
|
|
139
|
+
* Uses a TransformStream internally.
|
|
140
|
+
*
|
|
141
|
+
* @param {Object} [opts={}] - Configuration options for the stream.
|
|
142
|
+
* @param {number} [opts.readableHWM=65536] - High water mark for the readable side (bytes).
|
|
143
|
+
* @param {number} [opts.writableHWM=65536] - High water mark for the writable side (bytes).
|
|
144
|
+
*
|
|
145
|
+
* @returns {Object} An object containing:
|
|
146
|
+
* - `tx` {StreamWriter}: The writer for sending data to the stream.
|
|
147
|
+
* - `rx` {StreamReader}: The reader for receiving data from the stream.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
*
|
|
151
|
+
* const { tx, rx } = stream();
|
|
152
|
+
* const payload = Buffer.from('roundtrip');
|
|
153
|
+
*
|
|
154
|
+
* await tx.write(payload);
|
|
155
|
+
* await tx.close();
|
|
156
|
+
*
|
|
157
|
+
* const chunk1 = await rx.read();
|
|
158
|
+
* expect(chunk1).toEqual(payload);
|
|
159
|
+
*
|
|
160
|
+
* const done = await rx.read();
|
|
161
|
+
* expect(done).toBeNull();
|
|
162
|
+
*
|
|
163
|
+
*/
|
|
164
|
+
export function stream(opts = {}) {
|
|
165
|
+
const { readableHWM = 64 * 1024, writableHWM = 64 * 1024 } = opts;
|
|
166
|
+
const transform = new TransformStream({}, { highWaterMark: writableHWM }, { highWaterMark: readableHWM });
|
|
167
|
+
const tx = new StreamWriter(transform.writable);
|
|
168
|
+
const rx = new StreamReader(transform.readable);
|
|
169
|
+
return { tx, rx };
|
|
170
|
+
}
|
|
171
|
+
export class StreamReader {
|
|
172
|
+
/** Original source ReadableStream or AsyncIterable */
|
|
173
|
+
#source = null;
|
|
174
|
+
/** Active async iterator obtained from the source */
|
|
175
|
+
#iterator = null;
|
|
176
|
+
/** Whether the iterator completed on next() call */
|
|
177
|
+
#done = false;
|
|
178
|
+
/**
|
|
179
|
+
* Constructs a StreamReader.
|
|
180
|
+
*
|
|
181
|
+
* @param {AsyncIterable|Iterable} source - An async or sync iterable to consume e.g. ReadableStream, async generator, array.
|
|
182
|
+
* @throws {Error} If the provided source does not implement `[Symbol.asyncIterator]` or `[Symbol.iterator]`.
|
|
183
|
+
*/
|
|
184
|
+
constructor(source, opts = {}) {
|
|
185
|
+
if (!source ||
|
|
186
|
+
(typeof source[Symbol.asyncIterator] !== "function" &&
|
|
187
|
+
typeof source[Symbol.iterator] !== "function")) {
|
|
188
|
+
throw new Error("Failed to create StreamReader: provided source must implement [Symbol.asyncIterator]() or [Symbol.iterator]()");
|
|
189
|
+
}
|
|
190
|
+
this.#source = source;
|
|
191
|
+
// For ReadableStream, use values() with preventCancel so the underlying
|
|
192
|
+
// stream is not cancelled when the iterator is released.
|
|
193
|
+
if (source instanceof ReadableStream) {
|
|
194
|
+
this.#iterator = source.values({ preventCancel: opts.preventCancel ?? true });
|
|
195
|
+
}
|
|
196
|
+
else if (typeof source[Symbol.asyncIterator] === "function") {
|
|
197
|
+
this.#iterator = source[Symbol.asyncIterator]();
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// Wrap a sync iterator as an async one
|
|
201
|
+
const syncIterator = source[Symbol.iterator]();
|
|
202
|
+
this.#iterator = {
|
|
203
|
+
next() {
|
|
204
|
+
return Promise.resolve(syncIterator.next());
|
|
205
|
+
},
|
|
206
|
+
return(val) {
|
|
207
|
+
return Promise.resolve(syncIterator.return ? syncIterator.return(val) : { done: true, value: val });
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Reads the next chunk from the stream.
|
|
214
|
+
*
|
|
215
|
+
* @returns {Promise<any>} Resolves with the next chunk or null if the stream is done.
|
|
216
|
+
*/
|
|
217
|
+
async read() {
|
|
218
|
+
if (this.#done) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
this.#ensureIterator();
|
|
222
|
+
const { done, value } = await this.#iterator.next();
|
|
223
|
+
if (done) {
|
|
224
|
+
this.#done = true;
|
|
225
|
+
this.#iterator = null;
|
|
226
|
+
return value !== undefined ? value : null;
|
|
227
|
+
}
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Reads all chunks from the stream and concatenates them into a buffer.
|
|
232
|
+
*
|
|
233
|
+
* @returns {Promise<Buffer>} Resolves with the concatenated buffer.
|
|
234
|
+
*/
|
|
235
|
+
async readAll() {
|
|
236
|
+
this.#ensureIterator();
|
|
237
|
+
const chunks = [];
|
|
238
|
+
let c;
|
|
239
|
+
try {
|
|
240
|
+
while ((c = await this.read()) !== null) {
|
|
241
|
+
chunks.push(c);
|
|
242
|
+
}
|
|
243
|
+
return Buffer.concat(chunks);
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
this.close();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Cancels the stream, signalling a premature exit to the iterator.
|
|
251
|
+
*
|
|
252
|
+
* @returns {Promise<void>}
|
|
253
|
+
*/
|
|
254
|
+
async cancel(_reason) {
|
|
255
|
+
this.#ensureIterator();
|
|
256
|
+
if (typeof this.#iterator.return === "function") {
|
|
257
|
+
await this.#iterator.return();
|
|
258
|
+
}
|
|
259
|
+
this.#iterator = null;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Closes the reader and releases the iterator.
|
|
263
|
+
* Only calls return() if the iterator hasn't completed naturally,
|
|
264
|
+
* since return() is meant for premature exits only.
|
|
265
|
+
*/
|
|
266
|
+
close() {
|
|
267
|
+
if (!this.#iterator) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (!this.#done && typeof this.#iterator.return === "function") {
|
|
271
|
+
this.#iterator.return();
|
|
272
|
+
}
|
|
273
|
+
this.#iterator = null;
|
|
274
|
+
}
|
|
275
|
+
[symbolDispose]() {
|
|
276
|
+
this.close();
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Consumes the reader and returns the underlying async iterator.
|
|
280
|
+
*
|
|
281
|
+
* After calling this method the StreamReader is closed and must not be used.
|
|
282
|
+
*
|
|
283
|
+
* @returns {AsyncIterator} The remaining async iterator.
|
|
284
|
+
*/
|
|
285
|
+
intoAsyncIterator() {
|
|
286
|
+
const iterator = this.#iterator;
|
|
287
|
+
this.#iterator = null;
|
|
288
|
+
this.#source = null;
|
|
289
|
+
if (!iterator) {
|
|
290
|
+
throw new Error("StreamReader is closed");
|
|
291
|
+
}
|
|
292
|
+
return iterator;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Implements the async-iterable protocol so a StreamReader can be used
|
|
296
|
+
* directly with `for await…of` and any helper that consumes an
|
|
297
|
+
* async iterator (e.g. `readableStreamFromIterator`). Like
|
|
298
|
+
* `intoAsyncIterator()` above, this consumes the reader.
|
|
299
|
+
*
|
|
300
|
+
* @returns {AsyncIterator}
|
|
301
|
+
*/
|
|
302
|
+
[Symbol.asyncIterator]() {
|
|
303
|
+
return this.intoAsyncIterator();
|
|
304
|
+
}
|
|
305
|
+
#ensureIterator() {
|
|
306
|
+
if (this.#iterator === null) {
|
|
307
|
+
throw new Error("StreamReader is closed");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
export class StreamWriter {
|
|
312
|
+
/** Reference to the original stream object that implements getWriter */
|
|
313
|
+
#stream = null;
|
|
314
|
+
/** Active reader obtained from the stream */
|
|
315
|
+
#writer = null;
|
|
316
|
+
/**
|
|
317
|
+
* Constructs a StreamWriter.
|
|
318
|
+
*
|
|
319
|
+
* @param {WritableStream} writable - The writable stream to write to.
|
|
320
|
+
* @throws {Error} If the provided stream does not implement `getWriter`.
|
|
321
|
+
*/
|
|
322
|
+
constructor(writable) {
|
|
323
|
+
if (!writable || typeof writable.getWriter !== "function") {
|
|
324
|
+
throw new Error("Provided writable must implement getWriter()");
|
|
325
|
+
}
|
|
326
|
+
this.#stream = writable;
|
|
327
|
+
this.#writer = writable.getWriter();
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Writes a chunk to the stream.
|
|
331
|
+
*
|
|
332
|
+
* @param {*} chunk - The chunk to write.
|
|
333
|
+
* @returns {Promise<void>}
|
|
334
|
+
*/
|
|
335
|
+
async write(chunk) {
|
|
336
|
+
this.#ensureWriter();
|
|
337
|
+
return this.#writer.write(chunk);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Aborts the stream with the given reason.
|
|
341
|
+
*
|
|
342
|
+
* @param {*} reason - The reason for aborting the stream.
|
|
343
|
+
* @returns {Promise<void>}
|
|
344
|
+
*/
|
|
345
|
+
async abort(reason) {
|
|
346
|
+
this.#ensureWriter();
|
|
347
|
+
return this.#writer.abort(reason);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Closes the writer and the stream.
|
|
351
|
+
*
|
|
352
|
+
* @returns {Promise<void>}
|
|
353
|
+
*/
|
|
354
|
+
async close() {
|
|
355
|
+
this.#ensureWriter();
|
|
356
|
+
const writer = this.#writer;
|
|
357
|
+
this.#writer = null;
|
|
358
|
+
await writer.close();
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Closes the writer with an error.
|
|
362
|
+
*
|
|
363
|
+
* @param {Error} error - The error to close the writer with.
|
|
364
|
+
* @returns {Promise<void>}
|
|
365
|
+
*/
|
|
366
|
+
async closeWithError(error) {
|
|
367
|
+
this.#ensureWriter();
|
|
368
|
+
const writer = this.#writer;
|
|
369
|
+
this.#writer = null;
|
|
370
|
+
await writer.abort(error);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Converts the writer back into a writable stream.
|
|
374
|
+
*
|
|
375
|
+
* @returns {WritableStream} The original writable stream that implements `getWriter()`.
|
|
376
|
+
*/
|
|
377
|
+
async intoWritableStream() {
|
|
378
|
+
const stream = this.#stream;
|
|
379
|
+
this.#writer = null;
|
|
380
|
+
this.#stream = null;
|
|
381
|
+
return stream;
|
|
382
|
+
}
|
|
383
|
+
#ensureWriter() {
|
|
384
|
+
if (this.#writer === null) {
|
|
385
|
+
throw new Error("StreamWriter is closed");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { pipeline } from "stream/promises";
|
|
4
|
+
import { Router } from "../workers/resource-worker.js";
|
|
5
|
+
Router().op("stdout", handleOp).op("stderr", handleOp);
|
|
6
|
+
async function handleOp(msg) {
|
|
7
|
+
const { op, stream } = msg;
|
|
8
|
+
const readable = Readable.from(byteChunks(stream), { objectMode: false });
|
|
9
|
+
const writable = op === "stdout" ? process.stdout : process.stderr;
|
|
10
|
+
await pipeline(readable, writable, { end: false });
|
|
11
|
+
return { ok: true };
|
|
12
|
+
}
|
|
13
|
+
async function* byteChunks(stream) {
|
|
14
|
+
for await (const value of stream) {
|
|
15
|
+
yield nodeByteChunk(value);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function assertByte(value) {
|
|
19
|
+
if (!Number.isInteger(value) || value < 0 || value > 255) {
|
|
20
|
+
throw new RangeError(`Invalid byte stream value: ${value}`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function nodeByteChunk(value) {
|
|
25
|
+
if (value instanceof Uint8Array) {
|
|
26
|
+
return value; // also matches Buffer
|
|
27
|
+
}
|
|
28
|
+
if (value instanceof ArrayBuffer) {
|
|
29
|
+
return new Uint8Array(value);
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
for (const byte of value) {
|
|
33
|
+
assertByte(byte);
|
|
34
|
+
}
|
|
35
|
+
return Uint8Array.from(value);
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === "number") {
|
|
38
|
+
return Uint8Array.of(assertByte(value));
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|