@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.
Files changed (119) hide show
  1. package/dist/Mime.d.ts +3 -3
  2. package/dist/Mime.js +3 -3
  3. package/dist/NodeChildProcessSpawner.d.ts +1 -1
  4. package/dist/NodeChildProcessSpawner.js +1 -1
  5. package/dist/NodeClusterHttp.d.ts +49 -7
  6. package/dist/NodeClusterHttp.d.ts.map +1 -1
  7. package/dist/NodeClusterHttp.js +20 -10
  8. package/dist/NodeClusterHttp.js.map +1 -1
  9. package/dist/NodeClusterSocket.d.ts +57 -11
  10. package/dist/NodeClusterSocket.d.ts.map +1 -1
  11. package/dist/NodeClusterSocket.js +57 -11
  12. package/dist/NodeClusterSocket.js.map +1 -1
  13. package/dist/NodeCrypto.d.ts +10 -0
  14. package/dist/NodeCrypto.d.ts.map +1 -0
  15. package/dist/NodeCrypto.js +14 -0
  16. package/dist/NodeCrypto.js.map +1 -0
  17. package/dist/NodeFileSystem.d.ts +4 -2
  18. package/dist/NodeFileSystem.d.ts.map +1 -1
  19. package/dist/NodeFileSystem.js +22 -3
  20. package/dist/NodeFileSystem.js.map +1 -1
  21. package/dist/NodeHttpClient.d.ts +102 -24
  22. package/dist/NodeHttpClient.d.ts.map +1 -1
  23. package/dist/NodeHttpClient.js +124 -28
  24. package/dist/NodeHttpClient.js.map +1 -1
  25. package/dist/NodeHttpIncomingMessage.d.ts +30 -9
  26. package/dist/NodeHttpIncomingMessage.d.ts.map +1 -1
  27. package/dist/NodeHttpIncomingMessage.js +34 -8
  28. package/dist/NodeHttpIncomingMessage.js.map +1 -1
  29. package/dist/NodeHttpPlatform.d.ts +10 -4
  30. package/dist/NodeHttpPlatform.d.ts.map +1 -1
  31. package/dist/NodeHttpPlatform.js +34 -7
  32. package/dist/NodeHttpPlatform.js.map +1 -1
  33. package/dist/NodeHttpServer.d.ts +56 -16
  34. package/dist/NodeHttpServer.d.ts.map +1 -1
  35. package/dist/NodeHttpServer.js +117 -49
  36. package/dist/NodeHttpServer.js.map +1 -1
  37. package/dist/NodeHttpServerRequest.d.ts +29 -3
  38. package/dist/NodeHttpServerRequest.d.ts.map +1 -1
  39. package/dist/NodeHttpServerRequest.js +9 -2
  40. package/dist/NodeHttpServerRequest.js.map +1 -1
  41. package/dist/NodeMultipart.d.ts +32 -4
  42. package/dist/NodeMultipart.d.ts.map +1 -1
  43. package/dist/NodeMultipart.js +32 -4
  44. package/dist/NodeMultipart.js.map +1 -1
  45. package/dist/NodePath.d.ts +15 -6
  46. package/dist/NodePath.d.ts.map +1 -1
  47. package/dist/NodePath.js +30 -7
  48. package/dist/NodePath.js.map +1 -1
  49. package/dist/NodeRedis.d.ts +38 -9
  50. package/dist/NodeRedis.d.ts.map +1 -1
  51. package/dist/NodeRedis.js +41 -12
  52. package/dist/NodeRedis.js.map +1 -1
  53. package/dist/NodeRuntime.d.ts +27 -36
  54. package/dist/NodeRuntime.d.ts.map +1 -1
  55. package/dist/NodeRuntime.js +24 -13
  56. package/dist/NodeRuntime.js.map +1 -1
  57. package/dist/NodeServices.d.ts +29 -5
  58. package/dist/NodeServices.d.ts.map +1 -1
  59. package/dist/NodeServices.js +7 -3
  60. package/dist/NodeServices.js.map +1 -1
  61. package/dist/NodeSink.d.ts +2 -2
  62. package/dist/NodeSink.js +2 -2
  63. package/dist/NodeSocket.d.ts +18 -3
  64. package/dist/NodeSocket.d.ts.map +1 -1
  65. package/dist/NodeSocket.js +36 -4
  66. package/dist/NodeSocket.js.map +1 -1
  67. package/dist/NodeSocketServer.d.ts +2 -2
  68. package/dist/NodeSocketServer.js +2 -2
  69. package/dist/NodeStdio.d.ts +5 -2
  70. package/dist/NodeStdio.d.ts.map +1 -1
  71. package/dist/NodeStdio.js +22 -3
  72. package/dist/NodeStdio.js.map +1 -1
  73. package/dist/NodeStream.d.ts +2 -2
  74. package/dist/NodeStream.js +2 -2
  75. package/dist/NodeTerminal.d.ts +8 -2
  76. package/dist/NodeTerminal.d.ts.map +1 -1
  77. package/dist/NodeTerminal.js +19 -3
  78. package/dist/NodeTerminal.js.map +1 -1
  79. package/dist/NodeWorker.d.ts +9 -2
  80. package/dist/NodeWorker.d.ts.map +1 -1
  81. package/dist/NodeWorker.js +31 -6
  82. package/dist/NodeWorker.js.map +1 -1
  83. package/dist/NodeWorkerRunner.d.ts +5 -1
  84. package/dist/NodeWorkerRunner.d.ts.map +1 -1
  85. package/dist/NodeWorkerRunner.js +27 -5
  86. package/dist/NodeWorkerRunner.js.map +1 -1
  87. package/dist/Undici.d.ts +3 -3
  88. package/dist/Undici.js +3 -3
  89. package/dist/index.d.ts +376 -24
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +376 -24
  92. package/dist/index.js.map +1 -1
  93. package/package.json +9 -9
  94. package/src/Mime.ts +3 -3
  95. package/src/NodeChildProcessSpawner.ts +1 -1
  96. package/src/NodeClusterHttp.ts +54 -11
  97. package/src/NodeClusterSocket.ts +57 -11
  98. package/src/NodeCrypto.ts +16 -0
  99. package/src/NodeFileSystem.ts +22 -3
  100. package/src/NodeHttpClient.ts +132 -33
  101. package/src/NodeHttpIncomingMessage.ts +42 -12
  102. package/src/NodeHttpPlatform.ts +35 -6
  103. package/src/NodeHttpServer.ts +139 -53
  104. package/src/NodeHttpServerRequest.ts +29 -3
  105. package/src/NodeMultipart.ts +32 -4
  106. package/src/NodePath.ts +30 -7
  107. package/src/NodeRedis.ts +43 -14
  108. package/src/NodeRuntime.ts +42 -37
  109. package/src/NodeServices.ts +31 -5
  110. package/src/NodeSink.ts +2 -2
  111. package/src/NodeSocket.ts +41 -4
  112. package/src/NodeSocketServer.ts +2 -2
  113. package/src/NodeStdio.ts +22 -3
  114. package/src/NodeStream.ts +2 -2
  115. package/src/NodeTerminal.ts +19 -3
  116. package/src/NodeWorker.ts +31 -6
  117. package/src/NodeWorkerRunner.ts +27 -5
  118. package/src/Undici.ts +3 -3
  119. package/src/index.ts +377 -24
@@ -1,5 +1,34 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * The `NodeClusterSocket` module provides the Node.js socket transport for
3
+ * Effect Cluster runners. It wires `SocketRunner` to Node TCP sockets, supplies
4
+ * RPC client and server protocol layers, and builds a complete sharding layer
5
+ * with serialization, runner health, runner storage, and message storage.
6
+ *
7
+ * **Common tasks**
8
+ *
9
+ * - Run a Node process as a cluster runner over raw TCP sockets with
10
+ * {@link layer}
11
+ * - Connect a client-only process to an existing socket cluster without
12
+ * starting a runner server
13
+ * - Use SQL-backed storage for durable multi-process clusters, `local` storage
14
+ * for short-lived development, or `byo` storage when the deployment owns the
15
+ * persistence boundary
16
+ * - Check runner health with socket pings or Kubernetes pod readiness through
17
+ * {@link layerK8sHttpClient}
18
+ *
19
+ * **Gotchas**
20
+ *
21
+ * - `runnerAddress` is the host and port advertised to other runners; set
22
+ * `runnerListenAddress` when the local bind address differs from the
23
+ * externally reachable address
24
+ * - The socket transport is point-to-point RPC, not cluster gossip: runner
25
+ * membership, shard ownership, and persisted delivery are coordinated through
26
+ * `RunnerStorage`, `MessageStorage`, and `RunnerHealth`
27
+ * - `clientOnly` does not start a socket server or receive shard assignments
28
+ * - Ping health checks use the same socket protocol, so unreachable ports,
29
+ * firewalls, or serialization mismatches can make a runner appear unhealthy
30
+ *
31
+ * @since 4.0.0
3
32
  */
4
33
  import { layerClientProtocol, layerSocketServer } from "@effect/platform-node-shared/NodeClusterSocket"
5
34
  import type { ConfigError } from "effect/Config"
@@ -25,20 +54,30 @@ import * as Undici from "./Undici.ts"
25
54
 
26
55
  export {
27
56
  /**
28
- * @since 1.0.0
29
- * @category Re-exports
57
+ * Provides the cluster `RpcClientProtocol` using the shared socket client
58
+ * implementation.
59
+ *
60
+ * @category re-exports
61
+ * @since 4.0.0
30
62
  */
31
63
  layerClientProtocol,
32
64
  /**
33
- * @since 1.0.0
34
- * @category Re-exports
65
+ * Provides the socket server used by Node cluster runners through the shared
66
+ * socket server implementation.
67
+ *
68
+ * @category re-exports
69
+ * @since 4.0.0
35
70
  */
36
71
  layerSocketServer
37
72
  }
38
73
 
39
74
  /**
40
- * @since 1.0.0
41
- * @category Layers
75
+ * Builds the Node cluster socket sharding layer, configuring RPC
76
+ * serialization, message storage, runner health checks, and optional
77
+ * client-only mode.
78
+ *
79
+ * @category layers
80
+ * @since 4.0.0
42
81
  */
43
82
  export const layer = <
44
83
  const ClientOnly extends boolean = false,
@@ -111,8 +150,12 @@ export const layer = <
111
150
  }
112
151
 
113
152
  /**
114
- * @since 1.0.0
115
- * @category Layers
153
+ * Provides an Undici dispatcher for Kubernetes API calls, using the service
154
+ * account CA certificate when it is available and falling back to the default
155
+ * dispatcher otherwise.
156
+ *
157
+ * @category layers
158
+ * @since 4.0.0
116
159
  */
117
160
  export const layerDispatcherK8s: Layer.Layer<NodeHttpClient.Dispatcher> = Layer.effect(NodeHttpClient.Dispatcher)(
118
161
  Effect.gen(function*() {
@@ -140,8 +183,11 @@ export const layerDispatcherK8s: Layer.Layer<NodeHttpClient.Dispatcher> = Layer.
140
183
  )
141
184
 
142
185
  /**
143
- * @since 1.0.0
144
- * @category Layers
186
+ * Provides a `K8sHttpClient` backed by the Undici HTTP client and the
187
+ * Kubernetes-aware dispatcher.
188
+ *
189
+ * @category layers
190
+ * @since 4.0.0
145
191
  */
146
192
  export const layerK8sHttpClient: Layer.Layer<K8sHttpClient.K8sHttpClient> = K8sHttpClient.layer.pipe(
147
193
  Layer.provide(Layer.fresh(NodeHttpClient.layerUndiciNoDispatcher)),
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Node.js platform Crypto service layer.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+ import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
7
+ import type * as Crypto from "effect/Crypto"
8
+ import type * as Layer from "effect/Layer"
9
+
10
+ /**
11
+ * A layer that provides the Node.js Crypto service implementation.
12
+ *
13
+ * @category layers
14
+ * @since 1.0.0
15
+ */
16
+ export const layer: Layer.Layer<Crypto.Crypto> = NodeCrypto.layer
@@ -1,12 +1,31 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Provides the Node.js `FileSystem` layer for Effect programs.
3
+ *
4
+ * Use this module when a Node application, CLI, script, or test needs to
5
+ * satisfy the `FileSystem` service with real filesystem access for reading and
6
+ * writing files, creating directories and temporary files, inspecting metadata,
7
+ * managing links, or watching paths for changes.
8
+ *
9
+ * This module only exposes the Node-backed layer; filesystem operations are
10
+ * accessed through the `FileSystem` service from `effect/FileSystem`. Provide
11
+ * `NodeFileSystem.layer` at the edge of the program, or use
12
+ * `NodeServices.layer` when you also want the standard Node path, stdio,
13
+ * terminal, and child process services. The implementation is shared with
14
+ * other Node-compatible platform packages, so optional services such as
15
+ * `FileSystem.WatchBackend` are honored when present; otherwise file watching
16
+ * follows Node's `node:fs.watch` behavior. Paths are interpreted by Node, so
17
+ * relative paths use the current working directory and platform path rules.
18
+ *
19
+ * @since 4.0.0
3
20
  */
4
21
  import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem"
5
22
  import type { FileSystem } from "effect/FileSystem"
6
23
  import type * as Layer from "effect/Layer"
7
24
 
8
25
  /**
9
- * @since 1.0.0
10
- * @category layer
26
+ * Provides the `FileSystem` service backed by Node filesystem APIs.
27
+ *
28
+ * @category layers
29
+ * @since 4.0.0
11
30
  */
12
31
  export const layer: Layer.Layer<FileSystem> = NodeFileSystem.layer
@@ -1,12 +1,41 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Node.js implementations of the Effect `HttpClient`.
3
+ *
4
+ * This module provides the Node-specific layers and constructors for sending
5
+ * Effect HTTP client requests. It re-exports the fetch-based client for
6
+ * programs that want to use `globalThis.fetch`, provides an Undici-backed
7
+ * client for applications that need Undici dispatcher control, and provides a
8
+ * lower-level `node:http` / `node:https` client for integrations that need
9
+ * native Node agent configuration.
10
+ *
11
+ * Use these clients in server-side applications, CLIs, tests, and integrations
12
+ * where requests should participate in Effect resource management, interruption,
13
+ * streaming, and typed transport / decode errors. The Undici path sends each
14
+ * request through the current `Dispatcher`; `layerUndici` owns a scoped
15
+ * `Agent`, while `dispatcherLayerGlobal` uses Undici's process-global dispatcher
16
+ * without destroying it. The `node:http` path uses separate scoped HTTP and
17
+ * HTTPS agents, making it the right choice when native agent options such as
18
+ * TLS, proxy, keep-alive, or socket behavior need to be configured directly.
19
+ *
20
+ * The backends are not completely interchangeable. Fetch, Undici, and
21
+ * `node:http` expose different agent and dispatcher hooks, body implementations,
22
+ * abort behavior, upgrade support, and response body readers. This module
23
+ * converts Effect request bodies to the selected runtime representation:
24
+ * streams remain streaming, `FormData` may contribute generated content headers,
25
+ * and body read failures are reported as `HttpClientError` decode or transport
26
+ * errors.
27
+ *
28
+ * @since 4.0.0
3
29
  */
30
+ import * as Context from "effect/Context"
4
31
  import * as Effect from "effect/Effect"
5
32
  import { flow } from "effect/Function"
6
33
  import * as Inspectable from "effect/Inspectable"
7
34
  import * as Layer from "effect/Layer"
35
+ import * as Option from "effect/Option"
36
+ import { type Pipeable, pipeArguments } from "effect/Pipeable"
37
+ import type * as Schema from "effect/Schema"
8
38
  import type * as Scope from "effect/Scope"
9
- import * as ServiceMap from "effect/ServiceMap"
10
39
  import * as Stream from "effect/Stream"
11
40
  import * as Cookies from "effect/unstable/http/Cookies"
12
41
  import * as Headers from "effect/unstable/http/Headers"
@@ -33,18 +62,24 @@ import * as Undici from "./Undici.ts"
33
62
 
34
63
  export {
35
64
  /**
36
- * @since 1.0.0
65
+ * Fetch-based HTTP client implementation for Node.js.
66
+ *
37
67
  * @category Fetch
68
+ * @since 4.0.0
38
69
  */
39
70
  Fetch,
40
71
  /**
41
- * @since 1.0.0
72
+ * Layer that provides the fetch-based HTTP client implementation.
73
+ *
42
74
  * @category Fetch
75
+ * @since 4.0.0
43
76
  */
44
77
  layer as layerFetch,
45
78
  /**
46
- * @since 1.0.0
79
+ * Request initialization options accepted by the fetch-based HTTP client.
80
+ *
47
81
  * @category Fetch
82
+ * @since 4.0.0
48
83
  */
49
84
  RequestInit
50
85
  } from "effect/unstable/http/FetchHttpClient"
@@ -54,16 +89,22 @@ export {
54
89
  // -----------------------------------------------------------------------------
55
90
 
56
91
  /**
57
- * @since 1.0.0
92
+ * Service tag for the Undici `Dispatcher` used by the Undici-backed HTTP
93
+ * client.
94
+ *
58
95
  * @category Dispatcher
96
+ * @since 4.0.0
59
97
  */
60
- export class Dispatcher extends ServiceMap.Service<Dispatcher, Undici.Dispatcher>()(
98
+ export class Dispatcher extends Context.Service<Dispatcher, Undici.Dispatcher>()(
61
99
  "@effect/platform-node/NodeHttpClient/Dispatcher"
62
100
  ) {}
63
101
 
64
102
  /**
65
- * @since 1.0.0
103
+ * Acquires a new Undici `Agent` dispatcher and destroys it when the enclosing
104
+ * scope is finalized.
105
+ *
66
106
  * @category Dispatcher
107
+ * @since 4.0.0
67
108
  */
68
109
  export const makeDispatcher: Effect.Effect<Undici.Dispatcher, never, Scope.Scope> = Effect.acquireRelease(
69
110
  Effect.sync(() => new Undici.Agent()),
@@ -71,29 +112,41 @@ export const makeDispatcher: Effect.Effect<Undici.Dispatcher, never, Scope.Scope
71
112
  )
72
113
 
73
114
  /**
74
- * @since 1.0.0
115
+ * Provides the `Dispatcher` service using a scoped Undici `Agent`.
116
+ *
75
117
  * @category Dispatcher
118
+ * @since 4.0.0
76
119
  */
77
120
  export const layerDispatcher: Layer.Layer<Dispatcher> = Layer.effect(Dispatcher)(makeDispatcher)
78
121
 
79
122
  /**
80
- * @since 1.0.0
123
+ * Provides the `Dispatcher` service from Undici's process-global dispatcher,
124
+ * without creating or owning a new agent.
125
+ *
81
126
  * @category Dispatcher
127
+ * @since 4.0.0
82
128
  */
83
129
  export const dispatcherLayerGlobal: Layer.Layer<Dispatcher> = Layer.sync(Dispatcher)(() => Undici.getGlobalDispatcher())
84
130
 
85
131
  /**
86
- * @since 1.0.0
132
+ * Fiber reference containing default Undici request options applied to requests
133
+ * sent by `makeUndici`.
134
+ *
87
135
  * @category undici
136
+ * @since 4.0.0
88
137
  */
89
- export const UndiciOptions = ServiceMap.Reference<Partial<Undici.Dispatcher.RequestOptions>>(
138
+ export const UndiciOptions = Context.Reference<Partial<Undici.Dispatcher.RequestOptions>>(
90
139
  "@effect/platform-node/NodeHttpClient/UndiciOptions",
91
140
  { defaultValue: () => ({}) }
92
141
  )
93
142
 
94
143
  /**
95
- * @since 1.0.0
144
+ * Creates an `HttpClient` that sends requests through the current Undici
145
+ * `Dispatcher`, converts Effect HTTP bodies to Undici bodies, and maps
146
+ * transport and decode failures to `HttpClientError`.
147
+ *
96
148
  * @category undici
149
+ * @since 4.0.0
97
150
  */
98
151
  export const makeUndici = Effect.gen(function*() {
99
152
  const dispatcher = yield* Dispatcher
@@ -150,7 +203,7 @@ function convertBody(
150
203
 
151
204
  function noopErrorHandler(_: any) {}
152
205
 
153
- class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
206
+ class UndiciResponse extends Inspectable.Class implements HttpClientResponse, Pipeable {
154
207
  readonly [IncomingMessage.TypeId]: typeof IncomingMessage.TypeId
155
208
  readonly [Response.TypeId]: typeof Response.TypeId
156
209
  readonly request: HttpClientRequest
@@ -189,8 +242,8 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
189
242
  return this.cachedCookies = header ? Cookies.fromSetCookie(header) : Cookies.empty
190
243
  }
191
244
 
192
- get remoteAddress(): string | undefined {
193
- return undefined
245
+ get remoteAddress(): Option.Option<string> {
246
+ return Option.none()
194
247
  }
195
248
 
196
249
  get stream(): Stream.Stream<Uint8Array, Error.HttpClientError> {
@@ -207,10 +260,10 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
207
260
  })
208
261
  }
209
262
 
210
- get json(): Effect.Effect<unknown, Error.HttpClientError> {
263
+ get json(): Effect.Effect<Schema.Json, Error.HttpClientError> {
211
264
  return Effect.flatMap(this.text, (text) =>
212
265
  Effect.try({
213
- try: () => text === "" ? null : JSON.parse(text) as unknown,
266
+ try: () => text === "" ? null : JSON.parse(text),
214
267
  catch: (cause) =>
215
268
  new Error.HttpClientError({
216
269
  reason: new Error.DecodeError({
@@ -224,7 +277,10 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
224
277
 
225
278
  private textBody?: Effect.Effect<string, Error.HttpClientError>
226
279
  get text(): Effect.Effect<string, Error.HttpClientError> {
227
- return this.textBody ??= Effect.tryPromise({
280
+ if (this.textBody) {
281
+ return this.textBody
282
+ }
283
+ this.textBody = Effect.tryPromise({
228
284
  try: () => this.source.body.text(),
229
285
  catch: (cause) =>
230
286
  new Error.HttpClientError({
@@ -235,6 +291,8 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
235
291
  })
236
292
  })
237
293
  }).pipe(Effect.cached, Effect.runSync)
294
+ this.arrayBufferBody = Effect.map(this.textBody, (_) => new TextEncoder().encode(_).buffer)
295
+ return this.textBody
238
296
  }
239
297
 
240
298
  get urlParamsBody(): Effect.Effect<UrlParams.UrlParams, Error.HttpClientError> {
@@ -269,7 +327,10 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
269
327
 
270
328
  private arrayBufferBody?: Effect.Effect<ArrayBuffer, Error.HttpClientError>
271
329
  get arrayBuffer(): Effect.Effect<ArrayBuffer, Error.HttpClientError> {
272
- return this.arrayBufferBody ??= Effect.tryPromise({
330
+ if (this.arrayBufferBody) {
331
+ return this.arrayBufferBody
332
+ }
333
+ this.arrayBufferBody = Effect.tryPromise({
273
334
  try: () => this.source.body.arrayBuffer(),
274
335
  catch: (cause) =>
275
336
  new Error.HttpClientError({
@@ -280,6 +341,8 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
280
341
  })
281
342
  })
282
343
  }).pipe(Effect.cached, Effect.runSync)
344
+ this.textBody = Effect.map(this.arrayBufferBody, (_) => new TextDecoder().decode(_))
345
+ return this.arrayBufferBody
283
346
  }
284
347
 
285
348
  toJSON(): unknown {
@@ -289,21 +352,31 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse {
289
352
  status: this.status
290
353
  })
291
354
  }
355
+
356
+ pipe() {
357
+ return pipeArguments(this, arguments)
358
+ }
292
359
  }
293
360
 
294
361
  /**
295
- * @since 1.0.0
362
+ * Provides an Undici-backed `HttpClient` using the current `Dispatcher`
363
+ * service.
364
+ *
296
365
  * @category Undici
366
+ * @since 4.0.0
297
367
  */
298
368
  export const layerUndiciNoDispatcher: Layer.Layer<
299
369
  Client.HttpClient,
300
370
  never,
301
371
  Dispatcher
302
- > = Client.layerMergedServices(makeUndici)
372
+ > = Client.layerMergedContext(makeUndici)
303
373
 
304
374
  /**
305
- * @since 1.0.0
375
+ * Provides an Undici-backed `HttpClient` together with a scoped default
376
+ * Undici `Agent` dispatcher.
377
+ *
306
378
  * @category Undici
379
+ * @since 4.0.0
307
380
  */
308
381
  export const layerUndici: Layer.Layer<Client.HttpClient> = Layer.provide(layerUndiciNoDispatcher, layerDispatcher)
309
382
 
@@ -312,17 +385,23 @@ export const layerUndici: Layer.Layer<Client.HttpClient> = Layer.provide(layerUn
312
385
  // -----------------------------------------------------------------------------
313
386
 
314
387
  /**
315
- * @since 1.0.0
388
+ * Service tag for the paired Node `http` and `https` agents used by the
389
+ * node:http-backed HTTP client.
390
+ *
316
391
  * @category HttpAgent
392
+ * @since 4.0.0
317
393
  */
318
- export class HttpAgent extends ServiceMap.Service<HttpAgent, {
394
+ export class HttpAgent extends Context.Service<HttpAgent, {
319
395
  readonly http: Http.Agent
320
396
  readonly https: Https.Agent
321
397
  }>()("@effect/platform-node/NodeHttpClient/HttpAgent") {}
322
398
 
323
399
  /**
324
- * @since 1.0.0
400
+ * Acquires Node `http` and `https` agents with the supplied options and
401
+ * destroys both agents when the enclosing scope is finalized.
402
+ *
325
403
  * @category HttpAgent
404
+ * @since 4.0.0
326
405
  */
327
406
  export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<HttpAgent["Service"], never, Scope.Scope> =>
328
407
  Effect.zipWith(
@@ -338,22 +417,32 @@ export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<HttpAgent
338
417
  )
339
418
 
340
419
  /**
341
- * @since 1.0.0
420
+ * Provides the `HttpAgent` service using scoped Node `http` and `https`
421
+ * agents configured with the supplied options.
422
+ *
342
423
  * @category HttpAgent
424
+ * @since 4.0.0
343
425
  */
344
426
  export const layerAgentOptions: (options?: Https.AgentOptions | undefined) => Layer.Layer<
345
427
  HttpAgent
346
428
  > = flow(makeAgent, Layer.effect(HttpAgent))
347
429
 
348
430
  /**
349
- * @since 1.0.0
431
+ * Provides the `HttpAgent` service using default scoped Node `http` and
432
+ * `https` agents.
433
+ *
350
434
  * @category HttpAgent
435
+ * @since 4.0.0
351
436
  */
352
437
  export const layerAgent: Layer.Layer<HttpAgent> = layerAgentOptions()
353
438
 
354
439
  /**
355
- * @since 1.0.0
440
+ * Creates an `HttpClient` backed by Node `http` and `https`, using the
441
+ * current `HttpAgent`, streaming request bodies, and wrapping Node responses
442
+ * as `HttpClientResponse` values.
443
+ *
356
444
  * @category node:http
445
+ * @since 4.0.0
357
446
  */
358
447
  export const makeNodeHttp = Effect.gen(function*() {
359
448
  const agent = yield* HttpAgent
@@ -490,7 +579,7 @@ const waitForFinish = (nodeRequest: Http.ClientRequest, request: HttpClientReque
490
579
  })
491
580
  })
492
581
 
493
- class NodeHttpResponse extends NodeHttpIncomingMessage<Error.HttpClientError> implements HttpClientResponse {
582
+ class NodeHttpResponse extends NodeHttpIncomingMessage<Error.HttpClientError> implements HttpClientResponse, Pipeable {
494
583
  readonly [Response.TypeId]: typeof Response.TypeId
495
584
  readonly request: HttpClientRequest
496
585
 
@@ -555,20 +644,30 @@ class NodeHttpResponse extends NodeHttpIncomingMessage<Error.HttpClientError> im
555
644
  status: this.status
556
645
  })
557
646
  }
647
+
648
+ pipe() {
649
+ return pipeArguments(this, arguments)
650
+ }
558
651
  }
559
652
 
560
653
  /**
561
- * @since 1.0.0
654
+ * Provides a node:http-backed `HttpClient` using the current `HttpAgent`
655
+ * service.
656
+ *
562
657
  * @category node:http
658
+ * @since 4.0.0
563
659
  */
564
660
  export const layerNodeHttpNoAgent: Layer.Layer<
565
661
  Client.HttpClient,
566
662
  never,
567
663
  HttpAgent
568
- > = Client.layerMergedServices(makeNodeHttp)
664
+ > = Client.layerMergedContext(makeNodeHttp)
569
665
 
570
666
  /**
571
- * @since 1.0.0
667
+ * Provides a node:http-backed `HttpClient` together with default scoped Node
668
+ * `http` and `https` agents.
669
+ *
572
670
  * @category node:http
671
+ * @since 4.0.0
573
672
  */
574
673
  export const layerNodeHttp: Layer.Layer<Client.HttpClient> = Layer.provide(layerNodeHttpNoAgent, layerAgent)
@@ -1,8 +1,22 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Utilities for adapting Node `http.IncomingMessage` values to the Effect HTTP
3
+ * incoming message interface used by the platform Node server and client
4
+ * implementations.
5
+ *
6
+ * This module is useful when code needs to keep access to Node's request or
7
+ * response object while also exposing Effect's typed headers, remote address,
8
+ * body decoders, and stream interface. The body helpers consume Node's readable
9
+ * stream, cache decoded text and array-buffer results, and honor the
10
+ * `HttpIncomingMessage.MaxBodySize` fiber ref. Prefer a single body access
11
+ * strategy per message: raw `stream` access is not cached, and Node request
12
+ * bodies cannot be replayed once the underlying stream has been consumed.
13
+ *
14
+ * @since 4.0.0
3
15
  */
4
16
  import * as Effect from "effect/Effect"
5
17
  import * as Inspectable from "effect/Inspectable"
18
+ import * as Option from "effect/Option"
19
+ import type * as Schema from "effect/Schema"
6
20
  import type * as Stream from "effect/Stream"
7
21
  import * as Headers from "effect/unstable/http/Headers"
8
22
  import * as IncomingMessage from "effect/unstable/http/HttpIncomingMessage"
@@ -11,24 +25,30 @@ import type * as Http from "node:http"
11
25
  import * as NodeStream from "./NodeStream.ts"
12
26
 
13
27
  /**
14
- * @since 1.0.0
15
- * @category Constructors
28
+ * Base adapter from Node `IncomingMessage` to Effect HTTP incoming messages,
29
+ * exposing headers, remote address, stream access, and cached text, JSON, URL
30
+ * parameter, and array-buffer body decoders with caller-provided error mapping.
31
+ *
32
+ * @category constructors
33
+ * @since 4.0.0
16
34
  */
17
35
  export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
18
36
  implements IncomingMessage.HttpIncomingMessage<E>
19
37
  {
20
38
  /**
21
- * @since 1.0.0
39
+ * Marks this value as an HTTP incoming message for runtime guards.
40
+ *
41
+ * @since 4.0.0
22
42
  */
23
43
  readonly [IncomingMessage.TypeId]: typeof IncomingMessage.TypeId
24
44
  readonly source: Http.IncomingMessage
25
45
  readonly onError: (error: unknown) => E
26
- readonly remoteAddressOverride?: string | undefined
46
+ readonly remoteAddressOverride?: Option.Option<string> | undefined
27
47
 
28
48
  constructor(
29
49
  source: Http.IncomingMessage,
30
50
  onError: (error: unknown) => E,
31
- remoteAddressOverride?: string
51
+ remoteAddressOverride?: Option.Option<string>
32
52
  ) {
33
53
  super()
34
54
  this[IncomingMessage.TypeId] = IncomingMessage.TypeId
@@ -42,7 +62,7 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
42
62
  }
43
63
 
44
64
  get remoteAddress() {
45
- return this.remoteAddressOverride ?? this.source.socket.remoteAddress
65
+ return this.remoteAddressOverride ?? Option.fromNullishOr(this.source.socket.remoteAddress)
46
66
  }
47
67
 
48
68
  private textEffect: Effect.Effect<string, E> | undefined
@@ -52,7 +72,7 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
52
72
  }
53
73
  this.textEffect = Effect.runSync(Effect.cached(
54
74
  Effect.flatMap(
55
- IncomingMessage.MaxBodySize.asEffect(),
75
+ IncomingMessage.MaxBodySize,
56
76
  (maxBodySize) =>
57
77
  NodeStream.toString(() => this.source, {
58
78
  onError: this.onError,
@@ -60,6 +80,7 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
60
80
  })
61
81
  )
62
82
  ))
83
+ this.arrayBufferEffect = Effect.map(this.textEffect, (_) => new TextEncoder().encode(_).buffer)
63
84
  return this.textEffect
64
85
  }
65
86
 
@@ -67,15 +88,15 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
67
88
  return Effect.runSync(this.text)
68
89
  }
69
90
 
70
- get json(): Effect.Effect<unknown, E> {
91
+ get json(): Effect.Effect<Schema.Json, E> {
71
92
  return Effect.flatMap(this.text, (text) =>
72
93
  Effect.try({
73
- try: () => text === "" ? null : JSON.parse(text) as unknown,
94
+ try: () => text === "" ? null : JSON.parse(text),
74
95
  catch: this.onError
75
96
  }))
76
97
  }
77
98
 
78
- get jsonUnsafe(): unknown {
99
+ get jsonUnsafe(): Schema.Json {
79
100
  return Effect.runSync(this.json)
80
101
  }
81
102
 
@@ -94,12 +115,21 @@ export abstract class NodeHttpIncomingMessage<E> extends Inspectable.Class
94
115
  })
95
116
  }
96
117
 
118
+ private arrayBufferEffect: Effect.Effect<ArrayBuffer, E> | undefined
97
119
  get arrayBuffer(): Effect.Effect<ArrayBuffer, E> {
98
- return Effect.withFiber((fiber) =>
120
+ if (this.arrayBufferEffect) {
121
+ return this.arrayBufferEffect
122
+ }
123
+ this.arrayBufferEffect = Effect.withFiber((fiber) =>
99
124
  NodeStream.toArrayBuffer(() => this.source, {
100
125
  onError: this.onError,
101
126
  maxBytes: fiber.getRef(IncomingMessage.MaxBodySize)
102
127
  })
128
+ ).pipe(
129
+ Effect.cached,
130
+ Effect.runSync
103
131
  )
132
+ this.textEffect = Effect.map(this.arrayBufferEffect, (_) => new TextDecoder().decode(_))
133
+ return this.arrayBufferEffect
104
134
  }
105
135
  }