@effect/platform-node-shared 4.0.0-beta.7 → 4.0.0-beta.71
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/dist/NodeChildProcessSpawner.d.ts +18 -4
- package/dist/NodeChildProcessSpawner.d.ts.map +1 -1
- package/dist/NodeChildProcessSpawner.js +76 -17
- 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 +46 -8
- package/dist/NodeClusterSocket.js.map +1 -1
- package/dist/NodeCrypto.d.ts +42 -0
- package/dist/NodeCrypto.d.ts.map +1 -0
- package/dist/NodeCrypto.js +70 -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 +56 -26
- 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 +39 -7
- package/dist/NodePath.js.map +1 -1
- package/dist/NodeRuntime.d.ts +48 -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 +49 -4
- package/dist/NodeSink.d.ts.map +1 -1
- package/dist/NodeSink.js +23 -4
- package/dist/NodeSink.js.map +1 -1
- package/dist/NodeSocket.d.ts +50 -9
- package/dist/NodeSocket.d.ts.map +1 -1
- package/dist/NodeSocket.js +31 -15
- package/dist/NodeSocket.js.map +1 -1
- package/dist/NodeSocketServer.d.ts +24 -7
- package/dist/NodeSocketServer.d.ts.map +1 -1
- package/dist/NodeSocketServer.js +28 -11
- 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 +46 -7
- package/dist/NodeStdio.js.map +1 -1
- package/dist/NodeStream.d.ts +93 -20
- package/dist/NodeStream.d.ts.map +1 -1
- package/dist/NodeStream.js +85 -23
- 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/package.json +7 -6
- package/src/NodeChildProcessSpawner.ts +116 -28
- package/src/NodeClusterSocket.ts +46 -8
- package/src/NodeCrypto.ts +75 -0
- package/src/NodeFileSystem.ts +63 -28
- package/src/NodePath.ts +39 -7
- package/src/NodeRuntime.ts +50 -13
- package/src/NodeSink.ts +56 -4
- package/src/NodeSocket.ts +56 -16
- package/src/NodeSocketServer.ts +64 -12
- package/src/NodeStdio.ts +64 -23
- package/src/NodeStream.ts +106 -30
- package/src/NodeTerminal.ts +44 -4
- package/src/index.ts +65 -0
package/src/NodeFileSystem.ts
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
+
* **Mental model**
|
|
11
|
+
*
|
|
12
|
+
* {@link layer} installs a process-backed `FileSystem` service. Each operation
|
|
13
|
+
* delegates to the corresponding Node filesystem API, then maps Node failures
|
|
14
|
+
* into `PlatformError` values and invalid arguments into `BadArgument` failures.
|
|
15
|
+
* Paths keep Node's normal behavior: relative paths resolve from the current
|
|
16
|
+
* working directory and platform path rules still apply.
|
|
17
|
+
*
|
|
18
|
+
* **Common tasks**
|
|
19
|
+
*
|
|
20
|
+
* Provide {@link layer} at the Node runtime boundary, then depend on the
|
|
21
|
+
* `FileSystem` service from application code. Use the service for ordinary
|
|
22
|
+
* reads and writes, directory management, metadata inspection, links, temporary
|
|
23
|
+
* resources, and file watching without importing Node's `fs` APIs directly.
|
|
24
|
+
*
|
|
25
|
+
* **Gotchas**
|
|
26
|
+
*
|
|
27
|
+
* Open files are scoped resources with tracked read and write positions; append
|
|
28
|
+
* mode lets the operating system choose the write offset. File watching follows
|
|
29
|
+
* `node:fs.watch` semantics unless a custom watch backend is supplied, so
|
|
30
|
+
* recursive support, event coalescing, and reported paths vary by runtime and
|
|
31
|
+
* platform.
|
|
32
|
+
*
|
|
33
|
+
* @since 4.0.0
|
|
3
34
|
*/
|
|
4
35
|
import * as Cause from "effect/Cause"
|
|
5
36
|
import * as Effect from "effect/Effect"
|
|
@@ -232,7 +263,7 @@ const makeFile = (() => {
|
|
|
232
263
|
readonly fd: FileSystem.File.Descriptor
|
|
233
264
|
private readonly append: boolean
|
|
234
265
|
|
|
235
|
-
private position: bigint =
|
|
266
|
+
private position: bigint = BigInt(0)
|
|
236
267
|
|
|
237
268
|
constructor(
|
|
238
269
|
fd: FileSystem.File.Descriptor,
|
|
@@ -285,19 +316,19 @@ const makeFile = (() => {
|
|
|
285
316
|
const position = this.position
|
|
286
317
|
return Effect.map(
|
|
287
318
|
nodeReadAlloc(this.fd, { buffer, position }),
|
|
288
|
-
(bytesRead): Buffer
|
|
319
|
+
(bytesRead): Option.Option<Buffer> => {
|
|
289
320
|
if (bytesRead === 0) {
|
|
290
|
-
return
|
|
321
|
+
return Option.none()
|
|
291
322
|
}
|
|
292
323
|
|
|
293
324
|
this.position = position + BigInt(bytesRead)
|
|
294
325
|
if (bytesRead === sizeNumber) {
|
|
295
|
-
return buffer
|
|
326
|
+
return Option.some(buffer)
|
|
296
327
|
}
|
|
297
328
|
|
|
298
329
|
const dst = Buffer.allocUnsafeSlow(bytesRead)
|
|
299
330
|
buffer.copy(dst, 0, 0, bytesRead)
|
|
300
|
-
return dst
|
|
331
|
+
return Option.some(dst)
|
|
301
332
|
}
|
|
302
333
|
)
|
|
303
334
|
})
|
|
@@ -468,19 +499,19 @@ const makeFileInfo = (stat: NFS.Stats): FileSystem.File.Info => ({
|
|
|
468
499
|
stat.isSocket() ?
|
|
469
500
|
"Socket" :
|
|
470
501
|
"Unknown",
|
|
471
|
-
mtime: stat.mtime,
|
|
472
|
-
atime: stat.atime,
|
|
473
|
-
birthtime: stat.birthtime,
|
|
502
|
+
mtime: Option.fromNullishOr(stat.mtime),
|
|
503
|
+
atime: Option.fromNullishOr(stat.atime),
|
|
504
|
+
birthtime: Option.fromNullishOr(stat.birthtime),
|
|
474
505
|
dev: stat.dev,
|
|
475
|
-
rdev: stat.rdev,
|
|
476
|
-
ino: stat.ino,
|
|
506
|
+
rdev: Option.fromNullishOr(stat.rdev),
|
|
507
|
+
ino: Option.fromNullishOr(stat.ino),
|
|
477
508
|
mode: stat.mode,
|
|
478
|
-
nlink: stat.nlink,
|
|
479
|
-
uid: stat.uid,
|
|
480
|
-
gid: stat.gid,
|
|
509
|
+
nlink: Option.fromNullishOr(stat.nlink),
|
|
510
|
+
uid: Option.fromNullishOr(stat.uid),
|
|
511
|
+
gid: Option.fromNullishOr(stat.gid),
|
|
481
512
|
size: FileSystem.Size(stat.size),
|
|
482
|
-
blksize: FileSystem.Size(stat.blksize),
|
|
483
|
-
blocks: stat.blocks
|
|
513
|
+
blksize: stat.blksize !== undefined ? Option.some(FileSystem.Size(stat.blksize)) : Option.none(),
|
|
514
|
+
blocks: Option.fromNullishOr(stat.blocks)
|
|
484
515
|
})
|
|
485
516
|
const stat = (() => {
|
|
486
517
|
const nodeStat = effectify(
|
|
@@ -531,7 +562,9 @@ const watchNode = (path: string) =>
|
|
|
531
562
|
Stream.callback<FileSystem.WatchEvent, Error.PlatformError>((queue) =>
|
|
532
563
|
Effect.acquireRelease(
|
|
533
564
|
Effect.sync(() => {
|
|
534
|
-
const watcher = NFS.watch(path, {
|
|
565
|
+
const watcher = NFS.watch(path, {
|
|
566
|
+
recursive: true
|
|
567
|
+
}, (event, path) => {
|
|
535
568
|
if (!path) return
|
|
536
569
|
switch (event) {
|
|
537
570
|
case "rename": {
|
|
@@ -570,15 +603,14 @@ const watchNode = (path: string) =>
|
|
|
570
603
|
)
|
|
571
604
|
)
|
|
572
605
|
|
|
573
|
-
const watch = (backend: FileSystem.WatchBackend["Service"]
|
|
606
|
+
const watch = (backend: Option.Option<FileSystem.WatchBackend["Service"]>, path: string) =>
|
|
574
607
|
stat(path).pipe(
|
|
575
|
-
Effect.map((stat) =>
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}),
|
|
608
|
+
Effect.map((stat) =>
|
|
609
|
+
backend.pipe(
|
|
610
|
+
Option.flatMap((_) => _.register(path, stat)),
|
|
611
|
+
Option.getOrElse(() => watchNode(path))
|
|
612
|
+
)
|
|
613
|
+
),
|
|
582
614
|
Stream.unwrap
|
|
583
615
|
)
|
|
584
616
|
|
|
@@ -628,13 +660,16 @@ const makeFileSystem = Effect.map(Effect.serviceOption(FileSystem.WatchBackend),
|
|
|
628
660
|
truncate,
|
|
629
661
|
utimes,
|
|
630
662
|
watch(path) {
|
|
631
|
-
return watch(
|
|
663
|
+
return watch(backend, path)
|
|
632
664
|
},
|
|
633
665
|
writeFile
|
|
634
666
|
}))
|
|
635
667
|
|
|
636
668
|
/**
|
|
637
|
-
*
|
|
638
|
-
*
|
|
669
|
+
* Provides the `FileSystem` service backed by Node filesystem APIs, including
|
|
670
|
+
* file operations, directory operations, links, metadata, and file watching.
|
|
671
|
+
*
|
|
672
|
+
* @category layers
|
|
673
|
+
* @since 4.0.0
|
|
639
674
|
*/
|
|
640
675
|
export const layer: Layer.Layer<FileSystem.FileSystem> = Layer.effect(FileSystem.FileSystem)(makeFileSystem)
|
package/src/NodePath.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node-backed provider for Effect's `Path` service.
|
|
3
|
+
*
|
|
4
|
+
* This module turns Node's `node:path` and `node:url` APIs into `Layer`s for
|
|
5
|
+
* programs that depend on `Path`. Use it when code should receive path
|
|
6
|
+
* operations from the Effect environment instead of importing `node:path`
|
|
7
|
+
* directly, including configuration loading, filesystem composition, and file
|
|
8
|
+
* URL conversion.
|
|
9
|
+
*
|
|
10
|
+
* **Mental model**
|
|
11
|
+
*
|
|
12
|
+
* `layer` follows the platform semantics of the current Node runtime. The
|
|
13
|
+
* `layerPosix` and `layerWin32` variants pin the syntax rules to POSIX or
|
|
14
|
+
* Windows, which is useful for deterministic parsing, formatting, and tests.
|
|
15
|
+
* All three layers include `fromFileUrl` and `toFileUrl` behavior backed by
|
|
16
|
+
* Node's URL conversion functions.
|
|
17
|
+
*
|
|
18
|
+
* **Gotchas**
|
|
19
|
+
*
|
|
20
|
+
* Path operations are syntactic: they normalize separators, roots, drive
|
|
21
|
+
* letters, UNC segments, extensions, and relative segments without checking the
|
|
22
|
+
* filesystem. File URL conversion follows Node's validation and encoding
|
|
23
|
+
* rules, and invalid conversions fail with `BadArgument`.
|
|
24
|
+
*
|
|
25
|
+
* @since 4.0.0
|
|
3
26
|
*/
|
|
4
27
|
import * as Effect from "effect/Effect"
|
|
5
28
|
import * as Layer from "effect/Layer"
|
|
@@ -31,8 +54,11 @@ const toFileUrl = (path: string): Effect.Effect<URL, BadArgument> =>
|
|
|
31
54
|
})
|
|
32
55
|
|
|
33
56
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
57
|
+
* Provides the `Path` service using Node's POSIX path implementation plus
|
|
58
|
+
* file URL conversion helpers.
|
|
59
|
+
*
|
|
60
|
+
* @category layers
|
|
61
|
+
* @since 4.0.0
|
|
36
62
|
*/
|
|
37
63
|
export const layerPosix: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
38
64
|
[TypeId]: TypeId,
|
|
@@ -42,8 +68,11 @@ export const layerPosix: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
|
42
68
|
})
|
|
43
69
|
|
|
44
70
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
71
|
+
* Provides the `Path` service using Node's Windows path implementation plus
|
|
72
|
+
* file URL conversion helpers.
|
|
73
|
+
*
|
|
74
|
+
* @category layers
|
|
75
|
+
* @since 4.0.0
|
|
47
76
|
*/
|
|
48
77
|
export const layerWin32: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
49
78
|
[TypeId]: TypeId,
|
|
@@ -53,8 +82,11 @@ export const layerWin32: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
|
53
82
|
})
|
|
54
83
|
|
|
55
84
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
85
|
+
* Provides the default `Path` service using the host platform's Node path
|
|
86
|
+
* implementation plus file URL conversion helpers.
|
|
87
|
+
*
|
|
88
|
+
* @category layers
|
|
89
|
+
* @since 4.0.0
|
|
58
90
|
*/
|
|
59
91
|
export const layer: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
60
92
|
[TypeId]: TypeId,
|
package/src/NodeRuntime.ts
CHANGED
|
@@ -1,17 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node-compatible process runner for Effect programs.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the shared `runMain` implementation used by
|
|
5
|
+
* Node-compatible platform packages. Use it at the outer edge of a CLI, script,
|
|
6
|
+
* worker, server, or test harness when a single Effect should become the main
|
|
7
|
+
* fiber for the process while still following Node signal and exit-code
|
|
8
|
+
* conventions.
|
|
9
|
+
*
|
|
10
|
+
* **Mental model**
|
|
11
|
+
*
|
|
12
|
+
* `runMain` starts the supplied Effect as the process root. While that fiber is
|
|
13
|
+
* running, `SIGINT` and `SIGTERM` are translated into fiber interruption so
|
|
14
|
+
* scoped resources and finalizers get a chance to run. After the fiber exits,
|
|
15
|
+
* the signal listeners are removed and the configured teardown decides the
|
|
16
|
+
* process exit code.
|
|
17
|
+
*
|
|
18
|
+
* **Common tasks**
|
|
19
|
+
*
|
|
20
|
+
* - Launch a command-line program or long-running service from an Effect.
|
|
21
|
+
* - Share the same main-runner behavior across Node-compatible packages.
|
|
22
|
+
* - Customize teardown or runtime error reporting with the `runMain` options.
|
|
23
|
+
*
|
|
24
|
+
* **Gotchas**
|
|
25
|
+
*
|
|
26
|
+
* Clean success lets the Node event loop drain naturally instead of forcing
|
|
27
|
+
* `process.exit(0)`. Signal-triggered interruption or a non-zero teardown code
|
|
28
|
+
* calls `process.exit`, so long-lived handles should be acquired in Effect
|
|
29
|
+
* scopes and released by finalizers.
|
|
30
|
+
*
|
|
31
|
+
* @since 4.0.0
|
|
3
32
|
*/
|
|
4
33
|
import type { Effect } from "effect/Effect"
|
|
5
34
|
import * as Runtime from "effect/Runtime"
|
|
6
35
|
|
|
7
36
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
37
|
+
* Runs an Effect as the Node process main program, interrupting the fiber on
|
|
38
|
+
* `SIGINT` or `SIGTERM` and invoking the configured teardown to determine the
|
|
39
|
+
* process exit code.
|
|
40
|
+
*
|
|
41
|
+
* @category running
|
|
42
|
+
* @since 4.0.0
|
|
10
43
|
*/
|
|
11
44
|
export const runMain: {
|
|
12
45
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
46
|
+
* Runs an Effect as the Node process main program, interrupting the fiber on
|
|
47
|
+
* `SIGINT` or `SIGTERM` and invoking the configured teardown to determine the
|
|
48
|
+
* process exit code.
|
|
49
|
+
*
|
|
50
|
+
* @category running
|
|
51
|
+
* @since 4.0.0
|
|
15
52
|
*/
|
|
16
53
|
(
|
|
17
54
|
options?: {
|
|
@@ -20,8 +57,12 @@ export const runMain: {
|
|
|
20
57
|
}
|
|
21
58
|
): <E, A>(effect: Effect<A, E>) => void
|
|
22
59
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
60
|
+
* Runs an Effect as the Node process main program, interrupting the fiber on
|
|
61
|
+
* `SIGINT` or `SIGTERM` and invoking the configured teardown to determine the
|
|
62
|
+
* process exit code.
|
|
63
|
+
*
|
|
64
|
+
* @category running
|
|
65
|
+
* @since 4.0.0
|
|
25
66
|
*/
|
|
26
67
|
<E, A>(
|
|
27
68
|
effect: Effect<A, E>,
|
|
@@ -37,10 +78,8 @@ export const runMain: {
|
|
|
37
78
|
let receivedSignal = false
|
|
38
79
|
|
|
39
80
|
fiber.addObserver((exit) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
process.removeListener("SIGTERM", onSigint)
|
|
43
|
-
}
|
|
81
|
+
process.removeListener("SIGINT", onSigint)
|
|
82
|
+
process.removeListener("SIGTERM", onSigint)
|
|
44
83
|
teardown(exit, (code) => {
|
|
45
84
|
if (receivedSignal || code !== 0) {
|
|
46
85
|
process.exit(code)
|
|
@@ -50,8 +89,6 @@ export const runMain: {
|
|
|
50
89
|
|
|
51
90
|
function onSigint() {
|
|
52
91
|
receivedSignal = true
|
|
53
|
-
process.removeListener("SIGINT", onSigint)
|
|
54
|
-
process.removeListener("SIGTERM", onSigint)
|
|
55
92
|
fiber.interruptUnsafe(fiber.id)
|
|
56
93
|
}
|
|
57
94
|
|
package/src/NodeSink.ts
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Sink adapters for writing Effect stream chunks into Node writable streams.
|
|
3
|
+
*
|
|
4
|
+
* This module is the writable-stream boundary for Node APIs: push Effect
|
|
5
|
+
* `Stream` or `Channel` chunks into file streams, HTTP request or response
|
|
6
|
+
* bodies, process stdio, sockets, and transform inputs such as compression or
|
|
7
|
+
* encryption streams. It exposes a `Sink` constructor for ordinary stream
|
|
8
|
+
* pipelines plus lower-level `Channel` and pull helpers used by other Node
|
|
9
|
+
* stream adapters.
|
|
10
|
+
*
|
|
11
|
+
* **Mental model**
|
|
12
|
+
*
|
|
13
|
+
* {@link fromWritable} builds a `Sink` for normal stream pipelines.
|
|
14
|
+
* {@link fromWritableChannel} exposes the same bridge as a `Channel`, and
|
|
15
|
+
* {@link pullIntoWritable} is the lower-level loop for adapters that already
|
|
16
|
+
* have a pull and a Node writable. All three write chunks in order and follow
|
|
17
|
+
* Node writable backpressure.
|
|
18
|
+
*
|
|
19
|
+
* **Common tasks**
|
|
20
|
+
*
|
|
21
|
+
* Use {@link fromWritable} to pipe an Effect stream into a Node destination.
|
|
22
|
+
* Use {@link fromWritableChannel} when composing at the `Channel` level. Use
|
|
23
|
+
* {@link pullIntoWritable} inside custom Node stream adapters that need direct
|
|
24
|
+
* control over the upstream pull.
|
|
25
|
+
*
|
|
26
|
+
* **Gotchas**
|
|
27
|
+
*
|
|
28
|
+
* When `write` returns `false`, pulling pauses until `drain` so upstream
|
|
29
|
+
* producers do not overrun the writable buffer. Writable `error` events are
|
|
30
|
+
* mapped through `onError`, and the writable is ended and awaited via `finish`
|
|
31
|
+
* when upstream completes unless `endOnDone` is `false`. Use `endOnDone: false`
|
|
32
|
+
* for externally owned or long-lived writables.
|
|
33
|
+
*
|
|
34
|
+
* @since 4.0.0
|
|
3
35
|
*/
|
|
4
36
|
import type { NonEmptyReadonlyArray } from "effect/Array"
|
|
5
37
|
import * as Cause from "effect/Cause"
|
|
@@ -11,8 +43,12 @@ import * as Sink from "effect/Sink"
|
|
|
11
43
|
import type { Writable } from "node:stream"
|
|
12
44
|
|
|
13
45
|
/**
|
|
46
|
+
* Creates a `Sink` that writes chunks to a Node writable stream, respecting
|
|
47
|
+
* backpressure, mapping writable errors with `onError`, and ending the stream
|
|
48
|
+
* on completion unless `endOnDone` is `false`.
|
|
49
|
+
*
|
|
14
50
|
* @category constructors
|
|
15
|
-
* @since
|
|
51
|
+
* @since 4.0.0
|
|
16
52
|
*/
|
|
17
53
|
export const fromWritable = <E, A = Uint8Array | string>(
|
|
18
54
|
options: {
|
|
@@ -25,8 +61,12 @@ export const fromWritable = <E, A = Uint8Array | string>(
|
|
|
25
61
|
Sink.fromChannel(Channel.mapDone(fromWritableChannel<never, E, A>(options), (_) => [_]))
|
|
26
62
|
|
|
27
63
|
/**
|
|
64
|
+
* Creates a `Channel` that pulls chunks from upstream and writes them to a
|
|
65
|
+
* Node writable stream, respecting backpressure and optionally ending the
|
|
66
|
+
* writable when upstream is done.
|
|
67
|
+
*
|
|
28
68
|
* @category constructors
|
|
29
|
-
* @since
|
|
69
|
+
* @since 4.0.0
|
|
30
70
|
*/
|
|
31
71
|
export const fromWritableChannel = <IE, E, A = Uint8Array | string>(
|
|
32
72
|
options: {
|
|
@@ -42,7 +82,12 @@ export const fromWritableChannel = <IE, E, A = Uint8Array | string>(
|
|
|
42
82
|
})
|
|
43
83
|
|
|
44
84
|
/**
|
|
45
|
-
*
|
|
85
|
+
* Repeatedly pulls non-empty chunks and writes them to a Node writable stream,
|
|
86
|
+
* waiting for `drain` when needed, failing on writable errors, and ending the
|
|
87
|
+
* writable on upstream completion unless disabled.
|
|
88
|
+
*
|
|
89
|
+
* @category converting
|
|
90
|
+
* @since 4.0.0
|
|
46
91
|
*/
|
|
47
92
|
export const pullIntoWritable = <A, IE, E>(options: {
|
|
48
93
|
readonly pull: Pull.Pull<NonEmptyReadonlyArray<A>, IE, unknown>
|
|
@@ -66,6 +111,13 @@ export const pullIntoWritable = <A, IE, E>(options: {
|
|
|
66
111
|
})
|
|
67
112
|
}),
|
|
68
113
|
Effect.forever({ disableYield: true }),
|
|
114
|
+
Effect.raceFirst(Effect.callback<never, E>((resume) => {
|
|
115
|
+
const onError = (error: unknown) => resume(Effect.fail(options.onError(error)))
|
|
116
|
+
options.writable.once("error", onError)
|
|
117
|
+
return Effect.sync(() => {
|
|
118
|
+
options.writable.off("error", onError)
|
|
119
|
+
})
|
|
120
|
+
})),
|
|
69
121
|
options.endOnDone !== false ?
|
|
70
122
|
Pull.catchDone((_) => {
|
|
71
123
|
if ("closed" in options.writable && options.writable.closed) {
|
package/src/NodeSocket.ts
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node socket adapters for Effect sockets.
|
|
3
|
+
*
|
|
4
|
+
* This module opens `node:net` connections or wraps existing Node `Duplex`
|
|
5
|
+
* streams and presents them as `Socket.Socket` values, socket channels, or
|
|
6
|
+
* layers. It is the low-level bridge for TCP clients, Unix domain socket
|
|
7
|
+
* clients, and protocols that already expose a Node duplex stream.
|
|
8
|
+
*
|
|
9
|
+
* **Mental model**
|
|
10
|
+
*
|
|
11
|
+
* A socket acquired here is scoped. `makeNet` dials with
|
|
12
|
+
* `net.createConnection`, `fromDuplex` adapts any duplex returned by an Effect,
|
|
13
|
+
* `makeNetChannel` exposes the socket as a `Channel`, and `layerNet` provides
|
|
14
|
+
* it through the Effect environment. While a socket handler is running,
|
|
15
|
+
* `NetSocket` gives access to the underlying Node `net.Socket` for cases that
|
|
16
|
+
* need Node-specific operations.
|
|
17
|
+
*
|
|
18
|
+
* **Gotchas**
|
|
19
|
+
*
|
|
20
|
+
* `openTimeout` only limits opening the connection. Writes complete when Node
|
|
21
|
+
* accepts or flushes a chunk, close events are translated to `SocketError`
|
|
22
|
+
* values, and finalizers close or destroy the stream when the surrounding scope
|
|
23
|
+
* ends. Unix socket paths are supplied through `NetConnectOpts.path`, so use
|
|
24
|
+
* the same platform path rules as Node.
|
|
25
|
+
*
|
|
26
|
+
* @since 4.0.0
|
|
3
27
|
*/
|
|
4
28
|
import type { Array } from "effect"
|
|
5
29
|
import * as Channel from "effect/Channel"
|
|
30
|
+
import * as Context from "effect/Context"
|
|
6
31
|
import * as Deferred from "effect/Deferred"
|
|
7
32
|
import type * as Duration from "effect/Duration"
|
|
8
33
|
import * as Effect from "effect/Effect"
|
|
@@ -12,28 +37,34 @@ import { identity } from "effect/Function"
|
|
|
12
37
|
import * as Latch from "effect/Latch"
|
|
13
38
|
import * as Layer from "effect/Layer"
|
|
14
39
|
import * as Scope from "effect/Scope"
|
|
15
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
16
40
|
import * as Socket from "effect/unstable/socket/Socket"
|
|
17
41
|
import * as Net from "node:net"
|
|
18
42
|
import type { Duplex } from "node:stream"
|
|
19
43
|
|
|
20
44
|
/**
|
|
21
|
-
* @since 1.0.0
|
|
22
45
|
* @category re-exports
|
|
46
|
+
* @since 4.0.0
|
|
23
47
|
*/
|
|
24
48
|
export * as NodeWS from "ws"
|
|
25
49
|
|
|
26
50
|
/**
|
|
27
|
-
*
|
|
51
|
+
* Service tag for the underlying Node `net.Socket` associated with the current
|
|
52
|
+
* socket connection.
|
|
53
|
+
*
|
|
28
54
|
* @category tags
|
|
55
|
+
* @since 4.0.0
|
|
29
56
|
*/
|
|
30
|
-
export class NetSocket extends
|
|
57
|
+
export class NetSocket extends Context.Service<NetSocket, Net.Socket>()(
|
|
31
58
|
"@effect/platform-node/NodeSocket/NetSocket"
|
|
32
59
|
) {}
|
|
33
60
|
|
|
34
61
|
/**
|
|
35
|
-
*
|
|
62
|
+
* Opens a TCP connection with Node `net.createConnection` and exposes it as a
|
|
63
|
+
* `Socket.Socket`, supporting `openTimeout` and closing or destroying the
|
|
64
|
+
* socket when the enclosing scope is finalized.
|
|
65
|
+
*
|
|
36
66
|
* @category constructors
|
|
67
|
+
* @since 4.0.0
|
|
37
68
|
*/
|
|
38
69
|
export const makeNet = (
|
|
39
70
|
options: Net.NetConnectOpts & {
|
|
@@ -41,11 +72,11 @@ export const makeNet = (
|
|
|
41
72
|
}
|
|
42
73
|
): Effect.Effect<Socket.Socket> =>
|
|
43
74
|
fromDuplex(
|
|
44
|
-
Effect.
|
|
75
|
+
Effect.contextWith((context: Context.Context<Scope.Scope>) => {
|
|
45
76
|
let conn: Net.Socket | undefined
|
|
46
77
|
return Effect.flatMap(
|
|
47
78
|
Scope.addFinalizer(
|
|
48
|
-
|
|
79
|
+
Context.get(context, Scope.Scope),
|
|
49
80
|
Effect.sync(() => {
|
|
50
81
|
if (!conn) return
|
|
51
82
|
if (conn.closed === false) {
|
|
@@ -77,8 +108,12 @@ export const makeNet = (
|
|
|
77
108
|
)
|
|
78
109
|
|
|
79
110
|
/**
|
|
80
|
-
*
|
|
111
|
+
* Adapts a Node `Duplex` into a `Socket.Socket`, wiring data events to socket
|
|
112
|
+
* handlers, providing a scoped writer, and mapping open, read, write, and close
|
|
113
|
+
* failures to `SocketError`.
|
|
114
|
+
*
|
|
81
115
|
* @category constructors
|
|
116
|
+
* @since 4.0.0
|
|
82
117
|
*/
|
|
83
118
|
export const fromDuplex = <RO>(
|
|
84
119
|
open: Effect.Effect<Duplex, Socket.SocketError, RO>,
|
|
@@ -89,7 +124,7 @@ export const fromDuplex = <RO>(
|
|
|
89
124
|
Effect.withFiber<Socket.Socket, never, Exclude<RO, Scope.Scope>>((fiber) => {
|
|
90
125
|
let currentSocket: Duplex | undefined
|
|
91
126
|
const latch = Latch.makeUnsafe(false)
|
|
92
|
-
const openServices = fiber.
|
|
127
|
+
const openServices = fiber.context as Context.Context<RO>
|
|
93
128
|
|
|
94
129
|
const run = <R, E, _>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void, opts?: {
|
|
95
130
|
readonly onOpen?: Effect.Effect<void> | undefined
|
|
@@ -113,7 +148,7 @@ export const fromDuplex = <RO>(
|
|
|
113
148
|
options?.openTimeout ?
|
|
114
149
|
Effect.timeoutOrElse({
|
|
115
150
|
duration: options.openTimeout,
|
|
116
|
-
|
|
151
|
+
orElse: () =>
|
|
117
152
|
Effect.fail(
|
|
118
153
|
new Socket.SocketError({
|
|
119
154
|
reason: new Socket.SocketOpenError({ kind: "Timeout", cause: new Error("Connection timed out") })
|
|
@@ -166,7 +201,7 @@ export const fromDuplex = <RO>(
|
|
|
166
201
|
)
|
|
167
202
|
}
|
|
168
203
|
})).pipe(
|
|
169
|
-
Effect.
|
|
204
|
+
Effect.updateContext((input: Context.Context<R>) => Context.merge(openServices, input)),
|
|
170
205
|
Effect.onExit(() =>
|
|
171
206
|
Effect.sync(() => {
|
|
172
207
|
latch.closeUnsafe()
|
|
@@ -204,8 +239,7 @@ export const fromDuplex = <RO>(
|
|
|
204
239
|
})
|
|
205
240
|
)
|
|
206
241
|
|
|
207
|
-
return Effect.succeed(Socket.
|
|
208
|
-
[Socket.TypeId]: Socket.TypeId,
|
|
242
|
+
return Effect.succeed(Socket.make({
|
|
209
243
|
run,
|
|
210
244
|
runRaw: run,
|
|
211
245
|
writer
|
|
@@ -213,8 +247,11 @@ export const fromDuplex = <RO>(
|
|
|
213
247
|
})
|
|
214
248
|
|
|
215
249
|
/**
|
|
216
|
-
*
|
|
250
|
+
* Creates a `Channel` over a TCP socket, reading arrays of `Uint8Array`
|
|
251
|
+
* chunks and writing arrays of bytes, strings, or socket close events.
|
|
252
|
+
*
|
|
217
253
|
* @category constructors
|
|
254
|
+
* @since 4.0.0
|
|
218
255
|
*/
|
|
219
256
|
export const makeNetChannel = <IE = never>(
|
|
220
257
|
options: Net.NetConnectOpts
|
|
@@ -230,8 +267,11 @@ export const makeNetChannel = <IE = never>(
|
|
|
230
267
|
)
|
|
231
268
|
|
|
232
269
|
/**
|
|
233
|
-
*
|
|
270
|
+
* Provides a `Socket.Socket` by opening a TCP connection with the supplied
|
|
271
|
+
* Node `net` connection options.
|
|
272
|
+
*
|
|
234
273
|
* @category layers
|
|
274
|
+
* @since 4.0.0
|
|
235
275
|
*/
|
|
236
276
|
export const layerNet: (options: Net.NetConnectOpts) => Layer.Layer<
|
|
237
277
|
Socket.Socket,
|