@bytecodealliance/preview2-shim 0.14.1 → 0.14.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.
@@ -1,9 +1,18 @@
1
1
  import { resolve } from "node:dns/promises";
2
2
  import { createReadStream, createWriteStream } from "node:fs";
3
- import { hrtime } from "node:process";
3
+ import { _rawDebug, exit, hrtime, stderr, stdout } from "node:process";
4
+ import { PassThrough } from "node:stream";
4
5
  import { runAsWorker } from "../synckit/index.js";
5
- import { createHttpRequest } from "./worker-http.js";
6
- import { Writable } from "node:stream";
6
+ import {
7
+ clearOutgoingResponse,
8
+ createHttpRequest,
9
+ setOutgoingResponse,
10
+ startHttpServer,
11
+ stopHttpServer,
12
+ } from "./worker-http.js";
13
+
14
+ const noop = () => {};
15
+
7
16
  import {
8
17
  CALL_MASK,
9
18
  CALL_SHIFT,
@@ -11,10 +20,16 @@ import {
11
20
  CLOCKS_DURATION_SUBSCRIBE,
12
21
  CLOCKS_INSTANT_SUBSCRIBE,
13
22
  CLOCKS_NOW,
23
+ FILE,
14
24
  FUTURE_DISPOSE,
15
25
  FUTURE_GET_VALUE_AND_DISPOSE,
26
+ HTTP,
16
27
  HTTP_CREATE_REQUEST,
17
28
  HTTP_OUTPUT_STREAM_FINISH,
29
+ HTTP_SERVER_CLEAR_OUTGOING_RESPONSE,
30
+ HTTP_SERVER_SET_OUTGOING_RESPONSE,
31
+ HTTP_SERVER_START,
32
+ HTTP_SERVER_STOP,
18
33
  INPUT_STREAM_BLOCKING_READ,
19
34
  INPUT_STREAM_BLOCKING_SKIP,
20
35
  INPUT_STREAM_CREATE,
@@ -32,21 +47,66 @@ import {
32
47
  OUTPUT_STREAM_FLUSH,
33
48
  OUTPUT_STREAM_SPLICE,
34
49
  OUTPUT_STREAM_SUBSCRIBE,
35
- OUTPUT_STREAM_WRITE_ZEROES,
36
50
  OUTPUT_STREAM_WRITE,
37
- POLL_POLL_LIST,
51
+ OUTPUT_STREAM_WRITE_ZEROES,
38
52
  POLL_POLLABLE_BLOCK,
39
53
  POLL_POLLABLE_READY,
54
+ POLL_POLL_LIST,
40
55
  SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST,
41
56
  SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST,
42
57
  SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST,
43
- FILE,
44
- HTTP,
45
- SOCKET,
58
+ SOCKET_TCP_BIND,
59
+ SOCKET_TCP_CONNECT,
60
+ SOCKET_TCP_CREATE_HANDLE,
61
+ SOCKET_TCP_CREATE_INPUT_STREAM,
62
+ SOCKET_TCP_CREATE_OUTPUT_STREAM,
63
+ SOCKET_TCP_DISPOSE,
64
+ SOCKET_TCP_GET_LOCAL_ADDRESS,
65
+ SOCKET_TCP_GET_REMOTE_ADDRESS,
66
+ SOCKET_TCP_LISTEN,
67
+ SOCKET_TCP_SET_KEEP_ALIVE,
68
+ SOCKET_TCP_SHUTDOWN,
69
+ SOCKET_UDP_BIND,
70
+ SOCKET_UDP_CHECK_SEND,
71
+ SOCKET_UDP_CONNECT,
72
+ SOCKET_UDP_CREATE_HANDLE,
73
+ SOCKET_UDP_DISCONNECT,
74
+ SOCKET_UDP_DISPOSE,
75
+ SOCKET_UDP_GET_LOCAL_ADDRESS,
76
+ SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE,
77
+ SOCKET_UDP_GET_REMOTE_ADDRESS,
78
+ SOCKET_UDP_GET_SEND_BUFFER_SIZE,
79
+ SOCKET_UDP_RECEIVE,
80
+ SOCKET_UDP_SEND,
81
+ SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE,
82
+ SOCKET_UDP_SET_SEND_BUFFER_SIZE,
83
+ SOCKET_UDP_SET_UNICAST_HOP_LIMIT,
46
84
  STDERR,
47
85
  STDIN,
48
86
  STDOUT,
49
87
  } from "./calls.js";
88
+ import {
89
+ createTcpSocket,
90
+ socketTcpBind,
91
+ socketTcpConnect,
92
+ socketTcpDispose,
93
+ socketTcpGetLocalAddress,
94
+ socketTcpGetRemoteAddress,
95
+ socketTcpListen,
96
+ socketTcpSetKeepAlive,
97
+ socketTcpShutdown,
98
+ } from "./worker-socket-tcp.js";
99
+ import {
100
+ SocketUdpReceive,
101
+ createUdpSocket,
102
+ getSocketOrThrow,
103
+ socketUdpBind,
104
+ socketUdpCheckSend,
105
+ socketUdpConnect,
106
+ socketUdpDisconnect,
107
+ socketUdpDispose,
108
+ socketUdpSend,
109
+ } from "./worker-socket-udp.js";
50
110
 
51
111
  let streamCnt = 0,
52
112
  pollCnt = 0;
@@ -63,28 +123,28 @@ export const unfinishedFutures = new Map();
63
123
  /**
64
124
  * @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
65
125
  */
66
- export function createStream(nodeStream, flushPromise) {
126
+ export function createStream(nodeStream) {
67
127
  unfinishedStreams.set(++streamCnt, {
68
- flushPromise,
128
+ flushPromise: null,
69
129
  stream: nodeStream,
70
130
  });
71
131
  return streamCnt;
72
132
  }
73
133
 
74
134
  // Stdio
75
- createStream(process.stdin, Promise.resolve());
76
- createStream(process.stdout, Promise.resolve());
77
- createStream(process.stderr, Promise.resolve());
135
+ // Stdin created when used
136
+ createStream(stdout);
137
+ createStream(stderr);
78
138
 
79
139
  /**
80
140
  * @param {number} streamId
81
141
  * @param {NodeJS.ReadableStream | NodeJS.WritableStream} stream
82
142
  */
83
143
  function streamError(streamId, stream, err) {
84
- if (stream.end) stream.end();
144
+ if (typeof stream.end === "function") stream.end();
85
145
  // we delete the stream from unfinishedStreams as it is now "finished" (closed)
86
146
  unfinishedStreams.delete(streamId);
87
- return { tag: "last-operation-failed", val: err };
147
+ return { tag: "last-operation-failed", val: { code: err.code, message: err.message, stack: err.stack } };
88
148
  }
89
149
 
90
150
  /**
@@ -92,6 +152,7 @@ function streamError(streamId, stream, err) {
92
152
  * @returns {{ stream: NodeJS.ReadableStream | NodeJS.WritableStream, flushPromise: Promise<void> | null }}
93
153
  */
94
154
  export function getStreamOrThrow(streamId) {
155
+ if (!streamId) throw new Error("Internal error: no stream id provided");
95
156
  const stream = unfinishedStreams.get(streamId);
96
157
  // not in unfinished streams <=> closed
97
158
  if (!stream) throw { tag: "closed" };
@@ -126,15 +187,36 @@ function handle(call, id, payload) {
126
187
  switch (call) {
127
188
  // Http
128
189
  case HTTP_CREATE_REQUEST: {
129
- const { method, url, headers, body } = payload;
130
- return createFuture(createHttpRequest(method, url, headers, body));
190
+ const {
191
+ method,
192
+ scheme,
193
+ authority,
194
+ pathWithQuery,
195
+ headers,
196
+ body,
197
+ connectTimeout,
198
+ betweenBytesTimeout,
199
+ firstByteTimeout,
200
+ } = payload;
201
+ return createFuture(
202
+ createHttpRequest(
203
+ method,
204
+ scheme,
205
+ authority,
206
+ pathWithQuery,
207
+ headers,
208
+ body,
209
+ connectTimeout,
210
+ betweenBytesTimeout,
211
+ firstByteTimeout
212
+ )
213
+ );
131
214
  }
132
215
  case OUTPUT_STREAM_CREATE | HTTP: {
133
- const webTransformStream = new TransformStream();
134
- const stream = Writable.fromWeb(webTransformStream.writable);
216
+ const stream = new PassThrough();
135
217
  // content length is passed as payload
218
+ stream.contentLength = payload;
136
219
  stream.bytesRemaining = payload;
137
- stream.readableBodyStream = webTransformStream.readable;
138
220
  return createStream(stream);
139
221
  }
140
222
  case OUTPUT_STREAM_SUBSCRIBE | HTTP:
@@ -142,32 +224,35 @@ function handle(call, id, payload) {
142
224
  case OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP: {
143
225
  // http flush is a noop
144
226
  const { stream } = getStreamOrThrow(id);
145
- // this existing indicates it's still unattached
146
- // therefore there is no subscribe or backpressure
147
- if (stream.readableBodyStream) {
148
- switch (call) {
149
- case OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP:
150
- return handle(OUTPUT_STREAM_WRITE | HTTP, id, payload);
151
- case OUTPUT_STREAM_FLUSH | HTTP:
152
- return;
153
- case OUTPUT_STREAM_SUBSCRIBE | HTTP:
154
- return 0;
227
+ if (call === (OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP)) {
228
+ stream.bytesRemaining -= payload.byteLength;
229
+ if (stream.bytesRemaining < 0) {
230
+ throw {
231
+ tag: "last-operation-failed",
232
+ val: {
233
+ tag: "HTTP-request-body-size",
234
+ val: stream.contentLength - stream.bytesRemaining,
235
+ },
236
+ };
155
237
  }
156
238
  }
157
- if (call === (OUTPUT_STREAM_BLOCKING_WRITE_AND_FLUSH | HTTP))
158
- stream.bytesRemaining -= payload.byteLength;
159
239
  // otherwise fall through to generic implementation
160
240
  return handle(call & ~HTTP, id, payload);
161
241
  }
162
242
  case OUTPUT_STREAM_DISPOSE | HTTP:
163
- throw new Error('Internal error: HTTP output stream dispose is bypassed for FINISH');
243
+ throw new Error(
244
+ "Internal error: HTTP output stream dispose is bypassed for FINISH"
245
+ );
164
246
  case OUTPUT_STREAM_WRITE | HTTP: {
165
247
  const { stream } = getStreamOrThrow(id);
166
248
  stream.bytesRemaining -= payload.byteLength;
167
249
  if (stream.bytesRemaining < 0) {
168
250
  throw {
169
- tag: 'last-operation-failed',
170
- val: 'too much written to output stream'
251
+ tag: "last-operation-failed",
252
+ val: {
253
+ tag: "HTTP-request-body-size",
254
+ val: stream.contentLength - stream.bytesRemaining,
255
+ },
171
256
  };
172
257
  }
173
258
  const output = handle(OUTPUT_STREAM_WRITE, id, payload);
@@ -177,49 +262,178 @@ function handle(call, id, payload) {
177
262
  const { stream } = getStreamOrThrow(id);
178
263
  if (stream.bytesRemaining > 0) {
179
264
  throw {
180
- tag: 'internal-error',
181
- val: 'not enough written to body stream'
265
+ tag: "HTTP-request-body-size",
266
+ val: stream.contentLength - stream.bytesRemaining,
182
267
  };
183
268
  }
184
269
  if (stream.bytesRemaining < 0) {
185
270
  throw {
186
- tag: 'internal-error',
187
- val: 'too much written to body stream'
271
+ tag: "HTTP-request-body-size",
272
+ val: stream.contentLength - stream.bytesRemaining,
188
273
  };
189
274
  }
190
275
  stream.end();
191
276
  break;
192
277
  }
278
+ case HTTP_SERVER_START:
279
+ return startHttpServer(id, payload);
280
+ case HTTP_SERVER_STOP:
281
+ return stopHttpServer(id);
282
+ case HTTP_SERVER_SET_OUTGOING_RESPONSE:
283
+ return setOutgoingResponse(id, payload);
284
+ case HTTP_SERVER_CLEAR_OUTGOING_RESPONSE:
285
+ return clearOutgoingResponse(id);
286
+
287
+ // Sockets TCP
288
+ case SOCKET_TCP_CREATE_HANDLE:
289
+ return createFuture(createTcpSocket());
290
+
291
+ case SOCKET_TCP_BIND:
292
+ return socketTcpBind(id, payload);
293
+
294
+ case SOCKET_TCP_CONNECT:
295
+ return socketTcpConnect(id, payload);
296
+
297
+ case SOCKET_TCP_LISTEN:
298
+ return socketTcpListen(id, payload);
299
+
300
+ case SOCKET_TCP_GET_LOCAL_ADDRESS:
301
+ return socketTcpGetLocalAddress(id);
302
+
303
+ case SOCKET_TCP_GET_REMOTE_ADDRESS:
304
+ return socketTcpGetRemoteAddress(id);
305
+
306
+ case SOCKET_TCP_SHUTDOWN:
307
+ return socketTcpShutdown(id, payload);
308
+
309
+ case SOCKET_TCP_SET_KEEP_ALIVE:
310
+ return socketTcpSetKeepAlive(id, payload);
311
+
312
+ case SOCKET_TCP_DISPOSE:
313
+ return socketTcpDispose(id);
314
+
315
+ case SOCKET_TCP_CREATE_INPUT_STREAM:
316
+ case SOCKET_TCP_CREATE_OUTPUT_STREAM:
317
+ return createStream(new PassThrough());
318
+
319
+ // Sockets UDP
320
+ case SOCKET_UDP_CREATE_HANDLE: {
321
+ const { addressFamily, reuseAddr } = payload;
322
+ return createFuture(createUdpSocket(addressFamily, reuseAddr));
323
+ }
324
+
325
+ case SOCKET_UDP_BIND:
326
+ return socketUdpBind(id, payload);
327
+
328
+ case SOCKET_UDP_CHECK_SEND:
329
+ return socketUdpCheckSend(id);
330
+
331
+ case SOCKET_UDP_SEND:
332
+ return socketUdpSend(id, payload);
333
+
334
+ case SOCKET_UDP_RECEIVE:
335
+ return SocketUdpReceive(id, payload);
336
+
337
+ case SOCKET_UDP_CONNECT:
338
+ return socketUdpConnect(id, payload);
339
+
340
+ case SOCKET_UDP_DISCONNECT:
341
+ return socketUdpDisconnect(id);
342
+
343
+ case SOCKET_UDP_GET_LOCAL_ADDRESS: {
344
+ const socket = getSocketOrThrow(id);
345
+ return Promise.resolve(socket.address());
346
+ }
347
+
348
+ case SOCKET_UDP_GET_REMOTE_ADDRESS: {
349
+ const socket = getSocketOrThrow(id);
350
+ return Promise.resolve(socket.remoteAddress());
351
+ }
193
352
 
194
- // Sockets
195
353
  case SOCKET_RESOLVE_ADDRESS_CREATE_REQUEST:
196
354
  return createFuture(resolve(payload.hostname));
355
+
197
356
  case SOCKET_RESOLVE_ADDRESS_DISPOSE_REQUEST:
198
357
  return void unfinishedFutures.delete(id);
358
+
199
359
  case SOCKET_RESOLVE_ADDRESS_GET_AND_DISPOSE_REQUEST: {
200
360
  const future = unfinishedFutures.get(id);
201
361
  if (!future) {
202
362
  // future not ready yet
203
363
  if (unfinishedPolls.get(id)) {
204
- throw 'would-block';
364
+ throw "would-block";
205
365
  }
206
366
  throw new Error("future already got and dropped");
207
367
  }
208
368
  unfinishedFutures.delete(id);
209
369
  return future;
210
370
  }
211
- case OUTPUT_STREAM_CREATE | SOCKET: {
212
- // TODO: implement
371
+
372
+ case SOCKET_UDP_GET_RECEIVE_BUFFER_SIZE: {
373
+ const socket = getSocketOrThrow(id);
374
+ try {
375
+ return socket.getRecvBufferSize();
376
+ } catch (err) {
377
+ return err.errno;
378
+ }
379
+ }
380
+
381
+ case SOCKET_UDP_SET_RECEIVE_BUFFER_SIZE: {
382
+ const socket = getSocketOrThrow(id);
383
+ try {
384
+ return socket.setRecvBufferSize(65537);
385
+ } catch (err) {
386
+ return err.errno;
387
+ }
213
388
  }
214
- case INPUT_STREAM_CREATE | SOCKET: {
215
- // TODO: implement
389
+
390
+ case SOCKET_UDP_GET_SEND_BUFFER_SIZE: {
391
+ const socket = getSocketOrThrow(id);
392
+ try {
393
+ return socket.getSendBufferSize();
394
+ } catch (err) {
395
+ return err.errno;
396
+ }
216
397
  }
217
398
 
399
+ case SOCKET_UDP_SET_SEND_BUFFER_SIZE: {
400
+ const socket = getSocketOrThrow(id);
401
+ try {
402
+ return socket.setSendBufferSize(payload.value);
403
+ } catch (err) {
404
+ return err.errno;
405
+ }
406
+ }
407
+
408
+ case SOCKET_UDP_SET_UNICAST_HOP_LIMIT: {
409
+ const socket = getSocketOrThrow(id);
410
+ try {
411
+ return socket.setTTL(payload.value);
412
+ } catch (err) {
413
+ return err.errno;
414
+ }
415
+ }
416
+
417
+ case SOCKET_UDP_DISPOSE:
418
+ return socketUdpDispose(id);
419
+
218
420
  // Stdio
421
+ case OUTPUT_STREAM_BLOCKING_FLUSH | STDOUT:
422
+ case OUTPUT_STREAM_BLOCKING_FLUSH | STDERR:
423
+ // no blocking flush for stdio in Node.js
424
+ return;
219
425
  case OUTPUT_STREAM_DISPOSE | STDOUT:
220
426
  case OUTPUT_STREAM_DISPOSE | STDERR:
221
- case INPUT_STREAM_DISPOSE | STDIN:
222
427
  return;
428
+ case INPUT_STREAM_CREATE | STDIN: {
429
+ const stream = createReadStream(null, {
430
+ fd: 0,
431
+ highWaterMark: 64 * 1024,
432
+ });
433
+ // for some reason fs streams dont emit readable on end
434
+ stream.on("end", () => void stream.emit("readable"));
435
+ return createStream(stream);
436
+ }
223
437
 
224
438
  // Clocks
225
439
  case CLOCKS_NOW:
@@ -258,7 +472,9 @@ function handle(call, id, payload) {
258
472
  switch (call & CALL_MASK) {
259
473
  case INPUT_STREAM_READ: {
260
474
  const { stream } = getStreamOrThrow(id);
261
- const res = stream.read(Number(payload));
475
+ const res = stream.read(
476
+ Math.min(stream.readableLength, Number(payload))
477
+ );
262
478
  return res ?? new Uint8Array();
263
479
  }
264
480
  case INPUT_STREAM_BLOCKING_READ:
@@ -283,10 +499,6 @@ function handle(call, id, payload) {
283
499
  );
284
500
  case INPUT_STREAM_SUBSCRIBE: {
285
501
  const stream = unfinishedStreams.get(id)?.stream;
286
- if (id === 1) {
287
- // TODO: stdin subscribe
288
- return 0;
289
- }
290
502
  // already closed or errored -> immediately return poll
291
503
  // (poll 0 is immediately resolved)
292
504
  if (
@@ -304,10 +516,8 @@ function handle(call, id, payload) {
304
516
  .once("error", (reject = _reject));
305
517
  }).then(
306
518
  () => void stream.off("error", reject),
307
- (err) => {
308
- stream.off("readable", resolve);
309
- throw streamError(id, stream.stream, err);
310
- }
519
+ // error is read of stream itself when later accessed
520
+ (_err) => void stream.off("readable", resolve)
311
521
  )
312
522
  );
313
523
  }
@@ -344,8 +554,10 @@ function handle(call, id, payload) {
344
554
  );
345
555
  }
346
556
  return new Promise((resolve, reject) => {
557
+ stream.once("error", noop);
347
558
  stream.write(payload, (err) => {
348
559
  if (err) return void reject(streamError(id, stream, err));
560
+ stream.off("error", noop);
349
561
  resolve(BigInt(payload.byteLength));
350
562
  });
351
563
  });
@@ -413,15 +625,12 @@ function handle(call, id, payload) {
413
625
  }
414
626
  case OUTPUT_STREAM_SUBSCRIBE: {
415
627
  const { stream, flushPromise } = unfinishedStreams.get(id) ?? {};
416
- if (flushPromise) return flushPromise;
628
+ if (flushPromise)
629
+ return flushPromise.then(() => handle(call, id, payload));
417
630
  // not added to unfinishedPolls => it's an immediately resolved poll
418
- if (
419
- !stream ||
420
- stream.closed ||
421
- stream.errored ||
422
- !stream.writableNeedDrain
423
- )
424
- return 0;
631
+ if (!stream || stream.closed || stream.errored) return 0;
632
+ if (!stream.writableNeedDrain)
633
+ return createPoll(new Promise((resolve) => setTimeout(resolve)));
425
634
  let resolve, reject;
426
635
  return createPoll(
427
636
  new Promise((_resolve, _reject) => {
@@ -429,10 +638,8 @@ function handle(call, id, payload) {
429
638
  .once("drain", (resolve = _resolve))
430
639
  .once("error", (reject = _reject));
431
640
  }).then(() => void stream.off("error", reject)),
432
- (err) => {
433
- stream.off("drain", resolve);
434
- throw streamError(id, stream, err);
435
- }
641
+ // error is read off stream itself when later accessed
642
+ (_err) => void stream.off("drain", resolve)
436
643
  );
437
644
  }
438
645
  case OUTPUT_STREAM_BLOCKING_SPLICE: {
@@ -489,18 +696,21 @@ function handle(call, id, payload) {
489
696
  payload = [id];
490
697
  // [intentional case fall-through]
491
698
  case POLL_POLL_LIST: {
492
- const resolvedList = payload.filter((id) => !unfinishedPolls.has(id));
493
- if (resolvedList.length > 0) return resolvedList;
699
+ const doneList = [];
700
+ for (const [idx, id] of payload.entries()) {
701
+ if (!unfinishedPolls.has(id)) doneList.push(idx);
702
+ }
703
+ if (doneList.length > 0) return new Uint32Array(doneList);
494
704
  // if all polls are promise type, we just race them
495
705
  return Promise.race(
496
706
  payload.map((id) => unfinishedPolls.get(id))
497
707
  ).then(() => {
498
- const resolvedList = payload.filter(
499
- (id) => !unfinishedPolls.has(id)
500
- );
501
- if (resolvedList.length === 0)
708
+ for (const [idx, id] of payload.entries()) {
709
+ if (!unfinishedPolls.has(id)) doneList.push(idx);
710
+ }
711
+ if (doneList.length === 0)
502
712
  throw new Error("poll promise did not unregister poll");
503
- return resolvedList;
713
+ return new Uint32Array(doneList);
504
714
  });
505
715
  }
506
716
 
@@ -508,11 +718,7 @@ function handle(call, id, payload) {
508
718
  const future = unfinishedFutures.get(id);
509
719
  if (!future) {
510
720
  // future not ready yet
511
- if (unfinishedPolls.get(id)) {
512
- // if ((call & CALL_TYPE_MASK) ===
513
- // http futures throw
514
- throw undefined;
515
- }
721
+ if (unfinishedPolls.get(id)) throw undefined;
516
722
  throw new Error("future already got and dropped");
517
723
  }
518
724
  unfinishedFutures.delete(id);
@@ -539,9 +745,9 @@ export function createPoll(promise) {
539
745
  promise.then(
540
746
  () => void unfinishedPolls.delete(pollId),
541
747
  (err) => {
542
- process._rawDebug("Unexpected poll error");
543
- process._rawDebug(err);
544
- process.exit(1);
748
+ _rawDebug("Unexpected poll error");
749
+ _rawDebug(err);
750
+ exit(1);
545
751
  }
546
752
  )
547
753
  );
package/lib/nodejs/cli.js CHANGED
@@ -1,21 +1,31 @@
1
1
  import { argv, env, cwd } from "node:process";
2
2
  import {
3
+ ioCall,
3
4
  streams,
4
5
  inputStreamCreate,
5
6
  outputStreamCreate,
6
7
  } from "../io/worker-io.js";
7
- import { STDIN, STDOUT, STDERR } from "../io/calls.js";
8
+ import { INPUT_STREAM_CREATE, STDERR, STDIN, STDOUT } from "../io/calls.js";
8
9
  const { InputStream, OutputStream } = streams;
9
10
 
10
- export const _setEnv = env => void (_env = Object.entries(env));
11
- export const _setArgs = args => void (_args = args);
12
- export const _setCwd = cwd => void (_cwd = cwd);
13
- export const _setStdin = stdin => void (stdinStream = stdin);
14
- export const _setStdout = stdout => void (stdoutStream = stdout);
15
- export const _setStderr = stderr => void (stderrStream = stderr);
16
- export const _setTerminalStdin = terminalStdin => void (terminalStdinInstance = terminalStdin);
17
- export const _setTerminalStdout = terminalStdout => void (terminalStdoutInstance = terminalStdout);
18
- export const _setTerminalStderr = terminalStderr => void (terminalStderrInstance = terminalStderr);
11
+ export const _appendEnv = (env) => {
12
+ void (_env = [
13
+ ..._env.filter(([curKey]) => !(curKey in env)),
14
+ ...Object.entries(env),
15
+ ]);
16
+ }
17
+ export const _setEnv = (env) => void (_env = Object.entries(env));
18
+ export const _setArgs = (args) => void (_args = args);
19
+ export const _setCwd = (cwd) => void (_cwd = cwd);
20
+ export const _setStdin = (stdin) => void (stdinStream = stdin);
21
+ export const _setStdout = (stdout) => void (stdoutStream = stdout);
22
+ export const _setStderr = (stderr) => void (stderrStream = stderr);
23
+ export const _setTerminalStdin = (terminalStdin) =>
24
+ void (terminalStdinInstance = terminalStdin);
25
+ export const _setTerminalStdout = (terminalStdout) =>
26
+ void (terminalStdoutInstance = terminalStdout);
27
+ export const _setTerminalStderr = (terminalStderr) =>
28
+ void (terminalStderrInstance = terminalStderr);
19
29
 
20
30
  let _env = Object.entries(env),
21
31
  _args = argv.slice(1),
@@ -39,13 +49,19 @@ export const exit = {
39
49
  },
40
50
  };
41
51
 
42
- let stdinStream = inputStreamCreate(STDIN, 1);
52
+ // Stdin is created as a FILE descriptor
53
+ let stdinStream;
43
54
  let stdoutStream = outputStreamCreate(STDOUT, 2);
44
55
  let stderrStream = outputStreamCreate(STDERR, 3);
45
56
 
46
57
  export const stdin = {
47
58
  InputStream,
48
59
  getStdin() {
60
+ if (!stdinStream)
61
+ stdinStream = inputStreamCreate(
62
+ STDIN,
63
+ ioCall(INPUT_STREAM_CREATE | STDIN, null, null)
64
+ );
49
65
  return stdinStream;
50
66
  },
51
67
  };