@effect/platform-node 4.0.0-beta.7 → 4.0.0-beta.70
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 +3 -3
- package/dist/Mime.js +3 -3
- 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 +14 -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 +22 -3
- package/dist/NodeFileSystem.js.map +1 -1
- package/dist/NodeHttpClient.d.ts +102 -24
- package/dist/NodeHttpClient.d.ts.map +1 -1
- package/dist/NodeHttpClient.js +124 -28
- package/dist/NodeHttpClient.js.map +1 -1
- package/dist/NodeHttpIncomingMessage.d.ts +30 -9
- package/dist/NodeHttpIncomingMessage.d.ts.map +1 -1
- package/dist/NodeHttpIncomingMessage.js +34 -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 +32 -4
- package/dist/NodeMultipart.d.ts.map +1 -1
- package/dist/NodeMultipart.js +32 -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 +30 -7
- package/dist/NodePath.js.map +1 -1
- package/dist/NodeRedis.d.ts +38 -9
- package/dist/NodeRedis.d.ts.map +1 -1
- package/dist/NodeRedis.js +41 -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 +24 -13
- package/dist/NodeRuntime.js.map +1 -1
- package/dist/NodeServices.d.ts +29 -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 +36 -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 +22 -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 +19 -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 +31 -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 +27 -5
- package/dist/NodeWorkerRunner.js.map +1 -1
- package/dist/Undici.d.ts +3 -3
- package/dist/Undici.js +3 -3
- package/dist/index.d.ts +376 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +376 -24
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/Mime.ts +3 -3
- package/src/NodeChildProcessSpawner.ts +1 -1
- package/src/NodeClusterHttp.ts +54 -11
- package/src/NodeClusterSocket.ts +57 -11
- package/src/NodeCrypto.ts +16 -0
- package/src/NodeFileSystem.ts +22 -3
- package/src/NodeHttpClient.ts +132 -33
- package/src/NodeHttpIncomingMessage.ts +42 -12
- package/src/NodeHttpPlatform.ts +35 -6
- package/src/NodeHttpServer.ts +139 -53
- package/src/NodeHttpServerRequest.ts +29 -3
- package/src/NodeMultipart.ts +32 -4
- package/src/NodePath.ts +30 -7
- package/src/NodeRedis.ts +43 -14
- package/src/NodeRuntime.ts +42 -37
- package/src/NodeServices.ts +31 -5
- package/src/NodeSink.ts +2 -2
- package/src/NodeSocket.ts +41 -4
- package/src/NodeSocketServer.ts +2 -2
- package/src/NodeStdio.ts +22 -3
- package/src/NodeStream.ts +2 -2
- package/src/NodeTerminal.ts +19 -3
- package/src/NodeWorker.ts +31 -6
- package/src/NodeWorkerRunner.ts +27 -5
- package/src/Undici.ts +3 -3
- package/src/index.ts +377 -24
package/src/NodeHttpPlatform.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js implementation of the Effect HTTP platform service.
|
|
3
|
+
*
|
|
4
|
+
* This module connects the portable `HttpPlatform` file response helpers to
|
|
5
|
+
* Node runtime primitives. It is used by Node HTTP servers and static file
|
|
6
|
+
* handlers when returning local files, public assets, downloads, byte ranges,
|
|
7
|
+
* or Web `File` values as `HttpServerResponse` bodies.
|
|
8
|
+
*
|
|
9
|
+
* Path-based responses are served with `node:fs.createReadStream`; Web `File`
|
|
10
|
+
* responses are bridged with `Readable.fromWeb`. The implementation fills in
|
|
11
|
+
* `content-type` from `Mime`, falls back to `application/octet-stream`, and
|
|
12
|
+
* writes the `content-length` for the selected range or whole file. Node's
|
|
13
|
+
* stream `end` option is inclusive, so the platform converts Effect's half-open
|
|
14
|
+
* range before reading. Empty bodies use an empty readable stream.
|
|
15
|
+
*
|
|
16
|
+
* Provide `layer` at the Node runtime edge when file responses, static serving,
|
|
17
|
+
* or response bodies created from files need real filesystem and ETag support.
|
|
18
|
+
* These responses are raw Node streams, so they are intended for the Node HTTP
|
|
19
|
+
* server adapter; keep files available until the response body has been
|
|
20
|
+
* consumed and prefer the portable `HttpServerResponse` constructors when a
|
|
21
|
+
* response does not depend on Node file or stream behavior.
|
|
22
|
+
*
|
|
23
|
+
* @since 4.0.0
|
|
3
24
|
*/
|
|
4
25
|
import { pipe } from "effect/Function"
|
|
5
26
|
import * as Layer from "effect/Layer"
|
|
@@ -13,12 +34,17 @@ import Mime from "./Mime.ts"
|
|
|
13
34
|
import * as NodeFileSystem from "./NodeFileSystem.ts"
|
|
14
35
|
|
|
15
36
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
37
|
+
* Creates the Node `HttpPlatform`, serving file responses from Node readable
|
|
38
|
+
* streams and adding MIME type and content-length headers when needed.
|
|
39
|
+
*
|
|
40
|
+
* @category constructors
|
|
41
|
+
* @since 4.0.0
|
|
18
42
|
*/
|
|
19
43
|
export const make = Platform.make({
|
|
20
44
|
fileResponse(path, status, statusText, headers, start, end, contentLength) {
|
|
21
|
-
const stream =
|
|
45
|
+
const stream = contentLength === 0
|
|
46
|
+
? Readable.from([])
|
|
47
|
+
: Fs.createReadStream(path, { start, end: end === undefined ? undefined : end - 1 })
|
|
22
48
|
return ServerResponse.raw(stream, {
|
|
23
49
|
headers: {
|
|
24
50
|
...headers,
|
|
@@ -45,8 +71,11 @@ export const make = Platform.make({
|
|
|
45
71
|
})
|
|
46
72
|
|
|
47
73
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
74
|
+
* Provides the Node `HttpPlatform` together with the filesystem and ETag
|
|
75
|
+
* services it needs for file responses.
|
|
76
|
+
*
|
|
77
|
+
* @category layers
|
|
78
|
+
* @since 4.0.0
|
|
50
79
|
*/
|
|
51
80
|
export const layer: Layer.Layer<Platform.HttpPlatform> = pipe(
|
|
52
81
|
Layer.effect(Platform.HttpPlatform)(make),
|
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,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node-specific helpers for parsing HTTP `multipart/form-data` request bodies.
|
|
3
|
+
*
|
|
4
|
+
* This module adapts a Node `Readable` request body plus its incoming headers
|
|
5
|
+
* into the shared `Multipart` model. Use `stream` when an HTTP server route
|
|
6
|
+
* wants to handle form fields and uploaded files incrementally, for example API
|
|
7
|
+
* endpoints that validate text fields while piping file parts to storage. Use
|
|
8
|
+
* `persisted` when the whole form should be collected into a record and uploaded
|
|
9
|
+
* files should be written into scoped temporary files through the current
|
|
10
|
+
* `FileSystem` and `Path` services.
|
|
11
|
+
*
|
|
12
|
+
* Node request bodies are one-shot streams, so consume either `stream` or
|
|
13
|
+
* `persisted`, and make sure file parts are drained, piped, or otherwise
|
|
14
|
+
* deliberately handled. `contentEffect` loads a file into memory and should be
|
|
15
|
+
* reserved for small uploads. Persisted paths live only for the surrounding
|
|
16
|
+
* `Scope`, and filenames supplied by clients should be treated as metadata, not
|
|
17
|
+
* trusted filesystem paths.
|
|
18
|
+
*
|
|
19
|
+
* @since 4.0.0
|
|
3
20
|
*/
|
|
4
21
|
import * as Effect from "effect/Effect"
|
|
5
22
|
import type * as FileSystem from "effect/FileSystem"
|
|
@@ -16,8 +33,12 @@ import * as NodeStreamP from "node:stream/promises"
|
|
|
16
33
|
import * as NodeStream from "./NodeStream.ts"
|
|
17
34
|
|
|
18
35
|
/**
|
|
19
|
-
*
|
|
36
|
+
* Parses multipart data from a Node readable request body and headers into a
|
|
37
|
+
* stream of `Multipart.Part` values, converting parser failures to
|
|
38
|
+
* `MultipartError`.
|
|
39
|
+
*
|
|
20
40
|
* @category constructors
|
|
41
|
+
* @since 4.0.0
|
|
21
42
|
*/
|
|
22
43
|
export const stream = (
|
|
23
44
|
source: Readable,
|
|
@@ -39,8 +60,11 @@ export const stream = (
|
|
|
39
60
|
)
|
|
40
61
|
|
|
41
62
|
/**
|
|
42
|
-
*
|
|
63
|
+
* Parses multipart data from a Node readable request body and persists file
|
|
64
|
+
* parts using the current `FileSystem`, `Path`, and `Scope` services.
|
|
65
|
+
*
|
|
43
66
|
* @category constructors
|
|
67
|
+
* @since 4.0.0
|
|
44
68
|
*/
|
|
45
69
|
export const persisted = (
|
|
46
70
|
source: Readable,
|
|
@@ -57,7 +81,11 @@ export const persisted = (
|
|
|
57
81
|
}))
|
|
58
82
|
|
|
59
83
|
/**
|
|
60
|
-
*
|
|
84
|
+
* Returns the underlying Node readable stream for a multipart file produced by
|
|
85
|
+
* the Node multipart parser.
|
|
86
|
+
*
|
|
87
|
+
* @category converting
|
|
88
|
+
* @since 4.0.0
|
|
61
89
|
*/
|
|
62
90
|
export const fileToReadable = (file: Multipart.File): Readable => (file as FileImpl).file
|
|
63
91
|
|
package/src/NodePath.ts
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js layers for Effect's `Path` service.
|
|
3
|
+
*
|
|
4
|
+
* Use this module when an Effect program running on Node needs path operations
|
|
5
|
+
* from the `Path` service, such as joining and normalizing filesystem
|
|
6
|
+
* locations, resolving configuration or static asset paths, working with CLI
|
|
7
|
+
* path arguments, or converting between file paths and `file:` URLs.
|
|
8
|
+
*
|
|
9
|
+
* `layer` follows the host platform's `node:path` semantics. Use `layerPosix`
|
|
10
|
+
* or `layerWin32` when code needs stable POSIX or Windows behavior regardless
|
|
11
|
+
* of the operating system. These layers provide only path manipulation; they do
|
|
12
|
+
* not read the filesystem or validate that paths exist. `NodeServices.layer`
|
|
13
|
+
* already includes the default Node path layer, so provide this module directly
|
|
14
|
+
* when you want the narrower service or one of the platform-specific variants.
|
|
15
|
+
*
|
|
16
|
+
* @since 4.0.0
|
|
3
17
|
*/
|
|
4
18
|
import * as NodePath from "@effect/platform-node-shared/NodePath"
|
|
5
19
|
import type * as Layer from "effect/Layer"
|
|
6
20
|
import type { Path } from "effect/Path"
|
|
7
21
|
|
|
8
22
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
23
|
+
* Provides the default Node `Path` service using the platform's `node:path`
|
|
24
|
+
* implementation.
|
|
25
|
+
*
|
|
26
|
+
* @category layers
|
|
27
|
+
* @since 4.0.0
|
|
11
28
|
*/
|
|
12
29
|
export const layer: Layer.Layer<Path> = NodePath.layer
|
|
13
30
|
|
|
14
31
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
32
|
+
* Provides the `Path` service using Node's POSIX path implementation,
|
|
33
|
+
* regardless of the host platform.
|
|
34
|
+
*
|
|
35
|
+
* @category layers
|
|
36
|
+
* @since 4.0.0
|
|
17
37
|
*/
|
|
18
38
|
export const layerPosix: Layer.Layer<Path> = NodePath.layerPosix
|
|
19
39
|
|
|
20
40
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
41
|
+
* Provides the `Path` service using Node's Windows path implementation,
|
|
42
|
+
* regardless of the host platform.
|
|
43
|
+
*
|
|
44
|
+
* @category layers
|
|
45
|
+
* @since 4.0.0
|
|
23
46
|
*/
|
|
24
47
|
export const layerWin32: Layer.Layer<Path> = NodePath.layerWin32
|
package/src/NodeRedis.ts
CHANGED
|
@@ -1,20 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js Redis integration backed by `ioredis`.
|
|
3
|
+
*
|
|
4
|
+
* This module provides scoped layers that create an `ioredis` client and expose
|
|
5
|
+
* both the low-level `Redis` service used by Effect persistence modules and the
|
|
6
|
+
* `NodeRedis` service for direct access to the underlying client. It is useful
|
|
7
|
+
* for Node applications that want Redis-backed persistence, persisted queues,
|
|
8
|
+
* distributed rate limiting, or custom Redis commands alongside the Effect
|
|
9
|
+
* services that build on Redis.
|
|
10
|
+
*
|
|
11
|
+
* The client is acquired when the layer is built and closed with `quit` when
|
|
12
|
+
* the layer scope ends, so install the layer at the lifetime you want for the
|
|
13
|
+
* connection and pass `ioredis` options, or `layerConfig`, for connection,
|
|
14
|
+
* TLS, database, retry, and reconnect settings. Persistence and rate limiter
|
|
15
|
+
* stores build their own keys and Lua scripts on top of this service; choose
|
|
16
|
+
* stable prefixes and store ids to avoid collisions, account for persisted
|
|
17
|
+
* values that may fail to decode after schema changes, and avoid unbounded
|
|
18
|
+
* high-cardinality rate-limit keys unless you have a cleanup or bounding
|
|
19
|
+
* strategy.
|
|
20
|
+
*
|
|
21
|
+
* @since 4.0.0
|
|
3
22
|
*/
|
|
4
23
|
import * as Config from "effect/Config"
|
|
24
|
+
import * as Context from "effect/Context"
|
|
5
25
|
import * as Effect from "effect/Effect"
|
|
6
26
|
import * as Fn from "effect/Function"
|
|
7
27
|
import * as Layer from "effect/Layer"
|
|
8
28
|
import * as Scope from "effect/Scope"
|
|
9
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
10
29
|
import * as Redis from "effect/unstable/persistence/Redis"
|
|
11
30
|
import * as IoRedis from "ioredis"
|
|
12
31
|
|
|
13
32
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
33
|
+
* Service tag for the Node Redis integration, exposing the underlying
|
|
34
|
+
* `ioredis` client and a `use` helper that maps client failures to
|
|
35
|
+
* `RedisError`.
|
|
36
|
+
*
|
|
37
|
+
* @category services
|
|
38
|
+
* @since 4.0.0
|
|
16
39
|
*/
|
|
17
|
-
export class NodeRedis extends
|
|
40
|
+
export class NodeRedis extends Context.Service<NodeRedis, {
|
|
18
41
|
readonly client: IoRedis.Redis
|
|
19
42
|
readonly use: <A>(f: (client: IoRedis.Redis) => Promise<A>) => Effect.Effect<A, Redis.RedisError>
|
|
20
43
|
}>()("@effect/platform-node/NodeRedis") {}
|
|
@@ -45,30 +68,36 @@ const make = Effect.fnUntraced(function*(
|
|
|
45
68
|
use
|
|
46
69
|
})
|
|
47
70
|
|
|
48
|
-
return
|
|
49
|
-
|
|
71
|
+
return Context.make(NodeRedis, nodeRedis).pipe(
|
|
72
|
+
Context.add(Redis.Redis, redis)
|
|
50
73
|
)
|
|
51
74
|
})
|
|
52
75
|
|
|
53
76
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
77
|
+
* Provides `Redis` and `NodeRedis` services backed by an `ioredis` client
|
|
78
|
+
* created with the supplied options and closed when the layer scope ends.
|
|
79
|
+
*
|
|
80
|
+
* @category layers
|
|
81
|
+
* @since 4.0.0
|
|
56
82
|
*/
|
|
57
83
|
export const layer = (
|
|
58
84
|
options?: IoRedis.RedisOptions | undefined
|
|
59
|
-
): Layer.Layer<Redis.Redis | NodeRedis> => Layer.
|
|
85
|
+
): Layer.Layer<Redis.Redis | NodeRedis> => Layer.effectContext(make(options))
|
|
60
86
|
|
|
61
87
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
88
|
+
* Provides `Redis` and `NodeRedis` services from `Config`-backed ioredis
|
|
89
|
+
* options, closing the client when the layer scope ends.
|
|
90
|
+
*
|
|
91
|
+
* @category layers
|
|
92
|
+
* @since 4.0.0
|
|
64
93
|
*/
|
|
65
94
|
export const layerConfig: (
|
|
66
95
|
options: Config.Wrap<IoRedis.RedisOptions>
|
|
67
96
|
) => Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> = (
|
|
68
97
|
options: Config.Wrap<IoRedis.RedisOptions>
|
|
69
98
|
): Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> =>
|
|
70
|
-
Layer.
|
|
71
|
-
Config.unwrap(options).
|
|
99
|
+
Layer.effectContext(
|
|
100
|
+
Config.unwrap(options).pipe(
|
|
72
101
|
Effect.flatMap(make)
|
|
73
102
|
)
|
|
74
103
|
)
|