@effect/platform-node 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/Mime.js.map +1 -1
- package/dist/NodeClusterHttp.d.ts +1 -0
- package/dist/NodeClusterHttp.d.ts.map +1 -1
- package/dist/NodeClusterHttp.js +5 -1
- package/dist/NodeClusterHttp.js.map +1 -1
- package/dist/NodeClusterSocket.d.ts +1 -0
- package/dist/NodeClusterSocket.d.ts.map +1 -1
- package/dist/NodeClusterSocket.js +9 -2
- package/dist/NodeClusterSocket.js.map +1 -1
- package/dist/NodeCrypto.js.map +1 -1
- package/dist/NodeFileSystem.js.map +1 -1
- package/dist/NodeHttpClient.d.ts +18 -18
- package/dist/NodeHttpClient.d.ts.map +1 -1
- package/dist/NodeHttpClient.js +26 -20
- package/dist/NodeHttpClient.js.map +1 -1
- package/dist/NodeHttpIncomingMessage.js.map +1 -1
- package/dist/NodeHttpPlatform.d.ts +8 -4
- package/dist/NodeHttpPlatform.d.ts.map +1 -1
- package/dist/NodeHttpPlatform.js +38 -0
- package/dist/NodeHttpPlatform.js.map +1 -1
- package/dist/NodeHttpServer.d.ts +21 -16
- package/dist/NodeHttpServer.d.ts.map +1 -1
- package/dist/NodeHttpServer.js +23 -20
- package/dist/NodeHttpServer.js.map +1 -1
- package/dist/NodeHttpServerRequest.js.map +1 -1
- package/dist/NodeMultipart.d.ts.map +1 -1
- package/dist/NodeMultipart.js +3 -2
- package/dist/NodeMultipart.js.map +1 -1
- package/dist/NodeMultipartParser.d.ts +97 -0
- package/dist/NodeMultipartParser.d.ts.map +1 -0
- package/dist/NodeMultipartParser.js +108 -0
- package/dist/NodeMultipartParser.js.map +1 -0
- package/dist/NodePath.js.map +1 -1
- package/dist/NodeRedis.js.map +1 -1
- package/dist/NodeRuntime.js.map +1 -1
- package/dist/NodeServices.js.map +1 -1
- package/dist/NodeSocket.js.map +1 -1
- package/dist/NodeStdio.js.map +1 -1
- package/dist/NodeTerminal.js.map +1 -1
- package/dist/NodeWorker.js.map +1 -1
- package/dist/NodeWorkerRunner.d.ts.map +1 -1
- package/dist/NodeWorkerRunner.js +30 -18
- package/dist/NodeWorkerRunner.js.map +1 -1
- package/dist/Undici.d.ts +2 -2
- package/dist/Undici.js +2 -2
- package/dist/Undici.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/package.json +10 -11
- package/src/NodeClusterHttp.ts +4 -1
- package/src/NodeClusterSocket.ts +6 -1
- package/src/NodeHttpClient.ts +27 -20
- package/src/NodeHttpPlatform.ts +49 -0
- package/src/NodeHttpServer.ts +61 -45
- package/src/NodeMultipart.ts +5 -4
- package/src/NodeMultipartParser.ts +184 -0
- package/src/NodeWorkerRunner.ts +39 -23
- package/src/Undici.ts +2 -2
- package/src/index.ts +5 -0
package/src/NodeHttpClient.ts
CHANGED
|
@@ -51,14 +51,14 @@ export {
|
|
|
51
51
|
* Use to access or override the fetch implementation used by the Node
|
|
52
52
|
* fetch-based HTTP client.
|
|
53
53
|
*
|
|
54
|
-
* @category
|
|
54
|
+
* @category services
|
|
55
55
|
* @since 4.0.0
|
|
56
56
|
*/
|
|
57
57
|
Fetch,
|
|
58
58
|
/**
|
|
59
59
|
* Layer that provides the fetch-based HTTP client implementation.
|
|
60
60
|
*
|
|
61
|
-
* @category
|
|
61
|
+
* @category layers
|
|
62
62
|
* @since 4.0.0
|
|
63
63
|
*/
|
|
64
64
|
layer as layerFetch,
|
|
@@ -69,7 +69,7 @@ export {
|
|
|
69
69
|
*
|
|
70
70
|
* Use to provide default fetch request options for Node HTTP requests.
|
|
71
71
|
*
|
|
72
|
-
* @category
|
|
72
|
+
* @category services
|
|
73
73
|
* @since 4.0.0
|
|
74
74
|
*/
|
|
75
75
|
RequestInit
|
|
@@ -83,7 +83,7 @@ export {
|
|
|
83
83
|
* Service tag for the Undici `Dispatcher` used by the Undici-backed HTTP
|
|
84
84
|
* client.
|
|
85
85
|
*
|
|
86
|
-
* @category
|
|
86
|
+
* @category services
|
|
87
87
|
* @since 4.0.0
|
|
88
88
|
*/
|
|
89
89
|
export class Dispatcher extends Context.Service<Dispatcher, Undici.Dispatcher>()(
|
|
@@ -94,10 +94,12 @@ export class Dispatcher extends Context.Service<Dispatcher, Undici.Dispatcher>()
|
|
|
94
94
|
* Acquires a new Undici `Agent` dispatcher and destroys it when the enclosing
|
|
95
95
|
* scope is finalized.
|
|
96
96
|
*
|
|
97
|
-
* @category
|
|
97
|
+
* @category resource management
|
|
98
98
|
* @since 4.0.0
|
|
99
99
|
*/
|
|
100
100
|
export const makeDispatcher: Effect.Effect<Undici.Dispatcher, never, Scope.Scope> = Effect.acquireRelease(
|
|
101
|
+
// oxlint cannot resolve values re-exported through the local Undici facade.
|
|
102
|
+
// oxlint-disable-next-line import/namespace
|
|
101
103
|
Effect.sync(() => new Undici.Agent()),
|
|
102
104
|
(dispatcher) => Effect.promise(() => dispatcher.destroy())
|
|
103
105
|
)
|
|
@@ -105,7 +107,7 @@ export const makeDispatcher: Effect.Effect<Undici.Dispatcher, never, Scope.Scope
|
|
|
105
107
|
/**
|
|
106
108
|
* Provides the `Dispatcher` service using a scoped Undici `Agent`.
|
|
107
109
|
*
|
|
108
|
-
* @category
|
|
110
|
+
* @category layers
|
|
109
111
|
* @since 4.0.0
|
|
110
112
|
*/
|
|
111
113
|
export const layerDispatcher: Layer.Layer<Dispatcher> = Layer.effect(Dispatcher)(makeDispatcher)
|
|
@@ -114,16 +116,18 @@ export const layerDispatcher: Layer.Layer<Dispatcher> = Layer.effect(Dispatcher)
|
|
|
114
116
|
* Provides the `Dispatcher` service from Undici's process-global dispatcher,
|
|
115
117
|
* without creating or owning a new agent.
|
|
116
118
|
*
|
|
117
|
-
* @category
|
|
119
|
+
* @category layers
|
|
118
120
|
* @since 4.0.0
|
|
119
121
|
*/
|
|
122
|
+
// oxlint cannot resolve values re-exported through the local Undici facade.
|
|
123
|
+
// oxlint-disable-next-line import/namespace
|
|
120
124
|
export const dispatcherLayerGlobal: Layer.Layer<Dispatcher> = Layer.sync(Dispatcher)(() => Undici.getGlobalDispatcher())
|
|
121
125
|
|
|
122
126
|
/**
|
|
123
127
|
* Fiber reference containing default Undici request options applied to requests
|
|
124
128
|
* sent by `makeUndici`.
|
|
125
129
|
*
|
|
126
|
-
* @category
|
|
130
|
+
* @category services
|
|
127
131
|
* @since 4.0.0
|
|
128
132
|
*/
|
|
129
133
|
export const UndiciOptions = Context.Reference<Partial<Undici.Dispatcher.RequestOptions>>(
|
|
@@ -136,7 +140,7 @@ export const UndiciOptions = Context.Reference<Partial<Undici.Dispatcher.Request
|
|
|
136
140
|
* `Dispatcher`, converts Effect HTTP bodies to Undici bodies, and maps
|
|
137
141
|
* transport and decode failures to `HttpClientError`.
|
|
138
142
|
*
|
|
139
|
-
* @category
|
|
143
|
+
* @category constructors
|
|
140
144
|
* @since 4.0.0
|
|
141
145
|
*/
|
|
142
146
|
export const makeUndici = Effect.gen(function*() {
|
|
@@ -353,7 +357,7 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse, Pi
|
|
|
353
357
|
* Provides an Undici-backed `HttpClient` using the current `Dispatcher`
|
|
354
358
|
* service.
|
|
355
359
|
*
|
|
356
|
-
* @category
|
|
360
|
+
* @category layers
|
|
357
361
|
* @since 4.0.0
|
|
358
362
|
*/
|
|
359
363
|
export const layerUndiciNoDispatcher: Layer.Layer<
|
|
@@ -366,7 +370,7 @@ export const layerUndiciNoDispatcher: Layer.Layer<
|
|
|
366
370
|
* Provides an Undici-backed `HttpClient` together with a scoped default
|
|
367
371
|
* Undici `Agent` dispatcher.
|
|
368
372
|
*
|
|
369
|
-
* @category
|
|
373
|
+
* @category layers
|
|
370
374
|
* @since 4.0.0
|
|
371
375
|
*/
|
|
372
376
|
export const layerUndici: Layer.Layer<Client.HttpClient> = Layer.provide(layerUndiciNoDispatcher, layerDispatcher)
|
|
@@ -379,7 +383,7 @@ export const layerUndici: Layer.Layer<Client.HttpClient> = Layer.provide(layerUn
|
|
|
379
383
|
* Service tag for the paired Node `http` and `https` agents used by the
|
|
380
384
|
* node:http-backed HTTP client.
|
|
381
385
|
*
|
|
382
|
-
* @category
|
|
386
|
+
* @category services
|
|
383
387
|
* @since 4.0.0
|
|
384
388
|
*/
|
|
385
389
|
export class HttpAgent extends Context.Service<HttpAgent, {
|
|
@@ -391,7 +395,7 @@ export class HttpAgent extends Context.Service<HttpAgent, {
|
|
|
391
395
|
* Acquires Node `http` and `https` agents with the supplied options and
|
|
392
396
|
* destroys both agents when the enclosing scope is finalized.
|
|
393
397
|
*
|
|
394
|
-
* @category
|
|
398
|
+
* @category resource management
|
|
395
399
|
* @since 4.0.0
|
|
396
400
|
*/
|
|
397
401
|
export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<HttpAgent["Service"], never, Scope.Scope> =>
|
|
@@ -411,7 +415,7 @@ export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<HttpAgent
|
|
|
411
415
|
* Provides the `HttpAgent` service using scoped Node `http` and `https`
|
|
412
416
|
* agents configured with the supplied options.
|
|
413
417
|
*
|
|
414
|
-
* @category
|
|
418
|
+
* @category layers
|
|
415
419
|
* @since 4.0.0
|
|
416
420
|
*/
|
|
417
421
|
export const layerAgentOptions: (options?: Https.AgentOptions | undefined) => Layer.Layer<
|
|
@@ -422,7 +426,7 @@ export const layerAgentOptions: (options?: Https.AgentOptions | undefined) => La
|
|
|
422
426
|
* Provides the `HttpAgent` service using default scoped Node `http` and
|
|
423
427
|
* `https` agents.
|
|
424
428
|
*
|
|
425
|
-
* @category
|
|
429
|
+
* @category layers
|
|
426
430
|
* @since 4.0.0
|
|
427
431
|
*/
|
|
428
432
|
export const layerAgent: Layer.Layer<HttpAgent> = layerAgentOptions()
|
|
@@ -432,7 +436,7 @@ export const layerAgent: Layer.Layer<HttpAgent> = layerAgentOptions()
|
|
|
432
436
|
* current `HttpAgent`, streaming request bodies, and wrapping Node responses
|
|
433
437
|
* as `HttpClientResponse` values.
|
|
434
438
|
*
|
|
435
|
-
* @category
|
|
439
|
+
* @category constructors
|
|
436
440
|
* @since 4.0.0
|
|
437
441
|
*/
|
|
438
442
|
export const makeNodeHttp = Effect.gen(function*() {
|
|
@@ -451,8 +455,11 @@ export const makeNodeHttp = Effect.gen(function*() {
|
|
|
451
455
|
headers: request.headers,
|
|
452
456
|
signal
|
|
453
457
|
})
|
|
454
|
-
return Effect.
|
|
455
|
-
|
|
458
|
+
return Effect.raceFirst(
|
|
459
|
+
waitForResponse(nodeRequest, request),
|
|
460
|
+
sendBody(nodeRequest, request, request.body).pipe(Effect.andThen(Effect.never))
|
|
461
|
+
).pipe(
|
|
462
|
+
Effect.onError(() => Effect.sync(() => nodeRequest.destroy())),
|
|
456
463
|
Effect.map((_) => new NodeHttpResponse(request, _))
|
|
457
464
|
)
|
|
458
465
|
})
|
|
@@ -645,7 +652,7 @@ class NodeHttpResponse extends NodeHttpIncomingMessage<Error.HttpClientError> im
|
|
|
645
652
|
* Provides a node:http-backed `HttpClient` using the current `HttpAgent`
|
|
646
653
|
* service.
|
|
647
654
|
*
|
|
648
|
-
* @category
|
|
655
|
+
* @category layers
|
|
649
656
|
* @since 4.0.0
|
|
650
657
|
*/
|
|
651
658
|
export const layerNodeHttpNoAgent: Layer.Layer<
|
|
@@ -658,7 +665,7 @@ export const layerNodeHttpNoAgent: Layer.Layer<
|
|
|
658
665
|
* Provides a node:http-backed `HttpClient` together with default scoped Node
|
|
659
666
|
* `http` and `https` agents.
|
|
660
667
|
*
|
|
661
|
-
* @category
|
|
668
|
+
* @category layers
|
|
662
669
|
* @since 4.0.0
|
|
663
670
|
*/
|
|
664
671
|
export const layerNodeHttp: Layer.Layer<Client.HttpClient> = Layer.provide(layerNodeHttpNoAgent, layerAgent)
|
package/src/NodeHttpPlatform.ts
CHANGED
|
@@ -8,16 +8,63 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @since 4.0.0
|
|
10
10
|
*/
|
|
11
|
+
import * as NodeHttpCompression from "@effect/platform-node-shared/NodeHttpCompression"
|
|
12
|
+
import * as Effect from "effect/Effect"
|
|
11
13
|
import { pipe } from "effect/Function"
|
|
12
14
|
import * as Layer from "effect/Layer"
|
|
13
15
|
import * as EtagImpl from "effect/unstable/http/Etag"
|
|
14
16
|
import * as Headers from "effect/unstable/http/Headers"
|
|
17
|
+
import * as HttpBody from "effect/unstable/http/HttpBody"
|
|
15
18
|
import * as Platform from "effect/unstable/http/HttpPlatform"
|
|
16
19
|
import * as ServerResponse from "effect/unstable/http/HttpServerResponse"
|
|
17
20
|
import * as Fs from "node:fs"
|
|
18
21
|
import { Readable } from "node:stream"
|
|
19
22
|
import Mime from "./Mime.ts"
|
|
20
23
|
import * as NodeFileSystem from "./NodeFileSystem.ts"
|
|
24
|
+
import * as NodeStream from "./NodeStream.ts"
|
|
25
|
+
|
|
26
|
+
// replaces the response body while keeping every other field, dropping the
|
|
27
|
+
// now-stale Content-Length header
|
|
28
|
+
const compressedBody = (
|
|
29
|
+
response: ServerResponse.HttpServerResponse,
|
|
30
|
+
body: HttpBody.HttpBody
|
|
31
|
+
): ServerResponse.HttpServerResponse =>
|
|
32
|
+
ServerResponse.removeHeader(ServerResponse.setBody(response, body), "content-length")
|
|
33
|
+
|
|
34
|
+
const compression = NodeHttpCompression.make({
|
|
35
|
+
algorithms: NodeHttpCompression.algorithms,
|
|
36
|
+
compressResponse(response, algorithm, options) {
|
|
37
|
+
const body = response.body
|
|
38
|
+
switch (body._tag) {
|
|
39
|
+
case "Stream": {
|
|
40
|
+
return Effect.succeed(compressedBody(
|
|
41
|
+
response,
|
|
42
|
+
HttpBody.stream(
|
|
43
|
+
NodeStream.pipeThroughDuplex(body.stream, {
|
|
44
|
+
evaluate: () => NodeHttpCompression.compressTransform(algorithm, options)
|
|
45
|
+
}),
|
|
46
|
+
body.contentType
|
|
47
|
+
)
|
|
48
|
+
))
|
|
49
|
+
}
|
|
50
|
+
case "Raw": {
|
|
51
|
+
const readable = body.body instanceof Readable
|
|
52
|
+
? body.body
|
|
53
|
+
: Readable.fromWeb(new Response(body.body as BodyInit).body as any)
|
|
54
|
+
const transform = NodeHttpCompression.compressTransform(algorithm, options)
|
|
55
|
+
readable.on("error", (cause) => transform.destroy(cause))
|
|
56
|
+
transform.on("error", (cause) => readable.destroy(cause))
|
|
57
|
+
transform.on("close", () => readable.destroy())
|
|
58
|
+
return Effect.succeed(
|
|
59
|
+
compressedBody(response, HttpBody.raw(readable.pipe(transform), { contentType: body.contentType }))
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
default: {
|
|
63
|
+
return Effect.succeed(response)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
})
|
|
21
68
|
|
|
22
69
|
/**
|
|
23
70
|
* Creates the Node `HttpPlatform`, serving file responses from Node readable
|
|
@@ -27,6 +74,8 @@ import * as NodeFileSystem from "./NodeFileSystem.ts"
|
|
|
27
74
|
* @since 4.0.0
|
|
28
75
|
*/
|
|
29
76
|
export const make = Platform.make({
|
|
77
|
+
platform: "node",
|
|
78
|
+
compression,
|
|
30
79
|
fileResponse(path, status, statusText, headers, start, end, contentLength) {
|
|
31
80
|
const stream = contentLength === 0
|
|
32
81
|
? Readable.from([])
|
package/src/NodeHttpServer.ts
CHANGED
|
@@ -62,6 +62,26 @@ import * as NodeMultipart from "./NodeMultipart.ts"
|
|
|
62
62
|
import * as NodeServices from "./NodeServices.ts"
|
|
63
63
|
import { NodeWS } from "./NodeSocket.ts"
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Options accepted by the Node `HttpServer` constructors and layers.
|
|
67
|
+
*
|
|
68
|
+
* @category options
|
|
69
|
+
* @since 4.0.0
|
|
70
|
+
*/
|
|
71
|
+
export interface Options extends Net.ListenOptions {
|
|
72
|
+
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
73
|
+
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
74
|
+
/**
|
|
75
|
+
* Options forwarded to the underlying `ws` `WebSocketServer`, minus the
|
|
76
|
+
* wiring options the server manages itself. Use this to enable
|
|
77
|
+
* `permessage-deflate` compression or tune payload limits, e.g.
|
|
78
|
+
* `websocket: { perMessageDeflate: true }`.
|
|
79
|
+
*/
|
|
80
|
+
readonly websocket?:
|
|
81
|
+
| Omit<NodeWS.ServerOptions, "noServer" | "server" | "host" | "port" | "path">
|
|
82
|
+
| undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
65
85
|
/**
|
|
66
86
|
* Creates a scoped `HttpServer` from a Node `http.Server`, starts listening
|
|
67
87
|
* with the supplied options, registers request and upgrade handling, and closes
|
|
@@ -72,10 +92,7 @@ import { NodeWS } from "./NodeSocket.ts"
|
|
|
72
92
|
*/
|
|
73
93
|
export const make = Effect.fnUntraced(function*(
|
|
74
94
|
evaluate: LazyArg<Http.Server>,
|
|
75
|
-
options:
|
|
76
|
-
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
77
|
-
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
78
|
-
}
|
|
95
|
+
options: Options
|
|
79
96
|
) {
|
|
80
97
|
const scope = yield* Effect.scope
|
|
81
98
|
const server = evaluate()
|
|
@@ -116,7 +133,7 @@ export const make = Effect.fnUntraced(function*(
|
|
|
116
133
|
const address = server.address()!
|
|
117
134
|
|
|
118
135
|
const wss = yield* Effect.acquireRelease(
|
|
119
|
-
Effect.sync(() => new NodeWS.WebSocketServer({ noServer: true })),
|
|
136
|
+
Effect.sync(() => new NodeWS.WebSocketServer({ ...options.websocket, noServer: true })),
|
|
120
137
|
(wss) =>
|
|
121
138
|
Effect.callback<void>((resume) => {
|
|
122
139
|
wss.close(() => resume(Effect.void))
|
|
@@ -189,9 +206,8 @@ export const makeHandler = <
|
|
|
189
206
|
nodeRequest: Http.IncomingMessage,
|
|
190
207
|
nodeResponse: Http.ServerResponse
|
|
191
208
|
) {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe<any>(map))(handled), options.scope)
|
|
209
|
+
const context = Context.add(services, HttpServerRequest, new ServerRequestImpl(nodeRequest, nodeResponse))
|
|
210
|
+
const fiber = Fiber.runIn(Effect.runForkWith(context as Context.Context<any>)(handled), options.scope)
|
|
195
211
|
nodeResponse.on("close", () => {
|
|
196
212
|
if (!nodeResponse.writableEnded) {
|
|
197
213
|
fiber.interruptUnsafe(parent.id, ClientAbort.annotation)
|
|
@@ -256,9 +272,13 @@ export const makeUpgradeHandler = <
|
|
|
256
272
|
(ws) => Effect.sync(() => ws.close())
|
|
257
273
|
)
|
|
258
274
|
))
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
275
|
+
const context = Context.add(
|
|
276
|
+
services,
|
|
277
|
+
HttpServerRequest,
|
|
278
|
+
new ServerRequestImpl(nodeRequest, nodeResponse, upgradeEffect)
|
|
279
|
+
)
|
|
280
|
+
const fiber = Fiber.runIn(Effect.runForkWith(context as Context.Context<any>)(handledApp), options.scope)
|
|
281
|
+
socket.on("error", () => {})
|
|
262
282
|
socket.on("close", () => {
|
|
263
283
|
if (!socket.writableEnded) {
|
|
264
284
|
fiber.interruptUnsafe(parent.id, ClientAbort.annotation)
|
|
@@ -330,8 +350,9 @@ class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> impleme
|
|
|
330
350
|
return this.source.url!
|
|
331
351
|
}
|
|
332
352
|
|
|
353
|
+
private cachedMethod: HttpMethod | undefined
|
|
333
354
|
get method(): HttpMethod {
|
|
334
|
-
return this.source.method!.toUpperCase() as HttpMethod
|
|
355
|
+
return this.cachedMethod ??= this.source.method!.toUpperCase() as HttpMethod
|
|
335
356
|
}
|
|
336
357
|
|
|
337
358
|
override get headers(): Headers.Headers {
|
|
@@ -397,10 +418,7 @@ class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> impleme
|
|
|
397
418
|
*/
|
|
398
419
|
export const layerServer: (
|
|
399
420
|
evaluate: LazyArg<Http.Server<typeof Http.IncomingMessage, typeof Http.ServerResponse>>,
|
|
400
|
-
options:
|
|
401
|
-
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
402
|
-
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
403
|
-
}
|
|
421
|
+
options: Options
|
|
404
422
|
) => Layer.Layer<HttpServer.HttpServer, ServeError> = flow(make, Layer.effect(HttpServer.HttpServer))
|
|
405
423
|
|
|
406
424
|
/**
|
|
@@ -427,10 +445,7 @@ export const layerHttpServices: Layer.Layer<
|
|
|
427
445
|
*/
|
|
428
446
|
export const layer = (
|
|
429
447
|
evaluate: LazyArg<Http.Server>,
|
|
430
|
-
options:
|
|
431
|
-
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
432
|
-
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
433
|
-
}
|
|
448
|
+
options: Options
|
|
434
449
|
): Layer.Layer<
|
|
435
450
|
HttpServer.HttpServer | NodeServices.NodeServices | HttpPlatform.HttpPlatform | Etag.Generator,
|
|
436
451
|
ServeError
|
|
@@ -450,12 +465,7 @@ export const layer = (
|
|
|
450
465
|
*/
|
|
451
466
|
export const layerConfig = (
|
|
452
467
|
evaluate: LazyArg<Http.Server>,
|
|
453
|
-
options: Config.Wrap<
|
|
454
|
-
Net.ListenOptions & {
|
|
455
|
-
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
456
|
-
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
457
|
-
}
|
|
458
|
-
>
|
|
468
|
+
options: Config.Wrap<Options>
|
|
459
469
|
): Layer.Layer<
|
|
460
470
|
HttpServer.HttpServer | NodeServices.NodeServices | HttpPlatform.HttpPlatform | Etag.Generator,
|
|
461
471
|
ServeError | Config.ConfigError
|
|
@@ -517,14 +527,20 @@ const handleResponse = (
|
|
|
517
527
|
|
|
518
528
|
if (request.method === "HEAD") {
|
|
519
529
|
nodeResponse.writeHead(response.status, headers)
|
|
520
|
-
return Effect.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
530
|
+
return Effect.andThen(
|
|
531
|
+
cancelResponseBody(response.body),
|
|
532
|
+
Effect.callback<void>((resume) => {
|
|
533
|
+
let completed = false
|
|
534
|
+
const done = () => {
|
|
535
|
+
if (completed) return
|
|
536
|
+
completed = true
|
|
537
|
+
nodeResponse.off("close", done)
|
|
538
|
+
resume(Effect.void)
|
|
539
|
+
}
|
|
540
|
+
nodeResponse.once("close", done)
|
|
541
|
+
nodeResponse.end(done)
|
|
542
|
+
})
|
|
543
|
+
)
|
|
528
544
|
}
|
|
529
545
|
const body = response.body
|
|
530
546
|
switch (body._tag) {
|
|
@@ -608,17 +624,9 @@ const handleResponse = (
|
|
|
608
624
|
return body.stream.pipe(
|
|
609
625
|
Stream.orDie,
|
|
610
626
|
Stream.runForEachArray((array) => {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (!written && !needDrain) {
|
|
615
|
-
needDrain = true
|
|
616
|
-
drainLatch.closeUnsafe()
|
|
617
|
-
} else if (written && needDrain) {
|
|
618
|
-
needDrain = false
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
if (!needDrain) return Effect.void
|
|
627
|
+
const chunk = array.length > 1 ? Buffer.concat(array) : array[0]
|
|
628
|
+
if (nodeResponse.write(chunk)) return Effect.void
|
|
629
|
+
drainLatch.closeUnsafe()
|
|
622
630
|
return drainLatch.await
|
|
623
631
|
}),
|
|
624
632
|
Effect.interruptible,
|
|
@@ -631,6 +639,14 @@ const handleResponse = (
|
|
|
631
639
|
}
|
|
632
640
|
}
|
|
633
641
|
|
|
642
|
+
const cancelResponseBody = (body: HttpServerResponse["body"]): Effect.Effect<void> => {
|
|
643
|
+
const stream = body._tag === "Raw" ? body.body : undefined
|
|
644
|
+
if (stream instanceof Readable) {
|
|
645
|
+
return Effect.sync(() => stream.destroy())
|
|
646
|
+
}
|
|
647
|
+
return Effect.void
|
|
648
|
+
}
|
|
649
|
+
|
|
634
650
|
const handleCause = (
|
|
635
651
|
nodeResponse: Http.ServerResponse,
|
|
636
652
|
originalResponse: HttpServerResponse
|
package/src/NodeMultipart.ts
CHANGED
|
@@ -17,11 +17,12 @@ import type * as Path from "effect/Path"
|
|
|
17
17
|
import type * as Scope from "effect/Scope"
|
|
18
18
|
import * as Stream from "effect/Stream"
|
|
19
19
|
import * as Multipart from "effect/unstable/http/Multipart"
|
|
20
|
-
import * as
|
|
20
|
+
import * as MultipartParser from "effect/unstable/http/MultipartParser"
|
|
21
21
|
import * as NFS from "node:fs"
|
|
22
22
|
import type { IncomingHttpHeaders } from "node:http"
|
|
23
23
|
import type { Readable } from "node:stream"
|
|
24
24
|
import * as NodeStreamP from "node:stream/promises"
|
|
25
|
+
import * as MP from "./NodeMultipartParser.ts"
|
|
25
26
|
import * as NodeStream from "./NodeStream.ts"
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -103,13 +104,13 @@ class FieldImpl extends PartBase implements Multipart.Field {
|
|
|
103
104
|
readonly value: string
|
|
104
105
|
|
|
105
106
|
constructor(
|
|
106
|
-
info:
|
|
107
|
+
info: MultipartParser.PartInfo,
|
|
107
108
|
value: Uint8Array
|
|
108
109
|
) {
|
|
109
110
|
super()
|
|
110
111
|
this.key = info.name
|
|
111
112
|
this.contentType = info.contentType
|
|
112
|
-
this.value =
|
|
113
|
+
this.value = MultipartParser.decodeField(info, value)
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
toJSON(): unknown {
|
|
@@ -158,7 +159,7 @@ class FileImpl extends PartBase implements Multipart.File {
|
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
function convertError(cause:
|
|
162
|
+
function convertError(cause: MultipartParser.MultipartError): Multipart.MultipartError {
|
|
162
163
|
switch (cause._tag) {
|
|
163
164
|
case "ReachedLimit": {
|
|
164
165
|
switch (cause.limit) {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js streams adapter for the low-level multipart parser.
|
|
3
|
+
*
|
|
4
|
+
* @since 4.0.0
|
|
5
|
+
*/
|
|
6
|
+
// oxlint-disable typescript/no-unsafe-declaration-merging
|
|
7
|
+
/// <reference types="node" />
|
|
8
|
+
import type { BaseConfig, MultipartError, Parser, PartInfo } from "effect/unstable/http/MultipartParser"
|
|
9
|
+
import { make as makeParser } from "effect/unstable/http/MultipartParser"
|
|
10
|
+
import type { IncomingHttpHeaders } from "node:http"
|
|
11
|
+
import { Duplex, Readable } from "node:stream"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A part emitted by the Node.js multipart parser.
|
|
15
|
+
*
|
|
16
|
+
* @category models
|
|
17
|
+
* @since 4.0.0
|
|
18
|
+
*/
|
|
19
|
+
export type Part = Field | FileStream
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A parsed multipart field.
|
|
23
|
+
*
|
|
24
|
+
* @category models
|
|
25
|
+
* @since 4.0.0
|
|
26
|
+
*/
|
|
27
|
+
export interface Field {
|
|
28
|
+
readonly _tag: "Field"
|
|
29
|
+
readonly info: PartInfo
|
|
30
|
+
readonly value: Uint8Array
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A Node.js duplex stream that emits parsed multipart parts.
|
|
35
|
+
*
|
|
36
|
+
* @category models
|
|
37
|
+
* @since 4.0.0
|
|
38
|
+
*/
|
|
39
|
+
export interface MultipartStream extends Duplex {
|
|
40
|
+
[Symbol.asyncIterator](): NodeJS.AsyncIterator<Part>
|
|
41
|
+
|
|
42
|
+
on(event: "field", listener: (field: Field) => void): this
|
|
43
|
+
on(event: "file", listener: (file: FileStream) => void): this
|
|
44
|
+
on(event: "close", listener: () => void): this
|
|
45
|
+
on(event: "data", listener: (part: Part) => void): this
|
|
46
|
+
on(event: "drain", listener: () => void): this
|
|
47
|
+
on(event: "end", listener: () => void): this
|
|
48
|
+
on(event: "error", listener: (err: MultipartError) => void): this
|
|
49
|
+
on(event: "finish", listener: () => void): this
|
|
50
|
+
on(event: "pause", listener: () => void): this
|
|
51
|
+
on(event: "pipe", listener: (src: Readable) => void): this
|
|
52
|
+
on(event: "readable", listener: () => void): this
|
|
53
|
+
on(event: "resume", listener: () => void): this
|
|
54
|
+
on(event: "unpipe", listener: (src: Readable) => void): this
|
|
55
|
+
on(event: string | symbol, listener: (...args: Array<any>) => void): this
|
|
56
|
+
|
|
57
|
+
read(size?: number): Part | null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Configuration for the Node.js multipart parser.
|
|
62
|
+
*
|
|
63
|
+
* @category models
|
|
64
|
+
* @since 4.0.0
|
|
65
|
+
*/
|
|
66
|
+
export type NodeConfig = Omit<BaseConfig, "headers"> & {
|
|
67
|
+
readonly headers: IncomingHttpHeaders
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A Node.js duplex stream that parses multipart input.
|
|
72
|
+
*
|
|
73
|
+
* @category models
|
|
74
|
+
* @since 4.0.0
|
|
75
|
+
*/
|
|
76
|
+
export class MultipartStream extends Duplex {
|
|
77
|
+
private _parser: Parser
|
|
78
|
+
_canWrite = true
|
|
79
|
+
private _writeCallback: (() => void) | undefined
|
|
80
|
+
|
|
81
|
+
constructor(config: NodeConfig) {
|
|
82
|
+
super({ readableObjectMode: true })
|
|
83
|
+
let currentError: MultipartError | undefined
|
|
84
|
+
let currentFile: FileStream | undefined
|
|
85
|
+
this._parser = makeParser({
|
|
86
|
+
...(config as any),
|
|
87
|
+
onField: (info, value) => {
|
|
88
|
+
if (currentError !== undefined) return
|
|
89
|
+
const field: Field = { _tag: "Field", info, value }
|
|
90
|
+
this.push(field)
|
|
91
|
+
this.emit("field", field)
|
|
92
|
+
},
|
|
93
|
+
onFile: (info) => {
|
|
94
|
+
if (currentError !== undefined) return (_) => {}
|
|
95
|
+
const file = new FileStream(info, this)
|
|
96
|
+
currentFile = file
|
|
97
|
+
this.push(file)
|
|
98
|
+
this.emit("file", file)
|
|
99
|
+
return (chunk) => {
|
|
100
|
+
if (currentError !== undefined) return
|
|
101
|
+
this._canWrite = file.push(chunk)
|
|
102
|
+
if (chunk === null && !this._canWrite) {
|
|
103
|
+
currentFile = undefined
|
|
104
|
+
this._resume()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
onError: (error) => {
|
|
109
|
+
this.emit("error", error)
|
|
110
|
+
currentFile?.emit("error", error)
|
|
111
|
+
currentError = error
|
|
112
|
+
},
|
|
113
|
+
onDone: () => {
|
|
114
|
+
this.push(null)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_resume() {
|
|
120
|
+
this._canWrite = true
|
|
121
|
+
if (this._writeCallback !== undefined) {
|
|
122
|
+
const callback = this._writeCallback
|
|
123
|
+
this._writeCallback = undefined
|
|
124
|
+
callback()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override _read(_size: number) {}
|
|
129
|
+
|
|
130
|
+
override _write(
|
|
131
|
+
chunk: any,
|
|
132
|
+
encoding: BufferEncoding,
|
|
133
|
+
callback: (error?: Error | null | undefined) => void
|
|
134
|
+
): void {
|
|
135
|
+
this._parser.write(
|
|
136
|
+
chunk instanceof Uint8Array ? chunk : Buffer.from(chunk, encoding)
|
|
137
|
+
)
|
|
138
|
+
if (this._canWrite) {
|
|
139
|
+
callback()
|
|
140
|
+
} else {
|
|
141
|
+
this._writeCallback = callback
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
override _final(callback: (error?: Error | null | undefined) => void): void {
|
|
146
|
+
this._parser.end()
|
|
147
|
+
callback()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Creates a Node.js multipart parser stream.
|
|
153
|
+
*
|
|
154
|
+
* @category constructors
|
|
155
|
+
* @since 4.0.0
|
|
156
|
+
*/
|
|
157
|
+
export const make = (config: NodeConfig): MultipartStream => new MultipartStream(config)
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A readable stream containing a parsed multipart file.
|
|
161
|
+
*
|
|
162
|
+
* @category models
|
|
163
|
+
* @since 4.0.0
|
|
164
|
+
*/
|
|
165
|
+
export class FileStream extends Readable {
|
|
166
|
+
readonly _tag = "File"
|
|
167
|
+
readonly filename: string | undefined
|
|
168
|
+
readonly info: PartInfo
|
|
169
|
+
private _parent: MultipartStream
|
|
170
|
+
constructor(
|
|
171
|
+
info: PartInfo,
|
|
172
|
+
parent: MultipartStream
|
|
173
|
+
) {
|
|
174
|
+
super()
|
|
175
|
+
this.info = info
|
|
176
|
+
this._parent = parent
|
|
177
|
+
this.filename = info.filename
|
|
178
|
+
}
|
|
179
|
+
override _read(_size: number) {
|
|
180
|
+
if (this._parent._canWrite === false) {
|
|
181
|
+
this._parent._resume()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|