@bytecodealliance/preview2-shim 0.14.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/nodejs/cli.js CHANGED
@@ -51,8 +51,8 @@ export const exit = {
51
51
 
52
52
  // Stdin is created as a FILE descriptor
53
53
  let stdinStream;
54
- let stdoutStream = outputStreamCreate(STDOUT, 2);
55
- let stderrStream = outputStreamCreate(STDERR, 3);
54
+ let stdoutStream = outputStreamCreate(STDOUT, 1);
55
+ let stderrStream = outputStreamCreate(STDERR, 2);
56
56
 
57
57
  export const stdin = {
58
58
  InputStream,
@@ -60,7 +60,7 @@ export const stdin = {
60
60
  if (!stdinStream)
61
61
  stdinStream = inputStreamCreate(
62
62
  STDIN,
63
- ioCall(INPUT_STREAM_CREATE | STDIN, null, null)
63
+ ioCall(INPUT_STREAM_CREATE | STDIN, null, null),
64
64
  );
65
65
  return stdinStream;
66
66
  },
@@ -1,20 +1,23 @@
1
- import { ioCall, createPoll, resolvedPoll } from "../io/worker-io.js";
2
- import * as calls from "../io/calls.js";
1
+ import { ioCall, createPoll } from "../io/worker-io.js";
2
+ import {
3
+ CLOCKS_NOW,
4
+ CLOCKS_INSTANT_SUBSCRIBE,
5
+ CLOCKS_DURATION_SUBSCRIBE,
6
+ } from "../io/calls.js";
3
7
 
4
8
  export const monotonicClock = {
5
9
  resolution() {
6
10
  return 1n;
7
11
  },
8
12
  now() {
9
- return ioCall(calls.CLOCKS_NOW);
13
+ return ioCall(CLOCKS_NOW, null, null);
10
14
  },
11
15
  subscribeInstant(instant) {
12
- return createPoll(calls.CLOCKS_INSTANT_SUBSCRIBE, null, instant);
16
+ return createPoll(CLOCKS_INSTANT_SUBSCRIBE, null, instant);
13
17
  },
14
18
  subscribeDuration(duration) {
15
19
  duration = BigInt(duration);
16
- if (duration === 0n) return resolvedPoll();
17
- return createPoll(calls.CLOCKS_DURATION_SUBSCRIBE, null, duration);
20
+ return createPoll(CLOCKS_DURATION_SUBSCRIBE, null, duration);
18
21
  },
19
22
  };
20
23
 
@@ -1,11 +1,12 @@
1
1
  import {
2
- ioCall,
2
+ earlyDispose,
3
3
  inputStreamCreate,
4
+ ioCall,
4
5
  outputStreamCreate,
6
+ registerDispose,
5
7
  } from "../io/worker-io.js";
6
8
  import { INPUT_STREAM_CREATE, OUTPUT_STREAM_CREATE } from "../io/calls.js";
7
9
  import { FILE } from "../io/calls.js";
8
- // import { environment } from "./cli.js";
9
10
  import {
10
11
  closeSync,
11
12
  constants,
@@ -35,6 +36,7 @@ import { platform } from "node:process";
35
36
  const symbolDispose = Symbol.dispose || Symbol.for("dispose");
36
37
 
37
38
  const isWindows = platform === "win32";
39
+ const isMac = platform === "darwin";
38
40
 
39
41
  const nsMagnitude = 1_000_000_000_000n;
40
42
  function nsToDateTime(ns) {
@@ -54,16 +56,17 @@ function lookupType(obj) {
54
56
  return "unknown";
55
57
  }
56
58
 
57
- // Note: This should implement per-segment semantics of openAt, but we cannot currently
58
- // due to the lack of support for openat() in Node.js.
59
+ // Note: This should implement per-segment semantics of openAt, but we cannot
60
+ // currently due to the lack of support for openat() in Node.js.
59
61
  // Tracking issue: https://github.com/libuv/libuv/issues/4167
62
+
60
63
  /**
61
64
  * @implements {DescriptorProps}
62
65
  */
63
- let descriptorCnt = 3;
64
66
  class Descriptor {
65
67
  #hostPreopen;
66
68
  #fd;
69
+ #finalizer;
67
70
  #mode;
68
71
  #fullPath;
69
72
 
@@ -83,14 +86,17 @@ class Descriptor {
83
86
  static _create(fd, mode, fullPath) {
84
87
  const descriptor = new Descriptor();
85
88
  descriptor.#fd = fd;
89
+ descriptor.#finalizer = registerDispose(descriptor, null, fd, closeSync);
86
90
  descriptor.#mode = mode;
87
91
  descriptor.#fullPath = fullPath;
88
92
  return descriptor;
89
93
  }
90
94
 
91
- constructor() {
92
- // this id is purely for debugging purposes
93
- this._id = descriptorCnt++;
95
+ [symbolDispose]() {
96
+ if (this.#finalizer) {
97
+ earlyDispose(this.#finalizer);
98
+ this.#finalizer = null;
99
+ }
94
100
  }
95
101
 
96
102
  readViaStream(offset) {
@@ -125,6 +131,7 @@ class Descriptor {
125
131
  try {
126
132
  fdatasyncSync(this.#fd);
127
133
  } catch (e) {
134
+ if (e.code === "EPERM") return;
128
135
  throw convertFsError(e);
129
136
  }
130
137
  }
@@ -219,6 +226,7 @@ class Descriptor {
219
226
  try {
220
227
  fsyncSync(this.#fd);
221
228
  } catch (e) {
229
+ if (e.code === "EPERM") return;
222
230
  throw convertFsError(e);
223
231
  }
224
232
  }
@@ -327,10 +335,31 @@ class Descriptor {
327
335
  if (descriptorFlags.fileIntegritySync) fsOpenFlags |= constants.O_SYNC;
328
336
  if (descriptorFlags.dataIntegritySync) fsOpenFlags |= constants.O_DSYNC;
329
337
  if (!pathFlags.symlinkFollow) fsOpenFlags |= constants.O_NOFOLLOW;
330
-
331
- // Unsupported:
332
- // if (descriptorFlags.requestedWriteSync)
333
- // if (descriptorFlags.mutateDirectory)
338
+ if (descriptorFlags.requestedWriteSync || descriptorFlags.mutateDirectory)
339
+ throw "unsupported";
340
+ // Currently throw to match Wasmtime
341
+ if (descriptorFlags.fileIntegritySync || descriptorFlags.dataIntegritySync)
342
+ throw "unsupported";
343
+ if (isWindows) {
344
+ if (!pathFlags.symlinkFollow && !openFlags.create) {
345
+ let isSymlink = false;
346
+ try {
347
+ isSymlink = lstatSync(fullPath).isSymbolicLink();
348
+ } catch (e) {
349
+ //
350
+ }
351
+ if (isSymlink) throw openFlags.directory ? "not-directory" : "loop";
352
+ }
353
+ if (pathFlags.symlinkFollow && openFlags.directory) {
354
+ let isFile = false;
355
+ try {
356
+ isFile = !statSync(fullPath).isDirectory();
357
+ } catch (e) {
358
+ //
359
+ }
360
+ if (isFile) throw "not-directory";
361
+ }
362
+ }
334
363
  try {
335
364
  const fd = openSync(fullPath, fsOpenFlags);
336
365
  const descriptor = descriptorCreate(
@@ -339,13 +368,17 @@ class Descriptor {
339
368
  fullPath,
340
369
  preopenEntries
341
370
  );
342
- if (fullPath.endsWith("/")) {
371
+ if (fullPath.endsWith("/") && isWindows) {
343
372
  // check if its a directory
344
- if (!descriptor.getType() === "directory") throw "not-directory";
373
+ if (descriptor.getType() !== "directory") {
374
+ descriptor[symbolDispose]();
375
+ throw "not-directory";
376
+ }
345
377
  }
346
378
  return descriptor;
347
379
  } catch (e) {
348
- if (e.code === "ERR_INVALID_ARG_VALUE") throw "invalid";
380
+ if (e.code === "ERR_INVALID_ARG_VALUE")
381
+ throw isWindows ? "no-entry" : "invalid";
349
382
  throw convertFsError(e);
350
383
  }
351
384
  }
@@ -386,8 +419,18 @@ class Descriptor {
386
419
  try {
387
420
  symlinkSync(target, fullPath);
388
421
  } catch (e) {
389
- if (isWindows && (e.code === "EPERM" || e.code === "EEXIST"))
390
- throw "no-entry";
422
+ if (fullPath.endsWith("/") && e.code === "EEXIST") {
423
+ let isDir = false;
424
+ try {
425
+ isDir = statSync(fullPath).isDirectory();
426
+ } catch (_) {
427
+ //
428
+ }
429
+ if (!isDir) throw isWindows ? "no-entry" : "not-directory";
430
+ }
431
+ if (isWindows) {
432
+ if (e.code === "EPERM" || e.code === "EEXIST") throw "no-entry";
433
+ }
391
434
  throw convertFsError(e);
392
435
  }
393
436
  }
@@ -395,8 +438,18 @@ class Descriptor {
395
438
  unlinkFileAt(path) {
396
439
  const fullPath = this.#getFullPath(path, false);
397
440
  try {
441
+ if (fullPath.endsWith("/")) {
442
+ let isDir = false;
443
+ try {
444
+ isDir = statSync(fullPath).isDirectory();
445
+ } catch (e) {
446
+ //
447
+ }
448
+ throw isDir ? (isWindows ? "access" : (isMac ? "not-permitted" : "is-directory")) : "not-directory";
449
+ }
398
450
  unlinkSync(fullPath);
399
451
  } catch (e) {
452
+ if (isWindows && e.code === "EPERM") throw "access";
400
453
  throw convertFsError(e);
401
454
  }
402
455
  }
@@ -484,10 +537,6 @@ class Descriptor {
484
537
  );
485
538
  return descriptor.#fullPath + (subpath.length > 0 ? "/" : "") + subpath;
486
539
  }
487
-
488
- [symbolDispose]() {
489
- if (this.#fd) closeSync(this.#fd);
490
- }
491
540
  }
492
541
  const descriptorCreatePreopen = Descriptor._createPreopen;
493
542
  delete Descriptor._createPreopen;
@@ -496,6 +545,7 @@ delete Descriptor._create;
496
545
 
497
546
  class DirectoryEntryStream {
498
547
  #dir;
548
+ #finalizer;
499
549
  readDirectoryEntry() {
500
550
  let entry;
501
551
  try {
@@ -510,15 +560,23 @@ class DirectoryEntryStream {
510
560
  const type = lookupType(entry);
511
561
  return { name, type };
512
562
  }
513
- [symbolDispose]() {
514
- this.#dir.closeSync();
515
- }
516
-
517
563
  static _create(dir) {
518
564
  const dirStream = new DirectoryEntryStream();
565
+ dirStream.#finalizer = registerDispose(
566
+ dirStream,
567
+ null,
568
+ null,
569
+ dir.closeSync.bind(dir)
570
+ );
519
571
  dirStream.#dir = dir;
520
572
  return dirStream;
521
573
  }
574
+ [symbolDispose]() {
575
+ if (this.#finalizer) {
576
+ earlyDispose(this.#finalizer);
577
+ this.#finalizer = null;
578
+ }
579
+ }
522
580
  }
523
581
  const directoryEntryStreamCreate = DirectoryEntryStream._create;
524
582
  delete DirectoryEntryStream._create;
@@ -615,6 +673,10 @@ function convertFsError(e) {
615
673
  return "unsupported";
616
674
  case "ENOTTY":
617
675
  return "no-tty";
676
+ // windows gives this error for badly structured `//` reads
677
+ // this seems like a slightly better error than unknown given
678
+ // that it's a common footgun
679
+ case -4094:
618
680
  case "ENXIO":
619
681
  return "no-such-device";
620
682
  case "EOVERFLOW":
@@ -1,20 +1,24 @@
1
1
  import {
2
- INPUT_STREAM_DISPOSE,
2
+ FUTURE_DISPOSE,
3
+ FUTURE_SUBSCRIBE,
4
+ FUTURE_TAKE_VALUE,
3
5
  HTTP_CREATE_REQUEST,
6
+ HTTP_OUTGOING_BODY_DISPOSE,
4
7
  HTTP_OUTPUT_STREAM_FINISH,
8
+ HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
9
+ HTTP_SERVER_SET_OUTGOING_RESPONSE,
5
10
  HTTP_SERVER_START,
6
11
  HTTP_SERVER_STOP,
7
12
  OUTPUT_STREAM_CREATE,
8
- FUTURE_GET_VALUE_AND_DISPOSE,
9
- FUTURE_DISPOSE,
10
- HTTP_SERVER_SET_OUTGOING_RESPONSE,
11
- HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
13
+ OUTPUT_STREAM_DISPOSE,
12
14
  } from "../io/calls.js";
13
15
  import {
14
- ioCall,
15
- pollableCreate,
16
+ earlyDispose,
16
17
  inputStreamCreate,
18
+ ioCall,
17
19
  outputStreamCreate,
20
+ pollableCreate,
21
+ registerDispose,
18
22
  registerIncomingHttpHandler,
19
23
  } from "../io/worker-io.js";
20
24
  import { validateHeaderName, validateHeaderValue } from "node:http";
@@ -23,19 +27,14 @@ import { HTTP } from "../io/calls.js";
23
27
  const symbolDispose = Symbol.dispose || Symbol.for("dispose");
24
28
  export const _forbiddenHeaders = new Set(["connection", "keep-alive"]);
25
29
 
26
- let requestCnt = 1;
27
- let responseCnt = 1;
28
- let fieldsCnt = 1;
29
- let futureCnt = 1;
30
-
31
30
  class IncomingBody {
32
31
  #finished = false;
33
- #calledStream = false;
34
- #streamId = undefined;
32
+ #stream = undefined;
35
33
  stream() {
36
- if (this.#calledStream) throw undefined;
37
- this.#calledStream = true;
38
- return inputStreamCreate(HTTP, this.#streamId);
34
+ if (!this.#stream) throw undefined;
35
+ const stream = this.#stream;
36
+ this.#stream = null;
37
+ return stream;
39
38
  }
40
39
  static finish(incomingBody) {
41
40
  if (incomingBody.#finished)
@@ -43,15 +42,10 @@ class IncomingBody {
43
42
  incomingBody.#finished = true;
44
43
  return futureTrailersCreate();
45
44
  }
46
- [symbolDispose]() {
47
- if (!this.#finished) {
48
- ioCall(INPUT_STREAM_DISPOSE | HTTP, this.#streamId);
49
- this.#streamId = undefined;
50
- }
51
- }
45
+ [symbolDispose]() {}
52
46
  static _create(streamId) {
53
47
  const incomingBody = new IncomingBody();
54
- incomingBody.#streamId = streamId;
48
+ incomingBody.#stream = inputStreamCreate(HTTP, streamId);
55
49
  return incomingBody;
56
50
  }
57
51
  }
@@ -83,6 +77,7 @@ class IncomingRequest {
83
77
  consume() {
84
78
  return incomingBodyCreate(this.#streamId);
85
79
  }
80
+ [symbolDispose]() {}
86
81
  static _create(method, pathWithQuery, scheme, authority, headers, streamId) {
87
82
  const incomingRequest = new IncomingRequest();
88
83
  incomingRequest.#method = method;
@@ -98,21 +93,19 @@ const incomingRequestCreate = IncomingRequest._create;
98
93
  delete IncomingRequest._create;
99
94
 
100
95
  class FutureTrailers {
101
- _id = futureCnt++;
102
96
  #requested = false;
103
97
  subscribe() {
104
- return pollableCreate(0);
98
+ return pollableCreate(0, this);
105
99
  }
106
100
  get() {
107
- if (this.#requested)
108
- return { tag: "err" };
101
+ if (this.#requested) return { tag: "err" };
109
102
  this.#requested = true;
110
103
  return {
111
104
  tag: "ok",
112
105
  val: {
113
106
  tag: "ok",
114
107
  val: undefined,
115
- }
108
+ },
116
109
  };
117
110
  }
118
111
  static _create() {
@@ -124,7 +117,6 @@ const futureTrailersCreate = FutureTrailers._create;
124
117
  delete FutureTrailers._create;
125
118
 
126
119
  class OutgoingResponse {
127
- _id = responseCnt++;
128
120
  #body;
129
121
  /** @type {number} */ #statusCode = 200;
130
122
  /** @type {Fields} */ #headers;
@@ -155,7 +147,7 @@ class OutgoingResponse {
155
147
  let contentLength;
156
148
  if (contentLengthValues.length > 0)
157
149
  contentLength = Number(new TextDecoder().decode(contentLengthValues[0]));
158
- this.#body = outgoingBodyCreate(contentLength, true);
150
+ this.#body = outgoingBodyCreate(contentLength);
159
151
  return this.#body;
160
152
  }
161
153
 
@@ -206,7 +198,6 @@ class RequestOptions {
206
198
  }
207
199
 
208
200
  class OutgoingRequest {
209
- _id = requestCnt++;
210
201
  /** @type {Method} */ #method = { tag: "get" };
211
202
  /** @type {Scheme | undefined} */ #scheme = undefined;
212
203
  /** @type {string | undefined} */ #pathWithQuery = undefined;
@@ -223,7 +214,7 @@ class OutgoingRequest {
223
214
  let contentLength;
224
215
  if (contentLengthValues.length > 0)
225
216
  contentLength = Number(new TextDecoder().decode(contentLengthValues[0]));
226
- this.#body = outgoingBodyCreate(contentLength, false);
217
+ this.#body = outgoingBodyCreate(contentLength);
227
218
  }
228
219
  body() {
229
220
  if (this.#bodyRequested) throw new Error("Body already requested");
@@ -310,25 +301,14 @@ class OutgoingBody {
310
301
  #outputStream = null;
311
302
  #outputStreamId = null;
312
303
  #contentLength = undefined;
304
+ #finalizer;
313
305
  write() {
314
- if (!this.#outputStreamId) this.#createOutputStream();
315
306
  // can only call write once
316
307
  const outputStream = this.#outputStream;
317
308
  if (outputStream === null) throw undefined;
318
309
  this.#outputStream = null;
319
310
  return outputStream;
320
311
  }
321
- #createOutputStream() {
322
- this.#outputStream = outputStreamCreate(
323
- HTTP,
324
- (this.#outputStreamId = ioCall(
325
- OUTPUT_STREAM_CREATE | HTTP,
326
- null,
327
- this.#contentLength
328
- ))
329
- );
330
- this.#outputStream[symbolDispose] = () => {};
331
- }
332
312
  /**
333
313
  * @param {OutgoingBody} body
334
314
  * @param {Fields | undefined} trailers
@@ -337,19 +317,43 @@ class OutgoingBody {
337
317
  if (trailers) throw { tag: "internal-error", val: "trailers unsupported" };
338
318
  // this will verify content length, and also verify not already finished
339
319
  // throwing errors as appropriate
340
- if (body.#outputStreamId)
341
- ioCall(HTTP_OUTPUT_STREAM_FINISH, body.#outputStreamId, null);
320
+ ioCall(HTTP_OUTPUT_STREAM_FINISH, body.#outputStreamId, null);
342
321
  }
343
322
  static _outputStreamId(outgoingBody) {
344
323
  return outgoingBody.#outputStreamId;
345
324
  }
346
- static _create(contentLength, createBodyStream) {
325
+ static _create(contentLength) {
347
326
  const outgoingBody = new OutgoingBody();
348
327
  outgoingBody.#contentLength = contentLength;
349
- if (createBodyStream) outgoingBody.#createOutputStream();
328
+ outgoingBody.#outputStreamId = ioCall(
329
+ OUTPUT_STREAM_CREATE | HTTP,
330
+ null,
331
+ outgoingBody.#contentLength
332
+ );
333
+ outgoingBody.#outputStream = outputStreamCreate(
334
+ HTTP,
335
+ outgoingBody.#outputStreamId
336
+ );
337
+ outgoingBody.#finalizer = registerDispose(
338
+ outgoingBody,
339
+ null,
340
+ outgoingBody.#outputStreamId,
341
+ outgoingBodyDispose
342
+ );
350
343
  return outgoingBody;
351
344
  }
345
+ [symbolDispose]() {
346
+ if (this.#finalizer) {
347
+ earlyDispose(this.#finalizer);
348
+ this.#finalizer = null;
349
+ }
350
+ }
352
351
  }
352
+
353
+ function outgoingBodyDispose(id) {
354
+ ioCall(HTTP_OUTGOING_BODY_DISPOSE, id, null);
355
+ }
356
+
353
357
  const outgoingBodyOutputStreamId = OutgoingBody._outputStreamId;
354
358
  delete OutgoingBody._outputStreamId;
355
359
 
@@ -357,10 +361,9 @@ const outgoingBodyCreate = OutgoingBody._create;
357
361
  delete OutgoingBody._create;
358
362
 
359
363
  class IncomingResponse {
360
- _id = responseCnt++;
361
364
  /** @type {Fields} */ #headers = undefined;
362
365
  #status = 0;
363
- /** @type {number} */ #bodyStreamId;
366
+ /** @type {number} */ #bodyStream;
364
367
  status() {
365
368
  return this.#status;
366
369
  }
@@ -368,22 +371,19 @@ class IncomingResponse {
368
371
  return this.#headers;
369
372
  }
370
373
  consume() {
371
- if (this.#bodyStreamId === undefined) throw undefined;
372
- const bodyStreamId = this.#bodyStreamId;
373
- this.#bodyStreamId = undefined;
374
- return incomingBodyCreate(bodyStreamId);
374
+ if (this.#bodyStream === undefined) throw undefined;
375
+ const bodyStream = this.#bodyStream;
376
+ this.#bodyStream = undefined;
377
+ return bodyStream;
375
378
  }
376
379
  [symbolDispose]() {
377
- if (this.#bodyStreamId) {
378
- ioCall(INPUT_STREAM_DISPOSE | HTTP, this.#bodyStreamId);
379
- this.#bodyStreamId = undefined;
380
- }
380
+ if (this.#bodyStream) this.#bodyStream[symbolDispose]();
381
381
  }
382
382
  static _create(status, headers, bodyStreamId) {
383
383
  const res = new IncomingResponse();
384
384
  res.#status = status;
385
385
  res.#headers = headers;
386
- res.#bodyStreamId = bodyStreamId;
386
+ res.#bodyStream = incomingBodyCreate(bodyStreamId);
387
387
  return res;
388
388
  }
389
389
  }
@@ -392,38 +392,29 @@ const incomingResponseCreate = IncomingResponse._create;
392
392
  delete IncomingResponse._create;
393
393
 
394
394
  class FutureIncomingResponse {
395
- _id = futureCnt++;
396
- #pollId;
395
+ #id;
396
+ #finalizer;
397
397
  subscribe() {
398
- if (this.#pollId) return pollableCreate(this.#pollId);
399
- // 0 poll is immediately resolving
400
- return pollableCreate(0);
398
+ return pollableCreate(
399
+ ioCall(FUTURE_SUBSCRIBE | HTTP, this.#id, null),
400
+ this
401
+ );
401
402
  }
402
403
  get() {
403
- // already taken
404
- if (!this.#pollId) return { tag: "err" };
405
- const ret = ioCall(FUTURE_GET_VALUE_AND_DISPOSE | HTTP, this.#pollId);
406
- if (!ret) return;
407
- this.#pollId = undefined;
408
- if (ret.error) return { tag: "ok", val: { tag: "err", val: ret.value } };
409
- const { status, headers, bodyStreamId } = ret.value;
410
- const textEncoder = new TextEncoder();
411
- return {
412
- tag: "ok",
413
- val: {
414
- tag: "ok",
415
- val: incomingResponseCreate(
416
- status,
417
- fieldsFromEntriesChecked(
418
- headers.map(([key, val]) => [key, textEncoder.encode(val)])
419
- ),
420
- bodyStreamId
404
+ const ret = ioCall(FUTURE_TAKE_VALUE | HTTP, this.#id, null);
405
+ if (ret === undefined) return undefined;
406
+ if (ret.tag === "ok" && ret.val.tag === "ok") {
407
+ const textEncoder = new TextEncoder();
408
+ const { status, headers, bodyStreamId } = ret.val.val;
409
+ ret.val.val = incomingResponseCreate(
410
+ status,
411
+ fieldsFromEntriesChecked(
412
+ headers.map(([key, val]) => [key, textEncoder.encode(val)])
421
413
  ),
422
- },
423
- };
424
- }
425
- [symbolDispose]() {
426
- if (this.#pollId) ioCall(FUTURE_DISPOSE | HTTP, this.#pollId);
414
+ bodyStreamId
415
+ );
416
+ }
417
+ return ret;
427
418
  }
428
419
  static _create(
429
420
  method,
@@ -437,7 +428,7 @@ class FutureIncomingResponse {
437
428
  firstByteTimeout
438
429
  ) {
439
430
  const res = new FutureIncomingResponse();
440
- res.#pollId = ioCall(HTTP_CREATE_REQUEST, null, {
431
+ res.#id = ioCall(HTTP_CREATE_REQUEST, null, {
441
432
  method,
442
433
  scheme,
443
434
  authority,
@@ -448,15 +439,30 @@ class FutureIncomingResponse {
448
439
  betweenBytesTimeout,
449
440
  firstByteTimeout,
450
441
  });
442
+ res.#finalizer = registerDispose(
443
+ res,
444
+ null,
445
+ res.#id,
446
+ futureIncomingResponseDispose
447
+ );
451
448
  return res;
452
449
  }
450
+ [symbolDispose]() {
451
+ if (this.#finalizer) {
452
+ earlyDispose(this.#finalizer);
453
+ this.#finalizer = null;
454
+ }
455
+ }
456
+ }
457
+
458
+ function futureIncomingResponseDispose(id) {
459
+ ioCall(FUTURE_DISPOSE | HTTP, id, null);
453
460
  }
454
461
 
455
462
  const futureIncomingResponseCreate = FutureIncomingResponse._create;
456
463
  delete FutureIncomingResponse._create;
457
464
 
458
465
  class Fields {
459
- _id = fieldsCnt++;
460
466
  #immutable = false;
461
467
  /** @type {[string, Uint8Array[]][]} */ #entries = [];
462
468
  /** @type {Map<string, [string, Uint8Array[]][]>} */ #table = new Map();
@@ -664,26 +670,29 @@ export class HTTPServer {
664
670
  ),
665
671
  streamId
666
672
  );
673
+ let outgoingBodyStreamId;
667
674
  const responseOutparam = responseOutparamCreate((response) => {
668
675
  if (response.tag === "ok") {
669
676
  const outgoingResponse = response.val;
670
677
  const statusCode = outgoingResponse.statusCode();
671
678
  const headers = outgoingResponse.headers().entries();
672
679
  const body = outgoingResponseBody(outgoingResponse);
673
- const streamId = outgoingBodyOutputStreamId(body);
680
+ outgoingBodyStreamId = outgoingBodyOutputStreamId(body);
674
681
  ioCall(HTTP_SERVER_SET_OUTGOING_RESPONSE, responseId, {
675
682
  statusCode,
676
683
  headers,
677
- streamId,
684
+ streamId: outgoingBodyStreamId,
678
685
  });
679
686
  } else {
680
- ioCall(HTTP_SERVER_CLEAR_OUTGOING_RESPONSE, responseId);
681
- console.error("TODO: handle outparam error");
682
- console.error(response);
687
+ ioCall(HTTP_SERVER_CLEAR_OUTGOING_RESPONSE, responseId, null);
688
+ console.error(response.val);
683
689
  process.exit(1);
684
690
  }
685
691
  });
686
692
  incomingHandler.handle(request, responseOutparam);
693
+ if (outgoingBodyStreamId) {
694
+ ioCall(OUTPUT_STREAM_DISPOSE, outgoingBodyStreamId, null);
695
+ }
687
696
  }
688
697
  );
689
698
  }
@@ -694,6 +703,7 @@ export class HTTPServer {
694
703
  }
695
704
  stop() {
696
705
  clearInterval(this.#liveEventLoopInterval);
697
- ioCall(HTTP_SERVER_STOP, this.#id);
706
+ ioCall(HTTP_SERVER_STOP, this.#id, null);
707
+ httpServers.delete(this.#id);
698
708
  }
699
709
  }
@@ -15,5 +15,3 @@ export {
15
15
  sockets,
16
16
  cli
17
17
  }
18
-
19
- export { WasiSockets } from "./sockets/wasi-sockets.js";