@effect/platform-node-shared 4.0.0-beta.8 → 4.0.0-beta.81

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 (66) hide show
  1. package/dist/NodeChildProcessSpawner.d.ts +19 -5
  2. package/dist/NodeChildProcessSpawner.d.ts.map +1 -1
  3. package/dist/NodeChildProcessSpawner.js +77 -18
  4. package/dist/NodeChildProcessSpawner.js.map +1 -1
  5. package/dist/NodeClusterSocket.d.ts +10 -4
  6. package/dist/NodeClusterSocket.d.ts.map +1 -1
  7. package/dist/NodeClusterSocket.js +22 -8
  8. package/dist/NodeClusterSocket.js.map +1 -1
  9. package/dist/NodeCrypto.d.ts +27 -0
  10. package/dist/NodeCrypto.d.ts.map +1 -0
  11. package/dist/NodeCrypto.js +55 -0
  12. package/dist/NodeCrypto.js.map +1 -0
  13. package/dist/NodeFileSystem.d.ts +5 -2
  14. package/dist/NodeFileSystem.d.ts.map +1 -1
  15. package/dist/NodeFileSystem.js +33 -26
  16. package/dist/NodeFileSystem.js.map +1 -1
  17. package/dist/NodePath.d.ts +15 -6
  18. package/dist/NodePath.d.ts.map +1 -1
  19. package/dist/NodePath.js +23 -7
  20. package/dist/NodePath.js.map +1 -1
  21. package/dist/NodeRuntime.d.ts +26 -7
  22. package/dist/NodeRuntime.d.ts.map +1 -1
  23. package/dist/NodeRuntime.js +8 -8
  24. package/dist/NodeRuntime.js.map +1 -1
  25. package/dist/NodeSink.d.ts +33 -4
  26. package/dist/NodeSink.d.ts.map +1 -1
  27. package/dist/NodeSink.js +31 -4
  28. package/dist/NodeSink.js.map +1 -1
  29. package/dist/NodeSocket.d.ts +41 -10
  30. package/dist/NodeSocket.d.ts.map +1 -1
  31. package/dist/NodeSocket.js +39 -16
  32. package/dist/NodeSocket.js.map +1 -1
  33. package/dist/NodeSocketServer.d.ts +25 -8
  34. package/dist/NodeSocketServer.d.ts.map +1 -1
  35. package/dist/NodeSocketServer.js +29 -12
  36. package/dist/NodeSocketServer.js.map +1 -1
  37. package/dist/NodeStdio.d.ts +6 -5
  38. package/dist/NodeStdio.d.ts.map +1 -1
  39. package/dist/NodeStdio.js +23 -7
  40. package/dist/NodeStdio.js.map +1 -1
  41. package/dist/NodeStream.d.ts +76 -20
  42. package/dist/NodeStream.d.ts.map +1 -1
  43. package/dist/NodeStream.js +68 -23
  44. package/dist/NodeStream.js.map +1 -1
  45. package/dist/NodeTerminal.d.ts +9 -2
  46. package/dist/NodeTerminal.d.ts.map +1 -1
  47. package/dist/NodeTerminal.js +13 -3
  48. package/dist/NodeTerminal.js.map +1 -1
  49. package/dist/index.d.ts +52 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +53 -0
  52. package/dist/index.js.map +1 -0
  53. package/package.json +7 -6
  54. package/src/NodeChildProcessSpawner.ts +117 -29
  55. package/src/NodeClusterSocket.ts +22 -8
  56. package/src/NodeCrypto.ts +60 -0
  57. package/src/NodeFileSystem.ts +40 -28
  58. package/src/NodePath.ts +23 -7
  59. package/src/NodeRuntime.ts +28 -13
  60. package/src/NodeSink.ts +40 -4
  61. package/src/NodeSocket.ts +47 -17
  62. package/src/NodeSocketServer.ts +39 -13
  63. package/src/NodeStdio.ts +41 -23
  64. package/src/NodeStream.ts +89 -30
  65. package/src/NodeTerminal.ts +22 -4
  66. package/src/index.ts +65 -0
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":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@effect/platform-node-shared",
3
3
  "type": "module",
4
- "version": "4.0.0-beta.8",
4
+ "version": "4.0.0-beta.81",
5
5
  "license": "MIT",
6
6
  "description": "Unified interfaces for common platform-specific services",
7
7
  "homepage": "https://effect.website",
@@ -46,18 +46,19 @@
46
46
  "provenance": true
47
47
  },
48
48
  "peerDependencies": {
49
- "effect": "^4.0.0-beta.8"
49
+ "effect": "^4.0.0-beta.81"
50
50
  },
51
51
  "devDependencies": {
52
- "@types/node": "^25.2.0",
53
- "tar": "^7.5.7",
54
- "effect": "^4.0.0-beta.8"
52
+ "@types/node": "^25.7.0",
53
+ "tar": "^7.5.15",
54
+ "effect": "^4.0.0-beta.81"
55
55
  },
56
56
  "dependencies": {
57
57
  "@types/ws": "^8.18.1",
58
- "ws": "^8.19.0"
58
+ "ws": "^8.20.0"
59
59
  },
60
60
  "scripts": {
61
+ "codegen": "effect-utils codegen",
61
62
  "build": "tsc -b tsconfig.json && pnpm babel",
62
63
  "build:tsgo": "tsgo -b tsconfig.json && pnpm babel",
63
64
  "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps",
@@ -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,7 +31,13 @@ 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"
22
42
  import { handleErrnoException } from "./internal/utils.ts"
23
43
  import * as NodeSink from "./NodeSink.ts"
@@ -337,6 +357,23 @@ const make = Effect.gen(function*() {
337
357
  })
338
358
  }
339
359
 
360
+ const killProcessGroupOnExit = (
361
+ childProcess: NodeChildProcess.ChildProcess,
362
+ signal: NodeJS.Signals
363
+ ): void => {
364
+ if (globalThis.process.platform === "win32") {
365
+ NodeChildProcess.exec(`taskkill /pid ${childProcess.pid} /T /F`, () => {
366
+ // ignore errors during best-effort cleanup
367
+ })
368
+ return
369
+ }
370
+ try {
371
+ globalThis.process.kill(-childProcess.pid!, signal)
372
+ } catch {
373
+ // ignore errors during best-effort cleanup
374
+ }
375
+ }
376
+
340
377
  const killProcess = (
341
378
  command: ChildProcess.StandardCommand,
342
379
  childProcess: NodeChildProcess.ChildProcess,
@@ -368,7 +405,7 @@ const make = Effect.gen(function*() {
368
405
  ? kill(command, childProcess, killSignal)
369
406
  : Effect.timeoutOrElse(kill(command, childProcess, killSignal), {
370
407
  duration: options.forceKillAfter,
371
- onTimeout: () => kill(command, childProcess, "SIGKILL")
408
+ orElse: () => kill(command, childProcess, "SIGKILL")
372
409
  })
373
410
  }
374
411
 
@@ -413,6 +450,8 @@ const make = Effect.gen(function*() {
413
450
  const stdoutConfig = resolveOutputOption(cmd.options, "stdout")
414
451
  const stderrConfig = resolveOutputOption(cmd.options, "stderr")
415
452
  const resolvedAdditionalFds = resolveAdditionalFds(cmd.options)
453
+ let isReferenced = true
454
+ let cleanupOnNonZeroExit = false
416
455
 
417
456
  const cwd = yield* resolveWorkingDirectory(cmd.options)
418
457
  const env = resolveEnvironment(cmd.options)
@@ -438,6 +477,9 @@ const make = Effect.gen(function*() {
438
477
  }
439
478
  return yield* Effect.void
440
479
  }
480
+ if (!isReferenced) {
481
+ return yield* Effect.void
482
+ }
441
483
  // Process is still running, kill it
442
484
  return yield* killWithTimeout((command, childProcess, signal) =>
443
485
  Effect.catch(
@@ -452,6 +494,26 @@ const make = Effect.gen(function*() {
452
494
  )
453
495
 
454
496
  const pid = ProcessId(childProcess.pid!)
497
+ childProcess.on("exit", (code) => {
498
+ if (cleanupOnNonZeroExit && code !== 0 && Predicate.isNotNull(code)) {
499
+ killProcessGroupOnExit(childProcess, cmd.options.killSignal ?? "SIGTERM")
500
+ }
501
+ })
502
+ const reref = Effect.sync(() => {
503
+ if (!isReferenced) {
504
+ childProcess.ref()
505
+ isReferenced = true
506
+ cleanupOnNonZeroExit = false
507
+ }
508
+ })
509
+ const unref = Effect.sync(() => {
510
+ if (isReferenced) {
511
+ childProcess.unref()
512
+ isReferenced = false
513
+ cleanupOnNonZeroExit = true
514
+ }
515
+ return reref
516
+ })
455
517
  const stdin = yield* setupChildStdin(cmd, childProcess, stdinConfig)
456
518
  const { all, stderr, stdout } = setupChildOutputStreams(cmd, childProcess, stdoutConfig, stderrConfig)
457
519
  const { getInputFd, getOutputFd } = yield* setupAdditionalFds(cmd, childProcess, resolvedAdditionalFds)
@@ -489,14 +551,15 @@ const make = Effect.gen(function*() {
489
551
  stderr,
490
552
  all,
491
553
  getInputFd,
492
- getOutputFd
554
+ getOutputFd,
555
+ unref
493
556
  })
494
557
  }
495
558
  case "PipedCommand": {
496
559
  const { commands, pipeOptions } = flattenCommand(cmd)
497
560
  const [root, ...pipeline] = commands
498
561
 
499
- let handle = spawnCommand(root)
562
+ const handles = [yield* spawnCommand(root)]
500
563
 
501
564
  for (let i = 0; i < pipeline.length; i++) {
502
565
  const command = pipeline[i]
@@ -505,7 +568,7 @@ const make = Effect.gen(function*() {
505
568
 
506
569
  // Get the appropriate stream from the source based on `from` option
507
570
  const sourceStream = Stream.unwrap(
508
- Effect.map(handle, (h) => getSourceStream(h, options.from))
571
+ Effect.succeed(getSourceStream(handles[handles.length - 1], options.from))
509
572
  )
510
573
 
511
574
  // Determine where to pipe: stdin or custom fd
@@ -513,48 +576,73 @@ const make = Effect.gen(function*() {
513
576
 
514
577
  if (toOption === "stdin") {
515
578
  // Pipe to stdin (default behavior)
516
- handle = spawnCommand(ChildProcess.make(command.command, command.args, {
517
- ...command.options,
518
- stdin: { ...stdinConfig, stream: sourceStream }
519
- }))
579
+ handles.push(
580
+ yield* spawnCommand(ChildProcess.make(command.command, command.args, {
581
+ ...command.options,
582
+ stdin: { ...stdinConfig, stream: sourceStream }
583
+ }))
584
+ )
520
585
  } else {
521
586
  // Pipe to custom fd (fd3, fd4, etc.)
522
587
  const fd = ChildProcess.parseFdName(toOption)
523
588
  if (Predicate.isNotUndefined(fd)) {
524
589
  const fdName = ChildProcess.fdName(fd) as `fd${number}`
525
590
  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
- }))
591
+ handles.push(
592
+ yield* spawnCommand(ChildProcess.make(command.command, command.args, {
593
+ ...command.options,
594
+ additionalFds: {
595
+ ...existingFds,
596
+ [fdName]: { type: "input" as const, stream: sourceStream }
597
+ }
598
+ }))
599
+ )
533
600
  } else {
534
601
  // 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
- }))
602
+ handles.push(
603
+ yield* spawnCommand(ChildProcess.make(command.command, command.args, {
604
+ ...command.options,
605
+ stdin: { ...stdinConfig, stream: sourceStream }
606
+ }))
607
+ )
539
608
  }
540
609
  }
541
610
  }
542
611
 
543
- return yield* handle
612
+ const handle = handles[handles.length - 1]
613
+ const unref = Effect.gen(function*() {
614
+ const rerefs: Array<Effect.Effect<void, PlatformError.PlatformError>> = []
615
+ for (const handle of handles) {
616
+ rerefs.push(yield* handle.unref)
617
+ }
618
+ return Effect.forEach([...rerefs].reverse(), (reref) => reref, { discard: true })
619
+ })
620
+
621
+ return makeHandle({
622
+ pid: handle.pid,
623
+ exitCode: handle.exitCode,
624
+ isRunning: handle.isRunning,
625
+ kill: handle.kill,
626
+ stdin: handle.stdin,
627
+ stdout: handle.stdout,
628
+ stderr: handle.stderr,
629
+ all: handle.all,
630
+ getInputFd: handle.getInputFd,
631
+ getOutputFd: handle.getOutputFd,
632
+ unref
633
+ })
544
634
  }
545
635
  }
546
636
  })
547
637
 
548
- return ChildProcessSpawner.of({
549
- spawn: spawnCommand
550
- })
638
+ return makeSpawner(spawnCommand)
551
639
  })
552
640
 
553
641
  /**
554
- * Layer providing the `NodeChildProcessSpawner` implementation.
642
+ * Layer that provides the `NodeChildProcessSpawner` implementation.
555
643
  *
644
+ * @category layers
556
645
  * @since 4.0.0
557
- * @category Layers
558
646
  */
559
647
  export const layer: Layer.Layer<
560
648
  ChildProcessSpawner,
@@ -569,8 +657,8 @@ export const layer: Layer.Layer<
569
657
  /**
570
658
  * Result of flattening a pipeline of commands.
571
659
  *
660
+ * @category models
572
661
  * @since 4.0.0
573
- * @category Models
574
662
  */
575
663
  export interface FlattenedPipeline {
576
664
  readonly commands: Arr.NonEmptyReadonlyArray<ChildProcess.StandardCommand>
@@ -581,8 +669,8 @@ export interface FlattenedPipeline {
581
669
  * Flattens a `Command` into an array of `StandardCommand`s along with pipe
582
670
  * options for each connection.
583
671
  *
672
+ * @category transforming
584
673
  * @since 4.0.0
585
- * @category Utilities
586
674
  */
587
675
  export const flattenCommand = (
588
676
  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)
@@ -1,5 +1,13 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Shared Node-compatible implementation of Effect's `FileSystem` service.
3
+ *
4
+ * This module adapts Node's `node:fs`, `node:os`, and `node:path` APIs into a
5
+ * `FileSystem` layer for Effect programs running on Node-compatible runtimes.
6
+ * Platform packages use it to provide file and directory I/O, permissions,
7
+ * links, metadata, temporary files and directories, and file watching through
8
+ * the shared `FileSystem` service.
9
+ *
10
+ * @since 4.0.0
3
11
  */
4
12
  import * as Cause from "effect/Cause"
5
13
  import * as Effect from "effect/Effect"
@@ -232,7 +240,7 @@ const makeFile = (() => {
232
240
  readonly fd: FileSystem.File.Descriptor
233
241
  private readonly append: boolean
234
242
 
235
- private position: bigint = 0n
243
+ private position: bigint = BigInt(0)
236
244
 
237
245
  constructor(
238
246
  fd: FileSystem.File.Descriptor,
@@ -285,19 +293,19 @@ const makeFile = (() => {
285
293
  const position = this.position
286
294
  return Effect.map(
287
295
  nodeReadAlloc(this.fd, { buffer, position }),
288
- (bytesRead): Buffer | undefined => {
296
+ (bytesRead): Option.Option<Buffer> => {
289
297
  if (bytesRead === 0) {
290
- return undefined
298
+ return Option.none()
291
299
  }
292
300
 
293
301
  this.position = position + BigInt(bytesRead)
294
302
  if (bytesRead === sizeNumber) {
295
- return buffer
303
+ return Option.some(buffer)
296
304
  }
297
305
 
298
306
  const dst = Buffer.allocUnsafeSlow(bytesRead)
299
307
  buffer.copy(dst, 0, 0, bytesRead)
300
- return dst
308
+ return Option.some(dst)
301
309
  }
302
310
  )
303
311
  })
@@ -468,19 +476,19 @@ const makeFileInfo = (stat: NFS.Stats): FileSystem.File.Info => ({
468
476
  stat.isSocket() ?
469
477
  "Socket" :
470
478
  "Unknown",
471
- mtime: stat.mtime,
472
- atime: stat.atime,
473
- birthtime: stat.birthtime,
479
+ mtime: Option.fromNullishOr(stat.mtime),
480
+ atime: Option.fromNullishOr(stat.atime),
481
+ birthtime: Option.fromNullishOr(stat.birthtime),
474
482
  dev: stat.dev,
475
- rdev: stat.rdev,
476
- ino: stat.ino,
483
+ rdev: Option.fromNullishOr(stat.rdev),
484
+ ino: Option.fromNullishOr(stat.ino),
477
485
  mode: stat.mode,
478
- nlink: stat.nlink,
479
- uid: stat.uid,
480
- gid: stat.gid,
486
+ nlink: Option.fromNullishOr(stat.nlink),
487
+ uid: Option.fromNullishOr(stat.uid),
488
+ gid: Option.fromNullishOr(stat.gid),
481
489
  size: FileSystem.Size(stat.size),
482
- blksize: FileSystem.Size(stat.blksize),
483
- blocks: stat.blocks
490
+ blksize: stat.blksize !== undefined ? Option.some(FileSystem.Size(stat.blksize)) : Option.none(),
491
+ blocks: Option.fromNullishOr(stat.blocks)
484
492
  })
485
493
  const stat = (() => {
486
494
  const nodeStat = effectify(
@@ -531,7 +539,9 @@ const watchNode = (path: string) =>
531
539
  Stream.callback<FileSystem.WatchEvent, Error.PlatformError>((queue) =>
532
540
  Effect.acquireRelease(
533
541
  Effect.sync(() => {
534
- const watcher = NFS.watch(path, {}, (event, path) => {
542
+ const watcher = NFS.watch(path, {
543
+ recursive: true
544
+ }, (event, path) => {
535
545
  if (!path) return
536
546
  switch (event) {
537
547
  case "rename": {
@@ -570,15 +580,14 @@ const watchNode = (path: string) =>
570
580
  )
571
581
  )
572
582
 
573
- const watch = (backend: FileSystem.WatchBackend["Service"] | undefined, path: string) =>
583
+ const watch = (backend: Option.Option<FileSystem.WatchBackend["Service"]>, path: string) =>
574
584
  stat(path).pipe(
575
- Effect.map((stat) => {
576
- if (backend) {
577
- const stream = backend.register(path, stat)
578
- if (stream) return stream
579
- }
580
- return watchNode(path)
581
- }),
585
+ Effect.map((stat) =>
586
+ backend.pipe(
587
+ Option.flatMap((_) => _.register(path, stat)),
588
+ Option.getOrElse(() => watchNode(path))
589
+ )
590
+ ),
582
591
  Stream.unwrap
583
592
  )
584
593
 
@@ -628,13 +637,16 @@ const makeFileSystem = Effect.map(Effect.serviceOption(FileSystem.WatchBackend),
628
637
  truncate,
629
638
  utimes,
630
639
  watch(path) {
631
- return watch(Option.getOrUndefined(backend), path)
640
+ return watch(backend, path)
632
641
  },
633
642
  writeFile
634
643
  }))
635
644
 
636
645
  /**
637
- * @since 1.0.0
638
- * @category Layers
646
+ * Provides the `FileSystem` service backed by Node filesystem APIs, including
647
+ * file operations, directory operations, links, metadata, and file watching.
648
+ *
649
+ * @category layers
650
+ * @since 4.0.0
639
651
  */
640
652
  export const layer: Layer.Layer<FileSystem.FileSystem> = Layer.effect(FileSystem.FileSystem)(makeFileSystem)
package/src/NodePath.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Node-backed provider for Effect's `Path` service.
3
+ *
4
+ * This module turns Node's `node:path` and `node:url` APIs into `Path` layers.
5
+ * `layer` uses the host platform path implementation, while `layerPosix` and
6
+ * `layerWin32` provide fixed POSIX and Windows variants. All three layers also
7
+ * include helpers for converting between file paths and file URLs.
8
+ *
9
+ * @since 4.0.0
3
10
  */
4
11
  import * as Effect from "effect/Effect"
5
12
  import * as Layer from "effect/Layer"
@@ -31,8 +38,11 @@ const toFileUrl = (path: string): Effect.Effect<URL, BadArgument> =>
31
38
  })
32
39
 
33
40
  /**
34
- * @since 1.0.0
35
- * @category Layers
41
+ * Provides the `Path` service using Node's POSIX path implementation plus
42
+ * file URL conversion helpers.
43
+ *
44
+ * @category layers
45
+ * @since 4.0.0
36
46
  */
37
47
  export const layerPosix: Layer.Layer<Path> = Layer.succeed(Path)({
38
48
  [TypeId]: TypeId,
@@ -42,8 +52,11 @@ export const layerPosix: Layer.Layer<Path> = Layer.succeed(Path)({
42
52
  })
43
53
 
44
54
  /**
45
- * @since 1.0.0
46
- * @category Layers
55
+ * Provides the `Path` service using Node's Windows path implementation plus
56
+ * file URL conversion helpers.
57
+ *
58
+ * @category layers
59
+ * @since 4.0.0
47
60
  */
48
61
  export const layerWin32: Layer.Layer<Path> = Layer.succeed(Path)({
49
62
  [TypeId]: TypeId,
@@ -53,8 +66,11 @@ export const layerWin32: Layer.Layer<Path> = Layer.succeed(Path)({
53
66
  })
54
67
 
55
68
  /**
56
- * @since 1.0.0
57
- * @category Layers
69
+ * Provides the default `Path` service using the host platform's Node path
70
+ * implementation plus file URL conversion helpers.
71
+ *
72
+ * @category layers
73
+ * @since 4.0.0
58
74
  */
59
75
  export const layer: Layer.Layer<Path> = Layer.succeed(Path)({
60
76
  [TypeId]: TypeId,