@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/NodeHttpClient.ts
CHANGED
|
@@ -1,12 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Node.js implementations of the Effect `HttpClient`.
|
|
3
|
+
*
|
|
4
|
+
* This module supplies Node runtime backends for the platform-independent
|
|
5
|
+
* Effect HTTP client API. It re-exports the fetch-based client, defines an
|
|
6
|
+
* Undici-backed client, and defines a lower-level `node:http` / `node:https`
|
|
7
|
+
* client for integrations that need native agent configuration.
|
|
8
|
+
*
|
|
9
|
+
* **Mental model**
|
|
10
|
+
*
|
|
11
|
+
* All backends provide the same `HttpClient` service, so application code can
|
|
12
|
+
* depend on the Effect HTTP client interface while the layer chooses the Node
|
|
13
|
+
* implementation. The difference is where request execution and connection
|
|
14
|
+
* ownership live: fetch uses `globalThis.fetch`, Undici sends through a
|
|
15
|
+
* `Dispatcher`, and the `node:http` backend sends through scoped HTTP and HTTPS
|
|
16
|
+
* agents.
|
|
17
|
+
*
|
|
18
|
+
* **Common tasks**
|
|
19
|
+
*
|
|
20
|
+
* Use `layerFetch` when the built-in fetch implementation is enough. Use
|
|
21
|
+
* `layerUndici` for a scoped Undici `Agent`, or `layerUndiciNoDispatcher` when
|
|
22
|
+
* the caller provides the `Dispatcher` service, including the process-global
|
|
23
|
+
* dispatcher from `dispatcherLayerGlobal`. Use `layerNodeHttp` or
|
|
24
|
+
* `layerAgentOptions` when TLS, proxy, keep-alive, socket, or other native
|
|
25
|
+
* Node agent options must be configured directly.
|
|
26
|
+
*
|
|
27
|
+
* **Gotchas**
|
|
28
|
+
*
|
|
29
|
+
* Fetch, Undici, and `node:http` are not exact substitutes. They differ in
|
|
30
|
+
* dispatcher and agent hooks, request body support, abort behavior, upgrade
|
|
31
|
+
* support, and response body readers. Scoped layers destroy the agents or
|
|
32
|
+
* dispatchers they create when the layer scope ends; `dispatcherLayerGlobal`
|
|
33
|
+
* intentionally does not own or destroy Undici's process-global dispatcher.
|
|
34
|
+
* Body read failures are reported as `HttpClientError` decode or transport
|
|
35
|
+
* errors.
|
|
36
|
+
*
|
|
37
|
+
* @since 4.0.0
|
|
3
38
|
*/
|
|
39
|
+
import * as Context from "effect/Context"
|
|
4
40
|
import * as Effect from "effect/Effect"
|
|
5
41
|
import { flow } from "effect/Function"
|
|
6
42
|
import * as Inspectable from "effect/Inspectable"
|
|
7
43
|
import * as Layer from "effect/Layer"
|
|
44
|
+
import * as Option from "effect/Option"
|
|
45
|
+
import { type Pipeable, pipeArguments } from "effect/Pipeable"
|
|
46
|
+
import type * as Schema from "effect/Schema"
|
|
8
47
|
import type * as Scope from "effect/Scope"
|
|
9
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
10
48
|
import * as Stream from "effect/Stream"
|
|
11
49
|
import * as Cookies from "effect/unstable/http/Cookies"
|
|
12
50
|
import * as Headers from "effect/unstable/http/Headers"
|
|
@@ -33,18 +71,24 @@ import * as Undici from "./Undici.ts"
|
|
|
33
71
|
|
|
34
72
|
export {
|
|
35
73
|
/**
|
|
36
|
-
*
|
|
74
|
+
* Fetch-based HTTP client implementation for Node.js.
|
|
75
|
+
*
|
|
37
76
|
* @category Fetch
|
|
77
|
+
* @since 4.0.0
|
|
38
78
|
*/
|
|
39
79
|
Fetch,
|
|
40
80
|
/**
|
|
41
|
-
*
|
|
81
|
+
* Layer that provides the fetch-based HTTP client implementation.
|
|
82
|
+
*
|
|
42
83
|
* @category Fetch
|
|
84
|
+
* @since 4.0.0
|
|
43
85
|
*/
|
|
44
86
|
layer as layerFetch,
|
|
45
87
|
/**
|
|
46
|
-
*
|
|
88
|
+
* Request initialization options accepted by the fetch-based HTTP client.
|
|
89
|
+
*
|
|
47
90
|
* @category Fetch
|
|
91
|
+
* @since 4.0.0
|
|
48
92
|
*/
|
|
49
93
|
RequestInit
|
|
50
94
|
} from "effect/unstable/http/FetchHttpClient"
|
|
@@ -54,16 +98,22 @@ export {
|
|
|
54
98
|
// -----------------------------------------------------------------------------
|
|
55
99
|
|
|
56
100
|
/**
|
|
57
|
-
*
|
|
101
|
+
* Service tag for the Undici `Dispatcher` used by the Undici-backed HTTP
|
|
102
|
+
* client.
|
|
103
|
+
*
|
|
58
104
|
* @category Dispatcher
|
|
105
|
+
* @since 4.0.0
|
|
59
106
|
*/
|
|
60
|
-
export class Dispatcher extends
|
|
107
|
+
export class Dispatcher extends Context.Service<Dispatcher, Undici.Dispatcher>()(
|
|
61
108
|
"@effect/platform-node/NodeHttpClient/Dispatcher"
|
|
62
109
|
) {}
|
|
63
110
|
|
|
64
111
|
/**
|
|
65
|
-
*
|
|
112
|
+
* Acquires a new Undici `Agent` dispatcher and destroys it when the enclosing
|
|
113
|
+
* scope is finalized.
|
|
114
|
+
*
|
|
66
115
|
* @category Dispatcher
|
|
116
|
+
* @since 4.0.0
|
|
67
117
|
*/
|
|
68
118
|
export const makeDispatcher: Effect.Effect<Undici.Dispatcher, never, Scope.Scope> = Effect.acquireRelease(
|
|
69
119
|
Effect.sync(() => new Undici.Agent()),
|
|
@@ -71,29 +121,41 @@ export const makeDispatcher: Effect.Effect<Undici.Dispatcher, never, Scope.Scope
|
|
|
71
121
|
)
|
|
72
122
|
|
|
73
123
|
/**
|
|
74
|
-
*
|
|
124
|
+
* Provides the `Dispatcher` service using a scoped Undici `Agent`.
|
|
125
|
+
*
|
|
75
126
|
* @category Dispatcher
|
|
127
|
+
* @since 4.0.0
|
|
76
128
|
*/
|
|
77
129
|
export const layerDispatcher: Layer.Layer<Dispatcher> = Layer.effect(Dispatcher)(makeDispatcher)
|
|
78
130
|
|
|
79
131
|
/**
|
|
80
|
-
*
|
|
132
|
+
* Provides the `Dispatcher` service from Undici's process-global dispatcher,
|
|
133
|
+
* without creating or owning a new agent.
|
|
134
|
+
*
|
|
81
135
|
* @category Dispatcher
|
|
136
|
+
* @since 4.0.0
|
|
82
137
|
*/
|
|
83
138
|
export const dispatcherLayerGlobal: Layer.Layer<Dispatcher> = Layer.sync(Dispatcher)(() => Undici.getGlobalDispatcher())
|
|
84
139
|
|
|
85
140
|
/**
|
|
86
|
-
*
|
|
141
|
+
* Fiber reference containing default Undici request options applied to requests
|
|
142
|
+
* sent by `makeUndici`.
|
|
143
|
+
*
|
|
87
144
|
* @category undici
|
|
145
|
+
* @since 4.0.0
|
|
88
146
|
*/
|
|
89
|
-
export const UndiciOptions =
|
|
147
|
+
export const UndiciOptions = Context.Reference<Partial<Undici.Dispatcher.RequestOptions>>(
|
|
90
148
|
"@effect/platform-node/NodeHttpClient/UndiciOptions",
|
|
91
149
|
{ defaultValue: () => ({}) }
|
|
92
150
|
)
|
|
93
151
|
|
|
94
152
|
/**
|
|
95
|
-
*
|
|
153
|
+
* Creates an `HttpClient` that sends requests through the current Undici
|
|
154
|
+
* `Dispatcher`, converts Effect HTTP bodies to Undici bodies, and maps
|
|
155
|
+
* transport and decode failures to `HttpClientError`.
|
|
156
|
+
*
|
|
96
157
|
* @category undici
|
|
158
|
+
* @since 4.0.0
|
|
97
159
|
*/
|
|
98
160
|
export const makeUndici = Effect.gen(function*() {
|
|
99
161
|
const dispatcher = yield* Dispatcher
|
|
@@ -150,7 +212,7 @@ function convertBody(
|
|
|
150
212
|
|
|
151
213
|
function noopErrorHandler(_: any) {}
|
|
152
214
|
|
|
153
|
-
class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
|
|
215
|
+
class UndiciResponse extends Inspectable.Class implements HttpClientResponse, Pipeable {
|
|
154
216
|
readonly [IncomingMessage.TypeId]: typeof IncomingMessage.TypeId
|
|
155
217
|
readonly [Response.TypeId]: typeof Response.TypeId
|
|
156
218
|
readonly request: HttpClientRequest
|
|
@@ -189,8 +251,8 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
|
|
|
189
251
|
return this.cachedCookies = header ? Cookies.fromSetCookie(header) : Cookies.empty
|
|
190
252
|
}
|
|
191
253
|
|
|
192
|
-
get remoteAddress(): string
|
|
193
|
-
return
|
|
254
|
+
get remoteAddress(): Option.Option<string> {
|
|
255
|
+
return Option.none()
|
|
194
256
|
}
|
|
195
257
|
|
|
196
258
|
get stream(): Stream.Stream<Uint8Array, Error.HttpClientError> {
|
|
@@ -207,10 +269,10 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
|
|
|
207
269
|
})
|
|
208
270
|
}
|
|
209
271
|
|
|
210
|
-
get json(): Effect.Effect<
|
|
272
|
+
get json(): Effect.Effect<Schema.Json, Error.HttpClientError> {
|
|
211
273
|
return Effect.flatMap(this.text, (text) =>
|
|
212
274
|
Effect.try({
|
|
213
|
-
try: () => text === "" ? null : JSON.parse(text)
|
|
275
|
+
try: () => text === "" ? null : JSON.parse(text),
|
|
214
276
|
catch: (cause) =>
|
|
215
277
|
new Error.HttpClientError({
|
|
216
278
|
reason: new Error.DecodeError({
|
|
@@ -224,7 +286,10 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
|
|
|
224
286
|
|
|
225
287
|
private textBody?: Effect.Effect<string, Error.HttpClientError>
|
|
226
288
|
get text(): Effect.Effect<string, Error.HttpClientError> {
|
|
227
|
-
|
|
289
|
+
if (this.textBody) {
|
|
290
|
+
return this.textBody
|
|
291
|
+
}
|
|
292
|
+
this.textBody = Effect.tryPromise({
|
|
228
293
|
try: () => this.source.body.text(),
|
|
229
294
|
catch: (cause) =>
|
|
230
295
|
new Error.HttpClientError({
|
|
@@ -235,6 +300,8 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
|
|
|
235
300
|
})
|
|
236
301
|
})
|
|
237
302
|
}).pipe(Effect.cached, Effect.runSync)
|
|
303
|
+
this.arrayBufferBody = Effect.map(this.textBody, (_) => new TextEncoder().encode(_).buffer)
|
|
304
|
+
return this.textBody
|
|
238
305
|
}
|
|
239
306
|
|
|
240
307
|
get urlParamsBody(): Effect.Effect<UrlParams.UrlParams, Error.HttpClientError> {
|
|
@@ -269,7 +336,10 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
|
|
|
269
336
|
|
|
270
337
|
private arrayBufferBody?: Effect.Effect<ArrayBuffer, Error.HttpClientError>
|
|
271
338
|
get arrayBuffer(): Effect.Effect<ArrayBuffer, Error.HttpClientError> {
|
|
272
|
-
|
|
339
|
+
if (this.arrayBufferBody) {
|
|
340
|
+
return this.arrayBufferBody
|
|
341
|
+
}
|
|
342
|
+
this.arrayBufferBody = Effect.tryPromise({
|
|
273
343
|
try: () => this.source.body.arrayBuffer(),
|
|
274
344
|
catch: (cause) =>
|
|
275
345
|
new Error.HttpClientError({
|
|
@@ -280,6 +350,8 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
|
|
|
280
350
|
})
|
|
281
351
|
})
|
|
282
352
|
}).pipe(Effect.cached, Effect.runSync)
|
|
353
|
+
this.textBody = Effect.map(this.arrayBufferBody, (_) => new TextDecoder().decode(_))
|
|
354
|
+
return this.arrayBufferBody
|
|
283
355
|
}
|
|
284
356
|
|
|
285
357
|
toJSON(): unknown {
|
|
@@ -289,21 +361,31 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
|
|
|
289
361
|
status: this.status
|
|
290
362
|
})
|
|
291
363
|
}
|
|
364
|
+
|
|
365
|
+
pipe() {
|
|
366
|
+
return pipeArguments(this, arguments)
|
|
367
|
+
}
|
|
292
368
|
}
|
|
293
369
|
|
|
294
370
|
/**
|
|
295
|
-
*
|
|
371
|
+
* Provides an Undici-backed `HttpClient` using the current `Dispatcher`
|
|
372
|
+
* service.
|
|
373
|
+
*
|
|
296
374
|
* @category Undici
|
|
375
|
+
* @since 4.0.0
|
|
297
376
|
*/
|
|
298
377
|
export const layerUndiciNoDispatcher: Layer.Layer<
|
|
299
378
|
Client.HttpClient,
|
|
300
379
|
never,
|
|
301
380
|
Dispatcher
|
|
302
|
-
> = Client.
|
|
381
|
+
> = Client.layerMergedContext(makeUndici)
|
|
303
382
|
|
|
304
383
|
/**
|
|
305
|
-
*
|
|
384
|
+
* Provides an Undici-backed `HttpClient` together with a scoped default
|
|
385
|
+
* Undici `Agent` dispatcher.
|
|
386
|
+
*
|
|
306
387
|
* @category Undici
|
|
388
|
+
* @since 4.0.0
|
|
307
389
|
*/
|
|
308
390
|
export const layerUndici: Layer.Layer<Client.HttpClient> = Layer.provide(layerUndiciNoDispatcher, layerDispatcher)
|
|
309
391
|
|
|
@@ -312,17 +394,23 @@ export const layerUndici: Layer.Layer<Client.HttpClient> = Layer.provide(layerUn
|
|
|
312
394
|
// -----------------------------------------------------------------------------
|
|
313
395
|
|
|
314
396
|
/**
|
|
315
|
-
*
|
|
397
|
+
* Service tag for the paired Node `http` and `https` agents used by the
|
|
398
|
+
* node:http-backed HTTP client.
|
|
399
|
+
*
|
|
316
400
|
* @category HttpAgent
|
|
401
|
+
* @since 4.0.0
|
|
317
402
|
*/
|
|
318
|
-
export class HttpAgent extends
|
|
403
|
+
export class HttpAgent extends Context.Service<HttpAgent, {
|
|
319
404
|
readonly http: Http.Agent
|
|
320
405
|
readonly https: Https.Agent
|
|
321
406
|
}>()("@effect/platform-node/NodeHttpClient/HttpAgent") {}
|
|
322
407
|
|
|
323
408
|
/**
|
|
324
|
-
*
|
|
409
|
+
* Acquires Node `http` and `https` agents with the supplied options and
|
|
410
|
+
* destroys both agents when the enclosing scope is finalized.
|
|
411
|
+
*
|
|
325
412
|
* @category HttpAgent
|
|
413
|
+
* @since 4.0.0
|
|
326
414
|
*/
|
|
327
415
|
export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<HttpAgent["Service"], never, Scope.Scope> =>
|
|
328
416
|
Effect.zipWith(
|
|
@@ -338,22 +426,32 @@ export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<HttpAgent
|
|
|
338
426
|
)
|
|
339
427
|
|
|
340
428
|
/**
|
|
341
|
-
*
|
|
429
|
+
* Provides the `HttpAgent` service using scoped Node `http` and `https`
|
|
430
|
+
* agents configured with the supplied options.
|
|
431
|
+
*
|
|
342
432
|
* @category HttpAgent
|
|
433
|
+
* @since 4.0.0
|
|
343
434
|
*/
|
|
344
435
|
export const layerAgentOptions: (options?: Https.AgentOptions | undefined) => Layer.Layer<
|
|
345
436
|
HttpAgent
|
|
346
437
|
> = flow(makeAgent, Layer.effect(HttpAgent))
|
|
347
438
|
|
|
348
439
|
/**
|
|
349
|
-
*
|
|
440
|
+
* Provides the `HttpAgent` service using default scoped Node `http` and
|
|
441
|
+
* `https` agents.
|
|
442
|
+
*
|
|
350
443
|
* @category HttpAgent
|
|
444
|
+
* @since 4.0.0
|
|
351
445
|
*/
|
|
352
446
|
export const layerAgent: Layer.Layer<HttpAgent> = layerAgentOptions()
|
|
353
447
|
|
|
354
448
|
/**
|
|
355
|
-
*
|
|
449
|
+
* Creates an `HttpClient` backed by Node `http` and `https`, using the
|
|
450
|
+
* current `HttpAgent`, streaming request bodies, and wrapping Node responses
|
|
451
|
+
* as `HttpClientResponse` values.
|
|
452
|
+
*
|
|
356
453
|
* @category node:http
|
|
454
|
+
* @since 4.0.0
|
|
357
455
|
*/
|
|
358
456
|
export const makeNodeHttp = Effect.gen(function*() {
|
|
359
457
|
const agent = yield* HttpAgent
|
|
@@ -490,7 +588,7 @@ const waitForFinish = (nodeRequest: Http.ClientRequest, request: HttpClientReque
|
|
|
490
588
|
})
|
|
491
589
|
})
|
|
492
590
|
|
|
493
|
-
class NodeHttpResponse extends NodeHttpIncomingMessage<Error.HttpClientError> implements HttpClientResponse {
|
|
591
|
+
class NodeHttpResponse extends NodeHttpIncomingMessage<Error.HttpClientError> implements HttpClientResponse, Pipeable {
|
|
494
592
|
readonly [Response.TypeId]: typeof Response.TypeId
|
|
495
593
|
readonly request: HttpClientRequest
|
|
496
594
|
|
|
@@ -555,20 +653,30 @@ class NodeHttpResponse extends NodeHttpIncomingMessage<Error.HttpClientError> im
|
|
|
555
653
|
status: this.status
|
|
556
654
|
})
|
|
557
655
|
}
|
|
656
|
+
|
|
657
|
+
pipe() {
|
|
658
|
+
return pipeArguments(this, arguments)
|
|
659
|
+
}
|
|
558
660
|
}
|
|
559
661
|
|
|
560
662
|
/**
|
|
561
|
-
*
|
|
663
|
+
* Provides a node:http-backed `HttpClient` using the current `HttpAgent`
|
|
664
|
+
* service.
|
|
665
|
+
*
|
|
562
666
|
* @category node:http
|
|
667
|
+
* @since 4.0.0
|
|
563
668
|
*/
|
|
564
669
|
export const layerNodeHttpNoAgent: Layer.Layer<
|
|
565
670
|
Client.HttpClient,
|
|
566
671
|
never,
|
|
567
672
|
HttpAgent
|
|
568
|
-
> = Client.
|
|
673
|
+
> = Client.layerMergedContext(makeNodeHttp)
|
|
569
674
|
|
|
570
675
|
/**
|
|
571
|
-
*
|
|
676
|
+
* Provides a node:http-backed `HttpClient` together with default scoped Node
|
|
677
|
+
* `http` and `https` agents.
|
|
678
|
+
*
|
|
572
679
|
* @category node:http
|
|
680
|
+
* @since 4.0.0
|
|
573
681
|
*/
|
|
574
682
|
export const layerNodeHttp: Layer.Layer<Client.HttpClient> = Layer.provide(layerNodeHttpNoAgent, layerAgent)
|
|
@@ -1,8 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Adapter base for exposing Node `http.IncomingMessage` values as Effect HTTP
|
|
3
|
+
* incoming messages.
|
|
4
|
+
*
|
|
5
|
+
* Server requests and Node client responses both arrive as Node readable
|
|
6
|
+
* streams with raw header objects, socket metadata, and one-shot body
|
|
7
|
+
* consumption. This module's `NodeHttpIncomingMessage` class keeps the original
|
|
8
|
+
* Node message available while presenting Effect's `HttpIncomingMessage` shape:
|
|
9
|
+
* typed headers, remote address lookup, stream access, and text, JSON,
|
|
10
|
+
* URL-encoded, and array-buffer body readers.
|
|
11
|
+
*
|
|
12
|
+
* **Mental model**
|
|
13
|
+
*
|
|
14
|
+
* The Node message remains the source of truth. The adapter translates headers
|
|
15
|
+
* and remote address on demand, delegates raw streaming to `NodeStream`, and
|
|
16
|
+
* lets subclasses choose how unknown Node errors are mapped into their HTTP
|
|
17
|
+
* error type.
|
|
18
|
+
*
|
|
19
|
+
* **Gotchas**
|
|
20
|
+
*
|
|
21
|
+
* Node request and response bodies are one-shot streams. The `text` and
|
|
22
|
+
* `arrayBuffer` readers are cached and share decoded values with each other,
|
|
23
|
+
* but direct `stream` access is not cached and can consume the underlying Node
|
|
24
|
+
* stream before a decoder reads it. Body readers honor
|
|
25
|
+
* `HttpIncomingMessage.MaxBodySize`.
|
|
26
|
+
*
|
|
27
|
+
* @since 4.0.0
|
|
3
28
|
*/
|
|
4
29
|
import * as Effect from "effect/Effect"
|
|
5
30
|
import * as Inspectable from "effect/Inspectable"
|
|
31
|
+
import * as Option from "effect/Option"
|
|
32
|
+
import type * as Schema from "effect/Schema"
|
|
6
33
|
import type * as Stream from "effect/Stream"
|
|
7
34
|
import * as Headers from "effect/unstable/http/Headers"
|
|
8
35
|
import * as IncomingMessage from "effect/unstable/http/HttpIncomingMessage"
|
|
@@ -11,24 +38,30 @@ import type * as Http from "node:http"
|
|
|
11
38
|
import * as NodeStream from "./NodeStream.ts"
|
|
12
39
|
|
|
13
40
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
41
|
+
* Base adapter from Node `IncomingMessage` to Effect HTTP incoming messages,
|
|
42
|
+
* exposing headers, remote address, stream access, and cached text, JSON, URL
|
|
43
|
+
* parameter, and array-buffer body decoders with caller-provided error mapping.
|
|
44
|
+
*
|
|
45
|
+
* @category constructors
|
|
46
|
+
* @since 4.0.0
|
|
16
47
|
*/
|
|
17
48
|
export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
|
|
18
49
|
implements IncomingMessage.HttpIncomingMessage<E>
|
|
19
50
|
{
|
|
20
51
|
/**
|
|
21
|
-
*
|
|
52
|
+
* Marks this value as an HTTP incoming message for runtime guards.
|
|
53
|
+
*
|
|
54
|
+
* @since 4.0.0
|
|
22
55
|
*/
|
|
23
56
|
readonly [IncomingMessage.TypeId]: typeof IncomingMessage.TypeId
|
|
24
57
|
readonly source: Http.IncomingMessage
|
|
25
58
|
readonly onError: (error: unknown) => E
|
|
26
|
-
readonly remoteAddressOverride?: string | undefined
|
|
59
|
+
readonly remoteAddressOverride?: Option.Option<string> | undefined
|
|
27
60
|
|
|
28
61
|
constructor(
|
|
29
62
|
source: Http.IncomingMessage,
|
|
30
63
|
onError: (error: unknown) => E,
|
|
31
|
-
remoteAddressOverride?: string
|
|
64
|
+
remoteAddressOverride?: Option.Option<string>
|
|
32
65
|
) {
|
|
33
66
|
super()
|
|
34
67
|
this[IncomingMessage.TypeId] = IncomingMessage.TypeId
|
|
@@ -42,7 +75,7 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
|
|
|
42
75
|
}
|
|
43
76
|
|
|
44
77
|
get remoteAddress() {
|
|
45
|
-
return this.remoteAddressOverride ?? this.source.socket.remoteAddress
|
|
78
|
+
return this.remoteAddressOverride ?? Option.fromNullishOr(this.source.socket.remoteAddress)
|
|
46
79
|
}
|
|
47
80
|
|
|
48
81
|
private textEffect: Effect.Effect<string, E> | undefined
|
|
@@ -52,7 +85,7 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
|
|
|
52
85
|
}
|
|
53
86
|
this.textEffect = Effect.runSync(Effect.cached(
|
|
54
87
|
Effect.flatMap(
|
|
55
|
-
IncomingMessage.MaxBodySize
|
|
88
|
+
IncomingMessage.MaxBodySize,
|
|
56
89
|
(maxBodySize) =>
|
|
57
90
|
NodeStream.toString(() => this.source, {
|
|
58
91
|
onError: this.onError,
|
|
@@ -60,6 +93,7 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
|
|
|
60
93
|
})
|
|
61
94
|
)
|
|
62
95
|
))
|
|
96
|
+
this.arrayBufferEffect = Effect.map(this.textEffect, (_) => new TextEncoder().encode(_).buffer)
|
|
63
97
|
return this.textEffect
|
|
64
98
|
}
|
|
65
99
|
|
|
@@ -67,15 +101,15 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
|
|
|
67
101
|
return Effect.runSync(this.text)
|
|
68
102
|
}
|
|
69
103
|
|
|
70
|
-
get json(): Effect.Effect<
|
|
104
|
+
get json(): Effect.Effect<Schema.Json, E> {
|
|
71
105
|
return Effect.flatMap(this.text, (text) =>
|
|
72
106
|
Effect.try({
|
|
73
|
-
try: () => text === "" ? null : JSON.parse(text)
|
|
107
|
+
try: () => text === "" ? null : JSON.parse(text),
|
|
74
108
|
catch: this.onError
|
|
75
109
|
}))
|
|
76
110
|
}
|
|
77
111
|
|
|
78
|
-
get jsonUnsafe():
|
|
112
|
+
get jsonUnsafe(): Schema.Json {
|
|
79
113
|
return Effect.runSync(this.json)
|
|
80
114
|
}
|
|
81
115
|
|
|
@@ -94,12 +128,21 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
|
|
|
94
128
|
})
|
|
95
129
|
}
|
|
96
130
|
|
|
131
|
+
private arrayBufferEffect: Effect.Effect<ArrayBuffer, E> | undefined
|
|
97
132
|
get arrayBuffer(): Effect.Effect<ArrayBuffer, E> {
|
|
98
|
-
|
|
133
|
+
if (this.arrayBufferEffect) {
|
|
134
|
+
return this.arrayBufferEffect
|
|
135
|
+
}
|
|
136
|
+
this.arrayBufferEffect = Effect.withFiber((fiber) =>
|
|
99
137
|
NodeStream.toArrayBuffer(() => this.source, {
|
|
100
138
|
onError: this.onError,
|
|
101
139
|
maxBytes: fiber.getRef(IncomingMessage.MaxBodySize)
|
|
102
140
|
})
|
|
141
|
+
).pipe(
|
|
142
|
+
Effect.cached,
|
|
143
|
+
Effect.runSync
|
|
103
144
|
)
|
|
145
|
+
this.textEffect = Effect.map(this.arrayBufferEffect, (_) => new TextDecoder().decode(_))
|
|
146
|
+
return this.arrayBufferEffect
|
|
104
147
|
}
|
|
105
148
|
}
|
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),
|