@bytecodealliance/preview3-shim 0.2.1 → 0.2.2
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/dist/nodejs/cli.js +2 -1
- package/dist/nodejs/filesystem/descriptor.js +2 -1
- package/dist/nodejs/http/client.js +2 -1
- package/dist/nodejs/http/server.js +2 -1
- package/dist/nodejs/sockets/tcp.js +2 -1
- package/dist/nodejs/sockets/udp.js +2 -1
- package/dist/nodejs/workers/cli-worker.bundle.js +88 -0
- package/dist/nodejs/workers/filesystem-worker.bundle.js +236 -0
- package/dist/nodejs/workers/http-worker.bundle.js +403 -0
- package/dist/nodejs/workers/resource-worker.d.ts +1 -1
- package/dist/nodejs/workers/resource-worker.js +5 -5
- package/dist/nodejs/workers/tcp-worker.bundle.js +496 -0
- package/dist/nodejs/workers/udp-worker.bundle.js +270 -0
- package/package.json +3 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { once } from "node:events";
|
|
2
|
+
import { isMainThread, parentPort } from "worker_threads";
|
|
3
|
+
import dgram from "node:dgram";
|
|
4
|
+
//#region dist/nodejs/workers/resource-worker.js
|
|
5
|
+
function Router() {
|
|
6
|
+
const _hooks = {
|
|
7
|
+
beforeAll: null,
|
|
8
|
+
ops: /* @__PURE__ */ new Map()
|
|
9
|
+
};
|
|
10
|
+
if (isMainThread || !parentPort) throw new Error("Router can only be used in worker threads");
|
|
11
|
+
parentPort.on("message", async (msg) => {
|
|
12
|
+
const { _reply, _condvar, ...rest } = msg;
|
|
13
|
+
let result, error, transfer = [];
|
|
14
|
+
try {
|
|
15
|
+
if (_hooks.beforeAll) _hooks.beforeAll(rest);
|
|
16
|
+
const handler = _hooks.ops.get(rest.op);
|
|
17
|
+
if (!handler) throw new Error(`Unknown op ${rest.op}`);
|
|
18
|
+
const outcome = await handler(rest);
|
|
19
|
+
if (outcome && Array.isArray(outcome.transferable)) {
|
|
20
|
+
result = outcome.result;
|
|
21
|
+
transfer = outcome.transferable;
|
|
22
|
+
} else result = outcome;
|
|
23
|
+
} catch (err) {
|
|
24
|
+
error = serializeError(err);
|
|
25
|
+
}
|
|
26
|
+
_reply.postMessage({
|
|
27
|
+
result,
|
|
28
|
+
error
|
|
29
|
+
}, transfer);
|
|
30
|
+
_reply.close();
|
|
31
|
+
if (_condvar) notify(_condvar);
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
beforeAll(fn) {
|
|
35
|
+
_hooks.beforeAll = fn;
|
|
36
|
+
return this;
|
|
37
|
+
},
|
|
38
|
+
op(name, fn) {
|
|
39
|
+
_hooks.ops.set(name, fn);
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function serializeError(err) {
|
|
45
|
+
if (!(err instanceof Error)) return err;
|
|
46
|
+
const serialized = {
|
|
47
|
+
__resourceWorkerError: true,
|
|
48
|
+
name: err.name,
|
|
49
|
+
message: err.message,
|
|
50
|
+
stack: err.stack
|
|
51
|
+
};
|
|
52
|
+
for (const key of Reflect.ownKeys(err)) serialized[key] = err[key];
|
|
53
|
+
if (err.cause !== void 0) serialized.cause = serializeError(err.cause);
|
|
54
|
+
return serialized;
|
|
55
|
+
}
|
|
56
|
+
function notify(condvar) {
|
|
57
|
+
const lock = new Int32Array(condvar);
|
|
58
|
+
Atomics.store(lock, 0, 1);
|
|
59
|
+
Atomics.notify(lock, 0, 1);
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region dist/nodejs/sockets/address.js
|
|
63
|
+
const tupleToIpv6 = (segments) => {
|
|
64
|
+
if (segments.length !== 8) return null;
|
|
65
|
+
return segments.map((segment) => segment.toString(16)).join(":");
|
|
66
|
+
};
|
|
67
|
+
const tupleToIpv4 = (segments) => {
|
|
68
|
+
if (segments.length !== 4) return null;
|
|
69
|
+
return segments.map((segment) => segment.toString(10)).join(".");
|
|
70
|
+
};
|
|
71
|
+
const ipv6ToTuple = (ipv6) => {
|
|
72
|
+
const [lhs, rhs = ""] = ipv6.includes("::") ? ipv6.split("::") : [ipv6];
|
|
73
|
+
const lhsParts = lhs === "" ? [] : lhs.split(":");
|
|
74
|
+
const rhsParts = rhs === "" ? [] : rhs.split(":");
|
|
75
|
+
return [
|
|
76
|
+
...lhsParts,
|
|
77
|
+
...Array(8 - lhsParts.length - rhsParts.length).fill(0),
|
|
78
|
+
...rhsParts
|
|
79
|
+
].map((segment) => {
|
|
80
|
+
const val = parseInt(segment, 16);
|
|
81
|
+
if (val < 0 || val > 65535 || Number.isNaN(val)) throw new Error(`Invalid IPv6 segment: ${segment}`);
|
|
82
|
+
return val;
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const ipv4ToTuple = (ipv4) => {
|
|
86
|
+
return ipv4.split(".").map((segment) => {
|
|
87
|
+
const val = parseInt(segment, 10);
|
|
88
|
+
if (val < 0 || val > 255 || Number.isNaN(val)) throw new Error(`Invalid IPv4 segment: ${segment}`);
|
|
89
|
+
return val;
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
const isWildcardIpAddress = ({ val: { address } }) => address.every((b) => b === 0);
|
|
93
|
+
const isLoopbackIpAddress = ({ tag, val: { address } }) => tag === "ipv4" && address[0] === 127 || tag === "ipv6" && address.slice(0, 7).every((segment) => segment === 0) && address[7] === 1;
|
|
94
|
+
const ipAddressConflict = (a, b) => a.tag === b.tag && Number(a.val.port) === Number(b.val.port) && Number(a.val.port) !== 0 && (isWildcardIpAddress(a) || isWildcardIpAddress(b) || a.val.address.every((segment, idx) => segment === b.val.address[idx]));
|
|
95
|
+
/**
|
|
96
|
+
* Serialize a socket‐address to text.
|
|
97
|
+
* @param {{tag:string,val:{address:number[]}}} ipAddr
|
|
98
|
+
* @returns {string|null}
|
|
99
|
+
*/
|
|
100
|
+
const serializeIpAddress = ({ tag, val: { address } }) => {
|
|
101
|
+
switch (tag) {
|
|
102
|
+
case "ipv4": return tupleToIpv4(address);
|
|
103
|
+
case "ipv6": return tupleToIpv6(address);
|
|
104
|
+
default: throw new Error(`Unknown IP tag: ${tag}`);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Create a socket‐address object.
|
|
109
|
+
* @param {'ipv4'|'ipv6'} family
|
|
110
|
+
* @param {string} host IP text
|
|
111
|
+
* @param {number|bigint} port
|
|
112
|
+
* @returns {{tag:string,val:Object}}
|
|
113
|
+
*/
|
|
114
|
+
const makeIpAddress = (family, host, port) => {
|
|
115
|
+
let address;
|
|
116
|
+
switch (family) {
|
|
117
|
+
case "ipv4":
|
|
118
|
+
address = ipv4ToTuple(host);
|
|
119
|
+
break;
|
|
120
|
+
case "ipv6":
|
|
121
|
+
address = ipv6ToTuple(host);
|
|
122
|
+
break;
|
|
123
|
+
default: throw new Error(`Unknown IP family: ${family}`);
|
|
124
|
+
}
|
|
125
|
+
const base = {
|
|
126
|
+
address,
|
|
127
|
+
port
|
|
128
|
+
};
|
|
129
|
+
return family === "ipv4" ? {
|
|
130
|
+
tag: "ipv4",
|
|
131
|
+
val: base
|
|
132
|
+
} : {
|
|
133
|
+
tag: "ipv6",
|
|
134
|
+
val: {
|
|
135
|
+
...base,
|
|
136
|
+
flowInfo: 0,
|
|
137
|
+
scopeId: 0
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region dist/nodejs/workers/udp-worker.js
|
|
143
|
+
const sockets = /* @__PURE__ */ new Map();
|
|
144
|
+
let NEXT_SOCKET_ID = 0n;
|
|
145
|
+
function noLookup(ip, _opts, cb) {
|
|
146
|
+
cb(null, ip);
|
|
147
|
+
}
|
|
148
|
+
Router().beforeAll((msg) => {
|
|
149
|
+
if (msg.op !== "udp-create" && !sockets.has(msg.socketId)) throw new Error("Invalid socket ID");
|
|
150
|
+
}).op("udp-create", handleCreate).op("udp-bind", handleBind).op("udp-connect", handleConnect).op("udp-disconnect", handleDisconnect).op("udp-send", handleSend).op("udp-receive", handleReceive).op("udp-get-local-address", handleGetLocal).op("udp-set-unicast-hop-limit", handleSetHop).op("udp-recv-buffer-size", handleRecvBuffer).op("udp-send-buffer-size", handleSendBuffer).op("udp-dispose", handleDispose);
|
|
151
|
+
function handleCreate({ family }) {
|
|
152
|
+
const socketId = NEXT_SOCKET_ID++;
|
|
153
|
+
const socket = {
|
|
154
|
+
family,
|
|
155
|
+
connected: null,
|
|
156
|
+
localAddress: null,
|
|
157
|
+
receiveQueue: []
|
|
158
|
+
};
|
|
159
|
+
socket.udp = createSocket(socket);
|
|
160
|
+
sockets.set(socketId, socket);
|
|
161
|
+
return { socketId };
|
|
162
|
+
}
|
|
163
|
+
function createSocket(socket) {
|
|
164
|
+
const type = socket.family === "ipv6" ? "udp6" : "udp4";
|
|
165
|
+
const ipv6Only = socket.family === "ipv6";
|
|
166
|
+
const udp = dgram.createSocket({
|
|
167
|
+
type,
|
|
168
|
+
ipv6Only,
|
|
169
|
+
reuseAddr: false,
|
|
170
|
+
lookup: noLookup
|
|
171
|
+
});
|
|
172
|
+
udp.on("message", (msg, rinfo) => socket.receiveQueue.push({
|
|
173
|
+
msg,
|
|
174
|
+
rinfo
|
|
175
|
+
}));
|
|
176
|
+
udp.on("error", () => {});
|
|
177
|
+
return udp;
|
|
178
|
+
}
|
|
179
|
+
async function handleBind({ socketId, localAddress }) {
|
|
180
|
+
const socket = sockets.get(socketId);
|
|
181
|
+
if ([...sockets].some(([id, { localAddress: boundAddress }]) => id !== socketId && boundAddress && ipAddressConflict(boundAddress, localAddress))) {
|
|
182
|
+
const err = /* @__PURE__ */ new Error("EADDRINUSE");
|
|
183
|
+
err.code = "EADDRINUSE";
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
const addr = serializeIpAddress(localAddress);
|
|
187
|
+
const port = localAddress.val.port;
|
|
188
|
+
const onListening = once(socket.udp, "listening");
|
|
189
|
+
const onError = once(socket.udp, "error").then(([err]) => {
|
|
190
|
+
throw err;
|
|
191
|
+
});
|
|
192
|
+
socket.udp.bind(port, addr);
|
|
193
|
+
await Promise.race([onListening, onError]);
|
|
194
|
+
const boundAddr = socket.udp.address();
|
|
195
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
196
|
+
}
|
|
197
|
+
async function handleConnect({ socketId, remoteAddress }) {
|
|
198
|
+
const socket = sockets.get(socketId);
|
|
199
|
+
const preserveLocalAddress = socket.connected && socket.localAddress && isLoopbackIpAddress(socket.connected) && isLoopbackIpAddress(remoteAddress);
|
|
200
|
+
if (socket.connected) {
|
|
201
|
+
const localAddress = preserveLocalAddress ? socket.localAddress : null;
|
|
202
|
+
await new Promise((resolve) => socket.udp.close(resolve));
|
|
203
|
+
socket.udp = createSocket(socket);
|
|
204
|
+
if (localAddress) await handleBind({
|
|
205
|
+
socketId,
|
|
206
|
+
localAddress
|
|
207
|
+
});
|
|
208
|
+
else socket.localAddress = null;
|
|
209
|
+
}
|
|
210
|
+
const addr = serializeIpAddress(remoteAddress);
|
|
211
|
+
const port = remoteAddress.val.port;
|
|
212
|
+
await new Promise((resolve, reject) => {
|
|
213
|
+
socket.udp.connect(port, addr, (err) => err ? reject(err) : resolve());
|
|
214
|
+
});
|
|
215
|
+
const boundAddr = socket.udp.address();
|
|
216
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
217
|
+
socket.connected = remoteAddress;
|
|
218
|
+
}
|
|
219
|
+
function handleDisconnect({ socketId }) {
|
|
220
|
+
const socket = sockets.get(socketId);
|
|
221
|
+
socket.udp.disconnect();
|
|
222
|
+
socket.connected = null;
|
|
223
|
+
}
|
|
224
|
+
async function handleSend({ socketId, data, remoteAddress }) {
|
|
225
|
+
const socket = sockets.get(socketId);
|
|
226
|
+
const isConnected = socket.connected != null;
|
|
227
|
+
await new Promise((resolve, reject) => {
|
|
228
|
+
if (isConnected) socket.udp.send(data, (err) => err ? reject(err) : resolve());
|
|
229
|
+
else {
|
|
230
|
+
const addr = serializeIpAddress(remoteAddress);
|
|
231
|
+
const port = remoteAddress.val.port;
|
|
232
|
+
socket.udp.send(data, port, addr, (err) => err ? reject(err) : resolve());
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
const boundAddr = socket.udp.address();
|
|
236
|
+
socket.localAddress = makeIpAddress(socket.family, boundAddr.address, boundAddr.port);
|
|
237
|
+
}
|
|
238
|
+
async function handleReceive({ socketId }) {
|
|
239
|
+
const socket = sockets.get(socketId);
|
|
240
|
+
if (socket.receiveQueue.length === 0) await Promise.race([once(socket.udp, "message"), once(socket.udp, "error").then(([err]) => {
|
|
241
|
+
throw err;
|
|
242
|
+
})]);
|
|
243
|
+
const { msg, rinfo } = socket.receiveQueue.shift();
|
|
244
|
+
return {
|
|
245
|
+
data: msg,
|
|
246
|
+
remoteAddress: makeIpAddress(socket.family, rinfo.address, rinfo.port)
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function handleGetLocal({ socketId }) {
|
|
250
|
+
const socket = sockets.get(socketId);
|
|
251
|
+
const addr = socket.udp.address();
|
|
252
|
+
return makeIpAddress(socket.family, addr.address, addr.port);
|
|
253
|
+
}
|
|
254
|
+
function handleSetHop({ socketId, value }) {
|
|
255
|
+
sockets.get(socketId).udp.setTTL(value);
|
|
256
|
+
}
|
|
257
|
+
function handleRecvBuffer({ socketId }) {
|
|
258
|
+
const socket = sockets.get(socketId);
|
|
259
|
+
return BigInt(socket.udp.getRecvBufferSize());
|
|
260
|
+
}
|
|
261
|
+
function handleSendBuffer({ socketId }) {
|
|
262
|
+
const socket = sockets.get(socketId);
|
|
263
|
+
return BigInt(socket.udp.getSendBufferSize());
|
|
264
|
+
}
|
|
265
|
+
function handleDispose({ socketId }) {
|
|
266
|
+
sockets.get(socketId).udp.close();
|
|
267
|
+
sockets.delete(socketId);
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
export { noLookup };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytecodealliance/preview3-shim",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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": {
|
|
@@ -45,12 +45,13 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/node": "^24.12.4",
|
|
47
47
|
"globals": "^17.6.0",
|
|
48
|
+
"rolldown": "^1.2.0",
|
|
48
49
|
"typescript": "^6.0.3",
|
|
49
50
|
"vite": "^8.0.16",
|
|
50
51
|
"vitest": "^4.0.8"
|
|
51
52
|
},
|
|
52
53
|
"scripts": {
|
|
53
|
-
"build": "tsc --project tsconfig.build.json",
|
|
54
|
+
"build": "tsc --project tsconfig.build.json && node scripts/build-workers.mjs",
|
|
54
55
|
"build:watch": "tsc --watch",
|
|
55
56
|
"types:check": "tsc --noEmit",
|
|
56
57
|
"fmt": "oxfmt",
|