@bytecodealliance/preview3-shim 0.1.1 → 0.2.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 (112) hide show
  1. package/dist/nodejs/cli.d.ts +51 -0
  2. package/dist/nodejs/cli.js +106 -0
  3. package/dist/nodejs/clocks.d.ts +55 -0
  4. package/dist/nodejs/clocks.js +83 -0
  5. package/dist/nodejs/filesystem/descriptor.d.ts +443 -0
  6. package/dist/nodejs/filesystem/descriptor.js +1010 -0
  7. package/dist/nodejs/filesystem/error.d.ts +61 -0
  8. package/dist/nodejs/filesystem/error.js +86 -0
  9. package/dist/nodejs/filesystem/utils.d.ts +8 -0
  10. package/dist/nodejs/filesystem/utils.js +25 -0
  11. package/dist/nodejs/filesystem.js +2 -0
  12. package/dist/nodejs/finalization.d.ts +2 -0
  13. package/{lib → dist}/nodejs/finalization.js +15 -21
  14. package/dist/nodejs/future.d.ts +98 -0
  15. package/dist/nodejs/future.js +176 -0
  16. package/dist/nodejs/http/client.d.ts +22 -0
  17. package/dist/nodejs/http/client.js +138 -0
  18. package/dist/nodejs/http/error.d.ts +18 -0
  19. package/dist/nodejs/http/error.js +62 -0
  20. package/dist/nodejs/http/fields.d.ts +144 -0
  21. package/dist/nodejs/http/fields.js +327 -0
  22. package/dist/nodejs/http/request.d.ts +256 -0
  23. package/dist/nodejs/http/request.js +606 -0
  24. package/dist/nodejs/http/response.d.ts +82 -0
  25. package/dist/nodejs/http/response.js +202 -0
  26. package/dist/nodejs/http/server.d.ts +13 -0
  27. package/dist/nodejs/http/server.js +127 -0
  28. package/dist/nodejs/http.d.ts +19 -0
  29. package/{lib → dist}/nodejs/http.js +13 -18
  30. package/dist/nodejs/index.js +8 -0
  31. package/dist/nodejs/random.d.ts +5 -0
  32. package/{lib → dist}/nodejs/random.js +2 -8
  33. package/dist/nodejs/sockets/address.d.ts +78 -0
  34. package/dist/nodejs/sockets/address.js +120 -0
  35. package/dist/nodejs/sockets/error.d.ts +83 -0
  36. package/dist/nodejs/sockets/error.js +120 -0
  37. package/dist/nodejs/sockets/ip-name-lookup.d.ts +18 -0
  38. package/dist/nodejs/sockets/ip-name-lookup.js +84 -0
  39. package/dist/nodejs/sockets/tcp.d.ts +333 -0
  40. package/dist/nodejs/sockets/tcp.js +723 -0
  41. package/dist/nodejs/sockets/udp.d.ts +224 -0
  42. package/dist/nodejs/sockets/udp.js +475 -0
  43. package/dist/nodejs/sockets.d.ts +11 -0
  44. package/{lib → dist}/nodejs/sockets.js +2 -5
  45. package/dist/nodejs/stream.d.ts +161 -0
  46. package/dist/nodejs/stream.js +388 -0
  47. package/dist/nodejs/workers/cli-worker.d.ts +1 -0
  48. package/dist/nodejs/workers/cli-worker.js +41 -0
  49. package/dist/nodejs/workers/filesystem-worker.d.ts +1 -0
  50. package/dist/nodejs/workers/filesystem-worker.js +102 -0
  51. package/dist/nodejs/workers/http-worker.d.ts +1 -0
  52. package/dist/nodejs/workers/http-worker.js +355 -0
  53. package/dist/nodejs/workers/resource-worker.d.ts +13 -0
  54. package/dist/nodejs/workers/resource-worker.js +152 -0
  55. package/dist/nodejs/workers/tcp-worker.d.ts +2 -0
  56. package/dist/nodejs/workers/tcp-worker.js +318 -0
  57. package/dist/nodejs/workers/udp-worker.d.ts +1 -0
  58. package/dist/nodejs/workers/udp-worker.js +156 -0
  59. package/package.json +20 -12
  60. package/types/interfaces/wasi-cli-environment.d.ts +3 -3
  61. package/types/interfaces/wasi-cli-exit.d.ts +12 -1
  62. package/types/interfaces/wasi-cli-run.d.ts +1 -1
  63. package/types/interfaces/wasi-cli-stderr.d.ts +3 -3
  64. package/types/interfaces/wasi-cli-stdin.d.ts +5 -5
  65. package/types/interfaces/wasi-cli-stdout.d.ts +3 -3
  66. package/types/interfaces/wasi-cli-terminal-input.d.ts +1 -1
  67. package/types/interfaces/wasi-cli-terminal-output.d.ts +1 -1
  68. package/types/interfaces/wasi-cli-terminal-stderr.d.ts +1 -1
  69. package/types/interfaces/wasi-cli-terminal-stdin.d.ts +1 -1
  70. package/types/interfaces/wasi-cli-terminal-stdout.d.ts +1 -1
  71. package/types/interfaces/wasi-cli-types.d.ts +5 -5
  72. package/types/interfaces/wasi-clocks-monotonic-clock.d.ts +3 -3
  73. package/types/interfaces/wasi-clocks-system-clock.d.ts +6 -6
  74. package/types/interfaces/wasi-clocks-types.d.ts +1 -1
  75. package/types/interfaces/wasi-filesystem-preopens.d.ts +1 -1
  76. package/types/interfaces/wasi-filesystem-types.d.ts +74 -74
  77. package/types/interfaces/wasi-http-client.d.ts +1 -1
  78. package/types/interfaces/wasi-http-handler.d.ts +1 -1
  79. package/types/interfaces/wasi-http-types.d.ts +34 -34
  80. package/types/interfaces/wasi-random-insecure-seed.d.ts +5 -5
  81. package/types/interfaces/wasi-random-insecure.d.ts +5 -5
  82. package/types/interfaces/wasi-random-random.d.ts +5 -5
  83. package/types/interfaces/wasi-sockets-ip-name-lookup.d.ts +11 -11
  84. package/types/interfaces/wasi-sockets-types.d.ts +121 -121
  85. package/types/wasi-cli-command.d.ts +23 -23
  86. package/types/wasi-http-service.d.ts +14 -14
  87. package/lib/nodejs/cli.js +0 -127
  88. package/lib/nodejs/clocks.js +0 -100
  89. package/lib/nodejs/filesystem/descriptor.js +0 -1093
  90. package/lib/nodejs/filesystem/error.js +0 -92
  91. package/lib/nodejs/filesystem/utils.js +0 -19
  92. package/lib/nodejs/future.js +0 -190
  93. package/lib/nodejs/http/client.js +0 -161
  94. package/lib/nodejs/http/error.js +0 -71
  95. package/lib/nodejs/http/fields.js +0 -373
  96. package/lib/nodejs/http/request.js +0 -691
  97. package/lib/nodejs/http/response.js +0 -227
  98. package/lib/nodejs/http/server.js +0 -157
  99. package/lib/nodejs/sockets/address.js +0 -146
  100. package/lib/nodejs/sockets/error.js +0 -123
  101. package/lib/nodejs/sockets/ip-name-lookup.js +0 -93
  102. package/lib/nodejs/sockets/tcp.js +0 -801
  103. package/lib/nodejs/sockets/udp.js +0 -516
  104. package/lib/nodejs/stream.js +0 -437
  105. package/lib/nodejs/workers/cli-worker.js +0 -49
  106. package/lib/nodejs/workers/filesystem-worker.js +0 -119
  107. package/lib/nodejs/workers/http-worker.js +0 -417
  108. package/lib/nodejs/workers/resource-worker.js +0 -195
  109. package/lib/nodejs/workers/tcp-worker.js +0 -370
  110. package/lib/nodejs/workers/udp-worker.js +0 -194
  111. /package/{lib/nodejs/filesystem.js → dist/nodejs/filesystem.d.ts} +0 -0
  112. /package/{lib/nodejs/index.js → dist/nodejs/index.d.ts} +0 -0
@@ -0,0 +1,102 @@
1
+ import { promisify } from "util";
2
+ import { Router } from "./resource-worker.js";
3
+ import { FSError } from "../filesystem/error.js";
4
+ import { wasiTypeFromDirent } from "../filesystem/utils.js";
5
+ // Use fs callback API for BigInt offset support
6
+ import fs from "fs";
7
+ const readAsync = promisify(fs.read);
8
+ const writeAsync = promisify(fs.write);
9
+ const { opendir } = fs.promises;
10
+ const CHUNK_BYTES = 64 * 1024;
11
+ // Reuse a single buffer for all read/write ops
12
+ const BUFFER = Buffer.alloc(CHUNK_BYTES);
13
+ const MAX_SAFE_FILE_POSITION = BigInt(Number.MAX_SAFE_INTEGER);
14
+ /** Auto‐dispatch all ops */
15
+ Router().op("read", handleRead).op("write", handleWrite).op("readDir", handleReadDir);
16
+ async function handleRead({ fd, offset, stream }) {
17
+ if (typeof fd !== "number" || fd < 0) {
18
+ throw new TypeError(`Invalid file descriptor: ${fd}`);
19
+ }
20
+ if (!stream || typeof stream.getWriter !== "function") {
21
+ throw new TypeError("stream must have a getWriter() method");
22
+ }
23
+ const writer = stream.getWriter();
24
+ try {
25
+ let pos = BigInt(offset);
26
+ const start = pos;
27
+ while (true) {
28
+ const { bytesRead } = await readAsync(fd, BUFFER, 0, CHUNK_BYTES, filePosition(pos));
29
+ if (bytesRead === 0) {
30
+ break;
31
+ }
32
+ await writer.write(BUFFER.subarray(0, bytesRead));
33
+ pos += BigInt(bytesRead);
34
+ }
35
+ await writer.close();
36
+ return { bytesRead: pos - start };
37
+ }
38
+ catch (err) {
39
+ await writer.abort(err);
40
+ throw err;
41
+ }
42
+ }
43
+ async function handleWrite({ fd, offset, stream }) {
44
+ if (typeof fd !== "number" || fd < 0) {
45
+ throw new TypeError(`Invalid file descriptor: ${fd}`);
46
+ }
47
+ if (!stream || typeof stream.getReader !== "function") {
48
+ throw new TypeError("stream must have a getReader() method");
49
+ }
50
+ const reader = stream.getReader();
51
+ try {
52
+ let pos = BigInt(offset);
53
+ const start = pos;
54
+ while (true) {
55
+ const { done, value } = await reader.read();
56
+ if (done) {
57
+ break;
58
+ }
59
+ const buf = Buffer.from(value);
60
+ const { bytesWritten } = await writeAsync(fd, buf, 0, buf.length, filePosition(pos));
61
+ pos += BigInt(bytesWritten);
62
+ }
63
+ return { bytesWritten: pos - start };
64
+ }
65
+ catch (err) {
66
+ await reader.cancel(err);
67
+ throw err;
68
+ }
69
+ }
70
+ function filePosition(position) {
71
+ if (position < 0n || position > MAX_SAFE_FILE_POSITION) {
72
+ throw new FSError("invalid");
73
+ }
74
+ return Number(position);
75
+ }
76
+ async function handleReadDir({ fullPath, stream, preopens }) {
77
+ if (!stream || typeof stream.getWriter !== "function") {
78
+ throw new TypeError("stream must have a getWriter() method");
79
+ }
80
+ const writer = stream.getWriter();
81
+ const canAccess = preopens.some((p) => fullPath === p || fullPath.startsWith(p + "/"));
82
+ if (!canAccess) {
83
+ const err = new FSError("not-permitted");
84
+ await writer.abort(err);
85
+ throw err;
86
+ }
87
+ try {
88
+ const walker = await opendir(fullPath);
89
+ for await (const dirent of walker) {
90
+ await writer.write({
91
+ name: dirent.name,
92
+ type: wasiTypeFromDirent(dirent),
93
+ });
94
+ }
95
+ await writer.close();
96
+ return { ok: true };
97
+ }
98
+ catch (err) {
99
+ await writer.abort(err);
100
+ throw err;
101
+ }
102
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,355 @@
1
+ import { Readable, Writable, PassThrough } from "node:stream";
2
+ import { pipeline } from "stream/promises";
3
+ import { MessageChannel } from "node:worker_threads";
4
+ import { createServer } from "node:http";
5
+ import { request as httpRequest, Agent as HttpAgent } from "node:http";
6
+ import { request as httpsRequest, Agent as HttpsAgent } from "node:https";
7
+ import { HttpError } from "../http/error.js";
8
+ import { Router } from "../workers/resource-worker.js";
9
+ // Unique IDs for servers and requests
10
+ let NEXT_SERVER_ID = 0n;
11
+ let NEXT_REQUEST_ID = 0n;
12
+ Router()
13
+ .op("server-start", handleHttpServerStart)
14
+ .op("server-stop", handleHttpServerStop)
15
+ .op("server-next", handleNext)
16
+ .op("server-response", handleResponse)
17
+ .op("server-close", handleHttpServerClose)
18
+ .op("client-request", handleRequest);
19
+ class Queue {
20
+ _items = [];
21
+ _resolvers = [];
22
+ constructor() {
23
+ this._items = [];
24
+ this._resolvers = [];
25
+ }
26
+ push(item) {
27
+ if (this._resolvers.length) {
28
+ const resolve = this._resolvers.shift();
29
+ resolve(item);
30
+ }
31
+ else {
32
+ this._items.push(item);
33
+ }
34
+ }
35
+ async pop() {
36
+ if (this._items.length) {
37
+ return this._items.shift();
38
+ }
39
+ return new Promise((resolve) => {
40
+ this._resolvers.push(resolve);
41
+ });
42
+ }
43
+ }
44
+ // Map<serverId, { server, pending: Queue, inflight: Map }>
45
+ const servers = new Map();
46
+ async function handleHttpServerStart({ port, host }) {
47
+ const serverId = NEXT_SERVER_ID++;
48
+ const pending = new Queue();
49
+ const inflight = new Map();
50
+ const server = createServer((req, res) => {
51
+ const requestId = NEXT_REQUEST_ID++;
52
+ pending.push(requestId);
53
+ inflight.set(requestId, { req, res });
54
+ });
55
+ await new Promise((resolve, reject) => {
56
+ server.on("error", reject);
57
+ server.listen(port, host, () => resolve(undefined));
58
+ });
59
+ servers.set(serverId, { server, pending, inflight });
60
+ return serverId;
61
+ }
62
+ async function handleNext({ serverId }) {
63
+ const srv = servers.get(serverId);
64
+ if (!srv) {
65
+ throw new Error(`No such server: ${serverId}`);
66
+ }
67
+ const requestId = await srv.pending.pop();
68
+ if (requestId == null) {
69
+ return null;
70
+ }
71
+ const { req } = srv.inflight.get(requestId);
72
+ const { port1: tx, port2: trailers } = new MessageChannel();
73
+ const { readable: body, writable } = new TransformStream();
74
+ const stream = Writable.fromWeb(writable);
75
+ pipeline(req, stream)
76
+ .then(() => tx.postMessage({ val: toEntries(req.trailersDistinct || req.trailers || {}) }))
77
+ .catch((err) => tx.postMessage({ err }))
78
+ .finally(() => tx.close());
79
+ const headers = toEntries(req.headersDistinct);
80
+ const { method, url } = req;
81
+ const result = {
82
+ requestId,
83
+ headers,
84
+ body,
85
+ trailers,
86
+ method,
87
+ url,
88
+ };
89
+ return { result, transferable: [body, trailers] };
90
+ }
91
+ async function handleResponse({ serverId, requestId, statusCode, headers, trailers, stream }) {
92
+ const srv = servers.get(serverId);
93
+ if (!srv) {
94
+ throw new Error(`No such server: ${serverId}`);
95
+ }
96
+ const entry = srv.inflight.get(requestId);
97
+ if (!entry) {
98
+ throw new Error(`No inflight ${requestId}`);
99
+ }
100
+ const { res } = entry;
101
+ try {
102
+ res.writeHead(statusCode, toObject(headers));
103
+ if (stream) {
104
+ await pipeline(Readable.fromWeb(stream), res, { end: false });
105
+ const fields = await recvTrailers(trailers);
106
+ if (fields) {
107
+ res.addTrailers(toObject(fields));
108
+ }
109
+ res.end();
110
+ }
111
+ else {
112
+ res.end();
113
+ }
114
+ }
115
+ catch (err) {
116
+ res.destroy(err);
117
+ throw err;
118
+ }
119
+ finally {
120
+ srv.inflight.delete(requestId);
121
+ }
122
+ return null;
123
+ }
124
+ async function handleRequest(args) {
125
+ const transmit = transmitReporter(args.transmit);
126
+ try {
127
+ return await doHandleRequest(args, transmit);
128
+ }
129
+ catch (err) {
130
+ transmit.err(err);
131
+ throw HttpError.from(err).payload;
132
+ }
133
+ }
134
+ async function doHandleRequest({ url, method, headers, trailers, body, timeouts }, transmit) {
135
+ const parsed = new URL(url);
136
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
137
+ throw new HttpError("HTTP-protocol-error");
138
+ }
139
+ const { connectTimeoutNs, firstByteTimeoutNs, betweenBytesTimeoutNs } = timeouts;
140
+ const isHttps = parsed.protocol === "https:";
141
+ const AgentClass = isHttps ? HttpsAgent : HttpAgent;
142
+ const agent = new AgentClass({ keepAlive: true });
143
+ const request = isHttps ? httpsRequest : httpRequest;
144
+ const reqOpts = {
145
+ agent,
146
+ method,
147
+ host: parsed.hostname,
148
+ port: parsed.port,
149
+ path: parsed.pathname + parsed.search,
150
+ headers: toObject(headers),
151
+ timeout: connectTimeoutNs ? msecs(connectTimeoutNs) : undefined,
152
+ };
153
+ const req = request(reqOpts);
154
+ if (firstByteTimeoutNs) {
155
+ req.setTimeout(msecs(firstByteTimeoutNs));
156
+ }
157
+ let resStarted = false;
158
+ const response = new Promise((resolve, reject) => {
159
+ const onResponse = (response) => {
160
+ resStarted = true;
161
+ cleanup();
162
+ resolve(response);
163
+ };
164
+ const onConnect = (_response, socket) => {
165
+ cleanup();
166
+ socket.destroy();
167
+ reject(new HttpError("HTTP-protocol-error"));
168
+ };
169
+ const onError = (err) => {
170
+ cleanup();
171
+ reject(err);
172
+ };
173
+ const onTimeout = () => {
174
+ cleanup();
175
+ req.on("error", () => { });
176
+ req.destroy();
177
+ reject(new HttpError("connection-timeout"));
178
+ };
179
+ const onClose = () => {
180
+ cleanup();
181
+ reject(new HttpError("connection-terminated"));
182
+ };
183
+ const cleanup = () => {
184
+ req.off("response", onResponse);
185
+ req.off("connect", onConnect);
186
+ req.off("error", onError);
187
+ req.off("timeout", onTimeout);
188
+ req.off("close", onClose);
189
+ };
190
+ req.once("response", onResponse);
191
+ req.once("connect", onConnect);
192
+ req.once("error", onError);
193
+ req.once("timeout", onTimeout);
194
+ req.once("close", onClose);
195
+ });
196
+ const expectedLength = contentLength(headers);
197
+ const upload = body
198
+ ? sendRequestBody(req, body, trailers, () => resStarted, expectedLength)
199
+ : endRequest(req);
200
+ upload.then(() => transmit.ok(), (err) => transmit.err(err));
201
+ let res;
202
+ try {
203
+ res = await response;
204
+ }
205
+ catch (err) {
206
+ req.destroy();
207
+ throw err;
208
+ }
209
+ if (betweenBytesTimeoutNs) {
210
+ res.once("readable", () => res.setTimeout(msecs(betweenBytesTimeoutNs), () => {
211
+ res.destroy(new HttpError("connection-timeout"));
212
+ }));
213
+ }
214
+ const pass = new PassThrough();
215
+ const web = Readable.toWeb(pass);
216
+ const { port1: tx, port2: rx } = new MessageChannel();
217
+ pipeline(res, pass)
218
+ .then(() => tx.postMessage({ val: toEntries(res.trailersDistinct || res.trailers || {}) }))
219
+ .catch((err) => tx.postMessage({ err }))
220
+ .finally(() => tx.close());
221
+ const result = {
222
+ statusCode: res.statusCode,
223
+ headers: toEntries(res.headersDistinct),
224
+ body: web,
225
+ trailers: rx,
226
+ };
227
+ return { result, transferable: [web, rx] };
228
+ }
229
+ function handleHttpServerStop({ serverId }) {
230
+ const srv = servers.get(serverId);
231
+ if (!srv) {
232
+ throw new Error(`No such server: ${serverId}`);
233
+ }
234
+ srv.pending.push(null);
235
+ return serverId;
236
+ }
237
+ async function handleHttpServerClose({ serverId }) {
238
+ const srv = servers.get(serverId);
239
+ if (!srv) {
240
+ throw new Error(`No such server: ${serverId}`);
241
+ }
242
+ await new Promise((resolve) => srv.server.close(resolve));
243
+ servers.delete(serverId);
244
+ return serverId;
245
+ }
246
+ async function sendRequestBody(req, body, trailers, resStarted, expectedLength) {
247
+ try {
248
+ req.flushHeaders();
249
+ await pipeline(validateRequestBody(Readable.fromWeb(body), expectedLength), req, {
250
+ end: false,
251
+ });
252
+ const fields = await recvTrailers(trailers);
253
+ if (fields) {
254
+ req.addTrailers(toObject(fields));
255
+ }
256
+ await endRequest(req);
257
+ }
258
+ catch (err) {
259
+ if (!resStarted()) {
260
+ req.destroy(err);
261
+ }
262
+ throw err;
263
+ }
264
+ }
265
+ async function* validateRequestBody(body, expectedLength) {
266
+ let bytes = 0n;
267
+ for await (const chunk of body) {
268
+ bytes += BigInt(chunk.byteLength);
269
+ if (expectedLength !== null && bytes > expectedLength) {
270
+ throw new HttpError("HTTP-request-body-size", undefined, bytes);
271
+ }
272
+ yield chunk;
273
+ }
274
+ if (expectedLength !== null && bytes < expectedLength) {
275
+ throw new HttpError("HTTP-request-body-size", undefined, bytes);
276
+ }
277
+ }
278
+ function endRequest(req) {
279
+ return new Promise((resolve, reject) => {
280
+ let settled = false;
281
+ const settle = (fn, value) => {
282
+ if (settled) {
283
+ return;
284
+ }
285
+ settled = true;
286
+ req.off("error", onError);
287
+ req.off("close", onClose);
288
+ fn(value);
289
+ };
290
+ const onError = (err) => settle(reject, err);
291
+ const onClose = () => settle(reject, new HttpError("connection-terminated"));
292
+ req.once("error", onError);
293
+ req.once("close", onClose);
294
+ try {
295
+ req.end(() => settle(resolve, undefined));
296
+ }
297
+ catch (err) {
298
+ settle(reject, err);
299
+ }
300
+ });
301
+ }
302
+ function transmitReporter(port) {
303
+ let settled = false;
304
+ const send = (msg) => {
305
+ if (settled || !port) {
306
+ return;
307
+ }
308
+ settled = true;
309
+ port.postMessage(msg);
310
+ port.close();
311
+ };
312
+ return {
313
+ ok() {
314
+ send({ val: undefined });
315
+ },
316
+ err(err) {
317
+ send({ err: HttpError.from(err).payload });
318
+ },
319
+ };
320
+ }
321
+ async function recvTrailers(port) {
322
+ if (!port) {
323
+ return null;
324
+ }
325
+ return new Promise((resolve, reject) => {
326
+ port.once("message", ({ val, err }) => {
327
+ port.close();
328
+ if (err) {
329
+ reject(err);
330
+ }
331
+ else {
332
+ resolve(val);
333
+ }
334
+ });
335
+ });
336
+ }
337
+ const msecs = (time) => {
338
+ return typeof time === "bigint" ? Number(time / 1000000n) : time / 1e6;
339
+ };
340
+ const decoder = new TextDecoder();
341
+ const contentLength = (entries) => {
342
+ const entry = entries.findLast(([name]) => name.toLowerCase() === "content-length");
343
+ if (!entry) {
344
+ return null;
345
+ }
346
+ const value = decoder.decode(entry[1]);
347
+ return /^\d+$/.test(value) ? BigInt(value) : null;
348
+ };
349
+ const toObject = (entries) => {
350
+ return Object.fromEntries(entries.map(([key, val]) => [key, decoder.decode(val)]));
351
+ };
352
+ const encoder = new TextEncoder();
353
+ const toEntries = (obj) => {
354
+ return Object.entries(obj).flatMap(([k, v]) => Array.isArray(v) ? v.map((val) => [k, encoder.encode(val)]) : [[k, encoder.encode(v)]]);
355
+ };
@@ -0,0 +1,13 @@
1
+ export declare class ResourceWorker {
2
+ #private;
3
+ constructor(workerUrl: any);
4
+ terminate(): void;
5
+ /** Async IPC */
6
+ run(msg: any, transferable?: any[]): Promise<unknown>;
7
+ /** Synchronous IPC */
8
+ runSync(msg: any, transferable?: any[]): any;
9
+ }
10
+ export declare function Router(): {
11
+ beforeAll(fn: any): /*elided*/ any;
12
+ op(name: any, fn: any): /*elided*/ any;
13
+ };
@@ -0,0 +1,152 @@
1
+ import { Worker, parentPort, isMainThread, MessageChannel, receiveMessageOnPort, } from "worker_threads";
2
+ export class ResourceWorker {
3
+ #worker = null;
4
+ #workerUrl = null;
5
+ #pending = new Map();
6
+ constructor(workerUrl) {
7
+ this.#workerUrl = workerUrl;
8
+ }
9
+ #getWorker() {
10
+ if (this.#worker) {
11
+ return this.#worker;
12
+ }
13
+ this.#worker = new Worker(this.#workerUrl);
14
+ this.#worker.unref();
15
+ return this.#worker;
16
+ }
17
+ terminate() {
18
+ if (!this.#worker) {
19
+ return;
20
+ }
21
+ this.#pending.clear();
22
+ this.#worker.removeAllListeners();
23
+ this.#worker.terminate();
24
+ this.#worker = null;
25
+ }
26
+ /** Async IPC */
27
+ run(msg, transferable = []) {
28
+ const worker = this.#getWorker();
29
+ const { port1, port2 } = new MessageChannel();
30
+ return new Promise((resolve, reject) => {
31
+ port1.once("message", ({ result, error }) => {
32
+ port1.close();
33
+ if (error) {
34
+ reject(deserializeError(error));
35
+ return;
36
+ }
37
+ resolve(result);
38
+ });
39
+ worker.postMessage({ ...msg, _reply: port2 }, [port2, ...transferable]);
40
+ });
41
+ }
42
+ /** Synchronous IPC */
43
+ runSync(msg, transferable = []) {
44
+ const worker = this.#getWorker();
45
+ const { port1: rx, port2: tx } = new MessageChannel();
46
+ const _condvar = new SharedArrayBuffer(4);
47
+ const lock = new Int32Array(_condvar);
48
+ worker.postMessage({ ...msg, _condvar, _reply: tx }, [tx, ...transferable]);
49
+ Atomics.wait(lock, 0, 0);
50
+ const { message } = receiveMessageOnPort(rx) || {};
51
+ rx.close();
52
+ if (!message) {
53
+ throw new Error("No response from worker");
54
+ }
55
+ const { result, error } = message;
56
+ if (error) {
57
+ throw deserializeError(error);
58
+ }
59
+ return result;
60
+ }
61
+ }
62
+ export function Router() {
63
+ const _hooks = {
64
+ beforeAll: null,
65
+ ops: new Map(),
66
+ };
67
+ if (isMainThread || !parentPort) {
68
+ throw new Error("Router can only be used in worker threads");
69
+ }
70
+ parentPort.on("message", async (msg) => {
71
+ const { _reply, _condvar, ...rest } = msg;
72
+ let result, error, transfer = [];
73
+ try {
74
+ if (_hooks.beforeAll) {
75
+ _hooks.beforeAll(rest);
76
+ }
77
+ const handler = _hooks.ops.get(rest.op);
78
+ if (!handler) {
79
+ throw new Error(`Unknown op ${rest.op}`);
80
+ }
81
+ const outcome = await handler(rest);
82
+ if (outcome && Array.isArray(outcome.transferable)) {
83
+ result = outcome.result;
84
+ transfer = outcome.transferable;
85
+ }
86
+ else {
87
+ result = outcome;
88
+ }
89
+ }
90
+ catch (err) {
91
+ error = serializeError(err);
92
+ }
93
+ _reply.postMessage({ result, error }, transfer);
94
+ _reply.close();
95
+ // If this is a synchronous operation, notify the main thread.
96
+ // This will wake up the waiting thread and read the message we
97
+ // posted on reply channel.
98
+ if (_condvar) {
99
+ notify(_condvar);
100
+ }
101
+ });
102
+ return {
103
+ beforeAll(fn) {
104
+ _hooks.beforeAll = fn;
105
+ return this;
106
+ },
107
+ op(name, fn) {
108
+ _hooks.ops.set(name, fn);
109
+ return this;
110
+ },
111
+ };
112
+ }
113
+ function serializeError(err) {
114
+ if (!(err instanceof Error)) {
115
+ return err;
116
+ }
117
+ const serialized = {
118
+ __resourceWorkerError: true,
119
+ name: err.name,
120
+ message: err.message,
121
+ stack: err.stack,
122
+ };
123
+ for (const key of Reflect.ownKeys(err)) {
124
+ serialized[key] = err[key];
125
+ }
126
+ if (err.cause !== undefined) {
127
+ serialized.cause = serializeError(err.cause);
128
+ }
129
+ return serialized;
130
+ }
131
+ function deserializeError(err) {
132
+ if (!err || typeof err !== "object" || err.__resourceWorkerError !== true) {
133
+ return err;
134
+ }
135
+ const error = new Error(err.message);
136
+ error.name = err.name;
137
+ if (err.stack) {
138
+ error.stack = err.stack;
139
+ }
140
+ for (const key of Reflect.ownKeys(err)) {
141
+ if (key === "__resourceWorkerError" || key === "name" || key === "message" || key === "stack") {
142
+ continue;
143
+ }
144
+ error[key] = key === "cause" ? deserializeError(err[key]) : err[key];
145
+ }
146
+ return error;
147
+ }
148
+ function notify(condvar) {
149
+ const lock = new Int32Array(condvar);
150
+ Atomics.store(lock, 0, 1);
151
+ Atomics.notify(lock, 0, 1);
152
+ }
@@ -0,0 +1,2 @@
1
+ export declare function getDefaultSendBufferSize(): Promise<any>;
2
+ export declare function getDefaultReceiveBufferSize(): Promise<any>;