@bytecodealliance/preview2-shim 0.0.19 → 0.0.21

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 (40) hide show
  1. package/LICENSE +220 -0
  2. package/README.md +4 -6
  3. package/lib/browser/cli.js +79 -12
  4. package/lib/browser/filesystem.js +168 -198
  5. package/lib/browser/index.js +1 -5
  6. package/lib/browser/io.js +173 -29
  7. package/lib/common/io.js +6 -4
  8. package/lib/{http → common}/make-request.js +1 -1
  9. package/lib/nodejs/cli.js +5 -4
  10. package/lib/nodejs/filesystem.js +106 -73
  11. package/lib/nodejs/http.js +309 -3
  12. package/lib/nodejs/index.js +0 -5
  13. package/package.json +1 -1
  14. package/types/interfaces/wasi-cli-stderr.d.ts +1 -1
  15. package/types/interfaces/wasi-cli-stdin.d.ts +1 -1
  16. package/types/interfaces/wasi-cli-stdout.d.ts +1 -1
  17. package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
  18. package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
  19. package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
  20. package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +1 -1
  21. package/types/interfaces/wasi-clocks-timezone.d.ts +1 -1
  22. package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
  23. package/types/interfaces/wasi-filesystem-types.d.ts +8 -8
  24. package/types/interfaces/wasi-http-incoming-handler.d.ts +19 -0
  25. package/types/interfaces/wasi-http-outgoing-handler.d.ts +23 -0
  26. package/types/interfaces/wasi-http-types.d.ts +371 -0
  27. package/types/interfaces/wasi-io-streams.d.ts +13 -21
  28. package/types/interfaces/wasi-sockets-instance-network.d.ts +1 -1
  29. package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +5 -5
  30. package/types/interfaces/wasi-sockets-tcp-create-socket.d.ts +4 -4
  31. package/types/interfaces/wasi-sockets-tcp.d.ts +7 -7
  32. package/types/interfaces/wasi-sockets-udp-create-socket.d.ts +4 -4
  33. package/types/interfaces/wasi-sockets-udp.d.ts +5 -5
  34. package/types/wasi-cli-command.d.ts +3 -3
  35. package/types/wasi-http-proxy.d.ts +13 -0
  36. package/lib/browser/poll.js +0 -53
  37. package/lib/http/wasi-http.js +0 -382
  38. package/lib/nodejs/poll.js +0 -9
  39. /package/lib/{http/synckit → synckit}/index.d.ts +0 -0
  40. /package/lib/{http/synckit → synckit}/index.js +0 -0
@@ -1,5 +1,311 @@
1
- import { WasiHttp } from '../http/wasi-http.js';
2
1
  import { streams } from '../common/io.js';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { createSyncFn } from '../synckit/index.js';
3
4
 
4
- const http = new WasiHttp(streams);
5
- export const { incomingHandler, outgoingHandler, types } = http;
5
+ const { InputStream, OutputStream } = streams;
6
+
7
+ /**
8
+ * @typedef {import("../../types/interfaces/wasi-http-types").Method} Method
9
+ * @typedef {import("../../types/interfaces/wasi-http-types").RequestOptions} RequestOptions
10
+ * @typedef {import("../../types/interfaces/wasi-http-types").Scheme} Scheme
11
+ * @typedef {import("../../types/interfaces/wasi-http-types").Error} HttpError
12
+ */
13
+
14
+ const workerPath = fileURLToPath(new URL('../common/make-request.js', import.meta.url));
15
+
16
+ function send(req) {
17
+ const syncFn = createSyncFn(workerPath);
18
+ let rawResponse = syncFn(req);
19
+ let response = JSON.parse(rawResponse);
20
+ if (response.status) {
21
+ return {
22
+ ...response,
23
+ body: response.body ? Buffer.from(response.body, 'base64') : undefined,
24
+ };
25
+ }
26
+ // HttpError
27
+ throw { tag: 'unexpected-error', val: response.message };
28
+ }
29
+
30
+ function combineChunks (chunks) {
31
+ if (chunks.length === 0)
32
+ return new Uint8Array();
33
+ if (chunks.length === 1)
34
+ return chunks[0];
35
+ const totalLen = chunks.reduce((total, chunk) => total + chunk.byteLength);
36
+ const out = new Uint8Array(totalLen);
37
+ let idx = 0;
38
+ for (const chunk of chunks) {
39
+ out.set(chunk, idx);
40
+ idx += chunk.byteLength;
41
+ }
42
+ return out;
43
+ }
44
+
45
+ export class WasiHttp {
46
+ requestCnt = 1;
47
+ responseCnt = 1;
48
+ fieldsCnt = 1;
49
+ futureCnt = 1;
50
+
51
+ constructor() {
52
+ const http = this;
53
+
54
+ class IncomingBody {
55
+ #bodyFinished = false;
56
+ #chunks = [];
57
+ stream () {
58
+ const incomingBody = this;
59
+ return new InputStream({
60
+ blockingRead (_len) {
61
+ if (incomingBody.#bodyFinished)
62
+ throw { tag: 'closed' };
63
+ if (incomingBody.#chunks.length === 0)
64
+ return new Uint8Array([]);
65
+ // TODO: handle chunk splitting case where len is less than chunk length
66
+ return incomingBody.#chunks.shift();
67
+ },
68
+ subscribe () {
69
+ // TODO
70
+ }
71
+ });
72
+ }
73
+ static finish (incomingBody) {
74
+ incomingBody.#bodyFinished = true;
75
+ return futureTrailersCreate(new Fields([]), false);
76
+ }
77
+ static _addChunk (incomingBody, chunk) {
78
+ incomingBody.#chunks.push(chunk);
79
+ }
80
+ }
81
+ const incomingBodyAddChunk = IncomingBody._addChunk;
82
+ delete IncomingBody._addChunk;
83
+
84
+ // TODO
85
+ class IncomingRequest {}
86
+
87
+ class FutureTrailers {
88
+ id = this.futureCnt++;
89
+ #value;
90
+ #isError;
91
+ subscribe () {
92
+ // TODO
93
+ }
94
+ get () {
95
+ return { tag: this.#isError ? 'err' : 'ok', val: this.#value };
96
+ }
97
+ static _create (value, isError) {
98
+ const res = new FutureTrailers();
99
+ res.#value = value;
100
+ res.#isError = isError;
101
+ return res;
102
+ }
103
+ }
104
+ const futureTrailersCreate = FutureTrailers._create;
105
+ delete FutureTrailers._create;
106
+
107
+ class OutgoingResponse {
108
+ id = http.responseCnt++;
109
+ /** @type {number} */ _statusCode;
110
+ /** @type {Fields} */ _headers;
111
+
112
+ /**
113
+ * @param {number} statusCode
114
+ * @param {Fields} headers
115
+ */
116
+ constructor (statusCode, headers) {
117
+ this._statusCode = statusCode;
118
+ this.headers = headers;
119
+ }
120
+ }
121
+
122
+ class ResponseOutparam {
123
+ static set (_param, _response) {
124
+ // TODO
125
+ }
126
+ }
127
+
128
+ class OutgoingRequest {
129
+ id = http.requestCnt++;
130
+ /** @type {Method} */ #method = { tag: 'get' };
131
+ /** @type {Scheme | undefined} */ #scheme = { tag: 'HTTP' };
132
+ /** @type {string | undefined} */ #pathWithQuery = undefined;
133
+ /** @type {string | undefined} */ #authority = undefined;
134
+ /** @type {Fields} */ #headers = undefined;
135
+ /** @type {OutgoingBody} */ #body = new OutgoingBody();
136
+ constructor(method, pathWithQuery, scheme, authority, headers) {
137
+ this.#method = method;
138
+ this.#pathWithQuery = pathWithQuery;
139
+ this.#scheme = scheme;
140
+ this.#authority = authority;
141
+ this.#headers = headers;
142
+ }
143
+ write () {
144
+ return this.#body;
145
+ }
146
+
147
+ static _handle (request) {
148
+ const scheme = request.#scheme.tag === "HTTP" ? "http://" : "https://";
149
+ const url = scheme + request.#authority + request.#pathWithQuery;
150
+ const headers = {
151
+ "host": request.#authority,
152
+ };
153
+ const decoder = new TextDecoder();
154
+ for (const [key, value] of request.#headers.entries()) {
155
+ headers[key] = decoder.decode(value);
156
+ }
157
+
158
+ let res;
159
+ try {
160
+ res = send({
161
+ method: request.#method.tag,
162
+ uri: url,
163
+ headers: headers,
164
+ params: [],
165
+ body: combineChunks(outgoingBodyGetChunks(request.#body)),
166
+ });
167
+ } catch (err) {
168
+ return newFutureIncomingResponse(err.toString(), true);
169
+ }
170
+
171
+ const encoder = new TextEncoder();
172
+ const response = newIncomingResponse(res.status, new Fields(res.headers.map(([key, value]) => [key, encoder.encode(value)])), [res.body]);
173
+ return newFutureIncomingResponse({ tag: 'ok', val: response }, false);
174
+ }
175
+ }
176
+
177
+ const outgoingRequestHandle = OutgoingRequest._handle;
178
+ delete OutgoingRequest._handle;
179
+
180
+ class OutgoingBody {
181
+ #chunks = [];
182
+ write () {
183
+ const body = this;
184
+ return new OutputStream({
185
+ write (bytes) {
186
+ body.#chunks.push(bytes);
187
+ },
188
+ blockingFlush () {}
189
+ });
190
+ }
191
+ /**
192
+ * @param {OutgoingBody} body
193
+ * @param {Fields | undefined} trailers
194
+ */
195
+ static finish (_body, _trailers) {
196
+ // TODO
197
+ }
198
+
199
+ static _getChunks (body) {
200
+ return body.#chunks;
201
+ }
202
+ }
203
+ const outgoingBodyGetChunks = OutgoingBody._getChunks;
204
+ delete OutgoingBody._getChunks;
205
+
206
+ class IncomingResponse {
207
+ id = http.responseCnt++;
208
+ /** @type {InputStream} */ #body;
209
+ /** @type {Fields} */ #headers = undefined;
210
+ #status = 0;
211
+ #bodyConsumed = false;
212
+ status () {
213
+ return this.#status;
214
+ }
215
+ headers () {
216
+ return this.#headers;
217
+ }
218
+ consume () {
219
+ if (this.#bodyConsumed)
220
+ throw { tag: 'unexpected-error', val: 'request body already consumed' };
221
+ this.#bodyConsumed = true;
222
+ return this.#body;
223
+ }
224
+ static _create (status, headers, chunks) {
225
+ const res = new IncomingResponse();
226
+ res.#status = status;
227
+ res.#headers = headers;
228
+ res.#body = new IncomingBody();
229
+ for (const chunk of chunks) {
230
+ incomingBodyAddChunk(res.#body, chunk);
231
+ }
232
+ return res;
233
+ }
234
+ }
235
+
236
+ const newIncomingResponse = IncomingResponse._create;
237
+ delete IncomingResponse._create;
238
+
239
+ class FutureIncomingResponse {
240
+ id = this.futureCnt++;
241
+ #value;
242
+ #isError;
243
+ subscribe () {
244
+ // TODO
245
+ }
246
+ get () {
247
+ return { tag: this.#isError ? 'err' : 'ok', val: this.#value };
248
+ }
249
+ static _create (value, isError) {
250
+ const res = new FutureIncomingResponse();
251
+ res.#value = value;
252
+ res.#isError = isError;
253
+ return res;
254
+ }
255
+ }
256
+
257
+ const newFutureIncomingResponse = FutureIncomingResponse._create;
258
+ delete FutureIncomingResponse._create;
259
+
260
+ class Fields {
261
+ id = http.fieldsCnt++;
262
+ /** @type {Record<string, Uint8Array[]>} */ #fields;
263
+
264
+ /**
265
+ * @param {[string, Uint8Array[]][]} fields
266
+ */
267
+ constructor(fields) {
268
+ this.#fields = Object.fromEntries(fields);
269
+ }
270
+ get (name) {
271
+ return this.#fields[name];
272
+ }
273
+ set (name, value) {
274
+ this.#fields[name] = value;
275
+ }
276
+ delete (name) {
277
+ delete this.#fields[name];
278
+ }
279
+ entries () {
280
+ return Object.entries(this.#fields);
281
+ }
282
+ clone () {
283
+ return new Fields(this.entries());
284
+ }
285
+ }
286
+
287
+ this.outgoingHandler = {
288
+ /**
289
+ * @param {OutgoingRequest} request
290
+ * @param {RequestOptions | undefined} _options
291
+ * @returns {FutureIncomingResponse}
292
+ */
293
+ handle: outgoingRequestHandle
294
+ };
295
+
296
+ this.types = {
297
+ Fields,
298
+ FutureIncomingResponse,
299
+ FutureTrailers,
300
+ IncomingBody,
301
+ IncomingRequest,
302
+ IncomingResponse,
303
+ OutgoingBody,
304
+ OutgoingRequest,
305
+ OutgoingResponse,
306
+ ResponseOutparam
307
+ };
308
+ }
309
+ }
310
+
311
+ export const { outgoingHandler, types } = new WasiHttp();
@@ -2,7 +2,6 @@ import * as clocks from "./clocks.js";
2
2
  import * as filesystem from "./filesystem.js";
3
3
  import * as http from "./http.js";
4
4
  import * as io from "../common/io.js";
5
- import * as poll from "./poll.js";
6
5
  import * as random from "./random.js";
7
6
  import * as sockets from "./sockets.js";
8
7
  import * as cli from "./cli.js";
@@ -12,11 +11,7 @@ export {
12
11
  filesystem,
13
12
  http,
14
13
  io,
15
- poll,
16
14
  random,
17
15
  sockets,
18
16
  cli
19
17
  }
20
-
21
- export { WasiHttp } from "../http/wasi-http.js";
22
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/preview2-shim",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "WASI Preview2 shim for JS environments",
5
5
  "author": "Guy Bedford, Eduardo Rodrigues<16357187+eduardomourar@users.noreply.github.com>",
6
6
  "type": "module",
@@ -1,5 +1,5 @@
1
1
  export namespace WasiCliStderr {
2
2
  export function getStderr(): OutputStream;
3
3
  }
4
- import type { OutputStream } from '../interfaces/wasi-io-streams';
4
+ import type { OutputStream } from '../interfaces/wasi-io-streams.js';
5
5
  export { OutputStream };
@@ -1,5 +1,5 @@
1
1
  export namespace WasiCliStdin {
2
2
  export function getStdin(): InputStream;
3
3
  }
4
- import type { InputStream } from '../interfaces/wasi-io-streams';
4
+ import type { InputStream } from '../interfaces/wasi-io-streams.js';
5
5
  export { InputStream };
@@ -1,5 +1,5 @@
1
1
  export namespace WasiCliStdout {
2
2
  export function getStdout(): OutputStream;
3
3
  }
4
- import type { OutputStream } from '../interfaces/wasi-io-streams';
4
+ import type { OutputStream } from '../interfaces/wasi-io-streams.js';
5
5
  export { OutputStream };
@@ -5,5 +5,5 @@ export namespace WasiCliTerminalStderr {
5
5
  */
6
6
  export function getTerminalStderr(): TerminalOutput | undefined;
7
7
  }
8
- import type { TerminalOutput } from '../interfaces/wasi-cli-terminal-output';
8
+ import type { TerminalOutput } from '../interfaces/wasi-cli-terminal-output.js';
9
9
  export { TerminalOutput };
@@ -5,5 +5,5 @@ export namespace WasiCliTerminalStdin {
5
5
  */
6
6
  export function getTerminalStdin(): TerminalInput | undefined;
7
7
  }
8
- import type { TerminalInput } from '../interfaces/wasi-cli-terminal-input';
8
+ import type { TerminalInput } from '../interfaces/wasi-cli-terminal-input.js';
9
9
  export { TerminalInput };
@@ -5,5 +5,5 @@ export namespace WasiCliTerminalStdout {
5
5
  */
6
6
  export function getTerminalStdout(): TerminalOutput | undefined;
7
7
  }
8
- import type { TerminalOutput } from '../interfaces/wasi-cli-terminal-output';
8
+ import type { TerminalOutput } from '../interfaces/wasi-cli-terminal-output.js';
9
9
  export { TerminalOutput };
@@ -16,7 +16,7 @@ export namespace WasiClocksMonotonicClock {
16
16
  */
17
17
  export function subscribe(when: Instant, absolute: boolean): Pollable;
18
18
  }
19
- import type { Pollable } from '../interfaces/wasi-io-poll';
19
+ import type { Pollable } from '../interfaces/wasi-io-poll.js';
20
20
  export { Pollable };
21
21
  /**
22
22
  * A timestamp in nanoseconds.
@@ -14,7 +14,7 @@ export namespace WasiClocksTimezone {
14
14
  */
15
15
  export function utcOffset(when: Datetime): number;
16
16
  }
17
- import type { Datetime } from '../interfaces/wasi-clocks-wall-clock';
17
+ import type { Datetime } from '../interfaces/wasi-clocks-wall-clock.js';
18
18
  export { Datetime };
19
19
  /**
20
20
  * Information useful for displaying the timezone of a specific `datetime`.
@@ -4,5 +4,5 @@ export namespace WasiFilesystemPreopens {
4
4
  */
5
5
  export function getDirectories(): [Descriptor, string][];
6
6
  }
7
- import type { Descriptor } from '../interfaces/wasi-filesystem-types';
7
+ import type { Descriptor } from '../interfaces/wasi-filesystem-types.js';
8
8
  export { Descriptor };
@@ -391,13 +391,13 @@ export namespace WasiFilesystemTypes {
391
391
  */
392
392
  export function filesystemErrorCode(err: Error): ErrorCode | undefined;
393
393
  }
394
- import type { InputStream } from '../interfaces/wasi-io-streams';
394
+ import type { InputStream } from '../interfaces/wasi-io-streams.js';
395
395
  export { InputStream };
396
- import type { OutputStream } from '../interfaces/wasi-io-streams';
396
+ import type { OutputStream } from '../interfaces/wasi-io-streams.js';
397
397
  export { OutputStream };
398
- import type { Error } from '../interfaces/wasi-io-streams';
398
+ import type { Error } from '../interfaces/wasi-io-streams.js';
399
399
  export { Error };
400
- import type { Datetime } from '../interfaces/wasi-clocks-wall-clock';
400
+ import type { Datetime } from '../interfaces/wasi-clocks-wall-clock.js';
401
401
  export { Datetime };
402
402
  /**
403
403
  * File size or length of a region within a file.
@@ -808,10 +808,6 @@ export interface MetadataHashValue {
808
808
  upper: bigint,
809
809
  }
810
810
 
811
- export class DirectoryEntryStream {
812
- readDirectoryEntry(): DirectoryEntry | undefined;
813
- }
814
-
815
811
  export class Descriptor {
816
812
  readViaStream(offset: Filesize): InputStream;
817
813
  writeViaStream(offset: Filesize): OutputStream;
@@ -849,3 +845,7 @@ export class Descriptor {
849
845
  metadataHash(): MetadataHashValue;
850
846
  metadataHashAt(pathFlags: PathFlags, path: string): MetadataHashValue;
851
847
  }
848
+
849
+ export class DirectoryEntryStream {
850
+ readDirectoryEntry(): DirectoryEntry | undefined;
851
+ }
@@ -0,0 +1,19 @@
1
+ export namespace WasiHttpIncomingHandler {
2
+ /**
3
+ * This function is invoked with an incoming HTTP Request, and a resource
4
+ * `response-outparam` which provides the capability to reply with an HTTP
5
+ * Response. The response is sent by calling the `response-outparam.set`
6
+ * method, which allows execution to continue after the response has been
7
+ * sent. This enables both streaming to the response body, and performing other
8
+ * work.
9
+ *
10
+ * The implementor of this function must write a response to the
11
+ * `response-outparam` before returning, or else the caller will respond
12
+ * with an error on its behalf.
13
+ */
14
+ export function handle(request: IncomingRequest, responseOut: ResponseOutparam): void;
15
+ }
16
+ import type { IncomingRequest } from '../interfaces/wasi-http-types.js';
17
+ export { IncomingRequest };
18
+ import type { ResponseOutparam } from '../interfaces/wasi-http-types.js';
19
+ export { ResponseOutparam };
@@ -0,0 +1,23 @@
1
+ export namespace WasiHttpOutgoingHandler {
2
+ /**
3
+ * This function is invoked with an outgoing HTTP Request, and it returns
4
+ * a resource `future-incoming-response` which represents an HTTP Response
5
+ * which may arrive in the future.
6
+ *
7
+ * The `options` argument accepts optional parameters for the HTTP
8
+ * protocol's transport layer.
9
+ *
10
+ * This function may return an error if the `outgoing-request` is invalid
11
+ * or not allowed to be made. Otherwise, protocol errors are reported
12
+ * through the `future-incoming-response`.
13
+ */
14
+ export function handle(request: OutgoingRequest, options: RequestOptions | undefined): FutureIncomingResponse;
15
+ }
16
+ import type { OutgoingRequest } from '../interfaces/wasi-http-types.js';
17
+ export { OutgoingRequest };
18
+ import type { RequestOptions } from '../interfaces/wasi-http-types.js';
19
+ export { RequestOptions };
20
+ import type { FutureIncomingResponse } from '../interfaces/wasi-http-types.js';
21
+ export { FutureIncomingResponse };
22
+ import type { Error } from '../interfaces/wasi-http-types.js';
23
+ export { Error };