@bytecodealliance/preview3-shim 0.6.1 → 0.7.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.
Files changed (39) hide show
  1. package/README.md +9 -2
  2. package/dist/browser/cli/environment.d.ts +1 -0
  3. package/dist/browser/cli/environment.js +9 -4
  4. package/dist/browser/cli/exit.js +4 -3
  5. package/dist/browser/cli/run.d.ts +2 -0
  6. package/dist/browser/cli/run.js +11 -3
  7. package/dist/browser/cli/stderr.js +10 -3
  8. package/dist/browser/cli/stdin.js +3 -2
  9. package/dist/browser/cli/stdout.js +10 -3
  10. package/dist/browser/cli/terminal-stderr.js +2 -2
  11. package/dist/browser/cli/terminal-stdin.js +2 -2
  12. package/dist/browser/cli/terminal-stdout.js +2 -2
  13. package/dist/browser/cli.d.ts +3 -0
  14. package/dist/browser/cli.js +3 -0
  15. package/dist/browser/clocks/monotonic-clock.js +7 -7
  16. package/dist/browser/clocks/system-clock.js +4 -3
  17. package/dist/browser/filesystem/preopens.js +3 -2
  18. package/dist/browser/filesystem/types.d.ts +44 -1
  19. package/dist/browser/filesystem/types.js +139 -50
  20. package/dist/browser/filesystem.d.ts +13 -0
  21. package/dist/browser/filesystem.js +20 -0
  22. package/dist/browser/http/client.d.ts +2 -2
  23. package/dist/browser/http/client.js +138 -3
  24. package/dist/browser/http/handler.d.ts +4 -0
  25. package/dist/browser/http/handler.js +11 -3
  26. package/dist/browser/http/types.d.ts +44 -22
  27. package/dist/browser/http/types.js +192 -50
  28. package/dist/browser/http.d.ts +3 -0
  29. package/dist/browser/http.js +3 -0
  30. package/dist/browser/sockets/ip-name-lookup.js +34 -3
  31. package/dist/browser/sockets/types.d.ts +15 -0
  32. package/dist/browser/sockets/types.js +241 -49
  33. package/dist/browser/sockets.d.ts +2 -0
  34. package/dist/browser/sockets.js +2 -0
  35. package/dist/browser/streams.d.ts +20 -0
  36. package/dist/browser/streams.js +106 -0
  37. package/package.json +3 -3
  38. package/dist/common/errors.d.ts +0 -3
  39. package/dist/common/errors.js +0 -5
@@ -1,126 +1,318 @@
1
- import { Todo } from "../../common/errors.js";
1
+ import * as preview2Sockets from "@bytecodealliance/preview2-shim/sockets";
2
+ import { readableFromPreview2Input, writeToPreview2Output } from "../streams.js";
3
+ let tcpProvider = preview2Sockets;
4
+ let udpProvider = preview2Sockets;
5
+ class SocketError extends Error {
6
+ payload;
7
+ constructor(payload) {
8
+ super(payload.tag);
9
+ this.name = "SocketError";
10
+ this.payload = payload;
11
+ }
12
+ }
13
+ export function socketErrorCode(error) {
14
+ let tag = typeof error === "string" ? error : error?.tag;
15
+ if (!tag && error instanceof Error) {
16
+ tag = error.message;
17
+ }
18
+ if (tag === "would-block" || tag === "not-in-progress") {
19
+ return { tag: "invalid-state" };
20
+ }
21
+ if (tag) {
22
+ return { tag };
23
+ }
24
+ return { tag: "other", val: String(error) };
25
+ }
26
+ function socketError(error) {
27
+ throw error instanceof SocketError ? error : new SocketError(socketErrorCode(error));
28
+ }
29
+ function socketCall(operation) {
30
+ try {
31
+ return operation();
32
+ }
33
+ catch (error) {
34
+ socketError(error);
35
+ }
36
+ }
37
+ export function _setTcpProvider(provider) {
38
+ const previous = tcpProvider;
39
+ tcpProvider = provider;
40
+ return previous;
41
+ }
42
+ export function _setUdpProvider(provider) {
43
+ const previous = udpProvider;
44
+ udpProvider = provider;
45
+ return previous;
46
+ }
2
47
  export class TcpSocket {
48
+ #socket;
49
+ #input;
50
+ #output;
3
51
  static create(addressFamily) {
4
- throw new Todo();
52
+ try {
53
+ return TcpSocket._wrap(tcpProvider.tcpCreateSocket.createTcpSocket(addressFamily));
54
+ }
55
+ catch (error) {
56
+ socketError(error);
57
+ }
58
+ }
59
+ static _wrap(socket, input, output) {
60
+ const wrapped = new TcpSocket();
61
+ wrapped.#socket = socket;
62
+ wrapped.#input = input;
63
+ wrapped.#output = output;
64
+ return wrapped;
5
65
  }
6
66
  bind(localAddress) {
7
- throw new Todo();
8
- }
9
- connect(remoteAddress) {
10
- throw new Todo();
67
+ try {
68
+ this.#socket.startBind({}, localAddress);
69
+ this.#socket.finishBind();
70
+ }
71
+ catch (error) {
72
+ socketError(error);
73
+ }
74
+ }
75
+ async connect(remoteAddress) {
76
+ try {
77
+ this.#socket.startConnect({}, remoteAddress);
78
+ const connected = this.#socket.finishConnect();
79
+ if (Array.isArray(connected)) {
80
+ [this.#input, this.#output] = connected;
81
+ }
82
+ }
83
+ catch (error) {
84
+ socketError(error);
85
+ }
11
86
  }
12
87
  listen() {
13
- throw new Todo();
14
- }
15
- send(data) {
16
- throw new Todo();
88
+ try {
89
+ this.#socket.startListen();
90
+ this.#socket.finishListen();
91
+ }
92
+ catch (error) {
93
+ socketError(error);
94
+ }
95
+ return new ReadableStream({
96
+ pull: async (controller) => {
97
+ while (true) {
98
+ try {
99
+ const [socket, input, output] = this.#socket.accept();
100
+ controller.enqueue(TcpSocket._wrap(socket, input, output));
101
+ return;
102
+ }
103
+ catch (error) {
104
+ if (error === "would-block") {
105
+ await new Promise((resolve) => setTimeout(resolve, 0));
106
+ continue;
107
+ }
108
+ controller.error(new SocketError(socketErrorCode(error)));
109
+ return;
110
+ }
111
+ }
112
+ },
113
+ });
114
+ }
115
+ async send(data) {
116
+ if (!this.#output) {
117
+ return { tag: "err", val: { tag: "invalid-state" } };
118
+ }
119
+ try {
120
+ await writeToPreview2Output(data, this.#output);
121
+ return { tag: "ok", val: undefined };
122
+ }
123
+ catch (error) {
124
+ return { tag: "err", val: socketErrorCode(error) };
125
+ }
17
126
  }
18
127
  receive() {
19
- throw new Todo();
128
+ if (!this.#input) {
129
+ const error = { tag: "invalid-state" };
130
+ const stream = new ReadableStream({
131
+ start(controller) {
132
+ controller.error(new SocketError(error));
133
+ },
134
+ });
135
+ return [stream, Promise.resolve({ tag: "err", val: error })];
136
+ }
137
+ return readableFromPreview2Input(this.#input, socketErrorCode);
20
138
  }
21
139
  getLocalAddress() {
22
- throw new Todo();
140
+ try {
141
+ return this.#socket.localAddress();
142
+ }
143
+ catch (error) {
144
+ socketError(error);
145
+ }
23
146
  }
24
147
  getRemoteAddress() {
25
- throw new Todo();
148
+ try {
149
+ return this.#socket.remoteAddress();
150
+ }
151
+ catch (error) {
152
+ socketError(error);
153
+ }
26
154
  }
27
155
  getIsListening() {
28
- throw new Todo();
156
+ return socketCall(() => this.#socket.isListening());
29
157
  }
30
158
  getAddressFamily() {
31
- throw new Todo();
159
+ return socketCall(() => this.#socket.addressFamily());
32
160
  }
33
161
  setListenBacklogSize(value) {
34
- throw new Todo();
162
+ socketCall(() => this.#socket.setListenBacklogSize(value));
35
163
  }
36
164
  getKeepAliveEnabled() {
37
- throw new Todo();
165
+ return socketCall(() => this.#socket.keepAliveEnabled());
38
166
  }
39
167
  setKeepAliveEnabled(value) {
40
- throw new Todo();
168
+ socketCall(() => this.#socket.setKeepAliveEnabled(value));
41
169
  }
42
170
  getKeepAliveIdleTime() {
43
- throw new Todo();
171
+ return socketCall(() => this.#socket.keepAliveIdleTime());
44
172
  }
45
173
  setKeepAliveIdleTime(value) {
46
- throw new Todo();
174
+ socketCall(() => this.#socket.setKeepAliveIdleTime(value));
47
175
  }
48
176
  getKeepAliveInterval() {
49
- throw new Todo();
177
+ return socketCall(() => this.#socket.keepAliveInterval());
50
178
  }
51
179
  setKeepAliveInterval(value) {
52
- throw new Todo();
180
+ socketCall(() => this.#socket.setKeepAliveInterval(value));
53
181
  }
54
182
  getKeepAliveCount() {
55
- throw new Todo();
183
+ return socketCall(() => this.#socket.keepAliveCount());
56
184
  }
57
185
  setKeepAliveCount(value) {
58
- throw new Todo();
186
+ socketCall(() => this.#socket.setKeepAliveCount(value));
59
187
  }
60
188
  getHopLimit() {
61
- throw new Todo();
189
+ return socketCall(() => this.#socket.hopLimit());
62
190
  }
63
191
  setHopLimit(value) {
64
- throw new Todo();
192
+ socketCall(() => this.#socket.setHopLimit(value));
65
193
  }
66
194
  getReceiveBufferSize() {
67
- throw new Todo();
195
+ return socketCall(() => this.#socket.receiveBufferSize());
68
196
  }
69
197
  setReceiveBufferSize(value) {
70
- throw new Todo();
198
+ socketCall(() => this.#socket.setReceiveBufferSize(value));
71
199
  }
72
200
  getSendBufferSize() {
73
- throw new Todo();
201
+ return socketCall(() => this.#socket.sendBufferSize());
74
202
  }
75
203
  setSendBufferSize(value) {
76
- throw new Todo();
204
+ socketCall(() => this.#socket.setSendBufferSize(value));
77
205
  }
78
206
  }
79
207
  export class UdpSocket {
208
+ #socket;
209
+ #incoming;
210
+ #outgoing;
80
211
  static create(addressFamily) {
81
- throw new Todo();
212
+ try {
213
+ const wrapped = new UdpSocket();
214
+ wrapped.#socket = udpProvider.udpCreateSocket.createUdpSocket(addressFamily);
215
+ return wrapped;
216
+ }
217
+ catch (error) {
218
+ socketError(error);
219
+ }
82
220
  }
83
221
  bind(localAddress) {
84
- throw new Todo();
222
+ try {
223
+ this.#socket.startBind({}, localAddress);
224
+ this.#socket.finishBind();
225
+ }
226
+ catch (error) {
227
+ socketError(error);
228
+ }
85
229
  }
86
230
  connect(remoteAddress) {
87
- throw new Todo();
231
+ try {
232
+ [this.#incoming, this.#outgoing] = this.#socket.stream(remoteAddress);
233
+ }
234
+ catch (error) {
235
+ socketError(error);
236
+ }
88
237
  }
89
238
  disconnect() {
90
- throw new Todo();
91
- }
92
- send(data, remoteAddress) {
93
- throw new Todo();
94
- }
95
- receive() {
96
- throw new Todo();
239
+ try {
240
+ [this.#incoming, this.#outgoing] = this.#socket.stream(undefined);
241
+ }
242
+ catch (error) {
243
+ socketError(error);
244
+ }
245
+ }
246
+ async send(data, remoteAddress) {
247
+ try {
248
+ if (!this.#outgoing) {
249
+ [this.#incoming, this.#outgoing] = this.#socket.stream(remoteAddress);
250
+ }
251
+ this.#outgoing.checkSend();
252
+ this.#outgoing.send([{ data, remoteAddress }]);
253
+ }
254
+ catch (error) {
255
+ socketError(error);
256
+ }
257
+ }
258
+ async receive() {
259
+ if (!this.#incoming) {
260
+ try {
261
+ [this.#incoming, this.#outgoing] = this.#socket.stream(undefined);
262
+ }
263
+ catch (error) {
264
+ socketError(error);
265
+ }
266
+ }
267
+ while (true) {
268
+ try {
269
+ const [datagram] = this.#incoming.receive(1n);
270
+ if (datagram) {
271
+ return [datagram.data, datagram.remoteAddress];
272
+ }
273
+ await new Promise((resolve) => setTimeout(resolve, 0));
274
+ }
275
+ catch (error) {
276
+ socketError(error);
277
+ }
278
+ }
97
279
  }
98
280
  getLocalAddress() {
99
- throw new Todo();
281
+ try {
282
+ return this.#socket.localAddress();
283
+ }
284
+ catch (error) {
285
+ socketError(error);
286
+ }
100
287
  }
101
288
  getRemoteAddress() {
102
- throw new Todo();
289
+ try {
290
+ return this.#socket.remoteAddress();
291
+ }
292
+ catch (error) {
293
+ socketError(error);
294
+ }
103
295
  }
104
296
  getAddressFamily() {
105
- throw new Todo();
297
+ return socketCall(() => this.#socket.addressFamily());
106
298
  }
107
299
  getUnicastHopLimit() {
108
- throw new Todo();
300
+ return socketCall(() => this.#socket.unicastHopLimit());
109
301
  }
110
302
  setUnicastHopLimit(value) {
111
- throw new Todo();
303
+ socketCall(() => this.#socket.setUnicastHopLimit(value));
112
304
  }
113
305
  getReceiveBufferSize() {
114
- throw new Todo();
306
+ return socketCall(() => this.#socket.receiveBufferSize());
115
307
  }
116
308
  setReceiveBufferSize(value) {
117
- throw new Todo();
309
+ socketCall(() => this.#socket.setReceiveBufferSize(value));
118
310
  }
119
311
  getSendBufferSize() {
120
- throw new Todo();
312
+ return socketCall(() => this.#socket.sendBufferSize());
121
313
  }
122
314
  setSendBufferSize(value) {
123
- throw new Todo();
315
+ socketCall(() => this.#socket.setSendBufferSize(value));
124
316
  }
125
317
  }
126
318
  export default {
@@ -1,2 +1,4 @@
1
1
  export { default as ipNameLookup } from "./sockets/ip-name-lookup.js";
2
2
  export { default as types } from "./sockets/types.js";
3
+ export { _setTcpProvider, _setUdpProvider } from "./sockets/types.js";
4
+ export { InMemoryTcpClient, InMemoryTcpSockets, InMemoryUdpClient, InMemoryUdpSockets, } from "@bytecodealliance/preview2-shim/sockets";
@@ -1,2 +1,4 @@
1
1
  export { default as ipNameLookup } from "./sockets/ip-name-lookup.js";
2
2
  export { default as types } from "./sockets/types.js";
3
+ export { _setTcpProvider, _setUdpProvider } from "./sockets/types.js";
4
+ export { InMemoryTcpClient, InMemoryTcpSockets, InMemoryUdpClient, InMemoryUdpSockets, } from "@bytecodealliance/preview2-shim/sockets";
@@ -0,0 +1,20 @@
1
+ interface Preview2InputStream {
2
+ blockingRead(length: bigint): Uint8Array | Promise<Uint8Array>;
3
+ subscribe(): {
4
+ block(): void | Promise<void>;
5
+ };
6
+ }
7
+ interface Preview2OutputStream {
8
+ blockingWriteAndFlush(contents: Uint8Array): void;
9
+ blockingFlush(): void;
10
+ }
11
+ export declare function writeToPreview2Output(data: ReadableStream<number>, output: Preview2OutputStream): Promise<void>;
12
+ export declare function readableFromPreview2Input<E>(input: Preview2InputStream, errorCode: (error: unknown) => E): [ReadableStream<number>, Promise<{
13
+ tag: "ok";
14
+ val: void;
15
+ } | {
16
+ tag: "err";
17
+ val: E;
18
+ }>];
19
+ export declare function preview2StreamErrorCode(error: unknown): "io" | "illegal-byte-sequence" | "pipe";
20
+ export {};
@@ -0,0 +1,106 @@
1
+ const symbolDispose = Symbol.dispose ?? Symbol.for("dispose");
2
+ const BYTE_CHUNK_SIZE = 64 * 1024;
3
+ const P2_WRITE_CHUNK_SIZE = 4096;
4
+ function byteChunk(value) {
5
+ if (value instanceof Uint8Array) {
6
+ return value;
7
+ }
8
+ if (value instanceof ArrayBuffer) {
9
+ return new Uint8Array(value);
10
+ }
11
+ if (Array.isArray(value)) {
12
+ return Uint8Array.from(value);
13
+ }
14
+ if (typeof value === "number") {
15
+ if (!Number.isInteger(value) || value < 0 || value > 255) {
16
+ throw new RangeError(`invalid byte stream value: ${value}`);
17
+ }
18
+ return Uint8Array.of(value);
19
+ }
20
+ if (typeof value === "string") {
21
+ return new TextEncoder().encode(value);
22
+ }
23
+ throw new TypeError("byte stream values must be bytes or byte arrays");
24
+ }
25
+ function isIteratorResult(value) {
26
+ return value !== null && typeof value === "object" && "done" in value;
27
+ }
28
+ async function* byteChunks(data) {
29
+ const stream = data;
30
+ if (typeof stream.read === "function") {
31
+ while (true) {
32
+ const value = await stream.read({ count: BYTE_CHUNK_SIZE });
33
+ if (isIteratorResult(value)) {
34
+ if (value.done) {
35
+ return;
36
+ }
37
+ yield byteChunk(value.value);
38
+ }
39
+ else if (value === null) {
40
+ return;
41
+ }
42
+ else {
43
+ yield byteChunk(value);
44
+ }
45
+ }
46
+ }
47
+ if (typeof stream[Symbol.asyncIterator] === "function") {
48
+ for await (const value of stream) {
49
+ yield byteChunk(value);
50
+ }
51
+ return;
52
+ }
53
+ throw new TypeError("byte stream must provide read() or [Symbol.asyncIterator]()");
54
+ }
55
+ export async function writeToPreview2Output(data, output) {
56
+ for await (const bytes of byteChunks(data)) {
57
+ for (let offset = 0; offset < bytes.byteLength; offset += P2_WRITE_CHUNK_SIZE) {
58
+ output.blockingWriteAndFlush(bytes.subarray(offset, offset + P2_WRITE_CHUNK_SIZE));
59
+ }
60
+ }
61
+ output.blockingFlush();
62
+ }
63
+ export function readableFromPreview2Input(input, errorCode) {
64
+ let resolveResult = () => { };
65
+ const result = new Promise((resolve) => (resolveResult = resolve));
66
+ let settled = false;
67
+ const settle = (value) => {
68
+ if (!settled) {
69
+ settled = true;
70
+ resolveResult(value);
71
+ }
72
+ };
73
+ const readable = new ReadableStream({
74
+ async pull(controller) {
75
+ try {
76
+ const bytes = await input.blockingRead(BigInt(BYTE_CHUNK_SIZE));
77
+ for (const byte of bytes) {
78
+ controller.enqueue(byte);
79
+ }
80
+ }
81
+ catch (error) {
82
+ if (error?.tag === "closed") {
83
+ controller.close();
84
+ settle({ tag: "ok", val: undefined });
85
+ return;
86
+ }
87
+ settle({ tag: "err", val: errorCode(error) });
88
+ controller.error(error);
89
+ }
90
+ },
91
+ cancel() {
92
+ settle({ tag: "ok", val: undefined });
93
+ },
94
+ });
95
+ return [readable, result];
96
+ }
97
+ export function preview2StreamErrorCode(error) {
98
+ const code = error?.code;
99
+ if (code === "EPIPE" || code === "ERR_STREAM_PREMATURE_CLOSE") {
100
+ return "pipe";
101
+ }
102
+ if (code === "ERR_ENCODING_INVALID_ENCODED_DATA") {
103
+ return "illegal-byte-sequence";
104
+ }
105
+ return "io";
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/preview3-shim",
3
- "version": "0.6.1",
3
+ "version": "0.7.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": {
@@ -49,9 +49,10 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@bytecodealliance/jco-node-fs": "0.3.2",
52
- "@bytecodealliance/preview2-shim": "^0.25.0"
52
+ "@bytecodealliance/preview2-shim": "^0.26.0"
53
53
  },
54
54
  "devDependencies": {
55
+ "@bytecodealliance/jco": "1.25.2",
55
56
  "@types/node": "^24.12.4",
56
57
  "globals": "^17.6.0",
57
58
  "rolldown": "^1.2.4",
@@ -67,7 +68,6 @@
67
68
  "fmt:check": "oxfmt --check",
68
69
  "lint": "oxlint",
69
70
  "lint:fix": "oxlint --fix",
70
- "pretest": "pnpm run build",
71
71
  "test": "vitest --run -c test/vitest.ts",
72
72
  "prebench": "pnpm run build",
73
73
  "bench": "vitest bench --run"
@@ -1,3 +0,0 @@
1
- export declare class Todo extends Error {
2
- constructor();
3
- }
@@ -1,5 +0,0 @@
1
- export class Todo extends Error {
2
- constructor() {
3
- super("TODO: not yet implemented");
4
- }
5
- }