@effect/platform-node-shared 4.0.0-beta.1 → 4.0.0-beta.100
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/README.md +3 -3
- package/dist/NodeChildProcessSpawner.d.ts +19 -5
- package/dist/NodeChildProcessSpawner.d.ts.map +1 -1
- package/dist/NodeChildProcessSpawner.js +105 -118
- package/dist/NodeChildProcessSpawner.js.map +1 -1
- package/dist/NodeClusterSocket.d.ts +10 -4
- package/dist/NodeClusterSocket.d.ts.map +1 -1
- package/dist/NodeClusterSocket.js +22 -8
- package/dist/NodeClusterSocket.js.map +1 -1
- package/dist/NodeCrypto.d.ts +27 -0
- package/dist/NodeCrypto.d.ts.map +1 -0
- package/dist/NodeCrypto.js +55 -0
- package/dist/NodeCrypto.js.map +1 -0
- package/dist/NodeFileSystem.d.ts +5 -2
- package/dist/NodeFileSystem.d.ts.map +1 -1
- package/dist/NodeFileSystem.js +44 -28
- package/dist/NodeFileSystem.js.map +1 -1
- package/dist/NodePath.d.ts +15 -6
- package/dist/NodePath.d.ts.map +1 -1
- package/dist/NodePath.js +23 -7
- package/dist/NodePath.js.map +1 -1
- package/dist/NodeRuntime.d.ts +26 -7
- package/dist/NodeRuntime.d.ts.map +1 -1
- package/dist/NodeRuntime.js +8 -8
- package/dist/NodeRuntime.js.map +1 -1
- package/dist/NodeSink.d.ts +33 -4
- package/dist/NodeSink.d.ts.map +1 -1
- package/dist/NodeSink.js +31 -4
- package/dist/NodeSink.js.map +1 -1
- package/dist/NodeSocket.d.ts +43 -12
- package/dist/NodeSocket.d.ts.map +1 -1
- package/dist/NodeSocket.js +41 -17
- package/dist/NodeSocket.js.map +1 -1
- package/dist/NodeSocketServer.d.ts +25 -8
- package/dist/NodeSocketServer.d.ts.map +1 -1
- package/dist/NodeSocketServer.js +29 -12
- package/dist/NodeSocketServer.js.map +1 -1
- package/dist/NodeStdio.d.ts +6 -5
- package/dist/NodeStdio.d.ts.map +1 -1
- package/dist/NodeStdio.js +26 -10
- package/dist/NodeStdio.js.map +1 -1
- package/dist/NodeStream.d.ts +76 -20
- package/dist/NodeStream.d.ts.map +1 -1
- package/dist/NodeStream.js +74 -25
- package/dist/NodeStream.js.map +1 -1
- package/dist/NodeTerminal.d.ts +9 -2
- package/dist/NodeTerminal.d.ts.map +1 -1
- package/dist/NodeTerminal.js +13 -3
- package/dist/NodeTerminal.js.map +1 -1
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/utils.js +1 -1
- package/dist/internal/utils.js.map +1 -1
- package/package.json +9 -9
- package/src/NodeChildProcessSpawner.ts +157 -148
- package/src/NodeClusterSocket.ts +22 -8
- package/src/NodeCrypto.ts +60 -0
- package/src/NodeFileSystem.ts +58 -30
- package/src/NodePath.ts +23 -7
- package/src/NodeRuntime.ts +28 -13
- package/src/NodeSink.ts +40 -4
- package/src/NodeSocket.ts +51 -20
- package/src/NodeSocketServer.ts +39 -13
- package/src/NodeStdio.ts +42 -24
- package/src/NodeStream.ts +97 -33
- package/src/NodeTerminal.ts +22 -4
- package/src/index.ts +65 -0
- package/src/internal/utils.ts +4 -4
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Node.js implementation of
|
|
2
|
+
* Shared Node.js implementation of the child process spawner service.
|
|
3
|
+
*
|
|
4
|
+
* This module adapts `node:child_process.spawn` to the Effect
|
|
5
|
+
* `ChildProcessSpawner` service. Provide {@link layer} to run `ChildProcess`
|
|
6
|
+
* commands in Node-compatible runtimes: commands get scoped process handles
|
|
7
|
+
* with stdin sinks, stdout and stderr streams, exit-code waiting,
|
|
8
|
+
* interruption-time cleanup, process killing, and custom file-descriptor pipes.
|
|
9
|
+
*
|
|
10
|
+
* The implementation sits below the command-building API. It validates and
|
|
11
|
+
* resolves `cwd` through the Effect `FileSystem` and `Path` services,
|
|
12
|
+
* translates Node errno failures to `PlatformError`, and uses scopes to
|
|
13
|
+
* terminate referenced children when the owning effect is interrupted or
|
|
14
|
+
* finalized. Pipelines are flattened by {@link flattenCommand} and spawned one
|
|
15
|
+
* process at a time, wiring the selected source stream (`stdout`, `stderr`,
|
|
16
|
+
* `all`, or `fdN`) to the destination `stdin` or `fdN`.
|
|
3
17
|
*
|
|
4
18
|
* @since 4.0.0
|
|
5
19
|
*/
|
|
@@ -17,9 +31,15 @@ import * as Sink from "effect/Sink"
|
|
|
17
31
|
import * as Stream from "effect/Stream"
|
|
18
32
|
import * as ChildProcess from "effect/unstable/process/ChildProcess"
|
|
19
33
|
import type { ChildProcessHandle } from "effect/unstable/process/ChildProcessSpawner"
|
|
20
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
ChildProcessSpawner,
|
|
36
|
+
ExitCode,
|
|
37
|
+
make as makeSpawner,
|
|
38
|
+
makeHandle,
|
|
39
|
+
ProcessId
|
|
40
|
+
} from "effect/unstable/process/ChildProcessSpawner"
|
|
21
41
|
import * as NodeChildProcess from "node:child_process"
|
|
22
|
-
import
|
|
42
|
+
import { PassThrough } from "node:stream"
|
|
23
43
|
import { handleErrnoException } from "./internal/utils.ts"
|
|
24
44
|
import * as NodeSink from "./NodeSink.ts"
|
|
25
45
|
import * as NodeStream from "./NodeStream.ts"
|
|
@@ -181,15 +201,15 @@ const make = Effect.gen(function*() {
|
|
|
181
201
|
case "input": {
|
|
182
202
|
// Create a sink to write to for input file descriptors
|
|
183
203
|
let sink: Sink.Sink<void, Uint8Array, never, PlatformError.PlatformError> = Sink.drain
|
|
184
|
-
if (
|
|
204
|
+
if (nodeStream && "write" in nodeStream) {
|
|
185
205
|
sink = NodeSink.fromWritable({
|
|
186
|
-
evaluate: () => nodeStream
|
|
206
|
+
evaluate: () => nodeStream,
|
|
187
207
|
onError: (error) => toPlatformError(`fromWritable(fd${fd})`, toError(error), command)
|
|
188
208
|
})
|
|
189
209
|
}
|
|
190
210
|
|
|
191
211
|
// If user provided a stream, pipe it into the sink
|
|
192
|
-
if (
|
|
212
|
+
if (config.stream) {
|
|
193
213
|
yield* Effect.forkScoped(Stream.run(config.stream, sink))
|
|
194
214
|
}
|
|
195
215
|
|
|
@@ -200,15 +220,18 @@ const make = Effect.gen(function*() {
|
|
|
200
220
|
case "output": {
|
|
201
221
|
// Create a stream to read from for output file descriptors
|
|
202
222
|
let stream: Stream.Stream<Uint8Array, PlatformError.PlatformError> = Stream.empty
|
|
203
|
-
if (
|
|
223
|
+
if (nodeStream && "read" in nodeStream) {
|
|
224
|
+
const passThrough = new PassThrough()
|
|
225
|
+
nodeStream.on("error", (error) => passThrough.destroy(error))
|
|
226
|
+
nodeStream.pipe(passThrough)
|
|
204
227
|
stream = NodeStream.fromReadable({
|
|
205
|
-
evaluate: () =>
|
|
228
|
+
evaluate: () => passThrough,
|
|
206
229
|
onError: (error) => toPlatformError(`fromReadable(fd${fd})`, toError(error), command)
|
|
207
230
|
})
|
|
208
231
|
}
|
|
209
232
|
|
|
210
233
|
// If user provided a sink, transduce the stream through it
|
|
211
|
-
if (
|
|
234
|
+
if (config.sink) {
|
|
212
235
|
stream = Stream.transduce(stream, config.sink)
|
|
213
236
|
}
|
|
214
237
|
|
|
@@ -251,35 +274,6 @@ const make = Effect.gen(function*() {
|
|
|
251
274
|
return Effect.succeed(sink)
|
|
252
275
|
})
|
|
253
276
|
|
|
254
|
-
/**
|
|
255
|
-
* Given that `NodeStream.fromReadable` uses `.read` to read data from the
|
|
256
|
-
* provided `Readable` stream, consumers would race to read data from the
|
|
257
|
-
* `handle.stdout` and `handle.stderr` streams if they were also simultaneously
|
|
258
|
-
* reading from the `handle.all` stream.
|
|
259
|
-
*
|
|
260
|
-
* To solve this, we leverage the fact that NodeJS `Readable` streams can be
|
|
261
|
-
* piped to multiple destinations simultaneously. The logic for the solution
|
|
262
|
-
* is as follows:
|
|
263
|
-
*
|
|
264
|
-
* 1. Pipe each original stream to two `PassThrough` streams:
|
|
265
|
-
* - One dedicated PassThrough for individual access (.stdout / .stderr)
|
|
266
|
-
* - One shared PassThrough for combined access (.all)
|
|
267
|
-
* 2. Create Effect streams from the PassThrough streams (not the originals)
|
|
268
|
-
*
|
|
269
|
-
* **Diagram**
|
|
270
|
-
*
|
|
271
|
-
* ┌─────────────┐
|
|
272
|
-
* ┌────►│ passthrough │────► Effect stdout Stream
|
|
273
|
-
* │ └─────────────┘
|
|
274
|
-
* childProcess.stdout ────┤
|
|
275
|
-
* │ ┌─────────────┐
|
|
276
|
-
* └────►│ passthrough │────► Effect all Stream
|
|
277
|
-
* ┌────►│ │
|
|
278
|
-
* childProcess.stderr ────┤ └─────────────┘
|
|
279
|
-
* │ ┌─────────────┐
|
|
280
|
-
* └────►│ passthrough │────► Effect stderr Stream
|
|
281
|
-
* └─────────────┘
|
|
282
|
-
*/
|
|
283
277
|
const setupChildOutputStreams = (
|
|
284
278
|
command: ChildProcess.StandardCommand,
|
|
285
279
|
childProcess: NodeChildProcess.ChildProcess,
|
|
@@ -290,85 +284,37 @@ const make = Effect.gen(function*() {
|
|
|
290
284
|
stderr: Stream.Stream<Uint8Array, PlatformError.PlatformError>
|
|
291
285
|
all: Stream.Stream<Uint8Array, PlatformError.PlatformError>
|
|
292
286
|
} => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const onStreamEnd = () => {
|
|
316
|
-
endedCount++
|
|
317
|
-
if (endedCount >= totalStreams) {
|
|
318
|
-
combinedPassThrough.end()
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Pipe stdout to both its own PassThrough and the combined PassThrough
|
|
323
|
-
if (Predicate.isNotNull(nodeStdout) && Predicate.isNotNull(stdoutPassThrough)) {
|
|
324
|
-
nodeStdout.pipe(stdoutPassThrough)
|
|
325
|
-
nodeStdout.pipe(combinedPassThrough, { end: false })
|
|
326
|
-
nodeStdout.once("end", onStreamEnd)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Pipe stderr to both its own PassThrough and the combined PassThrough
|
|
330
|
-
if (Predicate.isNotNull(nodeStderr) && Predicate.isNotNull(stderrPassThrough)) {
|
|
331
|
-
nodeStderr.pipe(stderrPassThrough)
|
|
332
|
-
nodeStderr.pipe(combinedPassThrough, { end: false })
|
|
333
|
-
nodeStderr.once("end", onStreamEnd)
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Handle edge case: no streams available
|
|
337
|
-
if (totalStreams === 0) {
|
|
338
|
-
combinedPassThrough.end()
|
|
339
|
-
}
|
|
287
|
+
let stdout = childProcess.stdout ?
|
|
288
|
+
(() => {
|
|
289
|
+
const passThrough = new PassThrough()
|
|
290
|
+
childProcess.stdout!.on("error", (error) => passThrough.destroy(error))
|
|
291
|
+
childProcess.stdout!.pipe(passThrough)
|
|
292
|
+
return NodeStream.fromReadable({
|
|
293
|
+
evaluate: () => passThrough,
|
|
294
|
+
onError: (error) => toPlatformError("fromReadable(stdout)", toError(error), command)
|
|
295
|
+
})
|
|
296
|
+
})() :
|
|
297
|
+
Stream.empty
|
|
298
|
+
let stderr = childProcess.stderr ?
|
|
299
|
+
(() => {
|
|
300
|
+
const passThrough = new PassThrough()
|
|
301
|
+
childProcess.stderr!.on("error", (error) => passThrough.destroy(error))
|
|
302
|
+
childProcess.stderr!.pipe(passThrough)
|
|
303
|
+
return NodeStream.fromReadable({
|
|
304
|
+
evaluate: () => passThrough,
|
|
305
|
+
onError: (error) => toPlatformError("fromReadable(stderr)", toError(error), command)
|
|
306
|
+
})
|
|
307
|
+
})() :
|
|
308
|
+
Stream.empty
|
|
340
309
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (Predicate.isNotNull(stdoutPassThrough)) {
|
|
344
|
-
stdout = NodeStream.fromReadable({
|
|
345
|
-
evaluate: () => stdoutPassThrough,
|
|
346
|
-
onError: (error) => toPlatformError("fromReadable(stdout)", toError(error), command)
|
|
347
|
-
})
|
|
348
|
-
// Apply user-provided Sink if configured
|
|
349
|
-
if (Sink.isSink(stdoutConfig.stream)) {
|
|
350
|
-
stdout = Stream.transduce(stdout, stdoutConfig.stream)
|
|
351
|
-
}
|
|
310
|
+
if (Sink.isSink(stdoutConfig.stream)) {
|
|
311
|
+
stdout = Stream.transduce(stdout, stdoutConfig.stream)
|
|
352
312
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
let stderr: Stream.Stream<Uint8Array, PlatformError.PlatformError> = Stream.empty
|
|
356
|
-
if (Predicate.isNotNull(stderrPassThrough)) {
|
|
357
|
-
stderr = NodeStream.fromReadable({
|
|
358
|
-
evaluate: () => stderrPassThrough,
|
|
359
|
-
onError: (error) => toPlatformError("fromReadable(stderr)", toError(error), command)
|
|
360
|
-
})
|
|
361
|
-
// Apply user-provided Sink if configured
|
|
362
|
-
if (Sink.isSink(stderrConfig.stream)) {
|
|
363
|
-
stderr = Stream.transduce(stderr, stderrConfig.stream)
|
|
364
|
-
}
|
|
313
|
+
if (Sink.isSink(stderrConfig.stream)) {
|
|
314
|
+
stderr = Stream.transduce(stderr, stderrConfig.stream)
|
|
365
315
|
}
|
|
366
316
|
|
|
367
|
-
|
|
368
|
-
const all: Stream.Stream<Uint8Array, PlatformError.PlatformError> = NodeStream.fromReadable({
|
|
369
|
-
evaluate: () => combinedPassThrough,
|
|
370
|
-
onError: (error) => toPlatformError("fromReadable(all)", toError(error), command)
|
|
371
|
-
})
|
|
317
|
+
const all = Stream.merge(stdout, stderr)
|
|
372
318
|
|
|
373
319
|
return { stdout, stderr, all }
|
|
374
320
|
}
|
|
@@ -425,6 +371,23 @@ const make = Effect.gen(function*() {
|
|
|
425
371
|
})
|
|
426
372
|
}
|
|
427
373
|
|
|
374
|
+
const killProcessGroupOnExit = (
|
|
375
|
+
childProcess: NodeChildProcess.ChildProcess,
|
|
376
|
+
signal: NodeJS.Signals
|
|
377
|
+
): void => {
|
|
378
|
+
if (globalThis.process.platform === "win32") {
|
|
379
|
+
NodeChildProcess.exec(`taskkill /pid ${childProcess.pid} /T /F`, () => {
|
|
380
|
+
// ignore errors during best-effort cleanup
|
|
381
|
+
})
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
globalThis.process.kill(-childProcess.pid!, signal)
|
|
386
|
+
} catch {
|
|
387
|
+
// ignore errors during best-effort cleanup
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
428
391
|
const killProcess = (
|
|
429
392
|
command: ChildProcess.StandardCommand,
|
|
430
393
|
childProcess: NodeChildProcess.ChildProcess,
|
|
@@ -456,7 +419,7 @@ const make = Effect.gen(function*() {
|
|
|
456
419
|
? kill(command, childProcess, killSignal)
|
|
457
420
|
: Effect.timeoutOrElse(kill(command, childProcess, killSignal), {
|
|
458
421
|
duration: options.forceKillAfter,
|
|
459
|
-
|
|
422
|
+
orElse: () => kill(command, childProcess, "SIGKILL")
|
|
460
423
|
})
|
|
461
424
|
}
|
|
462
425
|
|
|
@@ -501,6 +464,7 @@ const make = Effect.gen(function*() {
|
|
|
501
464
|
const stdoutConfig = resolveOutputOption(cmd.options, "stdout")
|
|
502
465
|
const stderrConfig = resolveOutputOption(cmd.options, "stderr")
|
|
503
466
|
const resolvedAdditionalFds = resolveAdditionalFds(cmd.options)
|
|
467
|
+
let isReferenced = true
|
|
504
468
|
|
|
505
469
|
const cwd = yield* resolveWorkingDirectory(cmd.options)
|
|
506
470
|
const env = resolveEnvironment(cmd.options)
|
|
@@ -526,20 +490,40 @@ const make = Effect.gen(function*() {
|
|
|
526
490
|
}
|
|
527
491
|
return yield* Effect.void
|
|
528
492
|
}
|
|
493
|
+
if (!isReferenced) {
|
|
494
|
+
return yield* Effect.void
|
|
495
|
+
}
|
|
529
496
|
// Process is still running, kill it
|
|
530
497
|
return yield* killWithTimeout((command, childProcess, signal) =>
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
(
|
|
498
|
+
killProcessGroup(command, childProcess, signal).pipe(
|
|
499
|
+
Effect.catch(() => killProcess(command, childProcess, signal)),
|
|
500
|
+
Effect.andThen(Deferred.await(exitSignal))
|
|
534
501
|
)
|
|
535
502
|
).pipe(
|
|
536
|
-
Effect.andThen(Deferred.await(exitSignal)),
|
|
537
503
|
Effect.ignore
|
|
538
504
|
)
|
|
539
505
|
})
|
|
540
506
|
)
|
|
541
507
|
|
|
542
508
|
const pid = ProcessId(childProcess.pid!)
|
|
509
|
+
childProcess.on("exit", (code) => {
|
|
510
|
+
if (code !== 0 && Predicate.isNotNull(code)) {
|
|
511
|
+
killProcessGroupOnExit(childProcess, cmd.options.killSignal ?? "SIGTERM")
|
|
512
|
+
}
|
|
513
|
+
})
|
|
514
|
+
const reref = Effect.sync(() => {
|
|
515
|
+
if (!isReferenced) {
|
|
516
|
+
childProcess.ref()
|
|
517
|
+
isReferenced = true
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
const unref = Effect.sync(() => {
|
|
521
|
+
if (isReferenced) {
|
|
522
|
+
childProcess.unref()
|
|
523
|
+
isReferenced = false
|
|
524
|
+
}
|
|
525
|
+
return reref
|
|
526
|
+
})
|
|
543
527
|
const stdin = yield* setupChildStdin(cmd, childProcess, stdinConfig)
|
|
544
528
|
const { all, stderr, stdout } = setupChildOutputStreams(cmd, childProcess, stdoutConfig, stderrConfig)
|
|
545
529
|
const { getInputFd, getOutputFd } = yield* setupAdditionalFds(cmd, childProcess, resolvedAdditionalFds)
|
|
@@ -557,12 +541,11 @@ const make = Effect.gen(function*() {
|
|
|
557
541
|
const kill = (options?: ChildProcess.KillOptions | undefined) => {
|
|
558
542
|
const killWithTimeout = withTimeout(childProcess, cmd, options)
|
|
559
543
|
return killWithTimeout((command, childProcess, signal) =>
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
(
|
|
544
|
+
killProcessGroup(command, childProcess, signal).pipe(
|
|
545
|
+
Effect.catch(() => killProcess(command, childProcess, signal)),
|
|
546
|
+
Effect.andThen(Deferred.await(exitSignal))
|
|
563
547
|
)
|
|
564
548
|
).pipe(
|
|
565
|
-
Effect.andThen(Deferred.await(exitSignal)),
|
|
566
549
|
Effect.asVoid
|
|
567
550
|
)
|
|
568
551
|
}
|
|
@@ -577,14 +560,15 @@ const make = Effect.gen(function*() {
|
|
|
577
560
|
stderr,
|
|
578
561
|
all,
|
|
579
562
|
getInputFd,
|
|
580
|
-
getOutputFd
|
|
563
|
+
getOutputFd,
|
|
564
|
+
unref
|
|
581
565
|
})
|
|
582
566
|
}
|
|
583
567
|
case "PipedCommand": {
|
|
584
568
|
const { commands, pipeOptions } = flattenCommand(cmd)
|
|
585
569
|
const [root, ...pipeline] = commands
|
|
586
570
|
|
|
587
|
-
|
|
571
|
+
const handles = [yield* spawnCommand(root)]
|
|
588
572
|
|
|
589
573
|
for (let i = 0; i < pipeline.length; i++) {
|
|
590
574
|
const command = pipeline[i]
|
|
@@ -593,7 +577,7 @@ const make = Effect.gen(function*() {
|
|
|
593
577
|
|
|
594
578
|
// Get the appropriate stream from the source based on `from` option
|
|
595
579
|
const sourceStream = Stream.unwrap(
|
|
596
|
-
Effect.
|
|
580
|
+
Effect.succeed(getSourceStream(handles[handles.length - 1], options.from))
|
|
597
581
|
)
|
|
598
582
|
|
|
599
583
|
// Determine where to pipe: stdin or custom fd
|
|
@@ -601,48 +585,73 @@ const make = Effect.gen(function*() {
|
|
|
601
585
|
|
|
602
586
|
if (toOption === "stdin") {
|
|
603
587
|
// Pipe to stdin (default behavior)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
588
|
+
handles.push(
|
|
589
|
+
yield* spawnCommand(ChildProcess.make(command.command, command.args, {
|
|
590
|
+
...command.options,
|
|
591
|
+
stdin: { ...stdinConfig, stream: sourceStream }
|
|
592
|
+
}))
|
|
593
|
+
)
|
|
608
594
|
} else {
|
|
609
595
|
// Pipe to custom fd (fd3, fd4, etc.)
|
|
610
596
|
const fd = ChildProcess.parseFdName(toOption)
|
|
611
597
|
if (Predicate.isNotUndefined(fd)) {
|
|
612
598
|
const fdName = ChildProcess.fdName(fd) as `fd${number}`
|
|
613
599
|
const existingFds = command.options.additionalFds ?? {}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
600
|
+
handles.push(
|
|
601
|
+
yield* spawnCommand(ChildProcess.make(command.command, command.args, {
|
|
602
|
+
...command.options,
|
|
603
|
+
additionalFds: {
|
|
604
|
+
...existingFds,
|
|
605
|
+
[fdName]: { type: "input" as const, stream: sourceStream }
|
|
606
|
+
}
|
|
607
|
+
}))
|
|
608
|
+
)
|
|
621
609
|
} else {
|
|
622
610
|
// Invalid fd name, fall back to stdin
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
611
|
+
handles.push(
|
|
612
|
+
yield* spawnCommand(ChildProcess.make(command.command, command.args, {
|
|
613
|
+
...command.options,
|
|
614
|
+
stdin: { ...stdinConfig, stream: sourceStream }
|
|
615
|
+
}))
|
|
616
|
+
)
|
|
627
617
|
}
|
|
628
618
|
}
|
|
629
619
|
}
|
|
630
620
|
|
|
631
|
-
|
|
621
|
+
const handle = handles[handles.length - 1]
|
|
622
|
+
const unref = Effect.gen(function*() {
|
|
623
|
+
const rerefs: Array<Effect.Effect<void, PlatformError.PlatformError>> = []
|
|
624
|
+
for (const handle of handles) {
|
|
625
|
+
rerefs.push(yield* handle.unref)
|
|
626
|
+
}
|
|
627
|
+
return Effect.forEach([...rerefs].reverse(), (reref) => reref, { discard: true })
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
return makeHandle({
|
|
631
|
+
pid: handle.pid,
|
|
632
|
+
exitCode: handle.exitCode,
|
|
633
|
+
isRunning: handle.isRunning,
|
|
634
|
+
kill: handle.kill,
|
|
635
|
+
stdin: handle.stdin,
|
|
636
|
+
stdout: handle.stdout,
|
|
637
|
+
stderr: handle.stderr,
|
|
638
|
+
all: handle.all,
|
|
639
|
+
getInputFd: handle.getInputFd,
|
|
640
|
+
getOutputFd: handle.getOutputFd,
|
|
641
|
+
unref
|
|
642
|
+
})
|
|
632
643
|
}
|
|
633
644
|
}
|
|
634
645
|
})
|
|
635
646
|
|
|
636
|
-
return
|
|
637
|
-
spawn: spawnCommand
|
|
638
|
-
})
|
|
647
|
+
return makeSpawner(spawnCommand)
|
|
639
648
|
})
|
|
640
649
|
|
|
641
650
|
/**
|
|
642
|
-
* Layer
|
|
651
|
+
* Layer that provides the `NodeChildProcessSpawner` implementation.
|
|
643
652
|
*
|
|
653
|
+
* @category layers
|
|
644
654
|
* @since 4.0.0
|
|
645
|
-
* @category Layers
|
|
646
655
|
*/
|
|
647
656
|
export const layer: Layer.Layer<
|
|
648
657
|
ChildProcessSpawner,
|
|
@@ -657,8 +666,8 @@ export const layer: Layer.Layer<
|
|
|
657
666
|
/**
|
|
658
667
|
* Result of flattening a pipeline of commands.
|
|
659
668
|
*
|
|
669
|
+
* @category models
|
|
660
670
|
* @since 4.0.0
|
|
661
|
-
* @category Models
|
|
662
671
|
*/
|
|
663
672
|
export interface FlattenedPipeline {
|
|
664
673
|
readonly commands: Arr.NonEmptyReadonlyArray<ChildProcess.StandardCommand>
|
|
@@ -669,8 +678,8 @@ export interface FlattenedPipeline {
|
|
|
669
678
|
* Flattens a `Command` into an array of `StandardCommand`s along with pipe
|
|
670
679
|
* options for each connection.
|
|
671
680
|
*
|
|
681
|
+
* @category transforming
|
|
672
682
|
* @since 4.0.0
|
|
673
|
-
* @category Utilities
|
|
674
683
|
*/
|
|
675
684
|
export const flattenCommand = (
|
|
676
685
|
command: ChildProcess.Command
|
package/src/NodeClusterSocket.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node TCP socket transport for Effect Cluster runner-to-runner RPC.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the shared Node layers used by socket-based cluster
|
|
5
|
+
* transports. `layerClientProtocol` opens TCP sockets to peer runner addresses
|
|
6
|
+
* and wraps them in the current RPC serialization protocol. `layerSocketServer`
|
|
7
|
+
* exposes the socket server that receives incoming runner RPC traffic.
|
|
8
|
+
*
|
|
9
|
+
* @since 4.0.0
|
|
3
10
|
*/
|
|
4
11
|
import * as Effect from "effect/Effect"
|
|
5
12
|
import * as Layer from "effect/Layer"
|
|
13
|
+
import * as Option from "effect/Option"
|
|
6
14
|
import * as Runners from "effect/unstable/cluster/Runners"
|
|
7
15
|
import * as ShardingConfig from "effect/unstable/cluster/ShardingConfig"
|
|
8
16
|
import * as RpcClient from "effect/unstable/rpc/RpcClient"
|
|
@@ -13,8 +21,11 @@ import * as NodeSocket from "./NodeSocket.ts"
|
|
|
13
21
|
import * as NodeSocketServer from "./NodeSocketServer.ts"
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
24
|
+
* Provides the cluster `RpcClientProtocol` by opening TCP sockets to runner
|
|
25
|
+
* addresses and using the current RPC serialization service.
|
|
26
|
+
*
|
|
27
|
+
* @category layers
|
|
28
|
+
* @since 4.0.0
|
|
18
29
|
*/
|
|
19
30
|
export const layerClientProtocol: Layer.Layer<
|
|
20
31
|
Runners.RpcClientProtocol,
|
|
@@ -39,8 +50,11 @@ export const layerClientProtocol: Layer.Layer<
|
|
|
39
50
|
)
|
|
40
51
|
|
|
41
52
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
53
|
+
* Provides the socket server used by cluster runners, listening on
|
|
54
|
+
* `ShardingConfig.runnerListenAddress` or `runnerAddress`.
|
|
55
|
+
*
|
|
56
|
+
* @category layers
|
|
57
|
+
* @since 4.0.0
|
|
44
58
|
*/
|
|
45
59
|
export const layerSocketServer: Layer.Layer<
|
|
46
60
|
SocketServer.SocketServer,
|
|
@@ -48,9 +62,9 @@ export const layerSocketServer: Layer.Layer<
|
|
|
48
62
|
ShardingConfig.ShardingConfig
|
|
49
63
|
> = Effect.gen(function*() {
|
|
50
64
|
const config = yield* ShardingConfig.ShardingConfig
|
|
51
|
-
const listenAddress = config.runnerListenAddress
|
|
52
|
-
if (listenAddress
|
|
65
|
+
const listenAddress = Option.orElse(config.runnerListenAddress, () => config.runnerAddress)
|
|
66
|
+
if (Option.isNone(listenAddress)) {
|
|
53
67
|
return yield* Effect.die("layerSocketServer: ShardingConfig.runnerListenAddress is None")
|
|
54
68
|
}
|
|
55
|
-
return NodeSocketServer.layer(listenAddress)
|
|
69
|
+
return NodeSocketServer.layer(listenAddress.value)
|
|
56
70
|
}).pipe(Layer.unwrap)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node-compatible implementation of Effect's `Crypto` service.
|
|
3
|
+
*
|
|
4
|
+
* This module builds the service from `node:crypto`, using `randomBytes` for
|
|
5
|
+
* random data and `createHash` for supported digest algorithms. It exports
|
|
6
|
+
* `make` as the concrete service value and `layer` for providing it through
|
|
7
|
+
* Effect context.
|
|
8
|
+
*
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
import * as EffectCrypto from "effect/Crypto"
|
|
12
|
+
import * as Effect from "effect/Effect"
|
|
13
|
+
import * as Layer from "effect/Layer"
|
|
14
|
+
import * as PlatformError from "effect/PlatformError"
|
|
15
|
+
import * as NodeCrypto from "node:crypto"
|
|
16
|
+
|
|
17
|
+
const toHashAlgorithm = (algorithm: EffectCrypto.DigestAlgorithm): string => {
|
|
18
|
+
switch (algorithm) {
|
|
19
|
+
case "SHA-1":
|
|
20
|
+
return "sha1"
|
|
21
|
+
case "SHA-256":
|
|
22
|
+
return "sha256"
|
|
23
|
+
case "SHA-384":
|
|
24
|
+
return "sha384"
|
|
25
|
+
case "SHA-512":
|
|
26
|
+
return "sha512"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const digest: EffectCrypto.Crypto["digest"] = (algorithm, data) =>
|
|
31
|
+
Effect.try({
|
|
32
|
+
try: () => Uint8Array.from(NodeCrypto.createHash(toHashAlgorithm(algorithm)).update(data).digest()),
|
|
33
|
+
catch: (cause) =>
|
|
34
|
+
PlatformError.systemError({
|
|
35
|
+
module: "Crypto",
|
|
36
|
+
method: "digest",
|
|
37
|
+
_tag: "Unknown",
|
|
38
|
+
description: "Could not compute digest",
|
|
39
|
+
cause
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The default Node.js Crypto service implementation.
|
|
45
|
+
*
|
|
46
|
+
* @category constructors
|
|
47
|
+
* @since 1.0.0
|
|
48
|
+
*/
|
|
49
|
+
export const make: EffectCrypto.Crypto = EffectCrypto.make({
|
|
50
|
+
randomBytes: NodeCrypto.randomBytes,
|
|
51
|
+
digest
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Layer that provides the Node.js Crypto service implementation.
|
|
56
|
+
*
|
|
57
|
+
* @category layers
|
|
58
|
+
* @since 1.0.0
|
|
59
|
+
*/
|
|
60
|
+
export const layer: Layer.Layer<EffectCrypto.Crypto> = Layer.succeed(EffectCrypto.Crypto, make)
|