@bytecodealliance/preview2-shim 0.17.2 → 0.17.4

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,192 @@
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
+ HTTP_SERVER_GET_ADDRESS,
13
+ OUTPUT_STREAM_CREATE,
14
+ OUTPUT_STREAM_DISPOSE,
15
+ } from '../io/calls.js';
15
16
  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";
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';
27
28
  const { validateHeaderName = () => {}, validateHeaderValue = () => {} } = http;
28
29
 
29
- const symbolDispose = Symbol.dispose || Symbol.for("dispose");
30
- 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']);
31
32
 
32
33
  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
- }
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
+ }
53
57
  }
54
58
  const incomingBodyCreate = IncomingBody._create;
55
59
  delete IncomingBody._create;
56
60
 
57
61
  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
- }
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
+ }
93
104
  }
94
105
  const incomingRequestCreate = IncomingRequest._create;
95
106
  delete IncomingRequest._create;
96
107
 
97
108
  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
- }
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
+ }
117
130
  }
118
131
  const futureTrailersCreate = FutureTrailers._create;
119
132
  delete FutureTrailers._create;
120
133
 
121
134
  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
- }
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
+ }
159
175
  }
160
176
 
161
177
  const outgoingResponseBody = OutgoingResponse._body;
162
178
  delete OutgoingResponse._body;
163
179
 
164
180
  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
- }
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
+ }
174
190
  }
175
191
  const responseOutparamCreate = ResponseOutparam._create;
176
192
  delete ResponseOutparam._create;
@@ -178,186 +194,201 @@ delete ResponseOutparam._create;
178
194
  const defaultHttpTimeout = 600_000_000_000n;
179
195
 
180
196
  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
- }
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
+ }
202
218
  }
203
219
 
204
220
  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
- }
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
+ }
301
328
  }
302
329
 
303
330
  const outgoingRequestHandle = OutgoingRequest._handle;
304
331
  delete OutgoingRequest._handle;
305
332
 
306
333
  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
- }
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
+ }
357
388
  }
358
389
 
359
390
  function outgoingBodyDispose(id) {
360
- ioCall(HTTP_OUTGOING_BODY_DISPOSE, id, null);
391
+ ioCall(HTTP_OUTGOING_BODY_DISPOSE, id, null);
361
392
  }
362
393
 
363
394
  const outgoingBodyOutputStreamId = OutgoingBody._outputStreamId;
@@ -367,218 +398,237 @@ const outgoingBodyCreate = OutgoingBody._create;
367
398
  delete OutgoingBody._create;
368
399
 
369
400
  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
- }
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
+ }
395
430
  }
396
431
 
397
432
  const incomingResponseCreate = IncomingResponse._create;
398
433
  delete IncomingResponse._create;
399
434
 
400
435
  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
- }
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
+ }
462
499
  }
463
500
 
464
501
  function futureIncomingResponseDispose(id) {
465
- ioCall(FUTURE_DISPOSE | HTTP, id, null);
502
+ ioCall(FUTURE_DISPOSE | HTTP, id, null);
466
503
  }
467
504
 
468
505
  const futureIncomingResponseCreate = FutureIncomingResponse._create;
469
506
  delete FutureIncomingResponse._create;
470
507
 
471
508
  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
- }
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
+ }
582
632
  }
583
633
  const fieldsLock = Fields._lock;
584
634
  delete Fields._lock;
@@ -586,130 +636,160 @@ const fieldsFromEntriesChecked = Fields._fromEntriesChecked;
586
636
  delete Fields._fromEntriesChecked;
587
637
 
588
638
  export const outgoingHandler = {
589
- /**
590
- * @param {OutgoingRequest} request
591
- * @param {RequestOptions | undefined} options
592
- * @returns {FutureIncomingResponse}
593
- */
594
- handle: outgoingRequestHandle,
639
+ /**
640
+ * @param {OutgoingRequest} request
641
+ * @param {RequestOptions | undefined} options
642
+ * @returns {FutureIncomingResponse}
643
+ */
644
+ handle: outgoingRequestHandle,
595
645
  };
596
646
 
597
647
  function httpErrorCode(err) {
598
- if (err.payload) return err.payload;
599
- return {
600
- tag: "internal-error",
601
- val: err.message,
602
- };
648
+ if (err.payload) {
649
+ return err.payload;
650
+ }
651
+ return {
652
+ tag: 'internal-error',
653
+ val: err.message,
654
+ };
603
655
  }
604
656
 
605
657
  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,
658
+ Fields,
659
+ FutureIncomingResponse,
660
+ FutureTrailers,
661
+ IncomingBody,
662
+ IncomingRequest,
663
+ IncomingResponse,
664
+ OutgoingBody,
665
+ OutgoingRequest,
666
+ OutgoingResponse,
667
+ ResponseOutparam,
668
+ RequestOptions,
669
+ httpErrorCode,
618
670
  };
619
671
 
620
672
  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
- }
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
+ }
630
684
  }
631
685
 
632
686
  const supportedMethods = [
633
- "get",
634
- "head",
635
- "post",
636
- "put",
637
- "delete",
638
- "connect",
639
- "options",
640
- "trace",
641
- "patch",
687
+ 'get',
688
+ 'head',
689
+ 'post',
690
+ 'put',
691
+ 'delete',
692
+ 'connect',
693
+ 'options',
694
+ 'trace',
695
+ 'patch',
642
696
  ];
643
697
  function parseMethod(method) {
644
- const lowercase = method.toLowerCase();
645
- if (supportedMethods.includes(method.toLowerCase()))
646
- return { tag: lowercase };
647
- return { tag: "other", val: lowercase };
698
+ const lowercase = method.toLowerCase();
699
+ if (supportedMethods.includes(method.toLowerCase())) {
700
+ return { tag: lowercase };
701
+ }
702
+ return { tag: 'other', val: lowercase };
648
703
  }
649
704
 
650
705
  const httpServers = new Map();
651
706
  let httpServerCnt = 0;
652
707
  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);
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.');
695
715
  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
- }
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
+ }
773
+ );
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
+ }
715
795
  }