@bytecodealliance/preview2-shim 0.17.2 → 0.17.3

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.
@@ -1,176 +1,191 @@
1
1
  import {
2
- FUTURE_DISPOSE,
3
- FUTURE_SUBSCRIBE,
4
- FUTURE_TAKE_VALUE,
5
- HTTP_CREATE_REQUEST,
6
- HTTP_OUTGOING_BODY_DISPOSE,
7
- HTTP_OUTPUT_STREAM_FINISH,
8
- HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
9
- HTTP_SERVER_SET_OUTGOING_RESPONSE,
10
- HTTP_SERVER_START,
11
- HTTP_SERVER_STOP,
12
- OUTPUT_STREAM_CREATE,
13
- OUTPUT_STREAM_DISPOSE,
14
- } from "../io/calls.js";
2
+ FUTURE_DISPOSE,
3
+ FUTURE_SUBSCRIBE,
4
+ FUTURE_TAKE_VALUE,
5
+ HTTP_CREATE_REQUEST,
6
+ HTTP_OUTGOING_BODY_DISPOSE,
7
+ HTTP_OUTPUT_STREAM_FINISH,
8
+ HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
9
+ HTTP_SERVER_SET_OUTGOING_RESPONSE,
10
+ HTTP_SERVER_START,
11
+ HTTP_SERVER_STOP,
12
+ OUTPUT_STREAM_CREATE,
13
+ OUTPUT_STREAM_DISPOSE,
14
+ } from '../io/calls.js';
15
15
  import {
16
- earlyDispose,
17
- inputStreamCreate,
18
- ioCall,
19
- outputStreamCreate,
20
- pollableCreate,
21
- registerDispose,
22
- registerIncomingHttpHandler,
23
- } from "../io/worker-io.js";
24
- import { HTTP } from "../io/calls.js";
25
-
26
- import * as http from "node:http";
16
+ earlyDispose,
17
+ inputStreamCreate,
18
+ ioCall,
19
+ outputStreamCreate,
20
+ pollableCreate,
21
+ registerDispose,
22
+ registerIncomingHttpHandler,
23
+ } from '../io/worker-io.js';
24
+ import { HTTP } from '../io/calls.js';
25
+
26
+ import * as http from 'node:http';
27
27
  const { validateHeaderName = () => {}, validateHeaderValue = () => {} } = http;
28
28
 
29
- const symbolDispose = Symbol.dispose || Symbol.for("dispose");
30
- export const _forbiddenHeaders = new Set(["connection", "keep-alive", "host"]);
29
+ const symbolDispose = Symbol.dispose || Symbol.for('dispose');
30
+ export const _forbiddenHeaders = new Set(['connection', 'keep-alive', 'host']);
31
31
 
32
32
  class IncomingBody {
33
- #finished = false;
34
- #stream = undefined;
35
- stream() {
36
- if (!this.#stream) throw undefined;
37
- const stream = this.#stream;
38
- this.#stream = null;
39
- return stream;
40
- }
41
- static finish(incomingBody) {
42
- if (incomingBody.#finished)
43
- throw new Error("incoming body already finished");
44
- incomingBody.#finished = true;
45
- return futureTrailersCreate();
46
- }
47
- [symbolDispose]() {}
48
- static _create(streamId) {
49
- const incomingBody = new IncomingBody();
50
- incomingBody.#stream = inputStreamCreate(HTTP, streamId);
51
- return incomingBody;
52
- }
33
+ #finished = false;
34
+ #stream = undefined;
35
+ stream() {
36
+ if (!this.#stream) {
37
+ throw undefined;
38
+ }
39
+ const stream = this.#stream;
40
+ this.#stream = null;
41
+ return stream;
42
+ }
43
+ static finish(incomingBody) {
44
+ if (incomingBody.#finished) {
45
+ throw new Error('incoming body already finished');
46
+ }
47
+ incomingBody.#finished = true;
48
+ return futureTrailersCreate();
49
+ }
50
+ [symbolDispose]() {}
51
+ static _create(streamId) {
52
+ const incomingBody = new IncomingBody();
53
+ incomingBody.#stream = inputStreamCreate(HTTP, streamId);
54
+ return incomingBody;
55
+ }
53
56
  }
54
57
  const incomingBodyCreate = IncomingBody._create;
55
58
  delete IncomingBody._create;
56
59
 
57
60
  class IncomingRequest {
58
- #method;
59
- #pathWithQuery;
60
- #scheme;
61
- #authority;
62
- #headers;
63
- #streamId;
64
- method() {
65
- return this.#method;
66
- }
67
- pathWithQuery() {
68
- return this.#pathWithQuery;
69
- }
70
- scheme() {
71
- return this.#scheme;
72
- }
73
- authority() {
74
- return this.#authority;
75
- }
76
- headers() {
77
- return this.#headers;
78
- }
79
- consume() {
80
- return incomingBodyCreate(this.#streamId);
81
- }
82
- [symbolDispose]() {}
83
- static _create(method, pathWithQuery, scheme, authority, headers, streamId) {
84
- const incomingRequest = new IncomingRequest();
85
- incomingRequest.#method = method;
86
- incomingRequest.#pathWithQuery = pathWithQuery;
87
- incomingRequest.#scheme = scheme;
88
- incomingRequest.#authority = authority;
89
- incomingRequest.#headers = headers;
90
- incomingRequest.#streamId = streamId;
91
- return incomingRequest;
92
- }
61
+ #method;
62
+ #pathWithQuery;
63
+ #scheme;
64
+ #authority;
65
+ #headers;
66
+ #streamId;
67
+ method() {
68
+ return this.#method;
69
+ }
70
+ pathWithQuery() {
71
+ return this.#pathWithQuery;
72
+ }
73
+ scheme() {
74
+ return this.#scheme;
75
+ }
76
+ authority() {
77
+ return this.#authority;
78
+ }
79
+ headers() {
80
+ return this.#headers;
81
+ }
82
+ consume() {
83
+ return incomingBodyCreate(this.#streamId);
84
+ }
85
+ [symbolDispose]() {}
86
+ static _create(
87
+ method,
88
+ pathWithQuery,
89
+ scheme,
90
+ authority,
91
+ headers,
92
+ streamId
93
+ ) {
94
+ const incomingRequest = new IncomingRequest();
95
+ incomingRequest.#method = method;
96
+ incomingRequest.#pathWithQuery = pathWithQuery;
97
+ incomingRequest.#scheme = scheme;
98
+ incomingRequest.#authority = authority;
99
+ incomingRequest.#headers = headers;
100
+ incomingRequest.#streamId = streamId;
101
+ return incomingRequest;
102
+ }
93
103
  }
94
104
  const incomingRequestCreate = IncomingRequest._create;
95
105
  delete IncomingRequest._create;
96
106
 
97
107
  class FutureTrailers {
98
- #requested = false;
99
- subscribe() {
100
- return pollableCreate(0, this);
101
- }
102
- get() {
103
- if (this.#requested) return { tag: "err" };
104
- this.#requested = true;
105
- return {
106
- tag: "ok",
107
- val: {
108
- tag: "ok",
109
- val: undefined,
110
- },
111
- };
112
- }
113
- static _create() {
114
- const res = new FutureTrailers();
115
- return res;
116
- }
108
+ #requested = false;
109
+ subscribe() {
110
+ return pollableCreate(0, this);
111
+ }
112
+ get() {
113
+ if (this.#requested) {
114
+ return { tag: 'err' };
115
+ }
116
+ this.#requested = true;
117
+ return {
118
+ tag: 'ok',
119
+ val: {
120
+ tag: 'ok',
121
+ val: undefined,
122
+ },
123
+ };
124
+ }
125
+ static _create() {
126
+ const res = new FutureTrailers();
127
+ return res;
128
+ }
117
129
  }
118
130
  const futureTrailersCreate = FutureTrailers._create;
119
131
  delete FutureTrailers._create;
120
132
 
121
133
  class OutgoingResponse {
122
- #body;
123
- /** @type {number} */ #statusCode = 200;
124
- /** @type {Fields} */ #headers;
125
-
126
- /**
127
- * @param {number} statusCode
128
- * @param {Fields} headers
129
- */
130
- constructor(headers) {
131
- fieldsLock(headers);
132
- this.#headers = headers;
133
- }
134
-
135
- statusCode() {
136
- return this.#statusCode;
137
- }
138
-
139
- setStatusCode(statusCode) {
140
- this.#statusCode = statusCode;
141
- }
142
-
143
- headers() {
144
- return this.#headers;
145
- }
146
-
147
- body() {
148
- let contentLengthValues = this.#headers.get("content-length");
149
- let contentLength;
150
- if (contentLengthValues.length > 0)
151
- contentLength = Number(new TextDecoder().decode(contentLengthValues[0]));
152
- this.#body = outgoingBodyCreate(contentLength);
153
- return this.#body;
154
- }
155
-
156
- static _body(outgoingResponse) {
157
- return outgoingResponse.#body;
158
- }
134
+ #body;
135
+ /** @type {number} */ #statusCode = 200;
136
+ /** @type {Fields} */ #headers;
137
+
138
+ /**
139
+ * @param {number} statusCode
140
+ * @param {Fields} headers
141
+ */
142
+ constructor(headers) {
143
+ fieldsLock(headers);
144
+ this.#headers = headers;
145
+ }
146
+
147
+ statusCode() {
148
+ return this.#statusCode;
149
+ }
150
+
151
+ setStatusCode(statusCode) {
152
+ this.#statusCode = statusCode;
153
+ }
154
+
155
+ headers() {
156
+ return this.#headers;
157
+ }
158
+
159
+ body() {
160
+ let contentLengthValues = this.#headers.get('content-length');
161
+ let contentLength;
162
+ if (contentLengthValues.length > 0) {
163
+ contentLength = Number(
164
+ new TextDecoder().decode(contentLengthValues[0])
165
+ );
166
+ }
167
+ this.#body = outgoingBodyCreate(contentLength);
168
+ return this.#body;
169
+ }
170
+
171
+ static _body(outgoingResponse) {
172
+ return outgoingResponse.#body;
173
+ }
159
174
  }
160
175
 
161
176
  const outgoingResponseBody = OutgoingResponse._body;
162
177
  delete OutgoingResponse._body;
163
178
 
164
179
  class ResponseOutparam {
165
- #setListener;
166
- static set(param, response) {
167
- param.#setListener(response);
168
- }
169
- static _create(setListener) {
170
- const responseOutparam = new ResponseOutparam();
171
- responseOutparam.#setListener = setListener;
172
- return responseOutparam;
173
- }
180
+ #setListener;
181
+ static set(param, response) {
182
+ param.#setListener(response);
183
+ }
184
+ static _create(setListener) {
185
+ const responseOutparam = new ResponseOutparam();
186
+ responseOutparam.#setListener = setListener;
187
+ return responseOutparam;
188
+ }
174
189
  }
175
190
  const responseOutparamCreate = ResponseOutparam._create;
176
191
  delete ResponseOutparam._create;
@@ -178,186 +193,201 @@ delete ResponseOutparam._create;
178
193
  const defaultHttpTimeout = 600_000_000_000n;
179
194
 
180
195
  class RequestOptions {
181
- #connectTimeout = defaultHttpTimeout;
182
- #firstByteTimeout = defaultHttpTimeout;
183
- #betweenBytesTimeout = defaultHttpTimeout;
184
- connectTimeout() {
185
- return this.#connectTimeout;
186
- }
187
- setConnectTimeout(duration) {
188
- this.#connectTimeout = duration;
189
- }
190
- firstByteTimeout() {
191
- return this.#firstByteTimeout;
192
- }
193
- setFirstByteTimeout(duration) {
194
- this.#firstByteTimeout = duration;
195
- }
196
- betweenBytesTimeout() {
197
- return this.#betweenBytesTimeout;
198
- }
199
- setBetweenBytesTimeout(duration) {
200
- this.#betweenBytesTimeout = duration;
201
- }
196
+ #connectTimeout = defaultHttpTimeout;
197
+ #firstByteTimeout = defaultHttpTimeout;
198
+ #betweenBytesTimeout = defaultHttpTimeout;
199
+ connectTimeout() {
200
+ return this.#connectTimeout;
201
+ }
202
+ setConnectTimeout(duration) {
203
+ this.#connectTimeout = duration;
204
+ }
205
+ firstByteTimeout() {
206
+ return this.#firstByteTimeout;
207
+ }
208
+ setFirstByteTimeout(duration) {
209
+ this.#firstByteTimeout = duration;
210
+ }
211
+ betweenBytesTimeout() {
212
+ return this.#betweenBytesTimeout;
213
+ }
214
+ setBetweenBytesTimeout(duration) {
215
+ this.#betweenBytesTimeout = duration;
216
+ }
202
217
  }
203
218
 
204
219
  class OutgoingRequest {
205
- /** @type {Method} */ #method = { tag: "get" };
206
- /** @type {Scheme | undefined} */ #scheme = undefined;
207
- /** @type {string | undefined} */ #pathWithQuery = undefined;
208
- /** @type {string | undefined} */ #authority = undefined;
209
- /** @type {Fields} */ #headers;
210
- /** @type {OutgoingBody} */ #body;
211
- #bodyRequested = false;
212
- constructor(headers) {
213
- fieldsLock(headers);
214
- this.#headers = headers;
215
- let contentLengthValues = this.#headers.get("content-length");
216
- if (contentLengthValues.length === 0)
217
- contentLengthValues = this.#headers.get("Content-Length");
218
- let contentLength;
219
- if (contentLengthValues.length > 0)
220
- contentLength = Number(new TextDecoder().decode(contentLengthValues[0]));
221
- this.#body = outgoingBodyCreate(contentLength);
222
- }
223
- body() {
224
- if (this.#bodyRequested) throw new Error("Body already requested");
225
- this.#bodyRequested = true;
226
- return this.#body;
227
- }
228
- method() {
229
- return this.#method;
230
- }
231
- setMethod(method) {
232
- if (method.tag === "other" && !method.val.match(/^[a-zA-Z-]+$/))
233
- throw undefined;
234
- this.#method = method;
235
- }
236
- pathWithQuery() {
237
- return this.#pathWithQuery;
238
- }
239
- setPathWithQuery(pathWithQuery) {
240
- if (
241
- pathWithQuery &&
242
- !pathWithQuery.match(/^[a-zA-Z0-9.\-_~!$&'()*+,;=:@%?/]+$/)
243
- )
244
- throw undefined;
245
- this.#pathWithQuery = pathWithQuery;
246
- }
247
- scheme() {
248
- return this.#scheme;
249
- }
250
- setScheme(scheme) {
251
- if (scheme?.tag === "other" && !scheme.val.match(/^[a-zA-Z]+$/))
252
- throw undefined;
253
- this.#scheme = scheme;
254
- }
255
- authority() {
256
- return this.#authority;
257
- }
258
- setAuthority(authority) {
259
- if (authority) {
260
- const [host, port, ...extra] = authority.split(":");
261
- const portNum = Number(port);
262
- if (
263
- extra.length ||
264
- (port !== undefined &&
265
- (portNum.toString() !== port || portNum > 65535)) ||
266
- !host.match(/^[a-zA-Z0-9-.]+$/)
267
- )
268
- throw undefined;
269
- }
270
- this.#authority = authority;
271
- }
272
- headers() {
273
- return this.#headers;
274
- }
275
- [symbolDispose]() {}
276
- static _handle(request, options) {
277
- const connectTimeout = options?.connectTimeout();
278
- const betweenBytesTimeout = options?.betweenBytesTimeout();
279
- const firstByteTimeout = options?.firstByteTimeout();
280
- const scheme = schemeString(request.#scheme);
281
- // note: host header is automatically added by Node.js
282
- const headers = [];
283
- const decoder = new TextDecoder();
284
- for (const [key, value] of request.#headers.entries()) {
285
- headers.push([key, decoder.decode(value)]);
286
- }
287
- if (!request.#pathWithQuery)
288
- throw { tag: 'HTTP-request-URI-invalid' };
289
- return futureIncomingResponseCreate(
290
- request.#method.val || request.#method.tag,
291
- scheme,
292
- request.#authority,
293
- request.#pathWithQuery,
294
- headers,
295
- outgoingBodyOutputStreamId(request.#body),
296
- connectTimeout,
297
- betweenBytesTimeout,
298
- firstByteTimeout,
299
- );
300
- }
220
+ /** @type {Method} */ #method = { tag: 'get' };
221
+ /** @type {Scheme | undefined} */ #scheme = undefined;
222
+ /** @type {string | undefined} */ #pathWithQuery = undefined;
223
+ /** @type {string | undefined} */ #authority = undefined;
224
+ /** @type {Fields} */ #headers;
225
+ /** @type {OutgoingBody} */ #body;
226
+ #bodyRequested = false;
227
+ constructor(headers) {
228
+ fieldsLock(headers);
229
+ this.#headers = headers;
230
+ let contentLengthValues = this.#headers.get('content-length');
231
+ if (contentLengthValues.length === 0) {
232
+ contentLengthValues = this.#headers.get('Content-Length');
233
+ }
234
+ let contentLength;
235
+ if (contentLengthValues.length > 0) {
236
+ contentLength = Number(
237
+ new TextDecoder().decode(contentLengthValues[0])
238
+ );
239
+ }
240
+ this.#body = outgoingBodyCreate(contentLength);
241
+ }
242
+ body() {
243
+ if (this.#bodyRequested) {
244
+ throw new Error('Body already requested');
245
+ }
246
+ this.#bodyRequested = true;
247
+ return this.#body;
248
+ }
249
+ method() {
250
+ return this.#method;
251
+ }
252
+ setMethod(method) {
253
+ if (method.tag === 'other' && !method.val.match(/^[a-zA-Z-]+$/)) {
254
+ throw undefined;
255
+ }
256
+ this.#method = method;
257
+ }
258
+ pathWithQuery() {
259
+ return this.#pathWithQuery;
260
+ }
261
+ setPathWithQuery(pathWithQuery) {
262
+ if (
263
+ pathWithQuery &&
264
+ !pathWithQuery.match(/^[a-zA-Z0-9.\-_~!$&'()*+,;=:@%?/]+$/)
265
+ ) {
266
+ throw undefined;
267
+ }
268
+ this.#pathWithQuery = pathWithQuery;
269
+ }
270
+ scheme() {
271
+ return this.#scheme;
272
+ }
273
+ setScheme(scheme) {
274
+ if (scheme?.tag === 'other' && !scheme.val.match(/^[a-zA-Z]+$/)) {
275
+ throw undefined;
276
+ }
277
+ this.#scheme = scheme;
278
+ }
279
+ authority() {
280
+ return this.#authority;
281
+ }
282
+ setAuthority(authority) {
283
+ if (authority) {
284
+ const [host, port, ...extra] = authority.split(':');
285
+ const portNum = Number(port);
286
+ if (
287
+ extra.length ||
288
+ (port !== undefined &&
289
+ (portNum.toString() !== port || portNum > 65535)) ||
290
+ !host.match(/^[a-zA-Z0-9-.]+$/)
291
+ ) {
292
+ throw undefined;
293
+ }
294
+ }
295
+ this.#authority = authority;
296
+ }
297
+ headers() {
298
+ return this.#headers;
299
+ }
300
+ [symbolDispose]() {}
301
+ static _handle(request, options) {
302
+ const connectTimeout = options?.connectTimeout();
303
+ const betweenBytesTimeout = options?.betweenBytesTimeout();
304
+ const firstByteTimeout = options?.firstByteTimeout();
305
+ const scheme = schemeString(request.#scheme);
306
+ // note: host header is automatically added by Node.js
307
+ const headers = [];
308
+ const decoder = new TextDecoder();
309
+ for (const [key, value] of request.#headers.entries()) {
310
+ headers.push([key, decoder.decode(value)]);
311
+ }
312
+ if (!request.#pathWithQuery) {
313
+ throw { tag: 'HTTP-request-URI-invalid' };
314
+ }
315
+ return futureIncomingResponseCreate(
316
+ request.#method.val || request.#method.tag,
317
+ scheme,
318
+ request.#authority,
319
+ request.#pathWithQuery,
320
+ headers,
321
+ outgoingBodyOutputStreamId(request.#body),
322
+ connectTimeout,
323
+ betweenBytesTimeout,
324
+ firstByteTimeout
325
+ );
326
+ }
301
327
  }
302
328
 
303
329
  const outgoingRequestHandle = OutgoingRequest._handle;
304
330
  delete OutgoingRequest._handle;
305
331
 
306
332
  class OutgoingBody {
307
- #outputStream = null;
308
- #outputStreamId = null;
309
- #contentLength = undefined;
310
- #finalizer;
311
- write() {
312
- // can only call write once
313
- const outputStream = this.#outputStream;
314
- if (outputStream === null) throw undefined;
315
- this.#outputStream = null;
316
- return outputStream;
317
- }
318
- /**
319
- * @param {OutgoingBody} body
320
- * @param {Fields | undefined} trailers
321
- */
322
- static finish(body, trailers) {
323
- if (trailers) throw { tag: "internal-error", val: "trailers unsupported" };
324
- // this will verify content length, and also verify not already finished
325
- // throwing errors as appropriate
326
- ioCall(HTTP_OUTPUT_STREAM_FINISH, body.#outputStreamId, null);
327
- }
328
- static _outputStreamId(outgoingBody) {
329
- return outgoingBody.#outputStreamId;
330
- }
331
- static _create(contentLength) {
332
- const outgoingBody = new OutgoingBody();
333
- outgoingBody.#contentLength = contentLength;
334
- outgoingBody.#outputStreamId = ioCall(
335
- OUTPUT_STREAM_CREATE | HTTP,
336
- null,
337
- outgoingBody.#contentLength
338
- );
339
- outgoingBody.#outputStream = outputStreamCreate(
340
- HTTP,
341
- outgoingBody.#outputStreamId
342
- );
343
- outgoingBody.#finalizer = registerDispose(
344
- outgoingBody,
345
- null,
346
- outgoingBody.#outputStreamId,
347
- outgoingBodyDispose
348
- );
349
- return outgoingBody;
350
- }
351
- [symbolDispose]() {
352
- if (this.#finalizer) {
353
- earlyDispose(this.#finalizer);
354
- this.#finalizer = null;
355
- }
356
- }
333
+ #outputStream = null;
334
+ #outputStreamId = null;
335
+ #contentLength = undefined;
336
+ #finalizer;
337
+ write() {
338
+ // can only call write once
339
+ const outputStream = this.#outputStream;
340
+ if (outputStream === null) {
341
+ throw undefined;
342
+ }
343
+ this.#outputStream = null;
344
+ return outputStream;
345
+ }
346
+ /**
347
+ * @param {OutgoingBody} body
348
+ * @param {Fields | undefined} trailers
349
+ */
350
+ static finish(body, trailers) {
351
+ if (trailers) {
352
+ throw { tag: 'internal-error', val: 'trailers unsupported' };
353
+ }
354
+ // this will verify content length, and also verify not already finished
355
+ // throwing errors as appropriate
356
+ ioCall(HTTP_OUTPUT_STREAM_FINISH, body.#outputStreamId, null);
357
+ }
358
+ static _outputStreamId(outgoingBody) {
359
+ return outgoingBody.#outputStreamId;
360
+ }
361
+ static _create(contentLength) {
362
+ const outgoingBody = new OutgoingBody();
363
+ outgoingBody.#contentLength = contentLength;
364
+ outgoingBody.#outputStreamId = ioCall(
365
+ OUTPUT_STREAM_CREATE | HTTP,
366
+ null,
367
+ outgoingBody.#contentLength
368
+ );
369
+ outgoingBody.#outputStream = outputStreamCreate(
370
+ HTTP,
371
+ outgoingBody.#outputStreamId
372
+ );
373
+ outgoingBody.#finalizer = registerDispose(
374
+ outgoingBody,
375
+ null,
376
+ outgoingBody.#outputStreamId,
377
+ outgoingBodyDispose
378
+ );
379
+ return outgoingBody;
380
+ }
381
+ [symbolDispose]() {
382
+ if (this.#finalizer) {
383
+ earlyDispose(this.#finalizer);
384
+ this.#finalizer = null;
385
+ }
386
+ }
357
387
  }
358
388
 
359
389
  function outgoingBodyDispose(id) {
360
- ioCall(HTTP_OUTGOING_BODY_DISPOSE, id, null);
390
+ ioCall(HTTP_OUTGOING_BODY_DISPOSE, id, null);
361
391
  }
362
392
 
363
393
  const outgoingBodyOutputStreamId = OutgoingBody._outputStreamId;
@@ -367,218 +397,237 @@ const outgoingBodyCreate = OutgoingBody._create;
367
397
  delete OutgoingBody._create;
368
398
 
369
399
  class IncomingResponse {
370
- /** @type {Fields} */ #headers = undefined;
371
- #status = 0;
372
- /** @type {number} */ #bodyStream;
373
- status() {
374
- return this.#status;
375
- }
376
- headers() {
377
- return this.#headers;
378
- }
379
- consume() {
380
- if (this.#bodyStream === undefined) throw undefined;
381
- const bodyStream = this.#bodyStream;
382
- this.#bodyStream = undefined;
383
- return bodyStream;
384
- }
385
- [symbolDispose]() {
386
- if (this.#bodyStream) this.#bodyStream[symbolDispose]();
387
- }
388
- static _create(status, headers, bodyStreamId) {
389
- const res = new IncomingResponse();
390
- res.#status = status;
391
- res.#headers = headers;
392
- res.#bodyStream = incomingBodyCreate(bodyStreamId);
393
- return res;
394
- }
400
+ /** @type {Fields} */ #headers = undefined;
401
+ #status = 0;
402
+ /** @type {number} */ #bodyStream;
403
+ status() {
404
+ return this.#status;
405
+ }
406
+ headers() {
407
+ return this.#headers;
408
+ }
409
+ consume() {
410
+ if (this.#bodyStream === undefined) {
411
+ throw undefined;
412
+ }
413
+ const bodyStream = this.#bodyStream;
414
+ this.#bodyStream = undefined;
415
+ return bodyStream;
416
+ }
417
+ [symbolDispose]() {
418
+ if (this.#bodyStream) {
419
+ this.#bodyStream[symbolDispose]();
420
+ }
421
+ }
422
+ static _create(status, headers, bodyStreamId) {
423
+ const res = new IncomingResponse();
424
+ res.#status = status;
425
+ res.#headers = headers;
426
+ res.#bodyStream = incomingBodyCreate(bodyStreamId);
427
+ return res;
428
+ }
395
429
  }
396
430
 
397
431
  const incomingResponseCreate = IncomingResponse._create;
398
432
  delete IncomingResponse._create;
399
433
 
400
434
  class FutureIncomingResponse {
401
- #id;
402
- #finalizer;
403
- subscribe() {
404
- return pollableCreate(
405
- ioCall(FUTURE_SUBSCRIBE | HTTP, this.#id, null),
406
- this
407
- );
408
- }
409
- get() {
410
- const ret = ioCall(FUTURE_TAKE_VALUE | HTTP, this.#id, null);
411
- if (ret === undefined) return undefined;
412
- if (ret.tag === "ok" && ret.val.tag === "ok") {
413
- const textEncoder = new TextEncoder();
414
- const { status, headers, bodyStreamId } = ret.val.val;
415
- ret.val.val = incomingResponseCreate(
416
- status,
417
- fieldsFromEntriesChecked(
418
- headers.map(([key, val]) => [key, textEncoder.encode(val)])
419
- ),
420
- bodyStreamId
421
- );
422
- }
423
- return ret;
424
- }
425
- static _create(
426
- method,
427
- scheme,
428
- authority,
429
- pathWithQuery,
430
- headers,
431
- body,
432
- connectTimeout,
433
- betweenBytesTimeout,
434
- firstByteTimeout
435
- ) {
436
- const res = new FutureIncomingResponse();
437
- res.#id = ioCall(HTTP_CREATE_REQUEST, null, {
438
- method,
439
- scheme,
440
- authority,
441
- pathWithQuery,
442
- headers,
443
- body,
444
- connectTimeout,
445
- betweenBytesTimeout,
446
- firstByteTimeout,
447
- });
448
- res.#finalizer = registerDispose(
449
- res,
450
- null,
451
- res.#id,
452
- futureIncomingResponseDispose
453
- );
454
- return res;
455
- }
456
- [symbolDispose]() {
457
- if (this.#finalizer) {
458
- earlyDispose(this.#finalizer);
459
- this.#finalizer = null;
460
- }
461
- }
435
+ #id;
436
+ #finalizer;
437
+ subscribe() {
438
+ return pollableCreate(
439
+ ioCall(FUTURE_SUBSCRIBE | HTTP, this.#id, null),
440
+ this
441
+ );
442
+ }
443
+ get() {
444
+ const ret = ioCall(FUTURE_TAKE_VALUE | HTTP, this.#id, null);
445
+ if (ret === undefined) {
446
+ return undefined;
447
+ }
448
+ if (ret.tag === 'ok' && ret.val.tag === 'ok') {
449
+ const textEncoder = new TextEncoder();
450
+ const { status, headers, bodyStreamId } = ret.val.val;
451
+ ret.val.val = incomingResponseCreate(
452
+ status,
453
+ fieldsFromEntriesChecked(
454
+ headers.map(([key, val]) => [key, textEncoder.encode(val)])
455
+ ),
456
+ bodyStreamId
457
+ );
458
+ }
459
+ return ret;
460
+ }
461
+ static _create(
462
+ method,
463
+ scheme,
464
+ authority,
465
+ pathWithQuery,
466
+ headers,
467
+ body,
468
+ connectTimeout,
469
+ betweenBytesTimeout,
470
+ firstByteTimeout
471
+ ) {
472
+ const res = new FutureIncomingResponse();
473
+ res.#id = ioCall(HTTP_CREATE_REQUEST, null, {
474
+ method,
475
+ scheme,
476
+ authority,
477
+ pathWithQuery,
478
+ headers,
479
+ body,
480
+ connectTimeout,
481
+ betweenBytesTimeout,
482
+ firstByteTimeout,
483
+ });
484
+ res.#finalizer = registerDispose(
485
+ res,
486
+ null,
487
+ res.#id,
488
+ futureIncomingResponseDispose
489
+ );
490
+ return res;
491
+ }
492
+ [symbolDispose]() {
493
+ if (this.#finalizer) {
494
+ earlyDispose(this.#finalizer);
495
+ this.#finalizer = null;
496
+ }
497
+ }
462
498
  }
463
499
 
464
500
  function futureIncomingResponseDispose(id) {
465
- ioCall(FUTURE_DISPOSE | HTTP, id, null);
501
+ ioCall(FUTURE_DISPOSE | HTTP, id, null);
466
502
  }
467
503
 
468
504
  const futureIncomingResponseCreate = FutureIncomingResponse._create;
469
505
  delete FutureIncomingResponse._create;
470
506
 
471
507
  class Fields {
472
- #immutable = false;
473
- /** @type {[string, Uint8Array[]][]} */ #entries = [];
474
- /** @type {Map<string, [string, Uint8Array[]][]>} */ #table = new Map();
475
-
476
- /**
477
- * @param {[string, Uint8Array[][]][]} entries
478
- */
479
- static fromList(entries) {
480
- const fields = new Fields();
481
- for (const [key, value] of entries) {
482
- fields.append(key, value);
483
- }
484
- return fields;
485
- }
486
- get(name) {
487
- const tableEntries = this.#table.get(name.toLowerCase());
488
- if (!tableEntries) return [];
489
- return tableEntries.map(([, v]) => v);
490
- }
491
- set(name, values) {
492
- if (this.#immutable) throw { tag: "immutable" };
493
- try {
494
- validateHeaderName(name);
495
- } catch {
496
- throw { tag: "invalid-syntax" };
497
- }
498
- for (const value of values) {
499
- try {
500
- validateHeaderValue(name, new TextDecoder().decode(value));
501
- } catch {
502
- throw { tag: "invalid-syntax" };
503
- }
504
- throw { tag: "invalid-syntax" };
505
- }
506
- const lowercased = name.toLowerCase();
507
- if (_forbiddenHeaders.has(lowercased)) throw { tag: "forbidden" };
508
- const tableEntries = this.#table.get(lowercased);
509
- if (tableEntries)
510
- this.#entries = this.#entries.filter(
511
- (entry) => !tableEntries.includes(entry)
512
- );
513
- tableEntries.splice(0, tableEntries.length);
514
- for (const value of values) {
515
- const entry = [name, value];
516
- this.#entries.push(entry);
517
- tableEntries.push(entry);
518
- }
519
- }
520
- has(name) {
521
- return this.#table.has(name.toLowerCase());
522
- }
523
- delete(name) {
524
- if (this.#immutable) throw { tag: "immutable" };
525
- const lowercased = name.toLowerCase();
526
- const tableEntries = this.#table.get(lowercased);
527
- if (tableEntries) {
528
- this.#entries = this.#entries.filter(
529
- (entry) => !tableEntries.includes(entry)
530
- );
531
- this.#table.delete(lowercased);
532
- }
533
- }
534
- append(name, value) {
535
- if (this.#immutable) throw { tag: "immutable" };
536
- try {
537
- validateHeaderName(name);
538
- } catch {
539
- throw { tag: "invalid-syntax" };
540
- }
541
- try {
542
- validateHeaderValue(name, new TextDecoder().decode(value));
543
- } catch {
544
- throw { tag: "invalid-syntax" };
545
- }
546
- const lowercased = name.toLowerCase();
547
- if (_forbiddenHeaders.has(lowercased)) throw { tag: "forbidden" };
548
- const entry = [name, value];
549
- this.#entries.push(entry);
550
- const tableEntries = this.#table.get(lowercased);
551
- if (tableEntries) {
552
- tableEntries.push(entry);
553
- } else {
554
- this.#table.set(lowercased, [entry]);
555
- }
556
- }
557
- entries() {
558
- return this.#entries;
559
- }
560
- clone() {
561
- return fieldsFromEntriesChecked(this.#entries);
562
- }
563
- static _lock(fields) {
564
- fields.#immutable = true;
565
- return fields;
566
- }
567
- // assumes entries are already validated
568
- static _fromEntriesChecked(entries) {
569
- const fields = new Fields();
570
- fields.#entries = entries;
571
- for (const entry of entries) {
572
- const lowercase = entry[0].toLowerCase();
573
- const existing = fields.#table.get(lowercase);
574
- if (existing) {
575
- existing.push(entry);
576
- } else {
577
- fields.#table.set(lowercase, [entry]);
578
- }
579
- }
580
- return fields;
581
- }
508
+ #immutable = false;
509
+ /** @type {[string, Uint8Array[]][]} */ #entries = [];
510
+ /** @type {Map<string, [string, Uint8Array[]][]>} */ #table = new Map();
511
+
512
+ /**
513
+ * @param {[string, Uint8Array[][]][]} entries
514
+ */
515
+ static fromList(entries) {
516
+ const fields = new Fields();
517
+ for (const [key, value] of entries) {
518
+ fields.append(key, value);
519
+ }
520
+ return fields;
521
+ }
522
+ get(name) {
523
+ const tableEntries = this.#table.get(name.toLowerCase());
524
+ if (!tableEntries) {
525
+ return [];
526
+ }
527
+ return tableEntries.map(([, v]) => v);
528
+ }
529
+ set(name, values) {
530
+ if (this.#immutable) {
531
+ throw { tag: 'immutable' };
532
+ }
533
+ try {
534
+ validateHeaderName(name);
535
+ } catch {
536
+ throw { tag: 'invalid-syntax' };
537
+ }
538
+ for (const value of values) {
539
+ try {
540
+ validateHeaderValue(name, new TextDecoder().decode(value));
541
+ } catch {
542
+ throw { tag: 'invalid-syntax' };
543
+ }
544
+ throw { tag: 'invalid-syntax' };
545
+ }
546
+ const lowercased = name.toLowerCase();
547
+ if (_forbiddenHeaders.has(lowercased)) {
548
+ throw { tag: 'forbidden' };
549
+ }
550
+ const tableEntries = this.#table.get(lowercased);
551
+ if (tableEntries) {
552
+ this.#entries = this.#entries.filter(
553
+ (entry) => !tableEntries.includes(entry)
554
+ );
555
+ }
556
+ tableEntries.splice(0, tableEntries.length);
557
+ for (const value of values) {
558
+ const entry = [name, value];
559
+ this.#entries.push(entry);
560
+ tableEntries.push(entry);
561
+ }
562
+ }
563
+ has(name) {
564
+ return this.#table.has(name.toLowerCase());
565
+ }
566
+ delete(name) {
567
+ if (this.#immutable) {
568
+ throw { tag: 'immutable' };
569
+ }
570
+ const lowercased = name.toLowerCase();
571
+ const tableEntries = this.#table.get(lowercased);
572
+ if (tableEntries) {
573
+ this.#entries = this.#entries.filter(
574
+ (entry) => !tableEntries.includes(entry)
575
+ );
576
+ this.#table.delete(lowercased);
577
+ }
578
+ }
579
+ append(name, value) {
580
+ if (this.#immutable) {
581
+ throw { tag: 'immutable' };
582
+ }
583
+ try {
584
+ validateHeaderName(name);
585
+ } catch {
586
+ throw { tag: 'invalid-syntax' };
587
+ }
588
+ try {
589
+ validateHeaderValue(name, new TextDecoder().decode(value));
590
+ } catch {
591
+ throw { tag: 'invalid-syntax' };
592
+ }
593
+ const lowercased = name.toLowerCase();
594
+ if (_forbiddenHeaders.has(lowercased)) {
595
+ throw { tag: 'forbidden' };
596
+ }
597
+ const entry = [name, value];
598
+ this.#entries.push(entry);
599
+ const tableEntries = this.#table.get(lowercased);
600
+ if (tableEntries) {
601
+ tableEntries.push(entry);
602
+ } else {
603
+ this.#table.set(lowercased, [entry]);
604
+ }
605
+ }
606
+ entries() {
607
+ return this.#entries;
608
+ }
609
+ clone() {
610
+ return fieldsFromEntriesChecked(this.#entries);
611
+ }
612
+ static _lock(fields) {
613
+ fields.#immutable = true;
614
+ return fields;
615
+ }
616
+ // assumes entries are already validated
617
+ static _fromEntriesChecked(entries) {
618
+ const fields = new Fields();
619
+ fields.#entries = entries;
620
+ for (const entry of entries) {
621
+ const lowercase = entry[0].toLowerCase();
622
+ const existing = fields.#table.get(lowercase);
623
+ if (existing) {
624
+ existing.push(entry);
625
+ } else {
626
+ fields.#table.set(lowercase, [entry]);
627
+ }
628
+ }
629
+ return fields;
630
+ }
582
631
  }
583
632
  const fieldsLock = Fields._lock;
584
633
  delete Fields._lock;
@@ -586,130 +635,157 @@ const fieldsFromEntriesChecked = Fields._fromEntriesChecked;
586
635
  delete Fields._fromEntriesChecked;
587
636
 
588
637
  export const outgoingHandler = {
589
- /**
590
- * @param {OutgoingRequest} request
591
- * @param {RequestOptions | undefined} options
592
- * @returns {FutureIncomingResponse}
593
- */
594
- handle: outgoingRequestHandle,
638
+ /**
639
+ * @param {OutgoingRequest} request
640
+ * @param {RequestOptions | undefined} options
641
+ * @returns {FutureIncomingResponse}
642
+ */
643
+ handle: outgoingRequestHandle,
595
644
  };
596
645
 
597
646
  function httpErrorCode(err) {
598
- if (err.payload) return err.payload;
599
- return {
600
- tag: "internal-error",
601
- val: err.message,
602
- };
647
+ if (err.payload) {
648
+ return err.payload;
649
+ }
650
+ return {
651
+ tag: 'internal-error',
652
+ val: err.message,
653
+ };
603
654
  }
604
655
 
605
656
  export const types = {
606
- Fields,
607
- FutureIncomingResponse,
608
- FutureTrailers,
609
- IncomingBody,
610
- IncomingRequest,
611
- IncomingResponse,
612
- OutgoingBody,
613
- OutgoingRequest,
614
- OutgoingResponse,
615
- ResponseOutparam,
616
- RequestOptions,
617
- httpErrorCode,
657
+ Fields,
658
+ FutureIncomingResponse,
659
+ FutureTrailers,
660
+ IncomingBody,
661
+ IncomingRequest,
662
+ IncomingResponse,
663
+ OutgoingBody,
664
+ OutgoingRequest,
665
+ OutgoingResponse,
666
+ ResponseOutparam,
667
+ RequestOptions,
668
+ httpErrorCode,
618
669
  };
619
670
 
620
671
  function schemeString(scheme) {
621
- if (!scheme) return "https:";
622
- switch (scheme.tag) {
623
- case "HTTP":
624
- return "http:";
625
- case "HTTPS":
626
- return "https:";
627
- case "other":
628
- return scheme.val.toLowerCase() + ":";
629
- }
672
+ if (!scheme) {
673
+ return 'https:';
674
+ }
675
+ switch (scheme.tag) {
676
+ case 'HTTP':
677
+ return 'http:';
678
+ case 'HTTPS':
679
+ return 'https:';
680
+ case 'other':
681
+ return scheme.val.toLowerCase() + ':';
682
+ }
630
683
  }
631
684
 
632
685
  const supportedMethods = [
633
- "get",
634
- "head",
635
- "post",
636
- "put",
637
- "delete",
638
- "connect",
639
- "options",
640
- "trace",
641
- "patch",
686
+ 'get',
687
+ 'head',
688
+ 'post',
689
+ 'put',
690
+ 'delete',
691
+ 'connect',
692
+ 'options',
693
+ 'trace',
694
+ 'patch',
642
695
  ];
643
696
  function parseMethod(method) {
644
- const lowercase = method.toLowerCase();
645
- if (supportedMethods.includes(method.toLowerCase()))
646
- return { tag: lowercase };
647
- return { tag: "other", val: lowercase };
697
+ const lowercase = method.toLowerCase();
698
+ if (supportedMethods.includes(method.toLowerCase())) {
699
+ return { tag: lowercase };
700
+ }
701
+ return { tag: 'other', val: lowercase };
648
702
  }
649
703
 
650
704
  const httpServers = new Map();
651
705
  let httpServerCnt = 0;
652
706
  export class HTTPServer {
653
- #id = ++httpServerCnt;
654
- #liveEventLoopInterval;
655
- constructor(incomingHandler) {
656
- httpServers.set(this.#id, this);
657
- if (typeof incomingHandler?.handle !== "function") {
658
- console.error("Not a valid HTTP server component to execute.");
659
- process.exit(1);
660
- }
661
- registerIncomingHttpHandler(
662
- this.#id,
663
- ({ method, pathWithQuery, host, headers, responseId, streamId }) => {
664
- const textEncoder = new TextEncoder();
665
- const request = incomingRequestCreate(
666
- parseMethod(method),
667
- pathWithQuery,
668
- { tag: "HTTP" },
669
- host,
670
- fieldsLock(
671
- fieldsFromEntriesChecked(
672
- headers
673
- .filter(([key]) => !_forbiddenHeaders.has(key))
674
- .map(([key, val]) => [key, textEncoder.encode(val)])
675
- )
676
- ),
677
- streamId
678
- );
679
- let outgoingBodyStreamId;
680
- const responseOutparam = responseOutparamCreate((response) => {
681
- if (response.tag === "ok") {
682
- const outgoingResponse = response.val;
683
- const statusCode = outgoingResponse.statusCode();
684
- const headers = outgoingResponse.headers().entries();
685
- const body = outgoingResponseBody(outgoingResponse);
686
- outgoingBodyStreamId = outgoingBodyOutputStreamId(body);
687
- ioCall(HTTP_SERVER_SET_OUTGOING_RESPONSE, responseId, {
688
- statusCode,
689
- headers,
690
- streamId: outgoingBodyStreamId,
691
- });
692
- } else {
693
- ioCall(HTTP_SERVER_CLEAR_OUTGOING_RESPONSE, responseId, null);
694
- console.error(response.val);
707
+ #id = ++httpServerCnt;
708
+ #stopped = false;
709
+ #liveEventLoopInterval;
710
+ constructor(incomingHandler) {
711
+ httpServers.set(this.#id, this);
712
+ if (typeof incomingHandler?.handle !== 'function') {
713
+ console.error('Not a valid HTTP server component to execute.');
695
714
  process.exit(1);
696
- }
697
- });
698
- incomingHandler.handle(request, responseOutparam);
699
- if (outgoingBodyStreamId) {
700
- ioCall(OUTPUT_STREAM_DISPOSE, outgoingBodyStreamId, null);
701
- }
702
- }
703
- );
704
- }
705
- listen(port, host) {
706
- // set a dummy interval, to keep the process alive since the server is off-thread
707
- this.#liveEventLoopInterval = setInterval(() => {}, 10_000);
708
- ioCall(HTTP_SERVER_START, this.#id, { port, host });
709
- }
710
- stop() {
711
- clearInterval(this.#liveEventLoopInterval);
712
- ioCall(HTTP_SERVER_STOP, this.#id, null);
713
- httpServers.delete(this.#id);
714
- }
715
+ }
716
+ registerIncomingHttpHandler(
717
+ this.#id,
718
+ ({
719
+ method,
720
+ pathWithQuery,
721
+ host,
722
+ headers,
723
+ responseId,
724
+ streamId,
725
+ }) => {
726
+ const textEncoder = new TextEncoder();
727
+ const request = incomingRequestCreate(
728
+ parseMethod(method),
729
+ pathWithQuery,
730
+ { tag: 'HTTP' },
731
+ host,
732
+ fieldsLock(
733
+ fieldsFromEntriesChecked(
734
+ headers
735
+ .filter(([key]) => !_forbiddenHeaders.has(key))
736
+ .map(([key, val]) => [
737
+ key,
738
+ textEncoder.encode(val),
739
+ ])
740
+ )
741
+ ),
742
+ streamId
743
+ );
744
+ let outgoingBodyStreamId;
745
+ const responseOutparam = responseOutparamCreate((response) => {
746
+ if (response.tag === 'ok') {
747
+ const outgoingResponse = response.val;
748
+ const statusCode = outgoingResponse.statusCode();
749
+ const headers = outgoingResponse.headers().entries();
750
+ const body = outgoingResponseBody(outgoingResponse);
751
+ outgoingBodyStreamId = outgoingBodyOutputStreamId(body);
752
+ ioCall(HTTP_SERVER_SET_OUTGOING_RESPONSE, responseId, {
753
+ statusCode,
754
+ headers,
755
+ streamId: outgoingBodyStreamId,
756
+ });
757
+ } else {
758
+ ioCall(
759
+ HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
760
+ responseId,
761
+ null
762
+ );
763
+ console.error(response.val);
764
+ process.exit(1);
765
+ }
766
+ });
767
+ incomingHandler.handle(request, responseOutparam);
768
+ if (outgoingBodyStreamId) {
769
+ ioCall(OUTPUT_STREAM_DISPOSE, outgoingBodyStreamId, null);
770
+ }
771
+ }
772
+ );
773
+ }
774
+ listen(port, host) {
775
+ // set a dummy interval, to keep the process alive since the server is off-thread
776
+ this.#liveEventLoopInterval = setInterval(() => {}, 10_000);
777
+ ioCall(HTTP_SERVER_START, this.#id, { port, host });
778
+ }
779
+ stop() {
780
+ if (this.#stopped) {
781
+ return;
782
+ }
783
+ clearInterval(this.#liveEventLoopInterval);
784
+ ioCall(HTTP_SERVER_STOP, this.#id, null);
785
+ httpServers.delete(this.#id);
786
+ this.#stopped = true;
787
+ }
788
+ close() {
789
+ this.stop();
790
+ }
715
791
  }