@bytecodealliance/preview3-shim 0.2.0 → 0.2.2

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.
@@ -0,0 +1,403 @@
1
+ import { PassThrough, Readable, Writable } from "node:stream";
2
+ import { pipeline } from "stream/promises";
3
+ import { MessageChannel } from "node:worker_threads";
4
+ import { Agent, createServer, request } from "node:http";
5
+ import { Agent as Agent$1, request as request$1 } from "node:https";
6
+ import { isMainThread, parentPort } from "worker_threads";
7
+ //#region dist/nodejs/http/error.js
8
+ /**
9
+ * Custom error class for WASI http interface that properly formats payload for Jco compatibility
10
+ *
11
+ * https://bytecodealliance.github.io/jco/wit-type-representations.html#result-considerations-idiomatic-js-errors-for-host-implementations
12
+ */
13
+ var HttpError = class HttpError extends Error {
14
+ payload;
15
+ /**
16
+ * Create a new WASI http error
17
+ * @param {string} tag - The error tag/type
18
+ * @param {string} [message] - Human-readable error message
19
+ * @param {any} [val] - Optional value/data for the error
20
+ */
21
+ constructor(tag, message = void 0, val = void 0, opts = {}) {
22
+ super(message || `Error: ${tag}`, { cause: opts.cause });
23
+ this.name = "HttpError";
24
+ this.payload = val !== void 0 ? {
25
+ tag,
26
+ val
27
+ } : { tag };
28
+ }
29
+ static from(err) {
30
+ if (err instanceof HttpError) return err;
31
+ if (err?.tag) return new HttpError(err.tag, void 0, err.val);
32
+ const e = getFirstError(err);
33
+ let tag, val;
34
+ switch (e.code) {
35
+ case "ECONNRESET":
36
+ tag = "HTTP-protocol-error";
37
+ break;
38
+ case "ENOTFOUND":
39
+ tag = "DNS-error";
40
+ val = {
41
+ rcode: e.code,
42
+ infoCode: e.errno < 0 ? -e.errno : e.errno
43
+ };
44
+ break;
45
+ case "ECONNREFUSED":
46
+ tag = "connection-refused";
47
+ break;
48
+ default:
49
+ tag = "internal-error";
50
+ val = e.toString();
51
+ }
52
+ const httpErr = new HttpError(tag, e.message, val, { cause: e });
53
+ Error.captureStackTrace(httpErr, HttpError.from);
54
+ return httpErr;
55
+ }
56
+ };
57
+ function getFirstError(e) {
58
+ if (typeof e !== "object" || e === null) return e;
59
+ if (e.cause) return getFirstError(e.cause);
60
+ if (e instanceof AggregateError) return getFirstError(e.errors[0]);
61
+ return e;
62
+ }
63
+ //#endregion
64
+ //#region dist/nodejs/workers/resource-worker.js
65
+ function Router() {
66
+ const _hooks = {
67
+ beforeAll: null,
68
+ ops: /* @__PURE__ */ new Map()
69
+ };
70
+ if (isMainThread || !parentPort) throw new Error("Router can only be used in worker threads");
71
+ parentPort.on("message", async (msg) => {
72
+ const { _reply, _condvar, ...rest } = msg;
73
+ let result, error, transfer = [];
74
+ try {
75
+ if (_hooks.beforeAll) _hooks.beforeAll(rest);
76
+ const handler = _hooks.ops.get(rest.op);
77
+ if (!handler) throw new Error(`Unknown op ${rest.op}`);
78
+ const outcome = await handler(rest);
79
+ if (outcome && Array.isArray(outcome.transferable)) {
80
+ result = outcome.result;
81
+ transfer = outcome.transferable;
82
+ } else result = outcome;
83
+ } catch (err) {
84
+ error = serializeError(err);
85
+ }
86
+ _reply.postMessage({
87
+ result,
88
+ error
89
+ }, transfer);
90
+ _reply.close();
91
+ if (_condvar) notify(_condvar);
92
+ });
93
+ return {
94
+ beforeAll(fn) {
95
+ _hooks.beforeAll = fn;
96
+ return this;
97
+ },
98
+ op(name, fn) {
99
+ _hooks.ops.set(name, fn);
100
+ return this;
101
+ }
102
+ };
103
+ }
104
+ function serializeError(err) {
105
+ if (!(err instanceof Error)) return err;
106
+ const serialized = {
107
+ __resourceWorkerError: true,
108
+ name: err.name,
109
+ message: err.message,
110
+ stack: err.stack
111
+ };
112
+ for (const key of Reflect.ownKeys(err)) serialized[key] = err[key];
113
+ if (err.cause !== void 0) serialized.cause = serializeError(err.cause);
114
+ return serialized;
115
+ }
116
+ function notify(condvar) {
117
+ const lock = new Int32Array(condvar);
118
+ Atomics.store(lock, 0, 1);
119
+ Atomics.notify(lock, 0, 1);
120
+ }
121
+ //#endregion
122
+ //#region dist/nodejs/workers/http-worker.js
123
+ let NEXT_SERVER_ID = 0n;
124
+ let NEXT_REQUEST_ID = 0n;
125
+ Router().op("server-start", handleHttpServerStart).op("server-stop", handleHttpServerStop).op("server-next", handleNext).op("server-response", handleResponse).op("server-close", handleHttpServerClose).op("client-request", handleRequest);
126
+ var Queue = class {
127
+ _items = [];
128
+ _resolvers = [];
129
+ constructor() {
130
+ this._items = [];
131
+ this._resolvers = [];
132
+ }
133
+ push(item) {
134
+ if (this._resolvers.length) this._resolvers.shift()(item);
135
+ else this._items.push(item);
136
+ }
137
+ async pop() {
138
+ if (this._items.length) return this._items.shift();
139
+ return new Promise((resolve) => {
140
+ this._resolvers.push(resolve);
141
+ });
142
+ }
143
+ };
144
+ const servers = /* @__PURE__ */ new Map();
145
+ async function handleHttpServerStart({ port, host }) {
146
+ const serverId = NEXT_SERVER_ID++;
147
+ const pending = new Queue();
148
+ const inflight = /* @__PURE__ */ new Map();
149
+ const server = createServer((req, res) => {
150
+ const requestId = NEXT_REQUEST_ID++;
151
+ pending.push(requestId);
152
+ inflight.set(requestId, {
153
+ req,
154
+ res
155
+ });
156
+ });
157
+ await new Promise((resolve, reject) => {
158
+ server.on("error", reject);
159
+ server.listen(port, host, () => resolve(void 0));
160
+ });
161
+ servers.set(serverId, {
162
+ server,
163
+ pending,
164
+ inflight
165
+ });
166
+ return serverId;
167
+ }
168
+ async function handleNext({ serverId }) {
169
+ const srv = servers.get(serverId);
170
+ if (!srv) throw new Error(`No such server: ${serverId}`);
171
+ const requestId = await srv.pending.pop();
172
+ if (requestId == null) return null;
173
+ const { req } = srv.inflight.get(requestId);
174
+ const { port1: tx, port2: trailers } = new MessageChannel();
175
+ const { readable: body, writable } = new TransformStream();
176
+ pipeline(req, Writable.fromWeb(writable)).then(() => tx.postMessage({ val: toEntries(req.trailersDistinct || req.trailers || {}) })).catch((err) => tx.postMessage({ err })).finally(() => tx.close());
177
+ const headers = toEntries(req.headersDistinct);
178
+ const { method, url } = req;
179
+ return {
180
+ result: {
181
+ requestId,
182
+ headers,
183
+ body,
184
+ trailers,
185
+ method,
186
+ url
187
+ },
188
+ transferable: [body, trailers]
189
+ };
190
+ }
191
+ async function handleResponse({ serverId, requestId, statusCode, headers, trailers, stream }) {
192
+ const srv = servers.get(serverId);
193
+ if (!srv) throw new Error(`No such server: ${serverId}`);
194
+ const entry = srv.inflight.get(requestId);
195
+ if (!entry) throw new Error(`No inflight ${requestId}`);
196
+ const { res } = entry;
197
+ try {
198
+ res.writeHead(statusCode, toObject(headers));
199
+ if (stream) {
200
+ await pipeline(Readable.fromWeb(stream), res, { end: false });
201
+ const fields = await recvTrailers(trailers);
202
+ if (fields) res.addTrailers(toObject(fields));
203
+ res.end();
204
+ } else res.end();
205
+ } catch (err) {
206
+ res.destroy(err);
207
+ throw err;
208
+ } finally {
209
+ srv.inflight.delete(requestId);
210
+ }
211
+ return null;
212
+ }
213
+ async function handleRequest(args) {
214
+ const transmit = transmitReporter(args.transmit);
215
+ try {
216
+ return await doHandleRequest(args, transmit);
217
+ } catch (err) {
218
+ transmit.err(err);
219
+ throw HttpError.from(err).payload;
220
+ }
221
+ }
222
+ async function doHandleRequest({ url, method, headers, trailers, body, timeouts }, transmit) {
223
+ const parsed = new URL(url);
224
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new HttpError("HTTP-protocol-error");
225
+ const { connectTimeoutNs, firstByteTimeoutNs, betweenBytesTimeoutNs } = timeouts;
226
+ const isHttps = parsed.protocol === "https:";
227
+ const agent = new (isHttps ? Agent$1 : Agent)({ keepAlive: true });
228
+ const req = (isHttps ? request$1 : request)({
229
+ agent,
230
+ method,
231
+ host: parsed.hostname,
232
+ port: parsed.port,
233
+ path: parsed.pathname + parsed.search,
234
+ headers: toObject(headers),
235
+ timeout: connectTimeoutNs ? msecs(connectTimeoutNs) : void 0
236
+ });
237
+ if (firstByteTimeoutNs) req.setTimeout(msecs(firstByteTimeoutNs));
238
+ let resStarted = false;
239
+ const response = new Promise((resolve, reject) => {
240
+ const onResponse = (response) => {
241
+ resStarted = true;
242
+ cleanup();
243
+ resolve(response);
244
+ };
245
+ const onConnect = (_response, socket) => {
246
+ cleanup();
247
+ socket.destroy();
248
+ reject(new HttpError("HTTP-protocol-error"));
249
+ };
250
+ const onError = (err) => {
251
+ cleanup();
252
+ reject(err);
253
+ };
254
+ const onTimeout = () => {
255
+ cleanup();
256
+ req.on("error", () => {});
257
+ req.destroy();
258
+ reject(new HttpError("connection-timeout"));
259
+ };
260
+ const onClose = () => {
261
+ cleanup();
262
+ reject(new HttpError("connection-terminated"));
263
+ };
264
+ const cleanup = () => {
265
+ req.off("response", onResponse);
266
+ req.off("connect", onConnect);
267
+ req.off("error", onError);
268
+ req.off("timeout", onTimeout);
269
+ req.off("close", onClose);
270
+ };
271
+ req.once("response", onResponse);
272
+ req.once("connect", onConnect);
273
+ req.once("error", onError);
274
+ req.once("timeout", onTimeout);
275
+ req.once("close", onClose);
276
+ });
277
+ const expectedLength = contentLength(headers);
278
+ (body ? sendRequestBody(req, body, trailers, () => resStarted, expectedLength) : endRequest(req)).then(() => transmit.ok(), (err) => transmit.err(err));
279
+ let res;
280
+ try {
281
+ res = await response;
282
+ } catch (err) {
283
+ req.destroy();
284
+ throw err;
285
+ }
286
+ if (betweenBytesTimeoutNs) res.once("readable", () => res.setTimeout(msecs(betweenBytesTimeoutNs), () => {
287
+ res.destroy(new HttpError("connection-timeout"));
288
+ }));
289
+ const pass = new PassThrough();
290
+ const web = Readable.toWeb(pass);
291
+ const { port1: tx, port2: rx } = new MessageChannel();
292
+ pipeline(res, pass).then(() => tx.postMessage({ val: toEntries(res.trailersDistinct || res.trailers || {}) })).catch((err) => tx.postMessage({ err })).finally(() => tx.close());
293
+ return {
294
+ result: {
295
+ statusCode: res.statusCode,
296
+ headers: toEntries(res.headersDistinct),
297
+ body: web,
298
+ trailers: rx
299
+ },
300
+ transferable: [web, rx]
301
+ };
302
+ }
303
+ function handleHttpServerStop({ serverId }) {
304
+ const srv = servers.get(serverId);
305
+ if (!srv) throw new Error(`No such server: ${serverId}`);
306
+ srv.pending.push(null);
307
+ return serverId;
308
+ }
309
+ async function handleHttpServerClose({ serverId }) {
310
+ const srv = servers.get(serverId);
311
+ if (!srv) throw new Error(`No such server: ${serverId}`);
312
+ await new Promise((resolve) => srv.server.close(resolve));
313
+ servers.delete(serverId);
314
+ return serverId;
315
+ }
316
+ async function sendRequestBody(req, body, trailers, resStarted, expectedLength) {
317
+ try {
318
+ req.flushHeaders();
319
+ await pipeline(validateRequestBody(Readable.fromWeb(body), expectedLength), req, { end: false });
320
+ const fields = await recvTrailers(trailers);
321
+ if (fields) req.addTrailers(toObject(fields));
322
+ await endRequest(req);
323
+ } catch (err) {
324
+ if (!resStarted()) req.destroy(err);
325
+ throw err;
326
+ }
327
+ }
328
+ async function* validateRequestBody(body, expectedLength) {
329
+ let bytes = 0n;
330
+ for await (const chunk of body) {
331
+ bytes += BigInt(chunk.byteLength);
332
+ if (expectedLength !== null && bytes > expectedLength) throw new HttpError("HTTP-request-body-size", void 0, bytes);
333
+ yield chunk;
334
+ }
335
+ if (expectedLength !== null && bytes < expectedLength) throw new HttpError("HTTP-request-body-size", void 0, bytes);
336
+ }
337
+ function endRequest(req) {
338
+ return new Promise((resolve, reject) => {
339
+ let settled = false;
340
+ const settle = (fn, value) => {
341
+ if (settled) return;
342
+ settled = true;
343
+ req.off("error", onError);
344
+ req.off("close", onClose);
345
+ fn(value);
346
+ };
347
+ const onError = (err) => settle(reject, err);
348
+ const onClose = () => settle(reject, new HttpError("connection-terminated"));
349
+ req.once("error", onError);
350
+ req.once("close", onClose);
351
+ try {
352
+ req.end(() => settle(resolve, void 0));
353
+ } catch (err) {
354
+ settle(reject, err);
355
+ }
356
+ });
357
+ }
358
+ function transmitReporter(port) {
359
+ let settled = false;
360
+ const send = (msg) => {
361
+ if (settled || !port) return;
362
+ settled = true;
363
+ port.postMessage(msg);
364
+ port.close();
365
+ };
366
+ return {
367
+ ok() {
368
+ send({ val: void 0 });
369
+ },
370
+ err(err) {
371
+ send({ err: HttpError.from(err).payload });
372
+ }
373
+ };
374
+ }
375
+ async function recvTrailers(port) {
376
+ if (!port) return null;
377
+ return new Promise((resolve, reject) => {
378
+ port.once("message", ({ val, err }) => {
379
+ port.close();
380
+ if (err) reject(err);
381
+ else resolve(val);
382
+ });
383
+ });
384
+ }
385
+ const msecs = (time) => {
386
+ return typeof time === "bigint" ? Number(time / 1000000n) : time / 1e6;
387
+ };
388
+ const decoder = new TextDecoder();
389
+ const contentLength = (entries) => {
390
+ const entry = entries.findLast(([name]) => name.toLowerCase() === "content-length");
391
+ if (!entry) return null;
392
+ const value = decoder.decode(entry[1]);
393
+ return /^\d+$/.test(value) ? BigInt(value) : null;
394
+ };
395
+ const toObject = (entries) => {
396
+ return Object.fromEntries(entries.map(([key, val]) => [key, decoder.decode(val)]));
397
+ };
398
+ const encoder = new TextEncoder();
399
+ const toEntries = (obj) => {
400
+ return Object.entries(obj).flatMap(([k, v]) => Array.isArray(v) ? v.map((val) => [k, encoder.encode(val)]) : [[k, encoder.encode(v)]]);
401
+ };
402
+ //#endregion
403
+ export {};
@@ -1,6 +1,6 @@
1
1
  export declare class ResourceWorker {
2
2
  #private;
3
- constructor(workerUrl: any);
3
+ constructor(createWorker: any);
4
4
  terminate(): void;
5
5
  /** Async IPC */
6
6
  run(msg: any, transferable?: any[]): Promise<unknown>;
@@ -1,16 +1,16 @@
1
- import { Worker, parentPort, isMainThread, MessageChannel, receiveMessageOnPort, } from "worker_threads";
1
+ import { parentPort, isMainThread, MessageChannel, receiveMessageOnPort } from "worker_threads";
2
2
  export class ResourceWorker {
3
3
  #worker = null;
4
- #workerUrl = null;
4
+ #createWorker = null;
5
5
  #pending = new Map();
6
- constructor(workerUrl) {
7
- this.#workerUrl = workerUrl;
6
+ constructor(createWorker) {
7
+ this.#createWorker = createWorker;
8
8
  }
9
9
  #getWorker() {
10
10
  if (this.#worker) {
11
11
  return this.#worker;
12
12
  }
13
- this.#worker = new Worker(this.#workerUrl);
13
+ this.#worker = this.#createWorker();
14
14
  this.#worker.unref();
15
15
  return this.#worker;
16
16
  }