@effect/platform-node-shared 4.0.0-beta.10 → 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.
Files changed (69) hide show
  1. package/README.md +3 -3
  2. package/dist/NodeChildProcessSpawner.d.ts +19 -5
  3. package/dist/NodeChildProcessSpawner.d.ts.map +1 -1
  4. package/dist/NodeChildProcessSpawner.js +99 -29
  5. package/dist/NodeChildProcessSpawner.js.map +1 -1
  6. package/dist/NodeClusterSocket.d.ts +10 -4
  7. package/dist/NodeClusterSocket.d.ts.map +1 -1
  8. package/dist/NodeClusterSocket.js +22 -8
  9. package/dist/NodeClusterSocket.js.map +1 -1
  10. package/dist/NodeCrypto.d.ts +27 -0
  11. package/dist/NodeCrypto.d.ts.map +1 -0
  12. package/dist/NodeCrypto.js +55 -0
  13. package/dist/NodeCrypto.js.map +1 -0
  14. package/dist/NodeFileSystem.d.ts +5 -2
  15. package/dist/NodeFileSystem.d.ts.map +1 -1
  16. package/dist/NodeFileSystem.js +42 -26
  17. package/dist/NodeFileSystem.js.map +1 -1
  18. package/dist/NodePath.d.ts +15 -6
  19. package/dist/NodePath.d.ts.map +1 -1
  20. package/dist/NodePath.js +23 -7
  21. package/dist/NodePath.js.map +1 -1
  22. package/dist/NodeRuntime.d.ts +26 -7
  23. package/dist/NodeRuntime.d.ts.map +1 -1
  24. package/dist/NodeRuntime.js +8 -8
  25. package/dist/NodeRuntime.js.map +1 -1
  26. package/dist/NodeSink.d.ts +33 -4
  27. package/dist/NodeSink.d.ts.map +1 -1
  28. package/dist/NodeSink.js +31 -4
  29. package/dist/NodeSink.js.map +1 -1
  30. package/dist/NodeSocket.d.ts +41 -10
  31. package/dist/NodeSocket.d.ts.map +1 -1
  32. package/dist/NodeSocket.js +39 -16
  33. package/dist/NodeSocket.js.map +1 -1
  34. package/dist/NodeSocketServer.d.ts +25 -8
  35. package/dist/NodeSocketServer.d.ts.map +1 -1
  36. package/dist/NodeSocketServer.js +29 -12
  37. package/dist/NodeSocketServer.js.map +1 -1
  38. package/dist/NodeStdio.d.ts +6 -5
  39. package/dist/NodeStdio.d.ts.map +1 -1
  40. package/dist/NodeStdio.js +23 -7
  41. package/dist/NodeStdio.js.map +1 -1
  42. package/dist/NodeStream.d.ts +76 -20
  43. package/dist/NodeStream.d.ts.map +1 -1
  44. package/dist/NodeStream.js +71 -24
  45. package/dist/NodeStream.js.map +1 -1
  46. package/dist/NodeTerminal.d.ts +9 -2
  47. package/dist/NodeTerminal.d.ts.map +1 -1
  48. package/dist/NodeTerminal.js +13 -3
  49. package/dist/NodeTerminal.js.map +1 -1
  50. package/dist/index.d.ts +52 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +53 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/internal/utils.js.map +1 -1
  55. package/package.json +9 -9
  56. package/src/NodeChildProcessSpawner.ts +143 -46
  57. package/src/NodeClusterSocket.ts +22 -8
  58. package/src/NodeCrypto.ts +60 -0
  59. package/src/NodeFileSystem.ts +56 -28
  60. package/src/NodePath.ts +23 -7
  61. package/src/NodeRuntime.ts +28 -13
  62. package/src/NodeSink.ts +40 -4
  63. package/src/NodeSocket.ts +47 -17
  64. package/src/NodeSocketServer.ts +39 -13
  65. package/src/NodeStdio.ts +41 -23
  66. package/src/NodeStream.ts +92 -31
  67. package/src/NodeTerminal.ts +22 -4
  68. package/src/index.ts +65 -0
  69. package/src/internal/utils.ts +1 -1
package/dist/index.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @since 4.0.0
3
+ */
4
+ // @barrel: Auto-generated exports. Do not edit manually.
5
+ /**
6
+ * @since 4.0.0
7
+ */
8
+ export * as NodeChildProcessSpawner from "./NodeChildProcessSpawner.js";
9
+ /**
10
+ * @since 4.0.0
11
+ */
12
+ export * as NodeClusterSocket from "./NodeClusterSocket.js";
13
+ /**
14
+ * @since 1.0.0
15
+ */
16
+ export * as NodeCrypto from "./NodeCrypto.js";
17
+ /**
18
+ * @since 4.0.0
19
+ */
20
+ export * as NodeFileSystem from "./NodeFileSystem.js";
21
+ /**
22
+ * @since 4.0.0
23
+ */
24
+ export * as NodePath from "./NodePath.js";
25
+ /**
26
+ * @since 4.0.0
27
+ */
28
+ export * as NodeRuntime from "./NodeRuntime.js";
29
+ /**
30
+ * @since 4.0.0
31
+ */
32
+ export * as NodeSink from "./NodeSink.js";
33
+ /**
34
+ * @since 4.0.0
35
+ */
36
+ export * as NodeSocket from "./NodeSocket.js";
37
+ /**
38
+ * @since 4.0.0
39
+ */
40
+ export * as NodeSocketServer from "./NodeSocketServer.js";
41
+ /**
42
+ * @since 4.0.0
43
+ */
44
+ export * as NodeStdio from "./NodeStdio.js";
45
+ /**
46
+ * @since 4.0.0
47
+ */
48
+ export * as NodeStream from "./NodeStream.js";
49
+ /**
50
+ * @since 4.0.0
51
+ */
52
+ export * as NodeTerminal from "./NodeTerminal.js";
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["NodeChildProcessSpawner","NodeClusterSocket","NodeCrypto","NodeFileSystem","NodePath","NodeRuntime","NodeSink","NodeSocket","NodeSocketServer","NodeStdio","NodeStream","NodeTerminal"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAIA;AAEA;;;AAGA,OAAO,KAAKA,uBAAuB,MAAM,8BAA8B;AAEvE;;;AAGA,OAAO,KAAKC,iBAAiB,MAAM,wBAAwB;AAE3D;;;AAGA,OAAO,KAAKC,UAAU,MAAM,iBAAiB;AAE7C;;;AAGA,OAAO,KAAKC,cAAc,MAAM,qBAAqB;AAErD;;;AAGA,OAAO,KAAKC,QAAQ,MAAM,eAAe;AAEzC;;;AAGA,OAAO,KAAKC,WAAW,MAAM,kBAAkB;AAE/C;;;AAGA,OAAO,KAAKC,QAAQ,MAAM,eAAe;AAEzC;;;AAGA,OAAO,KAAKC,UAAU,MAAM,iBAAiB;AAE7C;;;AAGA,OAAO,KAAKC,gBAAgB,MAAM,uBAAuB;AAEzD;;;AAGA,OAAO,KAAKC,SAAS,MAAM,gBAAgB;AAE3C;;;AAGA,OAAO,KAAKC,UAAU,MAAM,iBAAiB;AAE7C;;;AAGA,OAAO,KAAKC,YAAY,MAAM,mBAAmB","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":["PlatformError","handleErrnoException","module","method","err","path","reason","code","systemError","_tag","pathOrDescriptor","syscall","cause"],"sources":["../../src/internal/utils.ts"],"sourcesContent":[null],"mappings":"AACA,OAAO,KAAKA,aAAa,MAAM,sBAAsB;AAGrD;AACA,OAAO,MAAMC,oBAAoB,GAAGA,CAACC,MAA6B,EAAEC,MAAc,KAClF,CACEC,GAA0B,EAC1B,CAACC,IAAI,CAAiD,KACvB;EAC/B,IAAIC,MAAM,GAAmB,SAAS;EAEtC,QAAQF,GAAG,CAACG,IAAI;IACd,KAAK,QAAQ;MACXD,MAAM,GAAG,UAAU;MACnB;IAEF,KAAK,QAAQ;MACXA,MAAM,GAAG,kBAAkB;MAC3B;IAEF,KAAK,QAAQ;MACXA,MAAM,GAAG,eAAe;MACxB;IAEF,KAAK,QAAQ;MACXA,MAAM,GAAG,aAAa;MACtB;IAEF,KAAK,SAAS;MACZA,MAAM,GAAG,aAAa;MACtB;IAEF,KAAK,OAAO;MACVA,MAAM,GAAG,MAAM;MACf;IAEF,KAAK,OAAO;MACVA,MAAM,GAAG,aAAa;MACtB;EACJ;EAEA,OAAON,aAAa,CAACQ,WAAW,CAAC;IAC/BC,IAAI,EAAEH,MAAM;IACZJ,MAAM;IACNC,MAAM;IACNO,gBAAgB,EAAEL,IAAuB;IACzCM,OAAO,EAAEP,GAAG,CAACO,OAAO;IACpBC,KAAK,EAAER;GACR,CAAC;AACJ,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"utils.js","names":["PlatformError","handleErrnoException","module","method","err","path","reason","code","systemError","_tag","pathOrDescriptor","syscall","cause"],"sources":["../../src/internal/utils.ts"],"sourcesContent":[null],"mappings":"AACA,OAAO,KAAKA,aAAa,MAAM,sBAAsB;AAGrD;AACA,OAAO,MAAMC,oBAAoB,GAAGA,CAACC,MAA6B,EAAEC,MAAc,KAClF,CACEC,GAA0B,EAC1B,CAACC,IAAI,CAA8E,KACpD;EAC/B,IAAIC,MAAM,GAAmB,SAAS;EAEtC,QAAQF,GAAG,CAACG,IAAI;IACd,KAAK,QAAQ;MACXD,MAAM,GAAG,UAAU;MACnB;IAEF,KAAK,QAAQ;MACXA,MAAM,GAAG,kBAAkB;MAC3B;IAEF,KAAK,QAAQ;MACXA,MAAM,GAAG,eAAe;MACxB;IAEF,KAAK,QAAQ;MACXA,MAAM,GAAG,aAAa;MACtB;IAEF,KAAK,SAAS;MACZA,MAAM,GAAG,aAAa;MACtB;IAEF,KAAK,OAAO;MACVA,MAAM,GAAG,MAAM;MACf;IAEF,KAAK,OAAO;MACVA,MAAM,GAAG,aAAa;MACtB;EACJ;EAEA,OAAON,aAAa,CAACQ,WAAW,CAAC;IAC/BC,IAAI,EAAEH,MAAM;IACZJ,MAAM;IACNC,MAAM;IACNO,gBAAgB,EAAEL,IAAuB;IACzCM,OAAO,EAAEP,GAAG,CAACO,OAAO;IACpBC,KAAK,EAAER;GACR,CAAC;AACJ,CAAC","ignoreList":[]}
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@effect/platform-node-shared",
3
3
  "type": "module",
4
- "version": "4.0.0-beta.10",
4
+ "version": "4.0.0-beta.100",
5
5
  "license": "MIT",
6
6
  "description": "Unified interfaces for common platform-specific services",
7
7
  "homepage": "https://effect.website",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/Effect-TS/effect-smol.git",
10
+ "url": "https://github.com/Effect-TS/effect.git",
11
11
  "directory": "packages/platform-node-shared"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://github.com/Effect-TS/effect-smol/issues"
14
+ "url": "https://github.com/Effect-TS/effect/issues"
15
15
  },
16
16
  "tags": [
17
17
  "typescript",
@@ -46,20 +46,20 @@
46
46
  "provenance": true
47
47
  },
48
48
  "peerDependencies": {
49
- "effect": "^4.0.0-beta.10"
49
+ "effect": "^4.0.0-beta.100"
50
50
  },
51
51
  "devDependencies": {
52
- "@types/node": "^25.2.0",
53
- "tar": "^7.5.7",
54
- "effect": "^4.0.0-beta.10"
52
+ "@types/node": "^26.1.1",
53
+ "tar": "^7.5.19",
54
+ "effect": "^4.0.0-beta.100"
55
55
  },
56
56
  "dependencies": {
57
57
  "@types/ws": "^8.18.1",
58
- "ws": "^8.19.0"
58
+ "ws": "^8.21.0"
59
59
  },
60
60
  "scripts": {
61
+ "codegen": "effect-utils codegen",
61
62
  "build": "tsc -b tsconfig.json && pnpm babel",
62
- "build:tsgo": "tsgo -b tsconfig.json && pnpm babel",
63
63
  "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps",
64
64
  "check": "tsc -b tsconfig.json",
65
65
  "test": "vitest",
@@ -1,5 +1,19 @@
1
1
  /**
2
- * Node.js implementation of `ChildProcessSpawner`.
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,8 +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 { ChildProcessSpawner, ExitCode, makeHandle, ProcessId } from "effect/unstable/process/ChildProcessSpawner"
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"
42
+ import { PassThrough } from "node:stream"
22
43
  import { handleErrnoException } from "./internal/utils.ts"
23
44
  import * as NodeSink from "./NodeSink.ts"
24
45
  import * as NodeStream from "./NodeStream.ts"
@@ -200,8 +221,11 @@ const make = Effect.gen(function*() {
200
221
  // Create a stream to read from for output file descriptors
201
222
  let stream: Stream.Stream<Uint8Array, PlatformError.PlatformError> = Stream.empty
202
223
  if (nodeStream && "read" in nodeStream) {
224
+ const passThrough = new PassThrough()
225
+ nodeStream.on("error", (error) => passThrough.destroy(error))
226
+ nodeStream.pipe(passThrough)
203
227
  stream = NodeStream.fromReadable({
204
- evaluate: () => nodeStream,
228
+ evaluate: () => passThrough,
205
229
  onError: (error) => toPlatformError(`fromReadable(fd${fd})`, toError(error), command)
206
230
  })
207
231
  }
@@ -261,16 +285,26 @@ const make = Effect.gen(function*() {
261
285
  all: Stream.Stream<Uint8Array, PlatformError.PlatformError>
262
286
  } => {
263
287
  let stdout = childProcess.stdout ?
264
- NodeStream.fromReadable({
265
- evaluate: () => childProcess.stdout!,
266
- onError: (error) => toPlatformError("fromReadable(stdout)", toError(error), command)
267
- }) :
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
+ })() :
268
297
  Stream.empty
269
298
  let stderr = childProcess.stderr ?
270
- NodeStream.fromReadable({
271
- evaluate: () => childProcess.stderr!,
272
- onError: (error) => toPlatformError("fromReadable(stderr)", toError(error), command)
273
- }) :
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
+ })() :
274
308
  Stream.empty
275
309
 
276
310
  if (Sink.isSink(stdoutConfig.stream)) {
@@ -337,6 +371,23 @@ const make = Effect.gen(function*() {
337
371
  })
338
372
  }
339
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
+
340
391
  const killProcess = (
341
392
  command: ChildProcess.StandardCommand,
342
393
  childProcess: NodeChildProcess.ChildProcess,
@@ -368,7 +419,7 @@ const make = Effect.gen(function*() {
368
419
  ? kill(command, childProcess, killSignal)
369
420
  : Effect.timeoutOrElse(kill(command, childProcess, killSignal), {
370
421
  duration: options.forceKillAfter,
371
- onTimeout: () => kill(command, childProcess, "SIGKILL")
422
+ orElse: () => kill(command, childProcess, "SIGKILL")
372
423
  })
373
424
  }
374
425
 
@@ -413,6 +464,7 @@ const make = Effect.gen(function*() {
413
464
  const stdoutConfig = resolveOutputOption(cmd.options, "stdout")
414
465
  const stderrConfig = resolveOutputOption(cmd.options, "stderr")
415
466
  const resolvedAdditionalFds = resolveAdditionalFds(cmd.options)
467
+ let isReferenced = true
416
468
 
417
469
  const cwd = yield* resolveWorkingDirectory(cmd.options)
418
470
  const env = resolveEnvironment(cmd.options)
@@ -438,20 +490,40 @@ const make = Effect.gen(function*() {
438
490
  }
439
491
  return yield* Effect.void
440
492
  }
493
+ if (!isReferenced) {
494
+ return yield* Effect.void
495
+ }
441
496
  // Process is still running, kill it
442
497
  return yield* killWithTimeout((command, childProcess, signal) =>
443
- Effect.catch(
444
- killProcessGroup(command, childProcess, signal),
445
- () => killProcess(command, childProcess, signal)
498
+ killProcessGroup(command, childProcess, signal).pipe(
499
+ Effect.catch(() => killProcess(command, childProcess, signal)),
500
+ Effect.andThen(Deferred.await(exitSignal))
446
501
  )
447
502
  ).pipe(
448
- Effect.andThen(Deferred.await(exitSignal)),
449
503
  Effect.ignore
450
504
  )
451
505
  })
452
506
  )
453
507
 
454
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
+ })
455
527
  const stdin = yield* setupChildStdin(cmd, childProcess, stdinConfig)
456
528
  const { all, stderr, stdout } = setupChildOutputStreams(cmd, childProcess, stdoutConfig, stderrConfig)
457
529
  const { getInputFd, getOutputFd } = yield* setupAdditionalFds(cmd, childProcess, resolvedAdditionalFds)
@@ -469,12 +541,11 @@ const make = Effect.gen(function*() {
469
541
  const kill = (options?: ChildProcess.KillOptions | undefined) => {
470
542
  const killWithTimeout = withTimeout(childProcess, cmd, options)
471
543
  return killWithTimeout((command, childProcess, signal) =>
472
- Effect.catch(
473
- killProcessGroup(command, childProcess, signal),
474
- () => killProcess(command, childProcess, signal)
544
+ killProcessGroup(command, childProcess, signal).pipe(
545
+ Effect.catch(() => killProcess(command, childProcess, signal)),
546
+ Effect.andThen(Deferred.await(exitSignal))
475
547
  )
476
548
  ).pipe(
477
- Effect.andThen(Deferred.await(exitSignal)),
478
549
  Effect.asVoid
479
550
  )
480
551
  }
@@ -489,14 +560,15 @@ const make = Effect.gen(function*() {
489
560
  stderr,
490
561
  all,
491
562
  getInputFd,
492
- getOutputFd
563
+ getOutputFd,
564
+ unref
493
565
  })
494
566
  }
495
567
  case "PipedCommand": {
496
568
  const { commands, pipeOptions } = flattenCommand(cmd)
497
569
  const [root, ...pipeline] = commands
498
570
 
499
- let handle = spawnCommand(root)
571
+ const handles = [yield* spawnCommand(root)]
500
572
 
501
573
  for (let i = 0; i < pipeline.length; i++) {
502
574
  const command = pipeline[i]
@@ -505,7 +577,7 @@ const make = Effect.gen(function*() {
505
577
 
506
578
  // Get the appropriate stream from the source based on `from` option
507
579
  const sourceStream = Stream.unwrap(
508
- Effect.map(handle, (h) => getSourceStream(h, options.from))
580
+ Effect.succeed(getSourceStream(handles[handles.length - 1], options.from))
509
581
  )
510
582
 
511
583
  // Determine where to pipe: stdin or custom fd
@@ -513,48 +585,73 @@ const make = Effect.gen(function*() {
513
585
 
514
586
  if (toOption === "stdin") {
515
587
  // Pipe to stdin (default behavior)
516
- handle = spawnCommand(ChildProcess.make(command.command, command.args, {
517
- ...command.options,
518
- stdin: { ...stdinConfig, stream: sourceStream }
519
- }))
588
+ handles.push(
589
+ yield* spawnCommand(ChildProcess.make(command.command, command.args, {
590
+ ...command.options,
591
+ stdin: { ...stdinConfig, stream: sourceStream }
592
+ }))
593
+ )
520
594
  } else {
521
595
  // Pipe to custom fd (fd3, fd4, etc.)
522
596
  const fd = ChildProcess.parseFdName(toOption)
523
597
  if (Predicate.isNotUndefined(fd)) {
524
598
  const fdName = ChildProcess.fdName(fd) as `fd${number}`
525
599
  const existingFds = command.options.additionalFds ?? {}
526
- handle = spawnCommand(ChildProcess.make(command.command, command.args, {
527
- ...command.options,
528
- additionalFds: {
529
- ...existingFds,
530
- [fdName]: { type: "input" as const, stream: sourceStream }
531
- }
532
- }))
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
+ )
533
609
  } else {
534
610
  // Invalid fd name, fall back to stdin
535
- handle = spawnCommand(ChildProcess.make(command.command, command.args, {
536
- ...command.options,
537
- stdin: { ...stdinConfig, stream: sourceStream }
538
- }))
611
+ handles.push(
612
+ yield* spawnCommand(ChildProcess.make(command.command, command.args, {
613
+ ...command.options,
614
+ stdin: { ...stdinConfig, stream: sourceStream }
615
+ }))
616
+ )
539
617
  }
540
618
  }
541
619
  }
542
620
 
543
- return yield* handle
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
+ })
544
643
  }
545
644
  }
546
645
  })
547
646
 
548
- return ChildProcessSpawner.of({
549
- spawn: spawnCommand
550
- })
647
+ return makeSpawner(spawnCommand)
551
648
  })
552
649
 
553
650
  /**
554
- * Layer providing the `NodeChildProcessSpawner` implementation.
651
+ * Layer that provides the `NodeChildProcessSpawner` implementation.
555
652
  *
653
+ * @category layers
556
654
  * @since 4.0.0
557
- * @category Layers
558
655
  */
559
656
  export const layer: Layer.Layer<
560
657
  ChildProcessSpawner,
@@ -569,8 +666,8 @@ export const layer: Layer.Layer<
569
666
  /**
570
667
  * Result of flattening a pipeline of commands.
571
668
  *
669
+ * @category models
572
670
  * @since 4.0.0
573
- * @category Models
574
671
  */
575
672
  export interface FlattenedPipeline {
576
673
  readonly commands: Arr.NonEmptyReadonlyArray<ChildProcess.StandardCommand>
@@ -581,8 +678,8 @@ export interface FlattenedPipeline {
581
678
  * Flattens a `Command` into an array of `StandardCommand`s along with pipe
582
679
  * options for each connection.
583
680
  *
681
+ * @category transforming
584
682
  * @since 4.0.0
585
- * @category Utilities
586
683
  */
587
684
  export const flattenCommand = (
588
685
  command: ChildProcess.Command
@@ -1,8 +1,16 @@
1
1
  /**
2
- * @since 1.0.0
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
- * @since 1.0.0
17
- * @category Layers
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
- * @since 1.0.0
43
- * @category Layers
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 ?? config.runnerAddress
52
- if (listenAddress === undefined) {
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)