@bytecodealliance/preview2-shim 0.14.0 → 0.14.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.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  WASI Preview2 implementations for Node.js & browsers.
4
4
 
5
- Browser support is considered experimental, and not currently suitable for production applications.
5
+ Node.js support is fully tested and conformant against the Wasmtime test suite.
6
6
 
7
- Node.js support is currently being stabilized, which can be tracked in https://github.com/bytecodealliance/jco/milestone/1.
7
+ Browser support is considered experimental, and not currently suitable for production applications.
8
8
 
9
9
  # License
10
10
 
@@ -39,12 +39,13 @@ function getChildEntry (parentEntry, subpath, openFlags) {
39
39
  if (!entry || !entry.dir) throw 'not-directory';
40
40
  segmentIdx = subpath.indexOf('/');
41
41
  const segment = segmentIdx === -1 ? subpath : subpath.slice(0, segmentIdx);
42
- if (segment === '.' || segment === '') return entry;
43
42
  if (segment === '..') throw 'no-entry';
44
- if (!entry.dir[segment] && openFlags.create)
43
+ if (segment === '.' || segment === '');
44
+ else if (!entry.dir[segment] && openFlags.create)
45
45
  entry = entry.dir[segment] = openFlags.directory ? { dir: {} } : { source: new Uint8Array([]) };
46
46
  else
47
47
  entry = entry.dir[segment];
48
+ subpath = subpath.slice(segmentIdx + 1);
48
49
  } while (segmentIdx !== -1)
49
50
  if (!entry) throw 'no-entry';
50
51
  return entry;
@@ -179,13 +180,13 @@ class Descriptor {
179
180
  stat() {
180
181
  let type = 'unknown', size = BigInt(0);
181
182
  if (this.#entry.source) {
182
- type = 'directory';
183
- }
184
- else if (this.#entry.dir) {
185
183
  type = 'regular-file';
186
184
  const source = getSource(this.#entry);
187
185
  size = BigInt(source.byteLength);
188
186
  }
187
+ else if (this.#entry.dir) {
188
+ type = 'directory';
189
+ }
189
190
  return {
190
191
  type,
191
192
  linkCount: BigInt(0),
@@ -1,5 +1,3 @@
1
- import { UnexpectedError } from "../http/error.js";
2
-
3
1
  /**
4
2
  * @param {import("../../types/interfaces/wasi-http-types").Request} req
5
3
  * @returns {string}
@@ -30,7 +28,7 @@ export function send(req) {
30
28
  body,
31
29
  };
32
30
  } catch (err) {
33
- throw new UnexpectedError(err.message);
31
+ throw new Error(err.message);
34
32
  }
35
33
  }
36
34
 
@@ -33,7 +33,7 @@ export const random = {
33
33
  for (var generated = 0; generated < len; generated += MAX_BYTES) {
34
34
  // buffer.slice automatically checks if the end is past the end of
35
35
  // the buffer so we don't have to here
36
- crypto.getRandomValues(bytes.slice(generated, generated + MAX_BYTES));
36
+ crypto.getRandomValues(bytes.subarray(generated, generated + MAX_BYTES));
37
37
  }
38
38
  } else {
39
39
  crypto.getRandomValues(bytes);
package/lib/io/calls.js CHANGED
@@ -43,13 +43,47 @@ export const FUTURE_DISPOSE = ++call_id << CALL_SHIFT;
43
43
  // Http
44
44
  export const HTTP_CREATE_REQUEST = ++call_id << 24;
45
45
  export const HTTP_OUTPUT_STREAM_FINISH = ++call_id << CALL_SHIFT;
46
+ // Http server
47
+ export const HTTP_SERVER_START = ++call_id << CALL_SHIFT;
48
+ export const HTTP_SERVER_STOP = ++call_id << CALL_SHIFT;
49
+ export const HTTP_SERVER_INCOMING_HANDLER = ++call_id << CALL_SHIFT;
50
+ export const HTTP_SERVER_SET_OUTGOING_RESPONSE = ++call_id << CALL_SHIFT;
51
+ export const HTTP_SERVER_CLEAR_OUTGOING_RESPONSE = ++call_id << CALL_SHIFT;
46
52
 
47
53
  // Clocks
48
54
  export const CLOCKS_NOW = ++call_id << 24;
49
55
  export const CLOCKS_DURATION_SUBSCRIBE = ++call_id << 24;
50
56
  export const CLOCKS_INSTANT_SUBSCRIBE = ++call_id << 24;
51
57
 
52
- // Sockets
58
+ // Sockets (TCP)
59
+ export const SOCKET_TCP_CREATE_HANDLE = ++call_id << 24;
60
+ export const SOCKET_TCP_BIND = ++call_id << 24;
61
+ export const SOCKET_TCP_CONNECT = ++call_id << 24;
62
+ export const SOCKET_TCP_LISTEN = ++call_id << 24;
63
+ export const SOCKET_TCP_ACCEPT = ++call_id << 24;
64
+ export const SOCKET_TCP_GET_LOCAL_ADDRESS = ++call_id << 24;
65
+ export const SOCKET_TCP_GET_REMOTE_ADDRESS = ++call_id << 24;
66
+ export const SOCKET_TCP_SHUTDOWN = ++call_id << 24;
67
+ export const SOCKET_TCP_SET_KEEP_ALIVE = ++call_id << 24;
68
+ export const SOCKET_TCP_DISPOSE = ++call_id << 24;
69
+ export const SOCKET_TCP_CREATE_INPUT_STREAM = ++call_id << 24;
70
+ export const SOCKET_TCP_CREATE_OUTPUT_STREAM = ++call_id << 24;
71
+ // Sockets (UDP)
72
+ export const SOCKET_UDP_CREATE_HANDLE = ++call_id << 24;
73
+ export const SOCKET_UDP_BIND = ++call_id << 24;
74
+ export const SOCKET_UDP_CONNECT = ++call_id << 24;
75
+ export const SOCKET_UDP_DISCONNECT = ++call_id << 24;
76
+ export const SOCKET_UDP_CHECK_SEND = ++call_id << 24;
77
+ export const SOCKET_UDP_SEND = ++call_id << 24;
78
+ export const SOCKET_UDP_RECEIVE = ++call_id << 24;
79
+ export const SOCKET_UDP_DISPOSE = ++call_id << 24;
80
+ export const SOCKET_UDP_GET_LOCAL_ADDRESS = ++call_id << 24;
81
+ export const SOCKET_UDP_GET_REMOTE_ADDRESS = ++call_id << 24;
82
+ export const SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE = ++call_id << 24;
83
+ export const SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE = ++call_id << 24;
84
+ export const SOCKET_UDP_GET_SEND_BUFFER_SIZE = ++call_id << 24;
85
+ export const SOCKET_UDP_SET_SEND_BUFFER_SIZE = ++call_id << 24;
86
+ export const SOCKET_UDP_SET_UNICAST_HOP_LIMIT = ++call_id << 24;
53
87
  export const SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST = ++call_id << 24;
54
88
  export const SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST = ++call_id << 24;
55
89
  export const SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST = ++call_id << 24;
@@ -62,3 +96,96 @@ export const STDERR = ++cnt;
62
96
  export const FILE = ++cnt;
63
97
  export const HTTP = ++cnt;
64
98
  export const SOCKET = ++cnt;
99
+
100
+ export const callTypeMap = {
101
+ [STDIN]: "STDIN",
102
+ [STDOUT]: "STDOUT",
103
+ [STDERR]: "STDERR",
104
+ [FILE]: "FILE",
105
+ [HTTP]: "HTTP",
106
+ [SOCKET]: "SOCKET",
107
+ };
108
+
109
+ export const callMap = {
110
+ [INPUT_STREAM_CREATE]: 'INPUT_STREAM_CREATE',
111
+ [INPUT_STREAM_READ]: "INPUT_STREAM_READ",
112
+ [INPUT_STREAM_BLOCKING_READ]: "INPUT_STREAM_BLOCKING_READ",
113
+ [INPUT_STREAM_SKIP]: "INPUT_STREAM_SKIP",
114
+ [INPUT_STREAM_BLOCKING_SKIP]: "INPUT_STREAM_BLOCKING_SKIP",
115
+ [INPUT_STREAM_SUBSCRIBE]: "INPUT_STREAM_SUBSCRIBE",
116
+ [INPUT_STREAM_DISPOSE]: "INPUT_STREAM_DISPOSE",
117
+
118
+ // Io Output Stream
119
+ [OUTPUT_STREAM_CREATE]: "OUTPUT_STREAM_CREATE",
120
+ [OUTPUT_STREAM_CHECK_WRITE]: "OUTPUT_STREAM_CHECK_WRITE",
121
+ [OUTPUT_STREAM_WRITE]: "OUTPUT_STREAM_WRITE",
122
+ [OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH]: "OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH",
123
+ [OUTPUT_STREAM_FLUSH]: "OUTPUT_STREAM_FLUSH",
124
+ [OUTPUT_STREAM_BLOCKING_FLUSH]: "OUTPUT_STREAM_BLOCKING_FLUSH",
125
+ [OUTPUT_STREAM_WRITE_ZEROES]: "OUTPUT_STREAM_WRITE_ZEROES",
126
+ [OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH]: "OUTPUT_STREAM_BLOCKING_WRITE_ZEROES_AND_FLUSH",
127
+ [OUTPUT_STREAM_SPLICE]: "OUTPUT_STREAM_SPLICE",
128
+ [OUTPUT_STREAM_BLOCKING_SPLICE]: "OUTPUT_STREAM_BLOCKING_SPLICE",
129
+ [OUTPUT_STREAM_SUBSCRIBE]: "OUTPUT_STREAM_SUBSCRIBE",
130
+ [OUTPUT_STREAM_DISPOSE]: "OUTPUT_STREAM_DISPOSE",
131
+
132
+ [OUTPUT_STREAM_GET_TOTAL_BYTES]: "OUTPUT_STREAM_GET_TOTAL_BYTES",
133
+
134
+ // Io Poll
135
+ [POLL_POLLABLE_READY]: "POLL_POLLABLE_READY",
136
+ [POLL_POLLABLE_BLOCK]: "POLL_POLLABLE_BLOCK",
137
+ [POLL_POLL_LIST]: "POLL_POLL_LIST",
138
+
139
+ // Futures
140
+ [FUTURE_GET_VALUE_AND_DISPOSE]: "FUTURE_GET_VALUE_AND_DISPOSE",
141
+ [FUTURE_DISPOSE]: "FUTURE_DISPOSE",
142
+
143
+ // Http
144
+ [HTTP_CREATE_REQUEST]: "HTTP_CREATE_REQUEST",
145
+ [HTTP_OUTPUT_STREAM_FINISH]: "HTTP_OUTPUT_STREAM_FINISH",
146
+ // Http server
147
+ [HTTP_SERVER_START]: "HTTP_SERVER_START",
148
+ [HTTP_SERVER_STOP]: "HTTP_SERVER_STOP",
149
+ [HTTP_SERVER_INCOMING_HANDLER]: "HTTP_SERVER_INCOMING_HANDLER",
150
+ [HTTP_SERVER_SET_OUTGOING_RESPONSE]: "HTTP_SERVER_SET_OUTGOING_RESPONSE",
151
+ [HTTP_SERVER_CLEAR_OUTGOING_RESPONSE]: "HTTP_SERVER_CLEAR_OUTGOING_RESPONSE",
152
+
153
+ // Clocks
154
+ [CLOCKS_NOW]: "CLOCKS_NOW",
155
+ [CLOCKS_DURATION_SUBSCRIBE]: "CLOCKS_DURATION_SUBSCRIBE",
156
+ [CLOCKS_INSTANT_SUBSCRIBE]: "CLOCKS_INSTANT_SUBSCRIBE",
157
+
158
+ // Sockets TCP
159
+ [SOCKET_TCP_CREATE_HANDLE]: "SOCKET_TCP_CREATE_HANDLE",
160
+ [SOCKET_TCP_BIND]: "SOCKET_TCP_BIND",
161
+ [SOCKET_TCP_CONNECT]: "SOCKET_TCP_CONNECT",
162
+ [SOCKET_TCP_LISTEN]: "SOCKET_TCP_LISTEN",
163
+ [SOCKET_TCP_ACCEPT]: "SOCKET_TCP_ACCEPT",
164
+ [SOCKET_TCP_GET_LOCAL_ADDRESS]: "SOCKET_TCP_GET_LOCAL_ADDRESS",
165
+ [SOCKET_TCP_GET_REMOTE_ADDRESS]: "SOCKET_TCP_GET_REMOTE_ADDRESS",
166
+ [SOCKET_TCP_SHUTDOWN]: "SOCKET_TCP_SHUTDOWN",
167
+ [SOCKET_TCP_SET_KEEP_ALIVE]: "SOCKET_TCP_SET_KEEP_ALIVE",
168
+ [SOCKET_TCP_DISPOSE]: "SOCKET_TCP_DISPOSE",
169
+ [SOCKET_TCP_CREATE_INPUT_STREAM]: "SOCKET_TCP_CREATE_INPUT_STREAM",
170
+ [SOCKET_TCP_CREATE_OUTPUT_STREAM]: "SOCKET_TCP_CREATE_OUTPUT_STREAM",
171
+ // Sockets UDP
172
+ [SOCKET_UDP_CREATE_HANDLE]: "SOCKET_UDP_CREATE_HANDLE",
173
+ [SOCKET_UDP_BIND]: "SOCKET_UDP_BIND",
174
+ [SOCKET_UDP_CONNECT]: "SOCKET_UDP_CONNECT",
175
+ [SOCKET_UDP_DISCONNECT]: "SOCKET_UDP_DISCONNECT",
176
+ [SOCKET_UDP_CHECK_SEND]: "SOCKET_UDP_CHECK_SEND",
177
+ [SOCKET_UDP_SEND]: "SOCKET_UDP_SEND",
178
+ [SOCKET_UDP_RECEIVE]: "SOCKET_UDP_RECEIVE",
179
+ [SOCKET_UDP_DISPOSE]: "SOCKET_UDP_DISPOSE",
180
+ [SOCKET_UDP_GET_LOCAL_ADDRESS]: "SOCKET_UDP_GET_LOCAL_ADDRESS",
181
+ [SOCKET_UDP_GET_REMOTE_ADDRESS]: "SOCKET_UDP_GET_REMOTE_ADDRESS",
182
+ [SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE]: "SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE",
183
+ [SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE]: "SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE",
184
+ [SOCKET_UDP_GET_SEND_BUFFER_SIZE]: "SOCKET_UDP_GET_SEND_BUFFER_SIZE",
185
+ [SOCKET_UDP_SET_SEND_BUFFER_SIZE]: "SOCKET_UDP_SET_SEND_BUFFER_SIZE",
186
+ [SOCKET_UDP_SET_UNICAST_HOP_LIMIT]: "SOCKET_UDP_SET_UNICAST_HOP_LIMIT",
187
+ // Socket DNS
188
+ [SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST]: "SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST",
189
+ [SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST]: "SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST",
190
+ [SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST]: "SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST",
191
+ };
@@ -1,14 +1,91 @@
1
- import { Readable } from "node:stream";
2
1
  import { createStream, getStreamOrThrow } from "./worker-thread.js";
2
+ import {
3
+ createServer,
4
+ request as httpRequest,
5
+ Agent as HttpAgent,
6
+ } from "node:http";
7
+ import { request as httpsRequest, Agent as HttpsAgent } from "node:https";
8
+ import { parentPort } from "node:worker_threads";
9
+ import { HTTP_SERVER_INCOMING_HANDLER } from "./calls.js";
3
10
 
4
- export async function createHttpRequest(method, url, headers, bodyId) {
5
- let body = null;
11
+ const agentOptions = {
12
+ keepAlive: true,
13
+ };
14
+ const httpAgent = new HttpAgent(agentOptions);
15
+ const httpsAgent = new HttpsAgent(agentOptions);
16
+
17
+ const servers = new Map();
18
+
19
+ let responseCnt = 0;
20
+ const responses = new Map();
21
+
22
+ export async function stopHttpServer(id) {
23
+ await new Promise((resolve) => servers.get(id).close(resolve));
24
+ }
25
+
26
+ export function clearOutgoingResponse(id) {
27
+ responses.delete(id);
28
+ }
29
+
30
+ export async function setOutgoingResponse(
31
+ id,
32
+ { statusCode, headers, streamId }
33
+ ) {
34
+ const response = responses.get(id);
35
+ const textDecoder = new TextDecoder();
36
+ response.writeHead(
37
+ statusCode,
38
+ Object.fromEntries(
39
+ headers.map(([key, val]) => [key, textDecoder.decode(val)])
40
+ )
41
+ );
42
+ response.flushHeaders();
43
+ const { stream } = getStreamOrThrow(streamId);
44
+ stream.pipe(response);
45
+ responses.delete(id);
46
+ }
47
+
48
+ export async function startHttpServer(id, { port, host }) {
49
+ const server = createServer((req, res) => {
50
+ // create the streams and their ids
51
+ const streamId = createStream(req);
52
+ const responseId = ++responseCnt;
53
+ parentPort.postMessage({
54
+ type: HTTP_SERVER_INCOMING_HANDLER,
55
+ id,
56
+ payload: {
57
+ responseId,
58
+ method: req.method,
59
+ host: req.headers.host || host || 'localhost',
60
+ pathWithQuery: req.url,
61
+ headers: Object.entries(req.headersDistinct).flatMap(([key, val]) => val.map(val => [key, val])),
62
+ streamId,
63
+ },
64
+ });
65
+ responses.set(responseId, res);
66
+ });
67
+ await new Promise((resolve, reject) => {
68
+ server.listen(port, host, resolve);
69
+ server.on("error", reject);
70
+ });
71
+ servers.set(id, server);
72
+ }
73
+
74
+ export async function createHttpRequest(
75
+ method,
76
+ scheme,
77
+ authority,
78
+ pathWithQuery,
79
+ headers,
80
+ bodyId,
81
+ connectTimeout,
82
+ betweenBytesTimeout,
83
+ firstByteTimeout
84
+ ) {
85
+ let stream = null;
6
86
  if (bodyId) {
7
87
  try {
8
- const { stream } = getStreamOrThrow(bodyId);
9
- body = stream.readableBodyStream;
10
- // this indicates we're attached
11
- stream.readableBodyStream = null;
88
+ ({ stream } = getStreamOrThrow(bodyId));
12
89
  } catch (e) {
13
90
  if (e.tag === "closed")
14
91
  throw { tag: "internal-error", val: "Unexpected closed body stream" };
@@ -25,71 +102,88 @@ export async function createHttpRequest(method, url, headers, bodyId) {
25
102
  }
26
103
  }
27
104
  try {
28
- const res = await fetch(url, {
29
- method,
30
- headers: new Headers(headers),
31
- body,
32
- redirect: "manual",
33
- duplex: "half",
105
+ // Make a request
106
+ let req;
107
+ switch (scheme) {
108
+ case "http:":
109
+ req = httpRequest({
110
+ agent: httpAgent,
111
+ method,
112
+ host: authority.split(':')[0],
113
+ port: authority.split(':')[1],
114
+ path: pathWithQuery,
115
+ timeout: connectTimeout && Number(connectTimeout),
116
+ });
117
+ break;
118
+ case "https:":
119
+ req = httpsRequest({
120
+ agent: httpsAgent,
121
+ method,
122
+ host: authority.split(':')[0],
123
+ port: authority.split(':')[1],
124
+ path: pathWithQuery,
125
+ timeout: connectTimeout && Number(connectTimeout),
126
+ });
127
+ break;
128
+ default:
129
+ throw { tag: "HTTP-protocol-error" };
130
+ }
131
+ for (const [key, value] of headers) {
132
+ req.appendHeader(key, value);
133
+ }
134
+ req.flushHeaders();
135
+ if (stream) {
136
+ stream.pipe(req);
137
+ } else {
138
+ req.end();
139
+ }
140
+ const res = await new Promise((resolve, reject) => {
141
+ req.on("response", resolve);
142
+ req.on("close", () => reject);
143
+ req.on("error", reject);
34
144
  });
35
- const bodyStreamId = createStream(Readable.fromWeb(res.body));
145
+ if (firstByteTimeout)
146
+ res.setTimeout(Number(firstByteTimeout));
147
+ if (betweenBytesTimeout)
148
+ res.on("readable", () => {
149
+ res.setTimeout(Number(betweenBytesTimeout));
150
+ });
151
+ res.on("end", () => void res.emit("readable"));
152
+ const bodyStreamId = createStream(res);
36
153
  return {
37
- status: res.status,
38
- headers: Array.from(res.headers),
154
+ status: res.statusCode,
155
+ headers: Array.from(Object.entries(res.headers)),
39
156
  bodyStreamId: bodyStreamId,
40
157
  };
41
158
  } catch (e) {
42
- if (e?.cause) {
43
- let err = e.cause;
44
- if (e.cause instanceof AggregateError) err = e.cause.errors[0];
45
- if (err.message === "unknown scheme")
159
+ if (e?.tag) throw e;
160
+ const err = getFirstError(e);
161
+ switch (err.code) {
162
+ case "ECONNRESET":
163
+ throw { tag: "HTTP-protocol-error" };
164
+ case "ENOTFOUND":
46
165
  throw {
47
- tag: "HTTP-protocol-error",
166
+ tag: "DNS-error",
167
+ val: {
168
+ rcode: err.code,
169
+ infoCode: err.errno,
170
+ },
171
+ };
172
+ case "ECONNREFUSED":
173
+ throw {
174
+ tag: "connection-refused",
48
175
  };
49
- switch (err.syscall) {
50
- case "connect": {
51
- if (err.code === "ECONNREFUSED")
52
- throw {
53
- tag: "connection-refused",
54
- };
55
- break;
56
- }
57
- case "getaddrinfo": {
58
- const { errno, code } = err;
59
- throw {
60
- tag: "DNS-error",
61
- val: {
62
- rcode: code,
63
- infoCode: errno,
64
- },
65
- };
66
- }
67
- }
68
- }
69
- if (e?.message?.includes("Failed to parse URL")) {
70
- throw {
71
- tag: "HTTP-request-URI-invalid",
72
- val: undefined,
73
- };
74
- }
75
- if (e?.message?.includes("HTTP")) {
76
- switch (e?.message.replace(/'[^']+'/, "'{}'")) {
77
- case "'{}' HTTP method is unsupported.":
78
- throw {
79
- tag: "HTTP-protocol-error",
80
- val: undefined,
81
- };
82
- case "'{}' is not a valid HTTP method.":
83
- throw {
84
- tag: "HTTP-request-method-invalid",
85
- val: undefined,
86
- };
87
- }
88
- throw {
89
- tag: "internal-error",
90
- val: e.toString(),
91
- };
92
176
  }
93
- throw e;
177
+ throw {
178
+ tag: "internal-error",
179
+ val: err.toString(),
180
+ };
94
181
  }
95
182
  }
183
+
184
+ function getFirstError(e) {
185
+ if (typeof e !== "object" || e === null) return e;
186
+ if (e.cause) return getFirstError(e.cause);
187
+ if (e instanceof AggregateError) return getFirstError(e.errors[0]);
188
+ return e;
189
+ }
@@ -2,7 +2,6 @@ import { fileURLToPath } from "node:url";
2
2
  import { createSyncFn } from "../synckit/index.js";
3
3
  import {
4
4
  CALL_MASK,
5
- CALL_SHIFT,
6
5
  CALL_TYPE_MASK,
7
6
  INPUT_STREAM_BLOCKING_READ,
8
7
  INPUT_STREAM_BLOCKING_SKIP,
@@ -24,6 +23,9 @@ import {
24
23
  POLL_POLL_LIST,
25
24
  POLL_POLLABLE_BLOCK,
26
25
  POLL_POLLABLE_READY,
26
+ HTTP_SERVER_INCOMING_HANDLER,
27
+ callTypeMap,
28
+ callMap
27
29
  } from "./calls.js";
28
30
  import { STDERR } from "./calls.js";
29
31
 
@@ -33,18 +35,37 @@ const workerPath = fileURLToPath(
33
35
  new URL("./worker-thread.js", import.meta.url)
34
36
  );
35
37
 
38
+ const httpIncomingHandlers = new Map();
39
+ export function registerIncomingHttpHandler (id, handler) {
40
+ httpIncomingHandlers.set(id, handler);
41
+ }
42
+
43
+ const instanceId = Math.round(Math.random() * 1000).toString();
44
+
36
45
  /**
37
46
  * @type {(call: number, id: number | null, payload: any) -> any}
38
47
  */
39
- export let ioCall = createSyncFn(workerPath);
48
+ export let ioCall = createSyncFn(workerPath, (type, id, payload) => {
49
+ // 'callbacks' from the worker
50
+ // ONLY happens for an http server incoming handler, and NOTHING else (not even sockets, since accept is sync!)
51
+ if (type !== HTTP_SERVER_INCOMING_HANDLER)
52
+ throw new Error('Internal error: only incoming handler callback is permitted');
53
+ const handler = httpIncomingHandlers.get(id);
54
+ if (!handler)
55
+ throw new Error(`Internal error: no incoming handler registered for server ${id}`);
56
+ handler(payload);
57
+ });
40
58
  if (DEBUG) {
41
59
  const _ioCall = ioCall;
42
60
  ioCall = function ioCall(num, id, payload) {
61
+ if (typeof id !== 'number' && id !== null)
62
+ throw new Error('id must be a number or null');
43
63
  let ret;
44
64
  try {
45
- process._rawDebug(
46
- (num & CALL_MASK) >> CALL_SHIFT,
47
- num & CALL_TYPE_MASK,
65
+ console.error(
66
+ instanceId,
67
+ callMap[(num & CALL_MASK)],
68
+ callTypeMap[num & CALL_TYPE_MASK],
48
69
  id,
49
70
  payload
50
71
  );
@@ -54,7 +75,7 @@ if (DEBUG) {
54
75
  ret = e;
55
76
  throw ret;
56
77
  } finally {
57
- process._rawDebug("->", ret);
78
+ console.error(instanceId, "->", ret);
58
79
  }
59
80
  };
60
81
  }
@@ -63,6 +84,10 @@ const symbolDispose = Symbol.dispose || Symbol.for("dispose");
63
84
 
64
85
  const _Error = Error;
65
86
  const IoError = class Error extends _Error {
87
+ constructor (payload) {
88
+ super(payload);
89
+ this.payload = payload;
90
+ }
66
91
  toDebugString() {
67
92
  return this.message;
68
93
  }
@@ -72,10 +97,9 @@ function streamIoErrorCall(call, id, payload) {
72
97
  try {
73
98
  return ioCall(call, id, payload);
74
99
  } catch (e) {
75
- if (e.tag === 'closed')
76
- throw e;
100
+ if (e.tag === "closed") throw e;
77
101
  if (e.tag === "last-operation-failed") {
78
- e.val = new IoError(e.val);
102
+ e.val = new IoError(Object.assign(new Error(e.val.message), e.val));
79
103
  throw e;
80
104
  }
81
105
  // any invalid error is a trap
@@ -206,16 +230,14 @@ class OutputStream {
206
230
  return streamIoErrorCall(
207
231
  OUTPUT_STREAM_SPLICE | this.#streamType,
208
232
  this.#id,
209
- src.#id,
210
- len
233
+ { src: src.#id, len }
211
234
  );
212
235
  }
213
236
  blockingSplice(src, len) {
214
237
  return streamIoErrorCall(
215
238
  OUTPUT_STREAM_BLOCKING_SPLICE | this.#streamType,
216
239
  this.#id,
217
- src.#id,
218
- len
240
+ { src: inputStreamId(src), len }
219
241
  );
220
242
  }
221
243
  subscribe() {
@@ -254,21 +276,16 @@ export const streams = { InputStream, OutputStream };
254
276
 
255
277
  class Pollable {
256
278
  #id;
257
- #ready = false;
258
279
  get _id() {
259
280
  return this.#id;
260
281
  }
261
282
  ready() {
262
- if (this.#ready) return true;
263
- const ready = ioCall(POLL_POLLABLE_READY, this.#id);
264
- if (ready) this.#ready = true;
265
- return ready;
283
+ if (this.#id === 0) return true;
284
+ return ioCall(POLL_POLLABLE_READY, this.#id);
266
285
  }
267
286
  block() {
268
- if (!this.#ready) {
269
- ioCall(POLL_POLLABLE_BLOCK, this.#id);
270
- this.#ready = true;
271
- }
287
+ if (this.#id === 0) return;
288
+ ioCall(POLL_POLLABLE_BLOCK, this.#id);
272
289
  }
273
290
  static _getId(pollable) {
274
291
  return pollable.#id;
@@ -276,40 +293,20 @@ class Pollable {
276
293
  static _create(id) {
277
294
  const pollable = new Pollable();
278
295
  pollable.#id = id;
279
- if (id === 0) pollable.#ready = true;
280
296
  return pollable;
281
297
  }
282
- static _listToIds(list) {
283
- return list.map((pollable) => pollable.#id);
284
- }
285
- static _markReady(pollable) {
286
- pollable.#ready = true;
287
- }
288
298
  }
289
299
 
290
300
  export const pollableCreate = Pollable._create;
291
301
  delete Pollable._create;
292
302
 
293
- const pollableListToIds = Pollable._listToIds;
294
- delete Pollable._listToIds;
295
-
296
- const pollableMarkReady = Pollable._markReady;
297
- delete Pollable._markReady;
298
-
299
303
  const pollableGetId = Pollable._getId;
300
304
  delete Pollable._getId;
301
305
 
302
306
  export const poll = {
303
307
  Pollable,
304
308
  poll(list) {
305
- const includeList = ioCall(POLL_POLL_LIST, null, pollableListToIds(list));
306
- return list.filter((pollable) => {
307
- if (includeList.includes(pollableGetId(pollable))) {
308
- pollableMarkReady(pollable);
309
- return true;
310
- }
311
- return false;
312
- });
309
+ return ioCall(POLL_POLL_LIST, null, list.map(pollableGetId));
313
310
  },
314
311
  };
315
312