@bytecodealliance/preview3-shim 0.6.0 → 0.6.1

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,4 +1,4 @@
1
- import { Server, Socket } from "node:net";
1
+ import net, { Server, Socket } from "node:net";
2
2
  import { once } from "node:events";
3
3
  import { isMainThread, parentPort } from "worker_threads";
4
4
  //#region dist/nodejs/workers/resource-worker.js
@@ -252,6 +252,7 @@ var SocketError = class SocketError extends Error {
252
252
  };
253
253
  //#endregion
254
254
  //#region dist/nodejs/workers/tcp-worker.js
255
+ const BoundSocket = net.BoundSocket;
255
256
  const sockets = /* @__PURE__ */ new Map();
256
257
  let NEXT_SOCKET_ID = 0n;
257
258
  Router().beforeAll((msg) => {
@@ -263,6 +264,7 @@ function handleTcpCreate({ family }) {
263
264
  family,
264
265
  tcp: null,
265
266
  server: null,
267
+ bound: null,
266
268
  acceptWriter: null,
267
269
  backlog: 128,
268
270
  localAddress: null,
@@ -283,6 +285,17 @@ async function handleTcpBind({ socketId, localAddress }) {
283
285
  err.code = "EADDRINUSE";
284
286
  throw err;
285
287
  }
288
+ if (BoundSocket) {
289
+ const bound = new BoundSocket({
290
+ host: address,
291
+ port,
292
+ ipv6Only: family === "ipv6"
293
+ });
294
+ const boundAddress = bound.address();
295
+ socket.bound = bound;
296
+ socket.localAddress = makeIpAddress(boundAddress.family.toLowerCase(), boundAddress.address, boundAddress.port);
297
+ return;
298
+ }
286
299
  const server = socket.server = createTcpServer(socket);
287
300
  const onListening = once(server, "listening");
288
301
  server.listen({
@@ -300,16 +313,20 @@ async function handleTcpConnect({ socketId, remoteAddress }) {
300
313
  const socket = sockets.get(socketId);
301
314
  const host = serializeIpAddress(remoteAddress);
302
315
  const port = remoteAddress.val.port;
303
- const localAddress = socket.localAddress;
316
+ const { bound, localAddress } = socket;
317
+ socket.bound = null;
304
318
  await closeTcpServer(socket);
305
- const tcp = socket.tcp = new Socket({ allowHalfOpen: true });
319
+ const tcp = socket.tcp = new Socket({
320
+ handle: bound ?? void 0,
321
+ allowHalfOpen: true
322
+ });
306
323
  tcp.setKeepAlive(socket.keepAliveEnabled, socket.keepAliveIdleTime);
307
324
  const onConnect = once(tcp, "connect");
308
325
  tcp.connect({
309
326
  port,
310
327
  host,
311
- localAddress: localAddress ? serializeIpAddress(localAddress) : void 0,
312
- localPort: localAddress?.val.port
328
+ localAddress: !bound && localAddress ? serializeIpAddress(localAddress) : void 0,
329
+ localPort: !bound ? localAddress?.val.port : void 0
313
330
  });
314
331
  await onConnect;
315
332
  }
@@ -322,7 +339,13 @@ async function handleTcpListen({ socketId, stream }) {
322
339
  if (!server) {
323
340
  server = socket.server = createTcpServer(socket);
324
341
  const onListening = once(server, "listening");
325
- server.listen({
342
+ const bound = socket.bound;
343
+ socket.bound = null;
344
+ if (bound) server.listen({
345
+ handle: bound,
346
+ backlog
347
+ });
348
+ else server.listen({
326
349
  host: family === "ipv6" ? "::" : "0.0.0.0",
327
350
  port: 0,
328
351
  backlog,
@@ -352,6 +375,7 @@ function createTcpServer(socket) {
352
375
  backlog: socket.backlog,
353
376
  tcp: conn,
354
377
  server: null,
378
+ bound: null,
355
379
  acceptWriter: null,
356
380
  localAddress: makeIpAddress(socket.family, conn.localAddress, conn.localPort),
357
381
  keepAliveEnabled: socket.keepAliveEnabled,
@@ -466,6 +490,10 @@ async function handleTcpReceive({ socketId, stream }) {
466
490
  }
467
491
  function cleanupDisposedSocket(socketId, socket) {
468
492
  if (!socket.disposed || socket.activeStreams > 0) return;
493
+ if (socket.bound) {
494
+ socket.bound.close();
495
+ socket.bound = null;
496
+ }
469
497
  if (socket.server) socket.server.close();
470
498
  if (socket.tcp) socket.tcp.destroy();
471
499
  sockets.delete(socketId);
@@ -1,8 +1,13 @@
1
- import { Socket, Server } from "node:net";
1
+ import net, { Socket, Server } from "node:net";
2
2
  import { once } from "node:events";
3
3
  import { Router } from "../workers/resource-worker.js";
4
4
  import { serializeIpAddress, makeIpAddress, ipAddressConflict } from "../sockets/address.js";
5
5
  import { SocketError } from "../sockets/error.js";
6
+ // node:net gained a standalone bind API in v26.4 (also on v24.x). It holds the
7
+ // endpoint with a real bind(2) until a Socket or Server adopts it, so connect and
8
+ // listen never have to release the port and reacquire it. Older releases fall
9
+ // back to reserving the endpoint with a listening server.
10
+ const BoundSocket = net.BoundSocket;
6
11
  // Socket instances stored by ID
7
12
  const sockets = new Map();
8
13
  // Unique IDs for sockets
@@ -34,6 +39,7 @@ function handleTcpCreate({ family }) {
34
39
  family,
35
40
  tcp: null,
36
41
  server: null,
42
+ bound: null,
37
43
  acceptWriter: null,
38
44
  backlog: 128,
39
45
  localAddress: null,
@@ -56,8 +62,15 @@ async function handleTcpBind({ socketId, localAddress }) {
56
62
  err.code = "EADDRINUSE";
57
63
  throw err;
58
64
  }
59
- // node:net has no public standalone TCP bind API. Keep a server open
60
- // to reserve the endpoint, then reuse it for listen or close it for connect.
65
+ if (BoundSocket) {
66
+ const bound = new BoundSocket({ host: address, port, ipv6Only: family === "ipv6" });
67
+ const boundAddress = bound.address();
68
+ socket.bound = bound;
69
+ socket.localAddress = makeIpAddress(boundAddress.family.toLowerCase(), boundAddress.address, boundAddress.port);
70
+ return;
71
+ }
72
+ // Without a standalone bind API, keep a server open to reserve the endpoint,
73
+ // then reuse it for listen or close it for connect.
61
74
  const server = (socket.server = createTcpServer(socket));
62
75
  const onListening = once(server, "listening");
63
76
  server.listen({
@@ -78,19 +91,24 @@ async function handleTcpConnect({ socketId, remoteAddress }) {
78
91
  const socket = sockets.get(socketId);
79
92
  const host = serializeIpAddress(remoteAddress);
80
93
  const port = remoteAddress.val.port;
81
- const localAddress = socket.localAddress;
94
+ const { bound, localAddress } = socket;
95
+ socket.bound = null;
82
96
  await closeTcpServer(socket);
83
- const tcp = (socket.tcp = new Socket({
84
- allowHalfOpen: true,
85
- }));
97
+ // An adopted bound handle already owns the local endpoint, so the connect
98
+ // must not name it again. Without one, the reservation server released the
99
+ // endpoint just above and the client re-binds it here.
100
+ // @types/node does not yet declare the `handle` option, so it is passed via a
101
+ // variable to skip the object literal excess property check.
102
+ const socketOptions = { handle: bound ?? undefined, allowHalfOpen: true };
103
+ const tcp = (socket.tcp = new Socket(socketOptions));
86
104
  tcp.setKeepAlive(socket.keepAliveEnabled, socket.keepAliveIdleTime);
87
105
  // TODO(tandr): Add lookup
88
106
  const onConnect = once(tcp, "connect");
89
107
  tcp.connect({
90
108
  port,
91
109
  host,
92
- localAddress: localAddress ? serializeIpAddress(localAddress) : undefined,
93
- localPort: localAddress?.val.port,
110
+ localAddress: !bound && localAddress ? serializeIpAddress(localAddress) : undefined,
111
+ localPort: !bound ? localAddress?.val.port : undefined,
94
112
  });
95
113
  // events.once rejects when the emitter produces an error while waiting and
96
114
  // removes its temporary listeners after settling. A separate error promise
@@ -107,12 +125,19 @@ async function handleTcpListen({ socketId, stream }) {
107
125
  if (!server) {
108
126
  server = socket.server = createTcpServer(socket);
109
127
  const onListening = once(server, "listening");
110
- server.listen({
111
- host: family === "ipv6" ? "::" : "0.0.0.0",
112
- port: 0,
113
- backlog,
114
- ipv6Only: family === "ipv6",
115
- });
128
+ const bound = socket.bound;
129
+ socket.bound = null;
130
+ if (bound) {
131
+ server.listen({ handle: bound, backlog });
132
+ }
133
+ else {
134
+ server.listen({
135
+ host: family === "ipv6" ? "::" : "0.0.0.0",
136
+ port: 0,
137
+ backlog,
138
+ ipv6Only: family === "ipv6",
139
+ });
140
+ }
116
141
  await onListening;
117
142
  }
118
143
  const addr = server.address();
@@ -141,6 +166,7 @@ function createTcpServer(socket) {
141
166
  backlog: socket.backlog,
142
167
  tcp: conn,
143
168
  server: null,
169
+ bound: null,
144
170
  acceptWriter: null,
145
171
  localAddress: makeIpAddress(socket.family, conn.localAddress, conn.localPort),
146
172
  keepAliveEnabled: socket.keepAliveEnabled,
@@ -286,6 +312,10 @@ function cleanupDisposedSocket(socketId, socket) {
286
312
  if (!socket.disposed || socket.activeStreams > 0) {
287
313
  return;
288
314
  }
315
+ if (socket.bound) {
316
+ socket.bound.close();
317
+ socket.bound = null;
318
+ }
289
319
  if (socket.server) {
290
320
  socket.server.close();
291
321
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytecodealliance/preview3-shim",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "WASI Preview3 shim for JS environments",
5
5
  "homepage": "https://github.com/bytecodealliance/jco/blob/main/packages/preview3-shim/#readme",
6
6
  "bugs": {
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@bytecodealliance/jco-node-fs": "0.3.2",
52
- "@bytecodealliance/preview2-shim": "^0.24.1"
52
+ "@bytecodealliance/preview2-shim": "^0.25.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^24.12.4",
@@ -57,7 +57,7 @@
57
57
  "rolldown": "^1.2.4",
58
58
  "typescript": "7.0.2",
59
59
  "vite": "^8.0.16",
60
- "vitest": "^4.1.10"
60
+ "vitest": "^4.1.11"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsc --project tsconfig.build.json && node scripts/build-workers.mjs",
@@ -68,7 +68,7 @@
68
68
  "lint": "oxlint",
69
69
  "lint:fix": "oxlint --fix",
70
70
  "pretest": "pnpm run build",
71
- "test": "vitest --run",
71
+ "test": "vitest --run -c test/vitest.ts",
72
72
  "prebench": "pnpm run build",
73
73
  "bench": "vitest bench --run"
74
74
  }