@effect/platform-node 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/Mime.d.ts +9 -3
- package/dist/Mime.d.ts.map +1 -1
- package/dist/Mime.js +9 -3
- package/dist/Mime.js.map +1 -1
- package/dist/NodeChildProcessSpawner.d.ts +1 -1
- package/dist/NodeChildProcessSpawner.js +1 -1
- package/dist/NodeClusterHttp.d.ts +49 -7
- package/dist/NodeClusterHttp.d.ts.map +1 -1
- package/dist/NodeClusterHttp.js +20 -10
- package/dist/NodeClusterHttp.js.map +1 -1
- package/dist/NodeClusterSocket.d.ts +57 -11
- package/dist/NodeClusterSocket.d.ts.map +1 -1
- package/dist/NodeClusterSocket.js +57 -11
- package/dist/NodeClusterSocket.js.map +1 -1
- package/dist/NodeCrypto.d.ts +10 -0
- package/dist/NodeCrypto.d.ts.map +1 -0
- package/dist/NodeCrypto.js +22 -0
- package/dist/NodeCrypto.js.map +1 -0
- package/dist/NodeFileSystem.d.ts +4 -2
- package/dist/NodeFileSystem.d.ts.map +1 -1
- package/dist/NodeFileSystem.js +30 -3
- package/dist/NodeFileSystem.js.map +1 -1
- package/dist/NodeHttpClient.d.ts +111 -24
- package/dist/NodeHttpClient.d.ts.map +1 -1
- package/dist/NodeHttpClient.js +133 -28
- package/dist/NodeHttpClient.js.map +1 -1
- package/dist/NodeHttpIncomingMessage.d.ts +43 -9
- package/dist/NodeHttpIncomingMessage.d.ts.map +1 -1
- package/dist/NodeHttpIncomingMessage.js +47 -8
- package/dist/NodeHttpIncomingMessage.js.map +1 -1
- package/dist/NodeHttpPlatform.d.ts +10 -4
- package/dist/NodeHttpPlatform.d.ts.map +1 -1
- package/dist/NodeHttpPlatform.js +34 -7
- package/dist/NodeHttpPlatform.js.map +1 -1
- package/dist/NodeHttpServer.d.ts +56 -16
- package/dist/NodeHttpServer.d.ts.map +1 -1
- package/dist/NodeHttpServer.js +117 -49
- package/dist/NodeHttpServer.js.map +1 -1
- package/dist/NodeHttpServerRequest.d.ts +29 -3
- package/dist/NodeHttpServerRequest.d.ts.map +1 -1
- package/dist/NodeHttpServerRequest.js +9 -2
- package/dist/NodeHttpServerRequest.js.map +1 -1
- package/dist/NodeMultipart.d.ts +47 -4
- package/dist/NodeMultipart.d.ts.map +1 -1
- package/dist/NodeMultipart.js +47 -4
- package/dist/NodeMultipart.js.map +1 -1
- package/dist/NodePath.d.ts +15 -6
- package/dist/NodePath.d.ts.map +1 -1
- package/dist/NodePath.js +43 -7
- package/dist/NodePath.js.map +1 -1
- package/dist/NodeRedis.d.ts +56 -9
- package/dist/NodeRedis.d.ts.map +1 -1
- package/dist/NodeRedis.js +59 -12
- package/dist/NodeRedis.js.map +1 -1
- package/dist/NodeRuntime.d.ts +27 -36
- package/dist/NodeRuntime.d.ts.map +1 -1
- package/dist/NodeRuntime.js +38 -13
- package/dist/NodeRuntime.js.map +1 -1
- package/dist/NodeServices.d.ts +44 -5
- package/dist/NodeServices.d.ts.map +1 -1
- package/dist/NodeServices.js +7 -3
- package/dist/NodeServices.js.map +1 -1
- package/dist/NodeSink.d.ts +2 -2
- package/dist/NodeSink.js +2 -2
- package/dist/NodeSocket.d.ts +18 -3
- package/dist/NodeSocket.d.ts.map +1 -1
- package/dist/NodeSocket.js +51 -4
- package/dist/NodeSocket.js.map +1 -1
- package/dist/NodeSocketServer.d.ts +2 -2
- package/dist/NodeSocketServer.js +2 -2
- package/dist/NodeStdio.d.ts +5 -2
- package/dist/NodeStdio.d.ts.map +1 -1
- package/dist/NodeStdio.js +30 -3
- package/dist/NodeStdio.js.map +1 -1
- package/dist/NodeStream.d.ts +2 -2
- package/dist/NodeStream.js +2 -2
- package/dist/NodeTerminal.d.ts +8 -2
- package/dist/NodeTerminal.d.ts.map +1 -1
- package/dist/NodeTerminal.js +29 -3
- package/dist/NodeTerminal.js.map +1 -1
- package/dist/NodeWorker.d.ts +9 -2
- package/dist/NodeWorker.d.ts.map +1 -1
- package/dist/NodeWorker.js +45 -6
- package/dist/NodeWorker.js.map +1 -1
- package/dist/NodeWorkerRunner.d.ts +5 -1
- package/dist/NodeWorkerRunner.d.ts.map +1 -1
- package/dist/NodeWorkerRunner.js +47 -5
- package/dist/NodeWorkerRunner.js.map +1 -1
- package/dist/Undici.d.ts +16 -3
- package/dist/Undici.d.ts.map +1 -1
- package/dist/Undici.js +16 -3
- package/dist/Undici.js.map +1 -1
- package/dist/index.d.ts +28 -26
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -26
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/Mime.ts +10 -3
- package/src/NodeChildProcessSpawner.ts +1 -1
- package/src/NodeClusterHttp.ts +54 -11
- package/src/NodeClusterSocket.ts +57 -11
- package/src/NodeCrypto.ts +24 -0
- package/src/NodeFileSystem.ts +30 -3
- package/src/NodeHttpClient.ts +141 -33
- package/src/NodeHttpIncomingMessage.ts +55 -12
- package/src/NodeHttpPlatform.ts +35 -6
- package/src/NodeHttpServer.ts +139 -53
- package/src/NodeHttpServerRequest.ts +29 -3
- package/src/NodeMultipart.ts +47 -4
- package/src/NodePath.ts +43 -7
- package/src/NodeRedis.ts +61 -14
- package/src/NodeRuntime.ts +56 -37
- package/src/NodeServices.ts +46 -5
- package/src/NodeSink.ts +2 -2
- package/src/NodeSocket.ts +56 -4
- package/src/NodeSocketServer.ts +2 -2
- package/src/NodeStdio.ts +30 -3
- package/src/NodeStream.ts +2 -2
- package/src/NodeTerminal.ts +29 -3
- package/src/NodeWorker.ts +45 -6
- package/src/NodeWorkerRunner.ts +47 -5
- package/src/Undici.ts +16 -3
- package/src/index.ts +29 -26
package/src/NodeHttpServer.ts
CHANGED
|
@@ -1,18 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js implementation of the Effect `HttpServer`.
|
|
3
|
+
*
|
|
4
|
+
* This module adapts a supplied Node `http.Server` into Effect's
|
|
5
|
+
* platform-independent HTTP server service. It starts the server with Node
|
|
6
|
+
* `listen` options, converts `request` events into `HttpServerRequest` values,
|
|
7
|
+
* writes `HttpServerResponse` bodies through Node's `ServerResponse`, and
|
|
8
|
+
* handles `upgrade` events by exposing the upgraded socket through
|
|
9
|
+
* `HttpServerRequest.upgrade`.
|
|
10
|
+
*
|
|
11
|
+
* Common use cases include serving an Effect HTTP application with {@link layer}
|
|
12
|
+
* or {@link layerConfig}, embedding request or upgrade handlers into an
|
|
13
|
+
* existing Node server with {@link makeHandler} and {@link makeUpgradeHandler},
|
|
14
|
+
* and using {@link layerTest} for integration tests that need an ephemeral
|
|
15
|
+
* listening port and a client pointed at it.
|
|
16
|
+
*
|
|
17
|
+
* Listen options are passed directly to Node, so host, port, backlog, and Unix
|
|
18
|
+
* socket path behavior follow `node:http`. The server begins listening when the
|
|
19
|
+
* `HttpServer` is acquired, and handlers are installed when `serve` is run.
|
|
20
|
+
* Request fibers are interrupted with `ClientAbort` when the client disconnects
|
|
21
|
+
* before a response finishes. WebSocket support only applies to Node `upgrade`
|
|
22
|
+
* requests, and ordinary HTTP requests fail if their application attempts to use
|
|
23
|
+
* `HttpServerRequest.upgrade`.
|
|
24
|
+
*
|
|
25
|
+
* Scope ownership is important: the server is closed when the acquiring scope
|
|
26
|
+
* finalizes, while each `serve` call installs its own request and upgrade
|
|
27
|
+
* listeners and removes them on finalization. Unless preemptive shutdown is
|
|
28
|
+
* disabled, finalizing a serve scope also starts a graceful server close, using
|
|
29
|
+
* the configured timeout or the default timeout.
|
|
30
|
+
*
|
|
31
|
+
* @since 4.0.0
|
|
3
32
|
*/
|
|
4
33
|
import * as Cause from "effect/Cause"
|
|
5
34
|
import * as Config from "effect/Config"
|
|
35
|
+
import * as Context from "effect/Context"
|
|
36
|
+
import * as Duration from "effect/Duration"
|
|
6
37
|
import * as Effect from "effect/Effect"
|
|
7
38
|
import * as Fiber from "effect/Fiber"
|
|
8
39
|
import type * as FileSystem from "effect/FileSystem"
|
|
9
40
|
import { flow, type LazyArg } from "effect/Function"
|
|
10
41
|
import * as Latch from "effect/Latch"
|
|
11
42
|
import * as Layer from "effect/Layer"
|
|
43
|
+
import type * as Option from "effect/Option"
|
|
12
44
|
import type * as Path from "effect/Path"
|
|
13
45
|
import type * as Record from "effect/Record"
|
|
14
46
|
import * as Scope from "effect/Scope"
|
|
15
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
16
47
|
import * as Stream from "effect/Stream"
|
|
17
48
|
import * as Cookies from "effect/unstable/http/Cookies"
|
|
18
49
|
import * as Etag from "effect/unstable/http/Etag"
|
|
@@ -27,7 +58,7 @@ import type * as HttpPlatform from "effect/unstable/http/HttpPlatform"
|
|
|
27
58
|
import * as HttpServer from "effect/unstable/http/HttpServer"
|
|
28
59
|
import {
|
|
29
60
|
causeResponse,
|
|
30
|
-
|
|
61
|
+
ClientAbort,
|
|
31
62
|
HttpServerError,
|
|
32
63
|
RequestParseError,
|
|
33
64
|
ResponseError,
|
|
@@ -50,30 +81,44 @@ import * as NodeServices from "./NodeServices.ts"
|
|
|
50
81
|
import { NodeWS } from "./NodeSocket.ts"
|
|
51
82
|
|
|
52
83
|
/**
|
|
53
|
-
*
|
|
84
|
+
* Creates a scoped `HttpServer` from a Node `http.Server`, starts listening
|
|
85
|
+
* with the supplied options, registers request and upgrade handling, and closes
|
|
86
|
+
* the server during scope finalization with optional graceful-shutdown control.
|
|
87
|
+
*
|
|
54
88
|
* @category constructors
|
|
89
|
+
* @since 4.0.0
|
|
55
90
|
*/
|
|
56
91
|
export const make = Effect.fnUntraced(function*(
|
|
57
92
|
evaluate: LazyArg<Http.Server>,
|
|
58
|
-
options: Net.ListenOptions
|
|
93
|
+
options: Net.ListenOptions & {
|
|
94
|
+
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
95
|
+
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
96
|
+
}
|
|
59
97
|
) {
|
|
60
98
|
const scope = yield* Effect.scope
|
|
61
99
|
const server = evaluate()
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
100
|
+
|
|
101
|
+
const shutdown = yield* Effect.callback<void>((resume) => {
|
|
102
|
+
if (!server.listening) {
|
|
103
|
+
return resume(Effect.void)
|
|
104
|
+
}
|
|
105
|
+
server.close((error) => {
|
|
106
|
+
if (error) {
|
|
107
|
+
resume(Effect.die(error))
|
|
108
|
+
} else {
|
|
109
|
+
resume(Effect.void)
|
|
67
110
|
}
|
|
68
|
-
server.close((error) => {
|
|
69
|
-
if (error) {
|
|
70
|
-
resume(Effect.die(error))
|
|
71
|
-
} else {
|
|
72
|
-
resume(Effect.void)
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
111
|
})
|
|
76
|
-
)
|
|
112
|
+
}).pipe(Effect.cached)
|
|
113
|
+
|
|
114
|
+
const preemptiveShutdown = options.disablePreemptiveShutdown ?
|
|
115
|
+
Effect.void :
|
|
116
|
+
Effect.timeoutOrElse(shutdown, {
|
|
117
|
+
duration: options.gracefulShutdownTimeout ?? Duration.seconds(20),
|
|
118
|
+
orElse: () => Effect.void
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
yield* Scope.addFinalizer(scope, shutdown)
|
|
77
122
|
|
|
78
123
|
yield* Effect.callback<void, ServeError>((resume) => {
|
|
79
124
|
function onError(cause: Error) {
|
|
@@ -111,7 +156,8 @@ export const make = Effect.fnUntraced(function*(
|
|
|
111
156
|
port: address.port
|
|
112
157
|
},
|
|
113
158
|
serve: Effect.fnUntraced(function*(httpApp, middleware) {
|
|
114
|
-
const
|
|
159
|
+
const serveScope = yield* Effect.scope
|
|
160
|
+
const scope = Scope.forkUnsafe(serveScope, "parallel")
|
|
115
161
|
const handler = yield* (makeHandler(httpApp, {
|
|
116
162
|
middleware: middleware as any,
|
|
117
163
|
scope
|
|
@@ -120,12 +166,11 @@ export const make = Effect.fnUntraced(function*(
|
|
|
120
166
|
middleware: middleware as any,
|
|
121
167
|
scope
|
|
122
168
|
})
|
|
123
|
-
yield*
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
169
|
+
yield* Scope.addFinalizerExit(serveScope, () => {
|
|
170
|
+
server.off("request", handler)
|
|
171
|
+
server.off("upgrade", upgradeHandler)
|
|
172
|
+
return preemptiveShutdown
|
|
173
|
+
})
|
|
129
174
|
server.on("request", handler)
|
|
130
175
|
server.on("upgrade", upgradeHandler)
|
|
131
176
|
})
|
|
@@ -133,8 +178,12 @@ export const make = Effect.fnUntraced(function*(
|
|
|
133
178
|
})
|
|
134
179
|
|
|
135
180
|
/**
|
|
136
|
-
*
|
|
181
|
+
* Creates a Node `request` event handler for an Effect HTTP application,
|
|
182
|
+
* injecting a `HttpServerRequest` and interrupting the request fiber if the
|
|
183
|
+
* client closes the response before it finishes.
|
|
184
|
+
*
|
|
137
185
|
* @category Handlers
|
|
186
|
+
* @since 4.0.0
|
|
138
187
|
*/
|
|
139
188
|
export const makeHandler = <
|
|
140
189
|
R,
|
|
@@ -152,26 +201,31 @@ export const makeHandler = <
|
|
|
152
201
|
Exclude<Effect.Services<App>, HttpServerRequest | Scope.Scope>
|
|
153
202
|
> => {
|
|
154
203
|
const handled = HttpEffect.toHandled(httpEffect, handleResponse, options.middleware as any)
|
|
155
|
-
return Effect.
|
|
156
|
-
|
|
204
|
+
return Effect.withFiber((parent) => {
|
|
205
|
+
const services = parent.context
|
|
206
|
+
return Effect.succeed(function handler(
|
|
157
207
|
nodeRequest: Http.IncomingMessage,
|
|
158
208
|
nodeResponse: Http.ServerResponse
|
|
159
209
|
) {
|
|
160
210
|
const map = new Map(services.mapUnsafe)
|
|
161
211
|
map.set(HttpServerRequest.key, new ServerRequestImpl(nodeRequest, nodeResponse))
|
|
162
|
-
const fiber = Fiber.runIn(Effect.runForkWith(
|
|
212
|
+
const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe<any>(map))(handled), options.scope)
|
|
163
213
|
nodeResponse.on("close", () => {
|
|
164
214
|
if (!nodeResponse.writableEnded) {
|
|
165
|
-
fiber.interruptUnsafe(
|
|
215
|
+
fiber.interruptUnsafe(parent.id, ClientAbort.annotation)
|
|
166
216
|
}
|
|
167
217
|
})
|
|
168
|
-
}
|
|
218
|
+
})
|
|
169
219
|
})
|
|
170
220
|
}
|
|
171
221
|
|
|
172
222
|
/**
|
|
173
|
-
*
|
|
223
|
+
* Creates a Node `upgrade` event handler for an Effect HTTP application,
|
|
224
|
+
* exposing the upgraded WebSocket as the request's `upgrade` effect and
|
|
225
|
+
* interrupting the request fiber when the socket closes early.
|
|
226
|
+
*
|
|
174
227
|
* @category Handlers
|
|
228
|
+
* @since 4.0.0
|
|
175
229
|
*/
|
|
176
230
|
export const makeUpgradeHandler = <
|
|
177
231
|
R,
|
|
@@ -190,8 +244,13 @@ export const makeUpgradeHandler = <
|
|
|
190
244
|
Exclude<Effect.Services<App>, HttpServerRequest | Scope.Scope>
|
|
191
245
|
> => {
|
|
192
246
|
const handledApp = HttpEffect.toHandled(httpEffect, handleResponse, options.middleware as any)
|
|
193
|
-
return Effect.
|
|
194
|
-
|
|
247
|
+
return Effect.withFiber((parent) => {
|
|
248
|
+
const services = parent.context
|
|
249
|
+
return Effect.succeed(function handler(
|
|
250
|
+
nodeRequest: Http.IncomingMessage,
|
|
251
|
+
socket: Duplex,
|
|
252
|
+
head: Buffer
|
|
253
|
+
) {
|
|
195
254
|
let nodeResponse_: Http.ServerResponse | undefined = undefined
|
|
196
255
|
const nodeResponse = () => {
|
|
197
256
|
if (nodeResponse_ === undefined) {
|
|
@@ -217,13 +276,14 @@ export const makeUpgradeHandler = <
|
|
|
217
276
|
))
|
|
218
277
|
const map = new Map(services.mapUnsafe)
|
|
219
278
|
map.set(HttpServerRequest.key, new ServerRequestImpl(nodeRequest, nodeResponse, upgradeEffect))
|
|
220
|
-
const fiber = Fiber.runIn(Effect.runForkWith(
|
|
279
|
+
const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe<any>(map))(handledApp), options.scope)
|
|
221
280
|
socket.on("close", () => {
|
|
222
281
|
if (!socket.writableEnded) {
|
|
223
|
-
fiber.interruptUnsafe(
|
|
282
|
+
fiber.interruptUnsafe(parent.id, ClientAbort.annotation)
|
|
224
283
|
}
|
|
225
284
|
})
|
|
226
|
-
})
|
|
285
|
+
})
|
|
286
|
+
})
|
|
227
287
|
}
|
|
228
288
|
|
|
229
289
|
class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> implements HttpServerRequest {
|
|
@@ -239,7 +299,7 @@ class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> impleme
|
|
|
239
299
|
upgradeEffect?: Effect.Effect<Socket.Socket, HttpServerError>,
|
|
240
300
|
url = source.url!,
|
|
241
301
|
headersOverride?: Headers.Headers,
|
|
242
|
-
remoteAddressOverride?: string
|
|
302
|
+
remoteAddressOverride?: Option.Option<string>
|
|
243
303
|
) {
|
|
244
304
|
super(source, (cause) =>
|
|
245
305
|
new HttpServerError({
|
|
@@ -271,7 +331,7 @@ class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> impleme
|
|
|
271
331
|
options: {
|
|
272
332
|
readonly url?: string | undefined
|
|
273
333
|
readonly headers?: Headers.Headers | undefined
|
|
274
|
-
readonly remoteAddress?: string | undefined
|
|
334
|
+
readonly remoteAddress?: Option.Option<string> | undefined
|
|
275
335
|
}
|
|
276
336
|
) {
|
|
277
337
|
return new ServerRequestImpl(
|
|
@@ -280,7 +340,7 @@ class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> impleme
|
|
|
280
340
|
this.upgradeEffect,
|
|
281
341
|
options.url ?? this.url,
|
|
282
342
|
options.headers ?? this.headersOverride,
|
|
283
|
-
options.remoteAddress
|
|
343
|
+
"remoteAddress" in options ? options.remoteAddress : this.remoteAddressOverride
|
|
284
344
|
)
|
|
285
345
|
}
|
|
286
346
|
|
|
@@ -347,17 +407,26 @@ class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> impleme
|
|
|
347
407
|
}
|
|
348
408
|
|
|
349
409
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
410
|
+
* Provides an `HttpServer` by creating and managing a scoped Node
|
|
411
|
+
* `http.Server` with the supplied listen and shutdown options.
|
|
412
|
+
*
|
|
413
|
+
* @category layers
|
|
414
|
+
* @since 4.0.0
|
|
352
415
|
*/
|
|
353
416
|
export const layerServer: (
|
|
354
417
|
evaluate: LazyArg<Http.Server<typeof Http.IncomingMessage, typeof Http.ServerResponse>>,
|
|
355
|
-
options: Net.ListenOptions
|
|
418
|
+
options: Net.ListenOptions & {
|
|
419
|
+
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
420
|
+
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
421
|
+
}
|
|
356
422
|
) => Layer.Layer<HttpServer.HttpServer, ServeError> = flow(make, Layer.effect(HttpServer.HttpServer))
|
|
357
423
|
|
|
358
424
|
/**
|
|
359
|
-
*
|
|
360
|
-
*
|
|
425
|
+
* Provides the Node HTTP support services used by `NodeHttpServer`, including
|
|
426
|
+
* the HTTP platform, ETag generator, and core Node platform services.
|
|
427
|
+
*
|
|
428
|
+
* @category layers
|
|
429
|
+
* @since 4.0.0
|
|
361
430
|
*/
|
|
362
431
|
export const layerHttpServices: Layer.Layer<
|
|
363
432
|
NodeServices.NodeServices | HttpPlatform.HttpPlatform | Etag.Generator
|
|
@@ -368,12 +437,18 @@ export const layerHttpServices: Layer.Layer<
|
|
|
368
437
|
)
|
|
369
438
|
|
|
370
439
|
/**
|
|
371
|
-
*
|
|
372
|
-
*
|
|
440
|
+
* Provides a Node `HttpServer` together with the Node HTTP platform, ETag, and
|
|
441
|
+
* core platform services required to serve requests.
|
|
442
|
+
*
|
|
443
|
+
* @category layers
|
|
444
|
+
* @since 4.0.0
|
|
373
445
|
*/
|
|
374
446
|
export const layer = (
|
|
375
447
|
evaluate: LazyArg<Http.Server>,
|
|
376
|
-
options: Net.ListenOptions
|
|
448
|
+
options: Net.ListenOptions & {
|
|
449
|
+
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
450
|
+
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
451
|
+
}
|
|
377
452
|
): Layer.Layer<
|
|
378
453
|
HttpServer.HttpServer | NodeServices.NodeServices | HttpPlatform.HttpPlatform | Etag.Generator,
|
|
379
454
|
ServeError
|
|
@@ -384,26 +459,37 @@ export const layer = (
|
|
|
384
459
|
)
|
|
385
460
|
|
|
386
461
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
462
|
+
* Provides a Node `HttpServer` and HTTP support services, reading the listen
|
|
463
|
+
* and shutdown options from a `Config` value.
|
|
464
|
+
*
|
|
465
|
+
* @category layers
|
|
466
|
+
* @since 4.0.0
|
|
389
467
|
*/
|
|
390
468
|
export const layerConfig = (
|
|
391
469
|
evaluate: LazyArg<Http.Server>,
|
|
392
|
-
options: Config.Wrap<
|
|
470
|
+
options: Config.Wrap<
|
|
471
|
+
Net.ListenOptions & {
|
|
472
|
+
readonly disablePreemptiveShutdown?: boolean | undefined
|
|
473
|
+
readonly gracefulShutdownTimeout?: Duration.Input | undefined
|
|
474
|
+
}
|
|
475
|
+
>
|
|
393
476
|
): Layer.Layer<
|
|
394
477
|
HttpServer.HttpServer | FileSystem.FileSystem | Path.Path | HttpPlatform.HttpPlatform | Etag.Generator,
|
|
395
478
|
ServeError | Config.ConfigError
|
|
396
479
|
> =>
|
|
397
480
|
Layer.mergeAll(
|
|
398
481
|
Layer.effect(HttpServer.HttpServer)(
|
|
399
|
-
Effect.flatMap(Config.unwrap(options)
|
|
482
|
+
Effect.flatMap(Config.unwrap(options), (options) => make(evaluate, options))
|
|
400
483
|
),
|
|
401
484
|
layerHttpServices
|
|
402
485
|
)
|
|
403
486
|
|
|
404
487
|
/**
|
|
405
|
-
*
|
|
488
|
+
* Provides a test HTTP server listening on an ephemeral port together with a
|
|
489
|
+
* Fetch-backed `HttpClient` configured for server integration tests.
|
|
490
|
+
*
|
|
406
491
|
* @category Testing
|
|
492
|
+
* @since 4.0.0
|
|
407
493
|
*/
|
|
408
494
|
export const layerTest: Layer.Layer<
|
|
409
495
|
| HttpServer.HttpServer
|
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Accessors for the Node.js objects backing a platform Node
|
|
3
|
+
* `HttpServerRequest`.
|
|
4
|
+
*
|
|
5
|
+
* Use this module at interop boundaries when an Effect HTTP handler needs the
|
|
6
|
+
* original `http.IncomingMessage` or `http.ServerResponse` for APIs that are
|
|
7
|
+
* specific to Node, such as existing middleware, socket inspection, raw stream
|
|
8
|
+
* piping, or response customization that cannot be expressed with the portable
|
|
9
|
+
* `HttpServerRequest` and `HttpServerResponse` interfaces.
|
|
10
|
+
*
|
|
11
|
+
* The returned request is the original Node request supplied to the server. It
|
|
12
|
+
* does not reflect Effect request overrides made by middleware, such as a
|
|
13
|
+
* rewritten URL, adjusted headers, or a substituted remote address. Its body is
|
|
14
|
+
* also Node's one-shot readable stream, so avoid mixing raw stream consumption
|
|
15
|
+
* with Effect body, multipart, or stream helpers unless ownership of the body
|
|
16
|
+
* is clear. The returned response is the Node response owned by the platform
|
|
17
|
+
* server; writing to it directly bypasses the usual Effect response writer and
|
|
18
|
+
* must be coordinated carefully to avoid duplicate writes. Upgrade requests may
|
|
19
|
+
* create that response lazily when it is first requested.
|
|
20
|
+
*
|
|
21
|
+
* @since 4.0.0
|
|
3
22
|
*/
|
|
4
23
|
import type { HttpServerRequest } from "effect/unstable/http/HttpServerRequest"
|
|
5
24
|
import type * as Http from "node:http"
|
|
6
25
|
|
|
7
26
|
/**
|
|
8
|
-
*
|
|
27
|
+
* Returns the underlying Node `IncomingMessage` for a platform Node
|
|
28
|
+
* `HttpServerRequest`.
|
|
29
|
+
*
|
|
9
30
|
* @category Accessors
|
|
31
|
+
* @since 4.0.0
|
|
10
32
|
*/
|
|
11
33
|
export const toIncomingMessage = (self: HttpServerRequest): Http.IncomingMessage => self.source as any
|
|
12
34
|
|
|
13
35
|
/**
|
|
14
|
-
*
|
|
36
|
+
* Returns the underlying Node `ServerResponse` for a platform Node
|
|
37
|
+
* `HttpServerRequest`, evaluating the stored response thunk when the response
|
|
38
|
+
* was created lazily.
|
|
39
|
+
*
|
|
15
40
|
* @category Accessors
|
|
41
|
+
* @since 4.0.0
|
|
16
42
|
*/
|
|
17
43
|
export const toServerResponse = (self: HttpServerRequest): Http.ServerResponse => {
|
|
18
44
|
const res = (self as any).response
|
package/src/NodeMultipart.ts
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js multipart parsing for HTTP `multipart/form-data` request bodies.
|
|
3
|
+
*
|
|
4
|
+
* `NodeMultipart` adapts a Node `Readable` plus incoming HTTP headers into
|
|
5
|
+
* Effect's shared multipart model. It can expose form parts as a stream for
|
|
6
|
+
* incremental processing, or collect a complete persisted form by writing file
|
|
7
|
+
* uploads to scoped temporary files through the current `FileSystem` and `Path`
|
|
8
|
+
* services.
|
|
9
|
+
*
|
|
10
|
+
* **Mental model**
|
|
11
|
+
*
|
|
12
|
+
* Multipart request bodies are one-shot byte streams. {@link stream} parses the
|
|
13
|
+
* body as it arrives: fields are decoded to strings and file parts keep their
|
|
14
|
+
* underlying Node readable stream. {@link persisted} consumes the same kind of
|
|
15
|
+
* body, builds a `Multipart.Persisted` record, and ties temporary upload files
|
|
16
|
+
* to the surrounding `Scope`.
|
|
17
|
+
*
|
|
18
|
+
* **Common tasks**
|
|
19
|
+
*
|
|
20
|
+
* - Use {@link stream} when a route validates fields while piping file uploads
|
|
21
|
+
* to storage.
|
|
22
|
+
* - Use {@link persisted} when a route needs a complete form value with scoped
|
|
23
|
+
* temporary files.
|
|
24
|
+
* - Use {@link fileToReadable} when a downstream Node library expects a
|
|
25
|
+
* `Readable`.
|
|
26
|
+
*
|
|
27
|
+
* **Gotchas**
|
|
28
|
+
*
|
|
29
|
+
* Consume a request body with only one parser. File parts must be drained,
|
|
30
|
+
* piped, or persisted so the request can finish reading. `contentEffect` loads
|
|
31
|
+
* an uploaded file into memory, so reserve it for small files. Client-supplied
|
|
32
|
+
* filenames are metadata, not trusted filesystem paths.
|
|
33
|
+
*
|
|
34
|
+
* @since 4.0.0
|
|
3
35
|
*/
|
|
4
36
|
import * as Effect from "effect/Effect"
|
|
5
37
|
import type * as FileSystem from "effect/FileSystem"
|
|
@@ -16,8 +48,12 @@ import * as NodeStreamP from "node:stream/promises"
|
|
|
16
48
|
import * as NodeStream from "./NodeStream.ts"
|
|
17
49
|
|
|
18
50
|
/**
|
|
19
|
-
*
|
|
51
|
+
* Parses multipart data from a Node readable request body and headers into a
|
|
52
|
+
* stream of `Multipart.Part` values, converting parser failures to
|
|
53
|
+
* `MultipartError`.
|
|
54
|
+
*
|
|
20
55
|
* @category constructors
|
|
56
|
+
* @since 4.0.0
|
|
21
57
|
*/
|
|
22
58
|
export const stream = (
|
|
23
59
|
source: Readable,
|
|
@@ -39,8 +75,11 @@ export const stream = (
|
|
|
39
75
|
)
|
|
40
76
|
|
|
41
77
|
/**
|
|
42
|
-
*
|
|
78
|
+
* Parses multipart data from a Node readable request body and persists file
|
|
79
|
+
* parts using the current `FileSystem`, `Path`, and `Scope` services.
|
|
80
|
+
*
|
|
43
81
|
* @category constructors
|
|
82
|
+
* @since 4.0.0
|
|
44
83
|
*/
|
|
45
84
|
export const persisted = (
|
|
46
85
|
source: Readable,
|
|
@@ -57,7 +96,11 @@ export const persisted = (
|
|
|
57
96
|
}))
|
|
58
97
|
|
|
59
98
|
/**
|
|
60
|
-
*
|
|
99
|
+
* Returns the underlying Node readable stream for a multipart file produced by
|
|
100
|
+
* the Node multipart parser.
|
|
101
|
+
*
|
|
102
|
+
* @category converting
|
|
103
|
+
* @since 4.0.0
|
|
61
104
|
*/
|
|
62
105
|
export const fileToReadable = (file: Multipart.File): Readable => (file as FileImpl).file
|
|
63
106
|
|
package/src/NodePath.ts
CHANGED
|
@@ -1,24 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js layers for Effect's `Path` service.
|
|
3
|
+
*
|
|
4
|
+
* This module adapts Node's path and file URL behavior to the
|
|
5
|
+
* platform-independent `Path` service. Provide one of its layers when a Node
|
|
6
|
+
* program needs to build, normalize, parse, resolve, or convert paths without
|
|
7
|
+
* depending directly on `node:path`.
|
|
8
|
+
*
|
|
9
|
+
* **Mental model**
|
|
10
|
+
*
|
|
11
|
+
* `Path` is a syntactic service: it manipulates strings and `file:` URLs. It
|
|
12
|
+
* does not read the filesystem, check permissions, or validate that paths
|
|
13
|
+
* exist. The selected layer decides which separator, drive-letter, UNC, and URL
|
|
14
|
+
* conversion rules are used.
|
|
15
|
+
*
|
|
16
|
+
* **Common tasks**
|
|
17
|
+
*
|
|
18
|
+
* Use `layer` for host-platform Node semantics, `layerPosix` for stable POSIX
|
|
19
|
+
* behavior, and `layerWin32` for stable Windows behavior. `NodeServices.layer`
|
|
20
|
+
* already includes `layer`, so import this module directly when a program wants
|
|
21
|
+
* only path support or a platform-specific variant.
|
|
22
|
+
*
|
|
23
|
+
* **Gotchas**
|
|
24
|
+
*
|
|
25
|
+
* Results that are correct on one platform may not be portable to another.
|
|
26
|
+
* `fromFileUrl` and `toFileUrl` use Node's `node:url` conversion rules and
|
|
27
|
+
* report invalid conversions as `BadArgument` failures.
|
|
28
|
+
*
|
|
29
|
+
* @since 4.0.0
|
|
3
30
|
*/
|
|
4
31
|
import * as NodePath from "@effect/platform-node-shared/NodePath"
|
|
5
32
|
import type * as Layer from "effect/Layer"
|
|
6
33
|
import type { Path } from "effect/Path"
|
|
7
34
|
|
|
8
35
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
36
|
+
* Provides the default Node `Path` service using the platform's `node:path`
|
|
37
|
+
* implementation.
|
|
38
|
+
*
|
|
39
|
+
* @category layers
|
|
40
|
+
* @since 4.0.0
|
|
11
41
|
*/
|
|
12
42
|
export const layer: Layer.Layer<Path> = NodePath.layer
|
|
13
43
|
|
|
14
44
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
45
|
+
* Provides the `Path` service using Node's POSIX path implementation,
|
|
46
|
+
* regardless of the host platform.
|
|
47
|
+
*
|
|
48
|
+
* @category layers
|
|
49
|
+
* @since 4.0.0
|
|
17
50
|
*/
|
|
18
51
|
export const layerPosix: Layer.Layer<Path> = NodePath.layerPosix
|
|
19
52
|
|
|
20
53
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
54
|
+
* Provides the `Path` service using Node's Windows path implementation,
|
|
55
|
+
* regardless of the host platform.
|
|
56
|
+
*
|
|
57
|
+
* @category layers
|
|
58
|
+
* @since 4.0.0
|
|
23
59
|
*/
|
|
24
60
|
export const layerWin32: Layer.Layer<Path> = NodePath.layerWin32
|
package/src/NodeRedis.ts
CHANGED
|
@@ -1,20 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js Redis integration backed by `ioredis`.
|
|
3
|
+
*
|
|
4
|
+
* This module creates a scoped `ioredis` client and exposes it in two forms:
|
|
5
|
+
* the generic `Redis` service consumed by Effect persistence modules, and the
|
|
6
|
+
* {@link NodeRedis} service for code that needs direct access to the underlying
|
|
7
|
+
* client.
|
|
8
|
+
*
|
|
9
|
+
* **Mental model**
|
|
10
|
+
*
|
|
11
|
+
* - {@link layer} creates one `ioredis` client from explicit client options
|
|
12
|
+
* - {@link layerConfig} reads the same options from `Config`
|
|
13
|
+
* - Building the layer opens the client, and closing the layer scope calls
|
|
14
|
+
* `quit`
|
|
15
|
+
* - The generic `Redis` service sends command strings through `client.call`
|
|
16
|
+
* - {@link NodeRedis} exposes the raw client plus `use`, which maps promise
|
|
17
|
+
* failures into `RedisError`
|
|
18
|
+
*
|
|
19
|
+
* **Common tasks**
|
|
20
|
+
*
|
|
21
|
+
* - Provide Redis-backed persistence, persisted queues, or distributed rate
|
|
22
|
+
* limiting in Node.js
|
|
23
|
+
* - Configure connection, TLS, database, retry, and reconnect behavior with
|
|
24
|
+
* standard `ioredis` options
|
|
25
|
+
* - Run custom Redis commands through {@link NodeRedis} when the generic
|
|
26
|
+
* `Redis` service does not cover the command shape you need
|
|
27
|
+
*
|
|
28
|
+
* **Gotchas**
|
|
29
|
+
*
|
|
30
|
+
* - Install the layer at the lifetime you want for the connection; a short
|
|
31
|
+
* scope opens and closes a Redis client for that scope
|
|
32
|
+
* - Persistence and rate limiter stores create their own keys and Lua scripts
|
|
33
|
+
* on top of this service, so choose stable prefixes and store ids
|
|
34
|
+
* - Persisted values may fail to decode after schema changes; plan migrations
|
|
35
|
+
* or cleanup for long-lived Redis data
|
|
36
|
+
* - Avoid unbounded high-cardinality rate-limit keys unless another process or
|
|
37
|
+
* key policy bounds their lifetime
|
|
38
|
+
*
|
|
39
|
+
* @since 4.0.0
|
|
3
40
|
*/
|
|
4
41
|
import * as Config from "effect/Config"
|
|
42
|
+
import * as Context from "effect/Context"
|
|
5
43
|
import * as Effect from "effect/Effect"
|
|
6
44
|
import * as Fn from "effect/Function"
|
|
7
45
|
import * as Layer from "effect/Layer"
|
|
8
46
|
import * as Scope from "effect/Scope"
|
|
9
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
10
47
|
import * as Redis from "effect/unstable/persistence/Redis"
|
|
11
48
|
import * as IoRedis from "ioredis"
|
|
12
49
|
|
|
13
50
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
51
|
+
* Service tag for the Node Redis integration, exposing the underlying
|
|
52
|
+
* `ioredis` client and a `use` helper that maps client failures to
|
|
53
|
+
* `RedisError`.
|
|
54
|
+
*
|
|
55
|
+
* @category services
|
|
56
|
+
* @since 4.0.0
|
|
16
57
|
*/
|
|
17
|
-
export class NodeRedis extends
|
|
58
|
+
export class NodeRedis extends Context.Service<NodeRedis, {
|
|
18
59
|
readonly client: IoRedis.Redis
|
|
19
60
|
readonly use: <A>(f: (client: IoRedis.Redis) => Promise<A>) => Effect.Effect<A, Redis.RedisError>
|
|
20
61
|
}>()("@effect/platform-node/NodeRedis") {}
|
|
@@ -45,30 +86,36 @@ const make = Effect.fnUntraced(function*(
|
|
|
45
86
|
use
|
|
46
87
|
})
|
|
47
88
|
|
|
48
|
-
return
|
|
49
|
-
|
|
89
|
+
return Context.make(NodeRedis, nodeRedis).pipe(
|
|
90
|
+
Context.add(Redis.Redis, redis)
|
|
50
91
|
)
|
|
51
92
|
})
|
|
52
93
|
|
|
53
94
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
95
|
+
* Provides `Redis` and `NodeRedis` services backed by an `ioredis` client
|
|
96
|
+
* created with the supplied options and closed when the layer scope ends.
|
|
97
|
+
*
|
|
98
|
+
* @category layers
|
|
99
|
+
* @since 4.0.0
|
|
56
100
|
*/
|
|
57
101
|
export const layer = (
|
|
58
102
|
options?: IoRedis.RedisOptions | undefined
|
|
59
|
-
): Layer.Layer<Redis.Redis | NodeRedis> => Layer.
|
|
103
|
+
): Layer.Layer<Redis.Redis | NodeRedis> => Layer.effectContext(make(options))
|
|
60
104
|
|
|
61
105
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
106
|
+
* Provides `Redis` and `NodeRedis` services from `Config`-backed ioredis
|
|
107
|
+
* options, closing the client when the layer scope ends.
|
|
108
|
+
*
|
|
109
|
+
* @category layers
|
|
110
|
+
* @since 4.0.0
|
|
64
111
|
*/
|
|
65
112
|
export const layerConfig: (
|
|
66
113
|
options: Config.Wrap<IoRedis.RedisOptions>
|
|
67
114
|
) => Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> = (
|
|
68
115
|
options: Config.Wrap<IoRedis.RedisOptions>
|
|
69
116
|
): Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> =>
|
|
70
|
-
Layer.
|
|
71
|
-
Config.unwrap(options).
|
|
117
|
+
Layer.effectContext(
|
|
118
|
+
Config.unwrap(options).pipe(
|
|
72
119
|
Effect.flatMap(make)
|
|
73
120
|
)
|
|
74
121
|
)
|