@effect/platform-node-shared 4.0.0-beta.102 → 4.0.0-beta.104
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.map +1 -1
- package/dist/NodeChildProcessSpawner.js +4 -1
- package/dist/NodeChildProcessSpawner.js.map +1 -1
- package/dist/NodeClusterSocket.js.map +1 -1
- package/dist/NodeCrypto.js.map +1 -1
- package/dist/NodeFileSystem.d.ts.map +1 -1
- package/dist/NodeFileSystem.js +7 -7
- package/dist/NodeFileSystem.js.map +1 -1
- package/dist/NodeHttpCompression.d.ts +36 -0
- package/dist/NodeHttpCompression.d.ts.map +1 -0
- package/dist/NodeHttpCompression.js +146 -0
- package/dist/NodeHttpCompression.js.map +1 -0
- package/dist/NodePath.d.ts.map +1 -1
- package/dist/NodePath.js +23 -20
- package/dist/NodePath.js.map +1 -1
- package/dist/NodeRuntime.js.map +1 -1
- package/dist/NodeSink.js.map +1 -1
- package/dist/NodeSocket.js.map +1 -1
- package/dist/NodeSocketServer.d.ts.map +1 -1
- package/dist/NodeSocketServer.js +25 -15
- package/dist/NodeSocketServer.js.map +1 -1
- package/dist/NodeStdio.js.map +1 -1
- package/dist/NodeStream.js.map +1 -1
- package/dist/NodeTerminal.d.ts.map +1 -1
- package/dist/NodeTerminal.js +61 -10
- package/dist/NodeTerminal.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/utils.js.map +1 -1
- package/package.json +6 -7
- package/src/NodeChildProcessSpawner.ts +3 -1
- package/src/NodeFileSystem.ts +16 -12
- package/src/NodeHttpCompression.ts +153 -0
- package/src/NodePath.ts +25 -27
- package/src/NodeSocketServer.ts +28 -15
- package/src/NodeTerminal.ts +60 -12
- package/src/index.ts +5 -0
- package/src/internal/utils.ts +1 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP response compression backed by `node:zlib`, shared by the Node.js, Bun,
|
|
3
|
+
* and Deno platforms.
|
|
4
|
+
*
|
|
5
|
+
* Byte-array bodies are compressed in one shot with the asynchronous
|
|
6
|
+
* `node:zlib` APIs, preserving an exact `Content-Length`. Streaming bodies go
|
|
7
|
+
* through `node:zlib` transform streams that flush each input chunk.
|
|
8
|
+
*
|
|
9
|
+
* @since 4.0.0
|
|
10
|
+
*/
|
|
11
|
+
import * as Effect from "effect/Effect"
|
|
12
|
+
import * as HttpBody from "effect/unstable/http/HttpBody"
|
|
13
|
+
import type * as Platform from "effect/unstable/http/HttpPlatform"
|
|
14
|
+
import * as Response from "effect/unstable/http/HttpServerResponse"
|
|
15
|
+
import type { Duplex } from "node:stream"
|
|
16
|
+
import { Readable } from "node:stream"
|
|
17
|
+
import * as Zlib from "node:zlib"
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The compression algorithms supported by the runtime's `node:zlib`. `zstd`
|
|
21
|
+
* requires Node.js 22.15 or newer.
|
|
22
|
+
*
|
|
23
|
+
* @category constants
|
|
24
|
+
* @since 4.0.0
|
|
25
|
+
*/
|
|
26
|
+
export const algorithms: ReadonlySet<Platform.CompressionAlgorithm> = new Set(
|
|
27
|
+
typeof Zlib.zstdCompress === "function"
|
|
28
|
+
? ["gzip", "deflate", "br", "zstd"]
|
|
29
|
+
: ["gzip", "deflate", "br"]
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const brotliParams = (level: number | undefined, sizeHint?: number): Zlib.BrotliOptions => {
|
|
33
|
+
const params: Record<number, number> = {}
|
|
34
|
+
if (level !== undefined) {
|
|
35
|
+
params[Zlib.constants.BROTLI_PARAM_QUALITY] = level
|
|
36
|
+
}
|
|
37
|
+
if (sizeHint !== undefined) {
|
|
38
|
+
params[Zlib.constants.BROTLI_PARAM_SIZE_HINT] = sizeHint
|
|
39
|
+
}
|
|
40
|
+
return { params }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const zstdParams = (level: number | undefined): Zlib.ZstdOptions | undefined =>
|
|
44
|
+
level === undefined || level === 3 ? undefined : { params: { [Zlib.constants.ZSTD_c_compressionLevel]: level } }
|
|
45
|
+
|
|
46
|
+
const compress = (
|
|
47
|
+
data: Uint8Array,
|
|
48
|
+
algorithm: Platform.CompressionAlgorithm,
|
|
49
|
+
options?: Platform.CompressionOptions | undefined
|
|
50
|
+
): Effect.Effect<Uint8Array> =>
|
|
51
|
+
Effect.callback((resume) => {
|
|
52
|
+
const complete = (error: Error | null, result: Uint8Array) =>
|
|
53
|
+
resume(error === null ? Effect.succeed(result) : Effect.die(error))
|
|
54
|
+
switch (algorithm) {
|
|
55
|
+
case "gzip": {
|
|
56
|
+
Zlib.gzip(data, { level: options?.level }, complete)
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
case "deflate": {
|
|
60
|
+
Zlib.deflate(data, { level: options?.level }, complete)
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
case "br": {
|
|
64
|
+
Zlib.brotliCompress(data, brotliParams(options?.level, data.byteLength), complete)
|
|
65
|
+
break
|
|
66
|
+
}
|
|
67
|
+
case "zstd": {
|
|
68
|
+
const params = zstdParams(options?.level)
|
|
69
|
+
if (params === undefined) {
|
|
70
|
+
Zlib.zstdCompress(data, complete)
|
|
71
|
+
} else {
|
|
72
|
+
Zlib.zstdCompress(data, params, complete)
|
|
73
|
+
}
|
|
74
|
+
break
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Creates a `Compression` that compresses byte-array bodies in one shot with
|
|
81
|
+
* the asynchronous `node:zlib` APIs, setting the exact `Content-Length` of the
|
|
82
|
+
* compressed body. All other bodies are delegated to `fallback`.
|
|
83
|
+
*
|
|
84
|
+
* @category constructors
|
|
85
|
+
* @since 4.0.0
|
|
86
|
+
*/
|
|
87
|
+
export const make = (fallback: Platform.Compression): Platform.Compression => ({
|
|
88
|
+
algorithms: fallback.algorithms,
|
|
89
|
+
compressResponse(response, algorithm, options) {
|
|
90
|
+
const body = response.body
|
|
91
|
+
if (body._tag !== "Uint8Array") {
|
|
92
|
+
return fallback.compressResponse(response, algorithm, options)
|
|
93
|
+
}
|
|
94
|
+
return Effect.map(compress(body.body, algorithm, options), (result) =>
|
|
95
|
+
Response.setHeader(
|
|
96
|
+
Response.setBody(response, HttpBody.uint8Array(result, body.contentType)),
|
|
97
|
+
"content-length",
|
|
98
|
+
result.byteLength.toString()
|
|
99
|
+
))
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Creates a `node:zlib` compression transform stream that flushes each input
|
|
105
|
+
* chunk, for streaming response bodies.
|
|
106
|
+
*
|
|
107
|
+
* @category constructors
|
|
108
|
+
* @since 4.0.0
|
|
109
|
+
*/
|
|
110
|
+
export const compressTransform = (
|
|
111
|
+
algorithm: Platform.CompressionAlgorithm,
|
|
112
|
+
options?: Platform.CompressionOptions | undefined
|
|
113
|
+
): Duplex => {
|
|
114
|
+
switch (algorithm) {
|
|
115
|
+
case "gzip": {
|
|
116
|
+
return Zlib.createGzip({ level: options?.level, flush: Zlib.constants.Z_SYNC_FLUSH })
|
|
117
|
+
}
|
|
118
|
+
case "deflate": {
|
|
119
|
+
return Zlib.createDeflate({ level: options?.level, flush: Zlib.constants.Z_SYNC_FLUSH })
|
|
120
|
+
}
|
|
121
|
+
case "br": {
|
|
122
|
+
return Zlib.createBrotliCompress({
|
|
123
|
+
...brotliParams(options?.level),
|
|
124
|
+
flush: Zlib.constants.BROTLI_OPERATION_FLUSH
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
case "zstd": {
|
|
128
|
+
return Zlib.createZstdCompress({
|
|
129
|
+
...zstdParams(options?.level),
|
|
130
|
+
flush: Zlib.constants.ZSTD_e_flush
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* A Web `ReadableStream` version of `compressTransform`, for platforms that
|
|
138
|
+
* stream response bodies as Web streams.
|
|
139
|
+
*
|
|
140
|
+
* @category constructors
|
|
141
|
+
* @since 4.0.0
|
|
142
|
+
*/
|
|
143
|
+
export const compressTransformWeb = (
|
|
144
|
+
algorithm: Platform.CompressionAlgorithm,
|
|
145
|
+
options?: Platform.CompressionOptions | undefined
|
|
146
|
+
) =>
|
|
147
|
+
(stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> => {
|
|
148
|
+
const transform = compressTransform(algorithm, options)
|
|
149
|
+
const source = Readable.fromWeb(stream as any)
|
|
150
|
+
source.on("error", (cause) => transform.destroy(cause))
|
|
151
|
+
transform.on("close", () => source.destroy())
|
|
152
|
+
return Readable.toWeb(source.pipe(transform)) as unknown as ReadableStream<Uint8Array>
|
|
153
|
+
}
|
package/src/NodePath.ts
CHANGED
|
@@ -15,27 +15,28 @@ import { BadArgument } from "effect/PlatformError"
|
|
|
15
15
|
import * as NodePath from "node:path"
|
|
16
16
|
import * as NodeUrl from "node:url"
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
Effect.
|
|
20
|
-
try
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
18
|
+
const fileUrlOps = (windows: boolean | undefined) => ({
|
|
19
|
+
fromFileUrl: (url: URL): Effect.Effect<string, BadArgument> =>
|
|
20
|
+
Effect.try({
|
|
21
|
+
try: () => NodeUrl.fileURLToPath(url, { windows }),
|
|
22
|
+
catch: (cause) =>
|
|
23
|
+
new BadArgument({
|
|
24
|
+
module: "Path",
|
|
25
|
+
method: "fromFileUrl",
|
|
26
|
+
cause
|
|
27
|
+
})
|
|
28
|
+
}),
|
|
29
|
+
toFileUrl: (path: string): Effect.Effect<URL, BadArgument> =>
|
|
30
|
+
Effect.try({
|
|
31
|
+
try: () => NodeUrl.pathToFileURL(path, { windows }),
|
|
32
|
+
catch: (cause) =>
|
|
33
|
+
new BadArgument({
|
|
34
|
+
module: "Path",
|
|
35
|
+
method: "toFileUrl",
|
|
36
|
+
cause
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
})
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Provides the `Path` service using Node's POSIX path implementation plus
|
|
@@ -47,8 +48,7 @@ const toFileUrl = (path: string): Effect.Effect<URL, BadArgument> =>
|
|
|
47
48
|
export const layerPosix: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
48
49
|
[TypeId]: TypeId,
|
|
49
50
|
...NodePath.posix,
|
|
50
|
-
|
|
51
|
-
toFileUrl
|
|
51
|
+
...fileUrlOps(false)
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
/**
|
|
@@ -61,8 +61,7 @@ export const layerPosix: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
|
61
61
|
export const layerWin32: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
62
62
|
[TypeId]: TypeId,
|
|
63
63
|
...NodePath.win32,
|
|
64
|
-
|
|
65
|
-
toFileUrl
|
|
64
|
+
...fileUrlOps(true)
|
|
66
65
|
})
|
|
67
66
|
|
|
68
67
|
/**
|
|
@@ -75,6 +74,5 @@ export const layerWin32: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
|
75
74
|
export const layer: Layer.Layer<Path> = Layer.succeed(Path)({
|
|
76
75
|
[TypeId]: TypeId,
|
|
77
76
|
...NodePath,
|
|
78
|
-
|
|
79
|
-
toFileUrl
|
|
77
|
+
...fileUrlOps(undefined)
|
|
80
78
|
})
|
package/src/NodeSocketServer.ts
CHANGED
|
@@ -52,12 +52,14 @@ export const make = Effect.fnUntraced(function*(
|
|
|
52
52
|
options: Net.ServerOpts & Net.ListenOptions
|
|
53
53
|
) {
|
|
54
54
|
const errorDeferred = Deferred.makeUnsafe<never, Error>()
|
|
55
|
-
const pending = new
|
|
55
|
+
const pending = new Map<Net.Socket, () => void>()
|
|
56
56
|
function defaultOnConnection(conn: Net.Socket) {
|
|
57
|
-
pending.add(conn)
|
|
58
57
|
const remove = () => {
|
|
59
58
|
pending.delete(conn)
|
|
59
|
+
conn.off("close", remove)
|
|
60
|
+
conn.off("error", remove)
|
|
60
61
|
}
|
|
62
|
+
pending.set(conn, remove)
|
|
61
63
|
conn.on("close", remove)
|
|
62
64
|
conn.on("error", remove)
|
|
63
65
|
}
|
|
@@ -66,6 +68,10 @@ export const make = Effect.fnUntraced(function*(
|
|
|
66
68
|
let server: Net.Server | undefined
|
|
67
69
|
yield* Effect.addFinalizer(() =>
|
|
68
70
|
Effect.callback<void>((resume) => {
|
|
71
|
+
pending.forEach((remove, conn) => {
|
|
72
|
+
remove()
|
|
73
|
+
conn.destroy()
|
|
74
|
+
})
|
|
69
75
|
server?.close(() => resume(Effect.void))
|
|
70
76
|
})
|
|
71
77
|
)
|
|
@@ -129,12 +135,10 @@ export const make = Effect.fnUntraced(function*(
|
|
|
129
135
|
trackFiber
|
|
130
136
|
)
|
|
131
137
|
}
|
|
132
|
-
pending.forEach((conn) => {
|
|
133
|
-
|
|
134
|
-
conn.removeAllListeners("close")
|
|
138
|
+
pending.forEach((remove, conn) => {
|
|
139
|
+
remove()
|
|
135
140
|
onConnection(conn)
|
|
136
141
|
})
|
|
137
|
-
pending.clear()
|
|
138
142
|
return yield* Effect.callback<never>((_resume) => {
|
|
139
143
|
return Effect.suspend(() => {
|
|
140
144
|
onConnection = prevOnConnection
|
|
@@ -190,20 +194,29 @@ export const makeWebSocket: (
|
|
|
190
194
|
> = Effect.fnUntraced(function*(
|
|
191
195
|
options: NodeWS.ServerOptions
|
|
192
196
|
) {
|
|
197
|
+
const pendingConnections = new Map<
|
|
198
|
+
globalThis.WebSocket,
|
|
199
|
+
readonly [request: Http.IncomingMessage, remove: () => void]
|
|
200
|
+
>()
|
|
193
201
|
const server = yield* Effect.acquireRelease(
|
|
194
202
|
Effect.sync(() => new NodeWS.WebSocketServer(options)),
|
|
195
203
|
(server) =>
|
|
196
204
|
Effect.callback<void>((resume) => {
|
|
205
|
+
pendingConnections.forEach(([, remove], conn) => {
|
|
206
|
+
remove()
|
|
207
|
+
const socket = conn as unknown as NodeWS.WebSocket
|
|
208
|
+
socket.terminate()
|
|
209
|
+
})
|
|
197
210
|
server.close(() => resume(Effect.void))
|
|
198
211
|
})
|
|
199
212
|
)
|
|
200
|
-
const pendingConnections = new Set<readonly [globalThis.WebSocket, Http.IncomingMessage]>()
|
|
201
213
|
function defaultHandler(conn: globalThis.WebSocket, req: Http.IncomingMessage) {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
214
|
+
const remove = () => {
|
|
215
|
+
pendingConnections.delete(conn)
|
|
216
|
+
conn.removeEventListener("close", remove)
|
|
217
|
+
}
|
|
218
|
+
pendingConnections.set(conn, [req, remove])
|
|
219
|
+
conn.addEventListener("close", remove)
|
|
207
220
|
}
|
|
208
221
|
let onConnection = defaultHandler
|
|
209
222
|
server.on("connection", (conn, req) => onConnection(conn as any, req))
|
|
@@ -248,10 +261,10 @@ export const makeWebSocket: (
|
|
|
248
261
|
trackFiber
|
|
249
262
|
)
|
|
250
263
|
}
|
|
251
|
-
|
|
264
|
+
pendingConnections.forEach(([req, remove], conn) => {
|
|
265
|
+
remove()
|
|
252
266
|
onConnection(conn, req)
|
|
253
|
-
}
|
|
254
|
-
pendingConnections.clear()
|
|
267
|
+
})
|
|
255
268
|
return yield* Effect.callback<never>((_resume) => {
|
|
256
269
|
return Effect.sync(() => {
|
|
257
270
|
onConnection = prevOnConnection
|
package/src/NodeTerminal.ts
CHANGED
|
@@ -35,25 +35,52 @@ export const make: (
|
|
|
35
35
|
function*(shouldQuit: (input: Terminal.UserInput) => boolean = defaultShouldQuit) {
|
|
36
36
|
const stdin = process.stdin
|
|
37
37
|
const stdout = process.stdout
|
|
38
|
+
const lines = yield* Queue.make<string, Cause.Done>()
|
|
39
|
+
|
|
40
|
+
// stdin "end" fires once per process, so remember end-of-input for readers
|
|
41
|
+
// created after the event (Bun never sets `readableEnded`).
|
|
42
|
+
let inputEnded = stdin.readableEnded
|
|
43
|
+
let readlineActive = false
|
|
44
|
+
const onStdinEnd = () => {
|
|
45
|
+
inputEnded = true
|
|
46
|
+
if (!readlineActive) {
|
|
47
|
+
Queue.endUnsafe(lines)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
stdin.once("end", onStdinEnd)
|
|
51
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => stdin.off("end", onStdinEnd)))
|
|
38
52
|
|
|
39
|
-
// Acquire readline interface with TTY setup/cleanup inside the scope
|
|
40
53
|
const rlRef = yield* RcRef.make({
|
|
41
54
|
acquire: Effect.acquireRelease(
|
|
42
55
|
Effect.sync(() => {
|
|
43
56
|
const rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 50 })
|
|
57
|
+
const onLine = (line: string) => Queue.offerUnsafe(lines, line)
|
|
58
|
+
const onClose = () => {
|
|
59
|
+
readlineActive = false
|
|
60
|
+
Queue.endUnsafe(lines)
|
|
61
|
+
}
|
|
62
|
+
readlineActive = true
|
|
44
63
|
readline.emitKeypressEvents(stdin, rl)
|
|
64
|
+
rl.on("line", onLine)
|
|
65
|
+
rl.once("close", onClose)
|
|
45
66
|
|
|
46
67
|
if (stdin.isTTY) {
|
|
47
68
|
stdin.setRawMode(true)
|
|
48
69
|
}
|
|
49
|
-
return rl
|
|
70
|
+
return { rl, onClose, onLine }
|
|
50
71
|
}),
|
|
51
|
-
(rl) =>
|
|
72
|
+
({ rl, onClose, onLine }) =>
|
|
52
73
|
Effect.sync(() => {
|
|
74
|
+
readlineActive = false
|
|
75
|
+
rl.off("line", onLine)
|
|
76
|
+
rl.off("close", onClose)
|
|
53
77
|
if (stdin.isTTY) {
|
|
54
78
|
stdin.setRawMode(false)
|
|
55
79
|
}
|
|
56
80
|
rl.close()
|
|
81
|
+
if (inputEnded) {
|
|
82
|
+
Queue.endUnsafe(lines)
|
|
83
|
+
}
|
|
57
84
|
})
|
|
58
85
|
)
|
|
59
86
|
})
|
|
@@ -62,7 +89,6 @@ export const make: (
|
|
|
62
89
|
const rows = Effect.sync(() => stdout.rows ?? 0)
|
|
63
90
|
|
|
64
91
|
const readInput = Effect.gen(function*() {
|
|
65
|
-
yield* RcRef.get(rlRef)
|
|
66
92
|
const queue = yield* Queue.make<Terminal.UserInput, Cause.Done>()
|
|
67
93
|
const handleKeypress = (s: string | undefined, k: readline.Key) => {
|
|
68
94
|
const userInput = {
|
|
@@ -74,18 +100,40 @@ export const make: (
|
|
|
74
100
|
Queue.endUnsafe(queue)
|
|
75
101
|
}
|
|
76
102
|
}
|
|
77
|
-
|
|
103
|
+
// Deno's `process.stdin` shim does not keep the event loop alive, so a
|
|
104
|
+
// program blocked on input can exit before `end` is ever delivered. A
|
|
105
|
+
// timer holds the loop open for as long as this reader is active.
|
|
106
|
+
const keepAlive = setInterval(() => {}, 2147483647)
|
|
107
|
+
// Without this, consumers (e.g. `Prompt.run`) hang forever on closed stdin.
|
|
108
|
+
const handleEnd = () => {
|
|
109
|
+
clearInterval(keepAlive)
|
|
110
|
+
Queue.endUnsafe(queue)
|
|
111
|
+
}
|
|
112
|
+
yield* Effect.addFinalizer(() =>
|
|
113
|
+
Effect.sync(() => {
|
|
114
|
+
clearInterval(keepAlive)
|
|
115
|
+
stdin.off("keypress", handleKeypress)
|
|
116
|
+
stdin.off("end", handleEnd)
|
|
117
|
+
})
|
|
118
|
+
)
|
|
78
119
|
stdin.on("keypress", handleKeypress)
|
|
120
|
+
if (inputEnded) {
|
|
121
|
+
handleEnd()
|
|
122
|
+
} else {
|
|
123
|
+
yield* RcRef.get(rlRef)
|
|
124
|
+
stdin.once("end", handleEnd)
|
|
125
|
+
}
|
|
79
126
|
return queue as Queue.Dequeue<Terminal.UserInput, Cause.Done>
|
|
80
127
|
})
|
|
81
128
|
|
|
82
|
-
const readLine = Effect.
|
|
83
|
-
|
|
84
|
-
Effect.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}))
|
|
129
|
+
const readLine = Effect.suspend(() =>
|
|
130
|
+
Queue.poll(lines).pipe(
|
|
131
|
+
Effect.flatMap(Option.match({
|
|
132
|
+
onNone: () => Effect.scoped(Effect.andThen(RcRef.get(rlRef), Queue.take(lines))),
|
|
133
|
+
onSome: Effect.succeed
|
|
134
|
+
})),
|
|
135
|
+
Effect.mapError(() => new Terminal.QuitError({}))
|
|
136
|
+
)
|
|
89
137
|
)
|
|
90
138
|
|
|
91
139
|
const display = (prompt: string) =>
|
package/src/index.ts
CHANGED
package/src/internal/utils.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { PathLike } from "node:fs"
|
|
|
6
6
|
export const handleErrnoException = (module: SystemError["module"], method: string) =>
|
|
7
7
|
(
|
|
8
8
|
err: NodeJS.ErrnoException,
|
|
9
|
-
[path]: [path: PathLike | number | string |
|
|
9
|
+
[path]: [path: PathLike | number | string | ReadonlyArray<string>, ...args: Array<any>]
|
|
10
10
|
): PlatformError.PlatformError => {
|
|
11
11
|
let reason: SystemErrorTag = "Unknown"
|
|
12
12
|
|