@bytecodealliance/preview2-shim 0.17.7 → 0.17.9

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