@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,26 @@
1
1
  /**
2
- * @since 1.0.0
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
- * @since 1.0.0
17
- * @category Constructors
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 = Fs.createReadStream(path, { start, end })
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
- * @since 1.0.0
49
- * @category Layers
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),
@@ -1,18 +1,49 @@
1
1
  /**
2
- * @since 1.0.0
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
- clientAbortFiberId,
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
- * @since 1.0.0
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
- yield* Scope.addFinalizer(
63
- scope,
64
- Effect.callback<void>((resume) => {
65
- if (!server.listening) {
66
- return resume(Effect.void)
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 scope = yield* Effect.scope
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* Effect.addFinalizer(() =>
124
- Effect.sync(() => {
125
- server.off("request", handler)
126
- server.off("upgrade", upgradeHandler)
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
- * @since 1.0.0
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.map(Effect.services<any>(), (services) => {
156
- return function handler(
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(ServiceMap.makeUnsafe<any>(map))(handled), options.scope)
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(clientAbortFiberId)
215
+ fiber.interruptUnsafe(parent.id, ClientAbort.annotation)
166
216
  }
167
217
  })
168
- }
218
+ })
169
219
  })
170
220
  }
171
221
 
172
222
  /**
173
- * @since 1.0.0
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.map(Effect.services<any>(), (services) =>
194
- (function handler(nodeRequest: Http.IncomingMessage, socket: Duplex, head: Buffer) {
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(ServiceMap.makeUnsafe<any>(map))(handledApp), options.scope)
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(clientAbortFiberId)
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 ?? this.remoteAddressOverride
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
- * @since 1.0.0
351
- * @category Layers
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
- * @since 1.0.0
360
- * @category Layers
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
- * @since 1.0.0
372
- * @category Layers
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
- * @since 1.0.0
388
- * @category Layers
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<Net.ListenOptions>
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).asEffect(), (options) => make(evaluate, options))
482
+ Effect.flatMap(Config.unwrap(options), (options) => make(evaluate, options))
400
483
  ),
401
484
  layerHttpServices
402
485
  )
403
486
 
404
487
  /**
405
- * @since 1.0.0
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
- * @since 1.0.0
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
- * @since 1.0.0
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
- * @since 1.0.0
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
@@ -1,5 +1,22 @@
1
1
  /**
2
- * @since 1.0.0
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
- * @since 1.0.0
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
- * @since 1.0.0
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
- * @since 1.0.0
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
- * @since 1.0.0
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
- * @since 1.0.0
10
- * @category layer
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
- * @since 1.0.0
16
- * @category layer
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
- * @since 1.0.0
22
- * @category layer
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
- * @since 1.0.0
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
- * @since 1.0.0
15
- * @category Service
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 ServiceMap.Service<NodeRedis, {
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 ServiceMap.make(NodeRedis, nodeRedis).pipe(
49
- ServiceMap.add(Redis.Redis, redis)
71
+ return Context.make(NodeRedis, nodeRedis).pipe(
72
+ Context.add(Redis.Redis, redis)
50
73
  )
51
74
  })
52
75
 
53
76
  /**
54
- * @since 1.0.0
55
- * @category Layers
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.effectServices(make(options))
85
+ ): Layer.Layer<Redis.Redis | NodeRedis> => Layer.effectContext(make(options))
60
86
 
61
87
  /**
62
- * @since 1.0.0
63
- * @category Layers
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.effectServices(
71
- Config.unwrap(options).asEffect().pipe(
99
+ Layer.effectContext(
100
+ Config.unwrap(options).pipe(
72
101
  Effect.flatMap(make)
73
102
  )
74
103
  )