@effect/platform-node-shared 4.0.0-beta.6 → 4.0.0-beta.60

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 (44) hide show
  1. package/dist/NodeChildProcessSpawner.d.ts.map +1 -1
  2. package/dist/NodeChildProcessSpawner.js +74 -15
  3. package/dist/NodeChildProcessSpawner.js.map +1 -1
  4. package/dist/NodeClusterSocket.d.ts.map +1 -1
  5. package/dist/NodeClusterSocket.js +4 -3
  6. package/dist/NodeClusterSocket.js.map +1 -1
  7. package/dist/NodeFileSystem.d.ts.map +1 -1
  8. package/dist/NodeFileSystem.js +19 -23
  9. package/dist/NodeFileSystem.js.map +1 -1
  10. package/dist/NodeRuntime.d.ts.map +1 -1
  11. package/dist/NodeRuntime.js +2 -6
  12. package/dist/NodeRuntime.js.map +1 -1
  13. package/dist/NodeSink.d.ts.map +1 -1
  14. package/dist/NodeSink.js +7 -1
  15. package/dist/NodeSink.js.map +1 -1
  16. package/dist/NodeSocket.d.ts +4 -4
  17. package/dist/NodeSocket.d.ts.map +1 -1
  18. package/dist/NodeSocket.js +8 -9
  19. package/dist/NodeSocket.js.map +1 -1
  20. package/dist/NodeSocketServer.d.ts +2 -2
  21. package/dist/NodeSocketServer.d.ts.map +1 -1
  22. package/dist/NodeSocketServer.js +6 -6
  23. package/dist/NodeSocketServer.js.map +1 -1
  24. package/dist/NodeStdio.d.ts +0 -3
  25. package/dist/NodeStdio.d.ts.map +1 -1
  26. package/dist/NodeStdio.js +8 -4
  27. package/dist/NodeStdio.js.map +1 -1
  28. package/dist/NodeStream.d.ts.map +1 -1
  29. package/dist/NodeStream.js +8 -7
  30. package/dist/NodeStream.js.map +1 -1
  31. package/dist/NodeTerminal.d.ts.map +1 -1
  32. package/dist/NodeTerminal.js +2 -1
  33. package/dist/NodeTerminal.js.map +1 -1
  34. package/package.json +6 -6
  35. package/src/NodeChildProcessSpawner.ts +98 -24
  36. package/src/NodeClusterSocket.ts +4 -3
  37. package/src/NodeFileSystem.ts +26 -25
  38. package/src/NodeRuntime.ts +2 -6
  39. package/src/NodeSink.ts +7 -0
  40. package/src/NodeSocket.ts +10 -11
  41. package/src/NodeSocketServer.ts +6 -6
  42. package/src/NodeStdio.ts +26 -20
  43. package/src/NodeStream.ts +13 -10
  44. package/src/NodeTerminal.ts +2 -1
@@ -17,7 +17,13 @@ import * as Sink from "effect/Sink"
17
17
  import * as Stream from "effect/Stream"
18
18
  import * as ChildProcess from "effect/unstable/process/ChildProcess"
19
19
  import type { ChildProcessHandle } from "effect/unstable/process/ChildProcessSpawner"
20
- import { ChildProcessSpawner, ExitCode, makeHandle, ProcessId } from "effect/unstable/process/ChildProcessSpawner"
20
+ import {
21
+ ChildProcessSpawner,
22
+ ExitCode,
23
+ make as makeSpawner,
24
+ makeHandle,
25
+ ProcessId
26
+ } from "effect/unstable/process/ChildProcessSpawner"
21
27
  import * as NodeChildProcess from "node:child_process"
22
28
  import { handleErrnoException } from "./internal/utils.ts"
23
29
  import * as NodeSink from "./NodeSink.ts"
@@ -337,6 +343,23 @@ const make = Effect.gen(function*() {
337
343
  })
338
344
  }
339
345
 
346
+ const killProcessGroupOnExit = (
347
+ childProcess: NodeChildProcess.ChildProcess,
348
+ signal: NodeJS.Signals
349
+ ): void => {
350
+ if (globalThis.process.platform === "win32") {
351
+ NodeChildProcess.exec(`taskkill /pid ${childProcess.pid} /T /F`, () => {
352
+ // ignore errors during best-effort cleanup
353
+ })
354
+ return
355
+ }
356
+ try {
357
+ globalThis.process.kill(-childProcess.pid!, signal)
358
+ } catch {
359
+ // ignore errors during best-effort cleanup
360
+ }
361
+ }
362
+
340
363
  const killProcess = (
341
364
  command: ChildProcess.StandardCommand,
342
365
  childProcess: NodeChildProcess.ChildProcess,
@@ -368,7 +391,7 @@ const make = Effect.gen(function*() {
368
391
  ? kill(command, childProcess, killSignal)
369
392
  : Effect.timeoutOrElse(kill(command, childProcess, killSignal), {
370
393
  duration: options.forceKillAfter,
371
- onTimeout: () => kill(command, childProcess, "SIGKILL")
394
+ orElse: () => kill(command, childProcess, "SIGKILL")
372
395
  })
373
396
  }
374
397
 
@@ -413,6 +436,8 @@ const make = Effect.gen(function*() {
413
436
  const stdoutConfig = resolveOutputOption(cmd.options, "stdout")
414
437
  const stderrConfig = resolveOutputOption(cmd.options, "stderr")
415
438
  const resolvedAdditionalFds = resolveAdditionalFds(cmd.options)
439
+ let isReferenced = true
440
+ let cleanupOnNonZeroExit = false
416
441
 
417
442
  const cwd = yield* resolveWorkingDirectory(cmd.options)
418
443
  const env = resolveEnvironment(cmd.options)
@@ -438,6 +463,9 @@ const make = Effect.gen(function*() {
438
463
  }
439
464
  return yield* Effect.void
440
465
  }
466
+ if (!isReferenced) {
467
+ return yield* Effect.void
468
+ }
441
469
  // Process is still running, kill it
442
470
  return yield* killWithTimeout((command, childProcess, signal) =>
443
471
  Effect.catch(
@@ -452,6 +480,26 @@ const make = Effect.gen(function*() {
452
480
  )
453
481
 
454
482
  const pid = ProcessId(childProcess.pid!)
483
+ childProcess.on("exit", (code) => {
484
+ if (cleanupOnNonZeroExit && code !== 0 && Predicate.isNotNull(code)) {
485
+ killProcessGroupOnExit(childProcess, cmd.options.killSignal ?? "SIGTERM")
486
+ }
487
+ })
488
+ const reref = Effect.sync(() => {
489
+ if (!isReferenced) {
490
+ childProcess.ref()
491
+ isReferenced = true
492
+ cleanupOnNonZeroExit = false
493
+ }
494
+ })
495
+ const unref = Effect.sync(() => {
496
+ if (isReferenced) {
497
+ childProcess.unref()
498
+ isReferenced = false
499
+ cleanupOnNonZeroExit = true
500
+ }
501
+ return reref
502
+ })
455
503
  const stdin = yield* setupChildStdin(cmd, childProcess, stdinConfig)
456
504
  const { all, stderr, stdout } = setupChildOutputStreams(cmd, childProcess, stdoutConfig, stderrConfig)
457
505
  const { getInputFd, getOutputFd } = yield* setupAdditionalFds(cmd, childProcess, resolvedAdditionalFds)
@@ -489,14 +537,15 @@ const make = Effect.gen(function*() {
489
537
  stderr,
490
538
  all,
491
539
  getInputFd,
492
- getOutputFd
540
+ getOutputFd,
541
+ unref
493
542
  })
494
543
  }
495
544
  case "PipedCommand": {
496
545
  const { commands, pipeOptions } = flattenCommand(cmd)
497
546
  const [root, ...pipeline] = commands
498
547
 
499
- let handle = spawnCommand(root)
548
+ const handles = [yield* spawnCommand(root)]
500
549
 
501
550
  for (let i = 0; i < pipeline.length; i++) {
502
551
  const command = pipeline[i]
@@ -505,7 +554,7 @@ const make = Effect.gen(function*() {
505
554
 
506
555
  // Get the appropriate stream from the source based on `from` option
507
556
  const sourceStream = Stream.unwrap(
508
- Effect.map(handle, (h) => getSourceStream(h, options.from))
557
+ Effect.succeed(getSourceStream(handles[handles.length - 1], options.from))
509
558
  )
510
559
 
511
560
  // Determine where to pipe: stdin or custom fd
@@ -513,41 +562,66 @@ const make = Effect.gen(function*() {
513
562
 
514
563
  if (toOption === "stdin") {
515
564
  // Pipe to stdin (default behavior)
516
- handle = spawnCommand(ChildProcess.make(command.command, command.args, {
517
- ...command.options,
518
- stdin: { ...stdinConfig, stream: sourceStream }
519
- }))
565
+ handles.push(
566
+ yield* spawnCommand(ChildProcess.make(command.command, command.args, {
567
+ ...command.options,
568
+ stdin: { ...stdinConfig, stream: sourceStream }
569
+ }))
570
+ )
520
571
  } else {
521
572
  // Pipe to custom fd (fd3, fd4, etc.)
522
573
  const fd = ChildProcess.parseFdName(toOption)
523
574
  if (Predicate.isNotUndefined(fd)) {
524
575
  const fdName = ChildProcess.fdName(fd) as `fd${number}`
525
576
  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
- }))
577
+ handles.push(
578
+ yield* spawnCommand(ChildProcess.make(command.command, command.args, {
579
+ ...command.options,
580
+ additionalFds: {
581
+ ...existingFds,
582
+ [fdName]: { type: "input" as const, stream: sourceStream }
583
+ }
584
+ }))
585
+ )
533
586
  } else {
534
587
  // 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
- }))
588
+ handles.push(
589
+ yield* spawnCommand(ChildProcess.make(command.command, command.args, {
590
+ ...command.options,
591
+ stdin: { ...stdinConfig, stream: sourceStream }
592
+ }))
593
+ )
539
594
  }
540
595
  }
541
596
  }
542
597
 
543
- return yield* handle
598
+ const handle = handles[handles.length - 1]
599
+ const unref = Effect.gen(function*() {
600
+ const rerefs: Array<Effect.Effect<void, PlatformError.PlatformError>> = []
601
+ for (const handle of handles) {
602
+ rerefs.push(yield* handle.unref)
603
+ }
604
+ return Effect.forEach([...rerefs].reverse(), (reref) => reref, { discard: true })
605
+ })
606
+
607
+ return makeHandle({
608
+ pid: handle.pid,
609
+ exitCode: handle.exitCode,
610
+ isRunning: handle.isRunning,
611
+ kill: handle.kill,
612
+ stdin: handle.stdin,
613
+ stdout: handle.stdout,
614
+ stderr: handle.stderr,
615
+ all: handle.all,
616
+ getInputFd: handle.getInputFd,
617
+ getOutputFd: handle.getOutputFd,
618
+ unref
619
+ })
544
620
  }
545
621
  }
546
622
  })
547
623
 
548
- return ChildProcessSpawner.of({
549
- spawn: spawnCommand
550
- })
624
+ return makeSpawner(spawnCommand)
551
625
  })
552
626
 
553
627
  /**
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import * as Effect from "effect/Effect"
5
5
  import * as Layer from "effect/Layer"
6
+ import * as Option from "effect/Option"
6
7
  import * as Runners from "effect/unstable/cluster/Runners"
7
8
  import * as ShardingConfig from "effect/unstable/cluster/ShardingConfig"
8
9
  import * as RpcClient from "effect/unstable/rpc/RpcClient"
@@ -48,9 +49,9 @@ export const layerSocketServer: Layer.Layer<
48
49
  ShardingConfig.ShardingConfig
49
50
  > = Effect.gen(function*() {
50
51
  const config = yield* ShardingConfig.ShardingConfig
51
- const listenAddress = config.runnerListenAddress ?? config.runnerAddress
52
- if (listenAddress === undefined) {
52
+ const listenAddress = Option.orElse(config.runnerListenAddress, () => config.runnerAddress)
53
+ if (Option.isNone(listenAddress)) {
53
54
  return yield* Effect.die("layerSocketServer: ShardingConfig.runnerListenAddress is None")
54
55
  }
55
- return NodeSocketServer.layer(listenAddress)
56
+ return NodeSocketServer.layer(listenAddress.value)
56
57
  }).pipe(Layer.unwrap)
@@ -232,7 +232,7 @@ const makeFile = (() => {
232
232
  readonly fd: FileSystem.File.Descriptor
233
233
  private readonly append: boolean
234
234
 
235
- private position: bigint = 0n
235
+ private position: bigint = BigInt(0)
236
236
 
237
237
  constructor(
238
238
  fd: FileSystem.File.Descriptor,
@@ -285,19 +285,19 @@ const makeFile = (() => {
285
285
  const position = this.position
286
286
  return Effect.map(
287
287
  nodeReadAlloc(this.fd, { buffer, position }),
288
- (bytesRead): Buffer | undefined => {
288
+ (bytesRead): Option.Option<Buffer> => {
289
289
  if (bytesRead === 0) {
290
- return undefined
290
+ return Option.none()
291
291
  }
292
292
 
293
293
  this.position = position + BigInt(bytesRead)
294
294
  if (bytesRead === sizeNumber) {
295
- return buffer
295
+ return Option.some(buffer)
296
296
  }
297
297
 
298
298
  const dst = Buffer.allocUnsafeSlow(bytesRead)
299
299
  buffer.copy(dst, 0, 0, bytesRead)
300
- return dst
300
+ return Option.some(dst)
301
301
  }
302
302
  )
303
303
  })
@@ -468,19 +468,19 @@ const makeFileInfo = (stat: NFS.Stats): FileSystem.File.Info => ({
468
468
  stat.isSocket() ?
469
469
  "Socket" :
470
470
  "Unknown",
471
- mtime: stat.mtime,
472
- atime: stat.atime,
473
- birthtime: stat.birthtime,
471
+ mtime: Option.fromNullishOr(stat.mtime),
472
+ atime: Option.fromNullishOr(stat.atime),
473
+ birthtime: Option.fromNullishOr(stat.birthtime),
474
474
  dev: stat.dev,
475
- rdev: stat.rdev,
476
- ino: stat.ino,
475
+ rdev: Option.fromNullishOr(stat.rdev),
476
+ ino: Option.fromNullishOr(stat.ino),
477
477
  mode: stat.mode,
478
- nlink: stat.nlink,
479
- uid: stat.uid,
480
- gid: stat.gid,
478
+ nlink: Option.fromNullishOr(stat.nlink),
479
+ uid: Option.fromNullishOr(stat.uid),
480
+ gid: Option.fromNullishOr(stat.gid),
481
481
  size: FileSystem.Size(stat.size),
482
- blksize: FileSystem.Size(stat.blksize),
483
- blocks: stat.blocks
482
+ blksize: stat.blksize !== undefined ? Option.some(FileSystem.Size(stat.blksize)) : Option.none(),
483
+ blocks: Option.fromNullishOr(stat.blocks)
484
484
  })
485
485
  const stat = (() => {
486
486
  const nodeStat = effectify(
@@ -531,7 +531,9 @@ const watchNode = (path: string) =>
531
531
  Stream.callback<FileSystem.WatchEvent, Error.PlatformError>((queue) =>
532
532
  Effect.acquireRelease(
533
533
  Effect.sync(() => {
534
- const watcher = NFS.watch(path, {}, (event, path) => {
534
+ const watcher = NFS.watch(path, {
535
+ recursive: true
536
+ }, (event, path) => {
535
537
  if (!path) return
536
538
  switch (event) {
537
539
  case "rename": {
@@ -570,15 +572,14 @@ const watchNode = (path: string) =>
570
572
  )
571
573
  )
572
574
 
573
- const watch = (backend: FileSystem.WatchBackend["Service"] | undefined, path: string) =>
575
+ const watch = (backend: Option.Option<FileSystem.WatchBackend["Service"]>, path: string) =>
574
576
  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
- }),
577
+ Effect.map((stat) =>
578
+ backend.pipe(
579
+ Option.flatMap((_) => _.register(path, stat)),
580
+ Option.getOrElse(() => watchNode(path))
581
+ )
582
+ ),
582
583
  Stream.unwrap
583
584
  )
584
585
 
@@ -628,7 +629,7 @@ const makeFileSystem = Effect.map(Effect.serviceOption(FileSystem.WatchBackend),
628
629
  truncate,
629
630
  utimes,
630
631
  watch(path) {
631
- return watch(Option.getOrUndefined(backend), path)
632
+ return watch(backend, path)
632
633
  },
633
634
  writeFile
634
635
  }))
@@ -37,10 +37,8 @@ export const runMain: {
37
37
  let receivedSignal = false
38
38
 
39
39
  fiber.addObserver((exit) => {
40
- if (!receivedSignal) {
41
- process.removeListener("SIGINT", onSigint)
42
- process.removeListener("SIGTERM", onSigint)
43
- }
40
+ process.removeListener("SIGINT", onSigint)
41
+ process.removeListener("SIGTERM", onSigint)
44
42
  teardown(exit, (code) => {
45
43
  if (receivedSignal || code !== 0) {
46
44
  process.exit(code)
@@ -50,8 +48,6 @@ export const runMain: {
50
48
 
51
49
  function onSigint() {
52
50
  receivedSignal = true
53
- process.removeListener("SIGINT", onSigint)
54
- process.removeListener("SIGTERM", onSigint)
55
51
  fiber.interruptUnsafe(fiber.id)
56
52
  }
57
53
 
package/src/NodeSink.ts CHANGED
@@ -66,6 +66,13 @@ export const pullIntoWritable = <A, IE, E>(options: {
66
66
  })
67
67
  }),
68
68
  Effect.forever({ disableYield: true }),
69
+ Effect.raceFirst(Effect.callback<never, E>((resume) => {
70
+ const onError = (error: unknown) => resume(Effect.fail(options.onError(error)))
71
+ options.writable.once("error", onError)
72
+ return Effect.sync(() => {
73
+ options.writable.off("error", onError)
74
+ })
75
+ })),
69
76
  options.endOnDone !== false ?
70
77
  Pull.catchDone((_) => {
71
78
  if ("closed" in options.writable && options.writable.closed) {
package/src/NodeSocket.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import type { Array } from "effect"
5
5
  import * as Channel from "effect/Channel"
6
+ import * as Context from "effect/Context"
6
7
  import * as Deferred from "effect/Deferred"
7
8
  import type * as Duration from "effect/Duration"
8
9
  import * as Effect from "effect/Effect"
@@ -12,7 +13,6 @@ import { identity } from "effect/Function"
12
13
  import * as Latch from "effect/Latch"
13
14
  import * as Layer from "effect/Layer"
14
15
  import * as Scope from "effect/Scope"
15
- import * as ServiceMap from "effect/ServiceMap"
16
16
  import * as Socket from "effect/unstable/socket/Socket"
17
17
  import * as Net from "node:net"
18
18
  import type { Duplex } from "node:stream"
@@ -27,7 +27,7 @@ export * as NodeWS from "ws"
27
27
  * @since 1.0.0
28
28
  * @category tags
29
29
  */
30
- export class NetSocket extends ServiceMap.Service<NetSocket, Net.Socket>()(
30
+ export class NetSocket extends Context.Service<NetSocket, Net.Socket>()(
31
31
  "@effect/platform-node/NodeSocket/NetSocket"
32
32
  ) {}
33
33
 
@@ -37,15 +37,15 @@ export class NetSocket extends ServiceMap.Service<NetSocket, Net.Socket>()(
37
37
  */
38
38
  export const makeNet = (
39
39
  options: Net.NetConnectOpts & {
40
- readonly openTimeout?: Duration.DurationInput | undefined
40
+ readonly openTimeout?: Duration.Input | undefined
41
41
  }
42
42
  ): Effect.Effect<Socket.Socket> =>
43
43
  fromDuplex(
44
- Effect.servicesWith((services: ServiceMap.ServiceMap<Scope.Scope>) => {
44
+ Effect.contextWith((context: Context.Context<Scope.Scope>) => {
45
45
  let conn: Net.Socket | undefined
46
46
  return Effect.flatMap(
47
47
  Scope.addFinalizer(
48
- ServiceMap.get(services, Scope.Scope),
48
+ Context.get(context, Scope.Scope),
49
49
  Effect.sync(() => {
50
50
  if (!conn) return
51
51
  if (conn.closed === false) {
@@ -83,13 +83,13 @@ export const makeNet = (
83
83
  export const fromDuplex = <RO>(
84
84
  open: Effect.Effect<Duplex, Socket.SocketError, RO>,
85
85
  options?: {
86
- readonly openTimeout?: Duration.DurationInput | undefined
86
+ readonly openTimeout?: Duration.Input | undefined
87
87
  }
88
88
  ): Effect.Effect<Socket.Socket, never, Exclude<RO, Scope.Scope>> =>
89
89
  Effect.withFiber<Socket.Socket, never, Exclude<RO, Scope.Scope>>((fiber) => {
90
90
  let currentSocket: Duplex | undefined
91
91
  const latch = Latch.makeUnsafe(false)
92
- const openServices = fiber.services as ServiceMap.ServiceMap<RO>
92
+ const openServices = fiber.context as Context.Context<RO>
93
93
 
94
94
  const run = <R, E, _>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void, opts?: {
95
95
  readonly onOpen?: Effect.Effect<void> | undefined
@@ -113,7 +113,7 @@ export const fromDuplex = <RO>(
113
113
  options?.openTimeout ?
114
114
  Effect.timeoutOrElse({
115
115
  duration: options.openTimeout,
116
- onTimeout: () =>
116
+ orElse: () =>
117
117
  Effect.fail(
118
118
  new Socket.SocketError({
119
119
  reason: new Socket.SocketOpenError({ kind: "Timeout", cause: new Error("Connection timed out") })
@@ -166,7 +166,7 @@ export const fromDuplex = <RO>(
166
166
  )
167
167
  }
168
168
  })).pipe(
169
- Effect.updateServices((input: ServiceMap.ServiceMap<R>) => ServiceMap.merge(openServices, input)),
169
+ Effect.updateContext((input: Context.Context<R>) => Context.merge(openServices, input)),
170
170
  Effect.onExit(() =>
171
171
  Effect.sync(() => {
172
172
  latch.closeUnsafe()
@@ -204,8 +204,7 @@ export const fromDuplex = <RO>(
204
204
  })
205
205
  )
206
206
 
207
- return Effect.succeed(Socket.Socket.of({
208
- [Socket.TypeId]: Socket.TypeId,
207
+ return Effect.succeed(Socket.make({
209
208
  run,
210
209
  runRaw: run,
211
210
  writer
@@ -2,6 +2,7 @@
2
2
  * @since 1.0.0
3
3
  */
4
4
  import type { Cause } from "effect/Cause"
5
+ import * as Context from "effect/Context"
5
6
  import * as Deferred from "effect/Deferred"
6
7
  import * as Effect from "effect/Effect"
7
8
  import * as Exit from "effect/Exit"
@@ -11,7 +12,6 @@ import * as Function from "effect/Function"
11
12
  import * as Layer from "effect/Layer"
12
13
  import * as References from "effect/References"
13
14
  import * as Scope from "effect/Scope"
14
- import * as ServiceMap from "effect/ServiceMap"
15
15
  import * as Socket from "effect/unstable/socket/Socket"
16
16
  import * as SocketServer from "effect/unstable/socket/SocketServer"
17
17
  import type * as Http from "node:http"
@@ -23,7 +23,7 @@ import { NodeWS } from "./NodeSocket.ts"
23
23
  * @since 1.0.0
24
24
  * @category tags
25
25
  */
26
- export class IncomingMessage extends ServiceMap.Service<
26
+ export class IncomingMessage extends Context.Service<
27
27
  IncomingMessage,
28
28
  Http.IncomingMessage
29
29
  >()("@effect/platform-node-shared/NodeSocketServer/IncomingMessage") {}
@@ -69,7 +69,7 @@ export const make = Effect.fnUntraced(function*(
69
69
 
70
70
  const run = Effect.fnUntraced(function*<R, E, _>(handler: (socket: Socket.Socket) => Effect.Effect<_, E, R>) {
71
71
  const scope = yield* Scope.make()
72
- const services = ServiceMap.omit(Scope.Scope)(yield* Effect.services<R>()) as ServiceMap.ServiceMap<R>
72
+ const services = Context.omit(Scope.Scope)(yield* Effect.context<R>()) as Context.Context<R>
73
73
  const trackFiber = Fiber.runIn(scope)
74
74
  const prevOnConnection = onConnection
75
75
  onConnection = function(conn: Net.Socket) {
@@ -109,7 +109,7 @@ export const make = Effect.fnUntraced(function*(
109
109
  ),
110
110
  Effect.flatMap(handler),
111
111
  Effect.catchCause(reportUnhandledError),
112
- Effect.runForkWith(ServiceMap.add(services, NodeSocket.NetSocket, conn)),
112
+ Effect.runForkWith(Context.add(services, NodeSocket.NetSocket, conn)),
113
113
  trackFiber
114
114
  )
115
115
  }
@@ -202,7 +202,7 @@ export const makeWebSocket: (
202
202
 
203
203
  const run = Effect.fnUntraced(function*<R, E, _>(handler: (socket: Socket.Socket) => Effect.Effect<_, E, R>) {
204
204
  const scope = yield* Scope.make()
205
- const services = ServiceMap.omit(Scope.Scope)(yield* Effect.services<R>()) as ServiceMap.ServiceMap<R>
205
+ const services = Context.omit(Scope.Scope)(yield* Effect.context<R>()) as Context.Context<R>
206
206
  const trackFiber = Fiber.runIn(scope)
207
207
  const prevOnConnection = onConnection
208
208
  onConnection = function(conn: globalThis.WebSocket, req: Http.IncomingMessage) {
@@ -221,7 +221,7 @@ export const makeWebSocket: (
221
221
  ),
222
222
  Effect.flatMap(handler),
223
223
  Effect.catchCause(reportUnhandledError),
224
- Effect.runForkWith(ServiceMap.makeUnsafe(map)),
224
+ Effect.runForkWith(Context.makeUnsafe(map)),
225
225
  trackFiber
226
226
  )
227
227
  }
package/src/NodeStdio.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
+ import * as Effect from "effect/Effect"
4
5
  import * as Layer from "effect/Layer"
5
6
  import { systemError } from "effect/PlatformError"
6
7
  import * as Stdio from "effect/Stdio"
@@ -14,26 +15,31 @@ import { fromReadable } from "./NodeStream.ts"
14
15
  export const layer: Layer.Layer<Stdio.Stdio> = Layer.succeed(
15
16
  Stdio.Stdio,
16
17
  Stdio.make({
17
- stdout: fromWritable({
18
- evaluate: () => process.stdout,
19
- onError: (cause) =>
20
- systemError({
21
- module: "Stdio",
22
- method: "stdout",
23
- _tag: "Unknown",
24
- cause
25
- })
26
- }),
27
- stderr: fromWritable({
28
- evaluate: () => process.stderr,
29
- onError: (cause) =>
30
- systemError({
31
- module: "Stdio",
32
- method: "stderr",
33
- _tag: "Unknown",
34
- cause
35
- })
36
- }),
18
+ args: Effect.sync(() => process.argv.slice(2)),
19
+ stdout: (options) =>
20
+ fromWritable({
21
+ evaluate: () => process.stdout,
22
+ onError: (cause) =>
23
+ systemError({
24
+ module: "Stdio",
25
+ method: "stdout",
26
+ _tag: "Unknown",
27
+ cause
28
+ }),
29
+ endOnDone: options?.endOnDone ?? false
30
+ }),
31
+ stderr: (options) =>
32
+ fromWritable({
33
+ evaluate: () => process.stderr,
34
+ onError: (cause) =>
35
+ systemError({
36
+ module: "Stdio",
37
+ method: "stderr",
38
+ _tag: "Unknown",
39
+ cause
40
+ }),
41
+ endOnDone: options?.endOnDone ?? false
42
+ }),
37
43
  stdin: fromReadable({
38
44
  evaluate: () => process.stdin,
39
45
  onError: (cause) =>
package/src/NodeStream.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  import * as Arr from "effect/Array"
5
5
  import * as Cause from "effect/Cause"
6
6
  import * as Channel from "effect/Channel"
7
+ import * as Context from "effect/Context"
7
8
  import * as Effect from "effect/Effect"
8
9
  import * as Exit from "effect/Exit"
9
10
  import * as Fiber from "effect/Fiber"
@@ -13,7 +14,6 @@ import * as Latch from "effect/Latch"
13
14
  import * as MutableRef from "effect/MutableRef"
14
15
  import * as Pull from "effect/Pull"
15
16
  import * as Scope from "effect/Scope"
16
- import * as ServiceMap from "effect/ServiceMap"
17
17
  import * as Stream from "effect/Stream"
18
18
  import type { Duplex } from "node:stream"
19
19
  import { Readable } from "node:stream"
@@ -170,7 +170,7 @@ export const pipeThroughSimple: {
170
170
  */
171
171
  export const toReadable = <E, R>(stream: Stream.Stream<string | Uint8Array, E, R>): Effect.Effect<Readable, never, R> =>
172
172
  Effect.map(
173
- Effect.services<R>(),
173
+ Effect.context<R>(),
174
174
  (context) => new StreamAdapter(context, stream)
175
175
  )
176
176
 
@@ -180,7 +180,7 @@ export const toReadable = <E, R>(stream: Stream.Stream<string | Uint8Array, E, R
180
180
  */
181
181
  export const toReadableNever = <E>(stream: Stream.Stream<string | Uint8Array, E, never>): Readable =>
182
182
  new StreamAdapter(
183
- ServiceMap.empty(),
183
+ Context.empty(),
184
184
  stream
185
185
  )
186
186
 
@@ -248,7 +248,7 @@ export const toArrayBuffer = <E = Cause.UnknownError>(
248
248
  const onError = options?.onError ?? defaultOnError
249
249
  return Effect.callback((resume) => {
250
250
  const stream = readable() as Readable
251
- let buffer = Buffer.alloc(0)
251
+ const buffers: Array<Uint8Array> = []
252
252
  let bytes = 0
253
253
  stream.once("error", (err) => {
254
254
  if ("closed" in stream && !stream.closed) {
@@ -257,13 +257,16 @@ export const toArrayBuffer = <E = Cause.UnknownError>(
257
257
  resume(Effect.fail(onError(err) as E))
258
258
  })
259
259
  stream.once("end", () => {
260
- if (buffer.buffer.byteLength === buffer.byteLength) {
261
- return resume(Effect.succeed(buffer.buffer))
260
+ const buffer = buffers.length === 1 ? buffers[0] : Buffer.concat(buffers)
261
+ if (buffer.byteOffset === 0 && buffer.buffer.byteLength === buffer.byteLength) {
262
+ return resume(Effect.succeed(buffer.buffer as ArrayBuffer))
262
263
  }
263
- resume(Effect.succeed(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)))
264
+ resume(
265
+ Effect.succeed(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength) as ArrayBuffer)
266
+ )
264
267
  })
265
268
  stream.on("data", (chunk) => {
266
- buffer = Buffer.concat([buffer, chunk])
269
+ buffers.push(chunk)
267
270
  bytes += chunk.length
268
271
  if (maxBytesNumber && bytes > maxBytesNumber) {
269
272
  resume(Effect.fail(onError(new Error("maxBytes exceeded")) as E))
@@ -361,7 +364,7 @@ class StreamAdapter<E, R> extends Readable {
361
364
  private fiber: Fiber.Fiber<void, E> | undefined = undefined
362
365
 
363
366
  constructor(
364
- context: ServiceMap.ServiceMap<R>,
367
+ context: Context.Context<R>,
365
368
  stream: Stream.Stream<Uint8Array | string, E, R>
366
369
  ) {
367
370
  super({})
@@ -379,7 +382,7 @@ class StreamAdapter<E, R> extends Readable {
379
382
  }
380
383
  }))).pipe(
381
384
  this.readLatch.whenOpen,
382
- Effect.provideServices(context),
385
+ Effect.provideContext(context),
383
386
  Effect.runFork
384
387
  )
385
388
  this.fiber.addObserver((exit) => {
@@ -4,6 +4,7 @@
4
4
  import type * as Cause from "effect/Cause"
5
5
  import * as Effect from "effect/Effect"
6
6
  import * as Layer from "effect/Layer"
7
+ import * as Option from "effect/Option"
7
8
  import { badArgument, type PlatformError } from "effect/PlatformError"
8
9
  import * as Predicate from "effect/Predicate"
9
10
  import * as Queue from "effect/Queue"
@@ -52,7 +53,7 @@ export const make: (
52
53
  const queue = yield* Queue.make<Terminal.UserInput, Cause.Done>()
53
54
  const handleKeypress = (s: string | undefined, k: readline.Key) => {
54
55
  const userInput = {
55
- input: s,
56
+ input: Option.fromUndefinedOr(s),
56
57
  key: { name: k.name ?? "", ctrl: !!k.ctrl, meta: !!k.meta, shift: !!k.shift }
57
58
  }
58
59
  Queue.offerUnsafe(queue, userInput)