@effect/platform-node 0.11.4 → 0.12.0
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/Http/NodeClient.d.ts +10 -0
- package/Http/NodeClient.d.ts.map +1 -1
- package/Http/NodeClient.js +14 -2
- package/Http/NodeClient.js.map +1 -1
- package/Http/Server.d.ts +34 -0
- package/Http/Server.d.ts.map +1 -0
- package/Http/Server.js +49 -0
- package/Http/Server.js.map +1 -0
- package/HttpServer.d.ts +9 -0
- package/HttpServer.d.ts.map +1 -0
- package/HttpServer.js +26 -0
- package/HttpServer.js.map +1 -0
- package/Stream.d.ts +14 -6
- package/Stream.d.ts.map +1 -1
- package/Stream.js.map +1 -1
- package/internal/fileSystem.js +9 -9
- package/internal/fileSystem.js.map +1 -1
- package/internal/http/formData.d.ts +5 -0
- package/internal/http/formData.d.ts.map +1 -0
- package/internal/http/formData.js +106 -0
- package/internal/http/formData.js.map +1 -0
- package/internal/http/incomingMessage.d.ts +2 -0
- package/internal/http/incomingMessage.d.ts.map +1 -0
- package/internal/http/incomingMessage.js +62 -0
- package/internal/http/incomingMessage.js.map +1 -0
- package/internal/http/nodeClient.js +30 -67
- package/internal/http/nodeClient.js.map +1 -1
- package/internal/http/server.d.ts +2 -0
- package/internal/http/server.d.ts.map +1 -0
- package/internal/http/server.js +218 -0
- package/internal/http/server.js.map +1 -0
- package/internal/stream.js +54 -35
- package/internal/stream.js.map +1 -1
- package/mjs/Http/NodeClient.mjs +10 -0
- package/mjs/Http/NodeClient.mjs.map +1 -1
- package/mjs/Http/Server.mjs +21 -0
- package/mjs/Http/Server.mjs.map +1 -0
- package/mjs/HttpServer.mjs +7 -0
- package/mjs/HttpServer.mjs.map +1 -0
- package/mjs/Stream.mjs.map +1 -1
- package/mjs/internal/fileSystem.mjs +9 -9
- package/mjs/internal/fileSystem.mjs.map +1 -1
- package/mjs/internal/http/formData.mjs +95 -0
- package/mjs/internal/http/formData.mjs.map +1 -0
- package/mjs/internal/http/incomingMessage.mjs +53 -0
- package/mjs/internal/http/incomingMessage.mjs.map +1 -0
- package/mjs/internal/http/nodeClient.mjs +27 -66
- package/mjs/internal/http/nodeClient.mjs.map +1 -1
- package/mjs/internal/http/server.mjs +207 -0
- package/mjs/internal/http/server.mjs.map +1 -0
- package/mjs/internal/stream.mjs +54 -35
- package/mjs/internal/stream.mjs.map +1 -1
- package/package.json +5 -2
- package/src/Http/NodeClient.ts +13 -0
- package/src/Http/Server.ts +45 -0
- package/src/HttpServer.ts +10 -0
- package/src/Stream.ts +14 -9
- package/src/internal/fileSystem.ts +16 -11
- package/src/internal/http/formData.ts +158 -0
- package/src/internal/http/incomingMessage.ts +78 -0
- package/src/internal/http/nodeClient.ts +64 -110
- package/src/internal/http/server.ts +323 -0
- package/src/internal/stream.ts +81 -46
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import type { LazyArg } from "@effect/data/Function"
|
|
2
|
+
import * as Option from "@effect/data/Option"
|
|
3
|
+
import * as Config from "@effect/io/Config"
|
|
4
|
+
import * as Effect from "@effect/io/Effect"
|
|
5
|
+
import * as Fiber from "@effect/io/Fiber"
|
|
6
|
+
import * as Layer from "@effect/io/Layer"
|
|
7
|
+
import * as Runtime from "@effect/io/Runtime"
|
|
8
|
+
import type * as Scope from "@effect/io/Scope"
|
|
9
|
+
import * as internalFormData from "@effect/platform-node/internal/http/formData"
|
|
10
|
+
import { IncomingMessageImpl } from "@effect/platform-node/internal/http/incomingMessage"
|
|
11
|
+
import * as NodeSink from "@effect/platform-node/Sink"
|
|
12
|
+
import * as FileSystem from "@effect/platform/FileSystem"
|
|
13
|
+
import type * as FormData from "@effect/platform/Http/FormData"
|
|
14
|
+
import * as Headers from "@effect/platform/Http/Headers"
|
|
15
|
+
import * as IncomingMessage from "@effect/platform/Http/IncomingMessage"
|
|
16
|
+
import type { Method } from "@effect/platform/Http/Method"
|
|
17
|
+
import * as Middleware from "@effect/platform/Http/Middleware"
|
|
18
|
+
import * as Server from "@effect/platform/Http/Server"
|
|
19
|
+
import * as Error from "@effect/platform/Http/ServerError"
|
|
20
|
+
import * as ServerRequest from "@effect/platform/Http/ServerRequest"
|
|
21
|
+
import * as ServerResponse from "@effect/platform/Http/ServerResponse"
|
|
22
|
+
import type * as Path from "@effect/platform/Path"
|
|
23
|
+
import * as Stream from "@effect/stream/Stream"
|
|
24
|
+
import type * as Http from "node:http"
|
|
25
|
+
import type * as Net from "node:net"
|
|
26
|
+
import { Readable } from "node:stream"
|
|
27
|
+
|
|
28
|
+
/** @internal */
|
|
29
|
+
export const make = (
|
|
30
|
+
evaluate: LazyArg<Http.Server>,
|
|
31
|
+
options: Net.ListenOptions
|
|
32
|
+
): Effect.Effect<Scope.Scope, never, Server.Server> =>
|
|
33
|
+
Effect.gen(function*(_) {
|
|
34
|
+
const server = evaluate()
|
|
35
|
+
|
|
36
|
+
const serverFiber = yield* _(
|
|
37
|
+
Effect.addFinalizer(() =>
|
|
38
|
+
Effect.async<never, never, void>((resume) => {
|
|
39
|
+
server.close((error) => {
|
|
40
|
+
if (error) {
|
|
41
|
+
resume(Effect.die(error))
|
|
42
|
+
} else {
|
|
43
|
+
resume(Effect.unit)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
),
|
|
48
|
+
Effect.zipRight(
|
|
49
|
+
Effect.async<never, Error.ServeError, never>((resume) => {
|
|
50
|
+
server.on("error", (error) => {
|
|
51
|
+
resume(Effect.fail(Error.ServeError({ error })))
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
),
|
|
55
|
+
Effect.scoped,
|
|
56
|
+
Effect.forkScoped
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
yield* _(Effect.async<never, never, void>((resume) => {
|
|
60
|
+
server.listen(options, () => {
|
|
61
|
+
resume(Effect.unit)
|
|
62
|
+
})
|
|
63
|
+
}))
|
|
64
|
+
|
|
65
|
+
const address = server.address()!
|
|
66
|
+
|
|
67
|
+
return Server.make({
|
|
68
|
+
address: typeof address === "string" ?
|
|
69
|
+
{
|
|
70
|
+
_tag: "UnixAddress",
|
|
71
|
+
path: address
|
|
72
|
+
} :
|
|
73
|
+
{
|
|
74
|
+
_tag: "TcpAddress",
|
|
75
|
+
hostname: address.address,
|
|
76
|
+
port: address.port
|
|
77
|
+
},
|
|
78
|
+
serve: (httpApp, middleware) => {
|
|
79
|
+
const handledApp = middleware ? middleware(respond(httpApp)) : respond(httpApp)
|
|
80
|
+
return Effect.flatMap(Effect.runtime(), (runtime) => {
|
|
81
|
+
const runFork = Runtime.runFork(runtime as Runtime.Runtime<unknown>)
|
|
82
|
+
function handler(nodeRequest: Http.IncomingMessage, nodeResponse: Http.ServerResponse) {
|
|
83
|
+
runFork(
|
|
84
|
+
Effect.provideService(
|
|
85
|
+
handledApp,
|
|
86
|
+
ServerRequest.ServerRequest,
|
|
87
|
+
new ServerRequestImpl(nodeRequest, nodeResponse)
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
return Effect.all([
|
|
92
|
+
Effect.acquireRelease(
|
|
93
|
+
Effect.sync(() => server.on("request", handler)),
|
|
94
|
+
() => Effect.sync(() => server.off("request", handler))
|
|
95
|
+
),
|
|
96
|
+
Fiber.join(serverFiber)
|
|
97
|
+
], { discard: true, concurrency: "unbounded" }) as Effect.Effect<never, Error.ServeError, never>
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
}).pipe(
|
|
102
|
+
Effect.locally(
|
|
103
|
+
IncomingMessage.maxBodySize,
|
|
104
|
+
Option.some(FileSystem.Size(1024 * 1024 * 10))
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const respond = Middleware.make((httpApp) =>
|
|
109
|
+
Effect.flatMap(ServerRequest.ServerRequest, (request) =>
|
|
110
|
+
Effect.tapErrorCause(
|
|
111
|
+
Effect.tap(
|
|
112
|
+
Effect.flatMap(httpApp, ServerResponse.toNonEffectBody),
|
|
113
|
+
(response) => handleResponse(request, response)
|
|
114
|
+
),
|
|
115
|
+
(_cause) =>
|
|
116
|
+
Effect.sync(() => {
|
|
117
|
+
const nodeResponse = (request as ServerRequestImpl).response
|
|
118
|
+
if (!nodeResponse.headersSent) {
|
|
119
|
+
nodeResponse.writeHead(500)
|
|
120
|
+
}
|
|
121
|
+
if (!nodeResponse.writableEnded) {
|
|
122
|
+
nodeResponse.end()
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
))
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
class ServerRequestImpl extends IncomingMessageImpl<Error.RequestError> implements ServerRequest.ServerRequest {
|
|
129
|
+
readonly [ServerRequest.TypeId]: ServerRequest.TypeId
|
|
130
|
+
|
|
131
|
+
constructor(
|
|
132
|
+
readonly source: Http.IncomingMessage,
|
|
133
|
+
readonly response: Http.ServerResponse,
|
|
134
|
+
readonly url = source.url!,
|
|
135
|
+
private headersOverride?: Headers.Headers
|
|
136
|
+
) {
|
|
137
|
+
super(source, (_) =>
|
|
138
|
+
Error.RequestError({
|
|
139
|
+
request: this,
|
|
140
|
+
reason: "Decode",
|
|
141
|
+
error: _
|
|
142
|
+
}))
|
|
143
|
+
this[ServerRequest.TypeId] = ServerRequest.TypeId
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get originalUrl(): string {
|
|
147
|
+
return this.source.url!
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get method(): Method {
|
|
151
|
+
return this.source.method as Method
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
get headers(): Headers.Headers {
|
|
155
|
+
this.headersOverride ??= Headers.fromInput(this.source.headers as any)
|
|
156
|
+
return this.headersOverride
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private formDataEffect:
|
|
160
|
+
| Effect.Effect<
|
|
161
|
+
Scope.Scope | FileSystem.FileSystem | Path.Path,
|
|
162
|
+
FormData.FormDataError,
|
|
163
|
+
globalThis.FormData
|
|
164
|
+
>
|
|
165
|
+
| undefined
|
|
166
|
+
get formData(): Effect.Effect<
|
|
167
|
+
Scope.Scope | FileSystem.FileSystem | Path.Path,
|
|
168
|
+
FormData.FormDataError,
|
|
169
|
+
globalThis.FormData
|
|
170
|
+
> {
|
|
171
|
+
if (this.formDataEffect) {
|
|
172
|
+
return this.formDataEffect
|
|
173
|
+
}
|
|
174
|
+
this.formDataEffect = Effect.runSync(Effect.cached(
|
|
175
|
+
internalFormData.formData(this.source)
|
|
176
|
+
))
|
|
177
|
+
return this.formDataEffect
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
get formDataStream(): Stream.Stream<never, FormData.FormDataError, FormData.Part> {
|
|
181
|
+
return internalFormData.fromRequest(this.source)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
setUrl(url: string): ServerRequest.ServerRequest {
|
|
185
|
+
return new ServerRequestImpl(
|
|
186
|
+
this.source,
|
|
187
|
+
this.response,
|
|
188
|
+
url,
|
|
189
|
+
this.headersOverride
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
replaceHeaders(headers: Headers.Headers): ServerRequest.ServerRequest {
|
|
194
|
+
return new ServerRequestImpl(
|
|
195
|
+
this.source,
|
|
196
|
+
this.response,
|
|
197
|
+
this.url,
|
|
198
|
+
headers
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
toString(): string {
|
|
203
|
+
return `ServerRequest(${this.method} ${this.url})`
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
toJSON(): unknown {
|
|
207
|
+
return {
|
|
208
|
+
_tag: "ServerRequest",
|
|
209
|
+
method: this.method,
|
|
210
|
+
url: this.url,
|
|
211
|
+
originalUrl: this.originalUrl,
|
|
212
|
+
headers: Object.fromEntries(this.headers)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** @internal */
|
|
218
|
+
export const layer = (
|
|
219
|
+
evaluate: LazyArg<Http.Server>,
|
|
220
|
+
options: Net.ListenOptions
|
|
221
|
+
) => Layer.scoped(Server.Server, make(evaluate, options))
|
|
222
|
+
|
|
223
|
+
/** @internal */
|
|
224
|
+
export const layerConfig = (
|
|
225
|
+
evaluate: LazyArg<Http.Server>,
|
|
226
|
+
options: Config.Config.Wrap<Net.ListenOptions>
|
|
227
|
+
) =>
|
|
228
|
+
Layer.scoped(
|
|
229
|
+
Server.Server,
|
|
230
|
+
Effect.flatMap(
|
|
231
|
+
Effect.config(Config.unwrap(options)),
|
|
232
|
+
(options) => make(evaluate, options)
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
const handleResponse = (
|
|
237
|
+
request: ServerRequest.ServerRequest,
|
|
238
|
+
response: ServerResponse.ServerResponse.NonEffectBody
|
|
239
|
+
) =>
|
|
240
|
+
Effect.suspend((): Effect.Effect<never, Error.ResponseError, void> => {
|
|
241
|
+
const nodeResponse = (request as ServerRequestImpl).response
|
|
242
|
+
switch (response.body._tag) {
|
|
243
|
+
case "Empty": {
|
|
244
|
+
nodeResponse.writeHead(
|
|
245
|
+
response.status,
|
|
246
|
+
response.headers === Headers.empty ? undefined : Object.fromEntries(response.headers)
|
|
247
|
+
)
|
|
248
|
+
nodeResponse.end()
|
|
249
|
+
return Effect.unit
|
|
250
|
+
}
|
|
251
|
+
case "Raw": {
|
|
252
|
+
const headers = response.headers === Headers.empty ? {} : Object.fromEntries(response.headers)
|
|
253
|
+
if (response.body.contentType) {
|
|
254
|
+
headers["content-type"] = response.body.contentType
|
|
255
|
+
}
|
|
256
|
+
if (response.body.contentLength) {
|
|
257
|
+
headers["content-length"] = response.body.contentLength.toString()
|
|
258
|
+
}
|
|
259
|
+
nodeResponse.writeHead(response.status, headers)
|
|
260
|
+
nodeResponse.end(response.body.body)
|
|
261
|
+
return Effect.unit
|
|
262
|
+
}
|
|
263
|
+
case "Uint8Array": {
|
|
264
|
+
const headers = response.headers === Headers.empty ? {} : Object.fromEntries(response.headers)
|
|
265
|
+
headers["content-type"] = response.body.contentType
|
|
266
|
+
headers["content-length"] = response.body.contentLength.toString()
|
|
267
|
+
nodeResponse.writeHead(response.status, headers)
|
|
268
|
+
nodeResponse.end(response.body.body)
|
|
269
|
+
return Effect.unit
|
|
270
|
+
}
|
|
271
|
+
case "FormData": {
|
|
272
|
+
const body = response.body
|
|
273
|
+
return Effect.async<never, Error.ResponseError, void>((resume) => {
|
|
274
|
+
const r = new Response(body.formData)
|
|
275
|
+
const headers = response.headers
|
|
276
|
+
? Object.fromEntries(response.headers)
|
|
277
|
+
: {}
|
|
278
|
+
headers["content-type"] = r.headers.get("content-type")!
|
|
279
|
+
nodeResponse.writeHead(response.status, headers)
|
|
280
|
+
Readable.fromWeb(r.body as any)
|
|
281
|
+
.pipe(nodeResponse)
|
|
282
|
+
.on("error", (error) => {
|
|
283
|
+
resume(Effect.fail(Error.ResponseError({
|
|
284
|
+
request,
|
|
285
|
+
response,
|
|
286
|
+
reason: "Decode",
|
|
287
|
+
error
|
|
288
|
+
})))
|
|
289
|
+
})
|
|
290
|
+
.once("finish", () => {
|
|
291
|
+
resume(Effect.unit)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
case "Stream": {
|
|
296
|
+
const headers = response.headers === Headers.empty ? {} : Object.fromEntries(response.headers)
|
|
297
|
+
headers["content-type"] = response.body.contentType
|
|
298
|
+
if (response.body.contentLength) {
|
|
299
|
+
headers["content-length"] = response.body.contentLength.toString()
|
|
300
|
+
}
|
|
301
|
+
nodeResponse.writeHead(response.status, headers)
|
|
302
|
+
return Stream.run(
|
|
303
|
+
Stream.mapError(
|
|
304
|
+
response.body.stream,
|
|
305
|
+
(error) =>
|
|
306
|
+
Error.ResponseError({
|
|
307
|
+
request,
|
|
308
|
+
response,
|
|
309
|
+
reason: "Decode",
|
|
310
|
+
error
|
|
311
|
+
})
|
|
312
|
+
),
|
|
313
|
+
NodeSink.fromWritable(() => nodeResponse, (error) =>
|
|
314
|
+
Error.ResponseError({
|
|
315
|
+
request,
|
|
316
|
+
response,
|
|
317
|
+
reason: "Decode",
|
|
318
|
+
error
|
|
319
|
+
}))
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
})
|
package/src/internal/stream.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { pipe } from "@effect/data/Function"
|
|
|
3
3
|
import * as Option from "@effect/data/Option"
|
|
4
4
|
import * as Effect from "@effect/io/Effect"
|
|
5
5
|
import type { FromReadableOptions } from "@effect/platform-node/Stream"
|
|
6
|
-
import type {
|
|
6
|
+
import type { SizeInput } from "@effect/platform/FileSystem"
|
|
7
7
|
import * as Stream from "@effect/stream/Stream"
|
|
8
8
|
import type { Readable } from "node:stream"
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@ import type { Readable } from "node:stream"
|
|
|
11
11
|
export const fromReadable = <E, A>(
|
|
12
12
|
evaluate: LazyArg<Readable>,
|
|
13
13
|
onError: (error: unknown) => E,
|
|
14
|
-
{ chunkSize
|
|
14
|
+
{ chunkSize }: FromReadableOptions = {}
|
|
15
15
|
): Stream.Stream<never, E, A> =>
|
|
16
16
|
pipe(
|
|
17
17
|
Effect.acquireRelease(Effect.sync(evaluate), (stream) =>
|
|
@@ -52,57 +52,92 @@ export const fromReadable = <E, A>(
|
|
|
52
52
|
|
|
53
53
|
const readChunk = <A>(
|
|
54
54
|
stream: Readable,
|
|
55
|
-
size:
|
|
55
|
+
size: SizeInput | undefined
|
|
56
56
|
): Effect.Effect<never, Option.Option<never>, A> =>
|
|
57
57
|
pipe(
|
|
58
|
-
Effect.sync(() => (size
|
|
58
|
+
Effect.sync(() => (size ? stream.read(Number(size)) : stream.read()) as A | null),
|
|
59
59
|
Effect.flatMap((_) => (_ ? Effect.succeed(_) : Effect.fail(Option.none())))
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
/** @internal */
|
|
63
63
|
export const toString = <E>(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
78
|
-
stream
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
64
|
+
options: {
|
|
65
|
+
readable: LazyArg<Readable>
|
|
66
|
+
onFailure: (error: unknown) => E
|
|
67
|
+
encoding?: BufferEncoding
|
|
68
|
+
maxBytes?: SizeInput
|
|
69
|
+
}
|
|
70
|
+
): Effect.Effect<never, E, string> => {
|
|
71
|
+
const maxBytesNumber = options.maxBytes ? Number(options.maxBytes) : undefined
|
|
72
|
+
return Effect.acquireUseRelease(
|
|
73
|
+
Effect.sync(() => {
|
|
74
|
+
const stream = options.readable()
|
|
75
|
+
stream.setEncoding(options.encoding ?? "utf8")
|
|
76
|
+
return stream
|
|
77
|
+
}),
|
|
78
|
+
(stream) =>
|
|
79
|
+
Effect.async((resume) => {
|
|
80
|
+
let string = ""
|
|
81
|
+
let bytes = 0
|
|
82
|
+
stream.once("error", (err) => {
|
|
83
|
+
resume(Effect.fail(options.onFailure(err)))
|
|
84
|
+
})
|
|
85
|
+
stream.once("end", () => {
|
|
86
|
+
resume(Effect.succeed(string))
|
|
87
|
+
})
|
|
88
|
+
stream.on("data", (chunk) => {
|
|
89
|
+
string += chunk
|
|
90
|
+
bytes += Buffer.byteLength(chunk)
|
|
91
|
+
if (maxBytesNumber && bytes > maxBytesNumber) {
|
|
92
|
+
resume(Effect.fail(options.onFailure(new Error("maxBytes exceeded"))))
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
}),
|
|
96
|
+
(stream) =>
|
|
97
|
+
Effect.sync(() => {
|
|
98
|
+
stream.removeAllListeners()
|
|
99
|
+
if (!stream.closed) {
|
|
100
|
+
stream.destroy()
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
)
|
|
104
|
+
}
|
|
86
105
|
|
|
87
106
|
/** @internal */
|
|
88
107
|
export const toUint8Array = <E>(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
stream
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
options: {
|
|
109
|
+
readable: LazyArg<Readable>
|
|
110
|
+
onFailure: (error: unknown) => E
|
|
111
|
+
maxBytes?: SizeInput
|
|
112
|
+
}
|
|
113
|
+
): Effect.Effect<never, E, Uint8Array> => {
|
|
114
|
+
const maxBytesNumber = options.maxBytes ? Number(options.maxBytes) : undefined
|
|
115
|
+
return Effect.acquireUseRelease(
|
|
116
|
+
Effect.sync(options.readable),
|
|
117
|
+
(stream) =>
|
|
118
|
+
Effect.async((resume) => {
|
|
119
|
+
let buffer = Buffer.alloc(0)
|
|
120
|
+
let bytes = 0
|
|
121
|
+
stream.once("error", (err) => {
|
|
122
|
+
resume(Effect.fail(options.onFailure(err)))
|
|
123
|
+
})
|
|
124
|
+
stream.once("end", () => {
|
|
125
|
+
resume(Effect.succeed(buffer))
|
|
126
|
+
})
|
|
127
|
+
stream.on("data", (chunk) => {
|
|
128
|
+
buffer = Buffer.concat([buffer, chunk])
|
|
129
|
+
bytes += chunk.length
|
|
130
|
+
if (maxBytesNumber && bytes > maxBytesNumber) {
|
|
131
|
+
resume(Effect.fail(options.onFailure(new Error("maxBytes exceeded"))))
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}),
|
|
135
|
+
(stream) =>
|
|
136
|
+
Effect.sync(() => {
|
|
137
|
+
stream.removeAllListeners()
|
|
138
|
+
if (!stream.closed) {
|
|
139
|
+
stream.destroy()
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
)
|
|
143
|
+
}
|