@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.
Files changed (123) hide show
  1. package/dist/Mime.d.ts +9 -3
  2. package/dist/Mime.d.ts.map +1 -1
  3. package/dist/Mime.js +9 -3
  4. package/dist/Mime.js.map +1 -1
  5. package/dist/NodeChildProcessSpawner.d.ts +1 -1
  6. package/dist/NodeChildProcessSpawner.js +1 -1
  7. package/dist/NodeClusterHttp.d.ts +49 -7
  8. package/dist/NodeClusterHttp.d.ts.map +1 -1
  9. package/dist/NodeClusterHttp.js +20 -10
  10. package/dist/NodeClusterHttp.js.map +1 -1
  11. package/dist/NodeClusterSocket.d.ts +57 -11
  12. package/dist/NodeClusterSocket.d.ts.map +1 -1
  13. package/dist/NodeClusterSocket.js +57 -11
  14. package/dist/NodeClusterSocket.js.map +1 -1
  15. package/dist/NodeCrypto.d.ts +10 -0
  16. package/dist/NodeCrypto.d.ts.map +1 -0
  17. package/dist/NodeCrypto.js +22 -0
  18. package/dist/NodeCrypto.js.map +1 -0
  19. package/dist/NodeFileSystem.d.ts +4 -2
  20. package/dist/NodeFileSystem.d.ts.map +1 -1
  21. package/dist/NodeFileSystem.js +30 -3
  22. package/dist/NodeFileSystem.js.map +1 -1
  23. package/dist/NodeHttpClient.d.ts +111 -24
  24. package/dist/NodeHttpClient.d.ts.map +1 -1
  25. package/dist/NodeHttpClient.js +133 -28
  26. package/dist/NodeHttpClient.js.map +1 -1
  27. package/dist/NodeHttpIncomingMessage.d.ts +43 -9
  28. package/dist/NodeHttpIncomingMessage.d.ts.map +1 -1
  29. package/dist/NodeHttpIncomingMessage.js +47 -8
  30. package/dist/NodeHttpIncomingMessage.js.map +1 -1
  31. package/dist/NodeHttpPlatform.d.ts +10 -4
  32. package/dist/NodeHttpPlatform.d.ts.map +1 -1
  33. package/dist/NodeHttpPlatform.js +34 -7
  34. package/dist/NodeHttpPlatform.js.map +1 -1
  35. package/dist/NodeHttpServer.d.ts +56 -16
  36. package/dist/NodeHttpServer.d.ts.map +1 -1
  37. package/dist/NodeHttpServer.js +117 -49
  38. package/dist/NodeHttpServer.js.map +1 -1
  39. package/dist/NodeHttpServerRequest.d.ts +29 -3
  40. package/dist/NodeHttpServerRequest.d.ts.map +1 -1
  41. package/dist/NodeHttpServerRequest.js +9 -2
  42. package/dist/NodeHttpServerRequest.js.map +1 -1
  43. package/dist/NodeMultipart.d.ts +47 -4
  44. package/dist/NodeMultipart.d.ts.map +1 -1
  45. package/dist/NodeMultipart.js +47 -4
  46. package/dist/NodeMultipart.js.map +1 -1
  47. package/dist/NodePath.d.ts +15 -6
  48. package/dist/NodePath.d.ts.map +1 -1
  49. package/dist/NodePath.js +43 -7
  50. package/dist/NodePath.js.map +1 -1
  51. package/dist/NodeRedis.d.ts +56 -9
  52. package/dist/NodeRedis.d.ts.map +1 -1
  53. package/dist/NodeRedis.js +59 -12
  54. package/dist/NodeRedis.js.map +1 -1
  55. package/dist/NodeRuntime.d.ts +27 -36
  56. package/dist/NodeRuntime.d.ts.map +1 -1
  57. package/dist/NodeRuntime.js +38 -13
  58. package/dist/NodeRuntime.js.map +1 -1
  59. package/dist/NodeServices.d.ts +44 -5
  60. package/dist/NodeServices.d.ts.map +1 -1
  61. package/dist/NodeServices.js +7 -3
  62. package/dist/NodeServices.js.map +1 -1
  63. package/dist/NodeSink.d.ts +2 -2
  64. package/dist/NodeSink.js +2 -2
  65. package/dist/NodeSocket.d.ts +18 -3
  66. package/dist/NodeSocket.d.ts.map +1 -1
  67. package/dist/NodeSocket.js +51 -4
  68. package/dist/NodeSocket.js.map +1 -1
  69. package/dist/NodeSocketServer.d.ts +2 -2
  70. package/dist/NodeSocketServer.js +2 -2
  71. package/dist/NodeStdio.d.ts +5 -2
  72. package/dist/NodeStdio.d.ts.map +1 -1
  73. package/dist/NodeStdio.js +30 -3
  74. package/dist/NodeStdio.js.map +1 -1
  75. package/dist/NodeStream.d.ts +2 -2
  76. package/dist/NodeStream.js +2 -2
  77. package/dist/NodeTerminal.d.ts +8 -2
  78. package/dist/NodeTerminal.d.ts.map +1 -1
  79. package/dist/NodeTerminal.js +29 -3
  80. package/dist/NodeTerminal.js.map +1 -1
  81. package/dist/NodeWorker.d.ts +9 -2
  82. package/dist/NodeWorker.d.ts.map +1 -1
  83. package/dist/NodeWorker.js +45 -6
  84. package/dist/NodeWorker.js.map +1 -1
  85. package/dist/NodeWorkerRunner.d.ts +5 -1
  86. package/dist/NodeWorkerRunner.d.ts.map +1 -1
  87. package/dist/NodeWorkerRunner.js +47 -5
  88. package/dist/NodeWorkerRunner.js.map +1 -1
  89. package/dist/Undici.d.ts +16 -3
  90. package/dist/Undici.d.ts.map +1 -1
  91. package/dist/Undici.js +16 -3
  92. package/dist/Undici.js.map +1 -1
  93. package/dist/index.d.ts +28 -26
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +28 -26
  96. package/dist/index.js.map +1 -1
  97. package/package.json +9 -9
  98. package/src/Mime.ts +10 -3
  99. package/src/NodeChildProcessSpawner.ts +1 -1
  100. package/src/NodeClusterHttp.ts +54 -11
  101. package/src/NodeClusterSocket.ts +57 -11
  102. package/src/NodeCrypto.ts +24 -0
  103. package/src/NodeFileSystem.ts +30 -3
  104. package/src/NodeHttpClient.ts +141 -33
  105. package/src/NodeHttpIncomingMessage.ts +55 -12
  106. package/src/NodeHttpPlatform.ts +35 -6
  107. package/src/NodeHttpServer.ts +139 -53
  108. package/src/NodeHttpServerRequest.ts +29 -3
  109. package/src/NodeMultipart.ts +47 -4
  110. package/src/NodePath.ts +43 -7
  111. package/src/NodeRedis.ts +61 -14
  112. package/src/NodeRuntime.ts +56 -37
  113. package/src/NodeServices.ts +46 -5
  114. package/src/NodeSink.ts +2 -2
  115. package/src/NodeSocket.ts +56 -4
  116. package/src/NodeSocketServer.ts +2 -2
  117. package/src/NodeStdio.ts +30 -3
  118. package/src/NodeStream.ts +2 -2
  119. package/src/NodeTerminal.ts +29 -3
  120. package/src/NodeWorker.ts +45 -6
  121. package/src/NodeWorkerRunner.ts +47 -5
  122. package/src/Undici.ts +16 -3
  123. package/src/index.ts +29 -26
@@ -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,37 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Node.js multipart parsing for HTTP `multipart/form-data` request bodies.
3
+ *
4
+ * `NodeMultipart` adapts a Node `Readable` plus incoming HTTP headers into
5
+ * Effect's shared multipart model. It can expose form parts as a stream for
6
+ * incremental processing, or collect a complete persisted form by writing file
7
+ * uploads to scoped temporary files through the current `FileSystem` and `Path`
8
+ * services.
9
+ *
10
+ * **Mental model**
11
+ *
12
+ * Multipart request bodies are one-shot byte streams. {@link stream} parses the
13
+ * body as it arrives: fields are decoded to strings and file parts keep their
14
+ * underlying Node readable stream. {@link persisted} consumes the same kind of
15
+ * body, builds a `Multipart.Persisted` record, and ties temporary upload files
16
+ * to the surrounding `Scope`.
17
+ *
18
+ * **Common tasks**
19
+ *
20
+ * - Use {@link stream} when a route validates fields while piping file uploads
21
+ * to storage.
22
+ * - Use {@link persisted} when a route needs a complete form value with scoped
23
+ * temporary files.
24
+ * - Use {@link fileToReadable} when a downstream Node library expects a
25
+ * `Readable`.
26
+ *
27
+ * **Gotchas**
28
+ *
29
+ * Consume a request body with only one parser. File parts must be drained,
30
+ * piped, or persisted so the request can finish reading. `contentEffect` loads
31
+ * an uploaded file into memory, so reserve it for small files. Client-supplied
32
+ * filenames are metadata, not trusted filesystem paths.
33
+ *
34
+ * @since 4.0.0
3
35
  */
4
36
  import * as Effect from "effect/Effect"
5
37
  import type * as FileSystem from "effect/FileSystem"
@@ -16,8 +48,12 @@ import * as NodeStreamP from "node:stream/promises"
16
48
  import * as NodeStream from "./NodeStream.ts"
17
49
 
18
50
  /**
19
- * @since 1.0.0
51
+ * Parses multipart data from a Node readable request body and headers into a
52
+ * stream of `Multipart.Part` values, converting parser failures to
53
+ * `MultipartError`.
54
+ *
20
55
  * @category constructors
56
+ * @since 4.0.0
21
57
  */
22
58
  export const stream = (
23
59
  source: Readable,
@@ -39,8 +75,11 @@ export const stream = (
39
75
  )
40
76
 
41
77
  /**
42
- * @since 1.0.0
78
+ * Parses multipart data from a Node readable request body and persists file
79
+ * parts using the current `FileSystem`, `Path`, and `Scope` services.
80
+ *
43
81
  * @category constructors
82
+ * @since 4.0.0
44
83
  */
45
84
  export const persisted = (
46
85
  source: Readable,
@@ -57,7 +96,11 @@ export const persisted = (
57
96
  }))
58
97
 
59
98
  /**
60
- * @since 1.0.0
99
+ * Returns the underlying Node readable stream for a multipart file produced by
100
+ * the Node multipart parser.
101
+ *
102
+ * @category converting
103
+ * @since 4.0.0
61
104
  */
62
105
  export const fileToReadable = (file: Multipart.File): Readable => (file as FileImpl).file
63
106
 
package/src/NodePath.ts CHANGED
@@ -1,24 +1,60 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Node.js layers for Effect's `Path` service.
3
+ *
4
+ * This module adapts Node's path and file URL behavior to the
5
+ * platform-independent `Path` service. Provide one of its layers when a Node
6
+ * program needs to build, normalize, parse, resolve, or convert paths without
7
+ * depending directly on `node:path`.
8
+ *
9
+ * **Mental model**
10
+ *
11
+ * `Path` is a syntactic service: it manipulates strings and `file:` URLs. It
12
+ * does not read the filesystem, check permissions, or validate that paths
13
+ * exist. The selected layer decides which separator, drive-letter, UNC, and URL
14
+ * conversion rules are used.
15
+ *
16
+ * **Common tasks**
17
+ *
18
+ * Use `layer` for host-platform Node semantics, `layerPosix` for stable POSIX
19
+ * behavior, and `layerWin32` for stable Windows behavior. `NodeServices.layer`
20
+ * already includes `layer`, so import this module directly when a program wants
21
+ * only path support or a platform-specific variant.
22
+ *
23
+ * **Gotchas**
24
+ *
25
+ * Results that are correct on one platform may not be portable to another.
26
+ * `fromFileUrl` and `toFileUrl` use Node's `node:url` conversion rules and
27
+ * report invalid conversions as `BadArgument` failures.
28
+ *
29
+ * @since 4.0.0
3
30
  */
4
31
  import * as NodePath from "@effect/platform-node-shared/NodePath"
5
32
  import type * as Layer from "effect/Layer"
6
33
  import type { Path } from "effect/Path"
7
34
 
8
35
  /**
9
- * @since 1.0.0
10
- * @category layer
36
+ * Provides the default Node `Path` service using the platform's `node:path`
37
+ * implementation.
38
+ *
39
+ * @category layers
40
+ * @since 4.0.0
11
41
  */
12
42
  export const layer: Layer.Layer<Path> = NodePath.layer
13
43
 
14
44
  /**
15
- * @since 1.0.0
16
- * @category layer
45
+ * Provides the `Path` service using Node's POSIX path implementation,
46
+ * regardless of the host platform.
47
+ *
48
+ * @category layers
49
+ * @since 4.0.0
17
50
  */
18
51
  export const layerPosix: Layer.Layer<Path> = NodePath.layerPosix
19
52
 
20
53
  /**
21
- * @since 1.0.0
22
- * @category layer
54
+ * Provides the `Path` service using Node's Windows path implementation,
55
+ * regardless of the host platform.
56
+ *
57
+ * @category layers
58
+ * @since 4.0.0
23
59
  */
24
60
  export const layerWin32: Layer.Layer<Path> = NodePath.layerWin32
package/src/NodeRedis.ts CHANGED
@@ -1,20 +1,61 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Node.js Redis integration backed by `ioredis`.
3
+ *
4
+ * This module creates a scoped `ioredis` client and exposes it in two forms:
5
+ * the generic `Redis` service consumed by Effect persistence modules, and the
6
+ * {@link NodeRedis} service for code that needs direct access to the underlying
7
+ * client.
8
+ *
9
+ * **Mental model**
10
+ *
11
+ * - {@link layer} creates one `ioredis` client from explicit client options
12
+ * - {@link layerConfig} reads the same options from `Config`
13
+ * - Building the layer opens the client, and closing the layer scope calls
14
+ * `quit`
15
+ * - The generic `Redis` service sends command strings through `client.call`
16
+ * - {@link NodeRedis} exposes the raw client plus `use`, which maps promise
17
+ * failures into `RedisError`
18
+ *
19
+ * **Common tasks**
20
+ *
21
+ * - Provide Redis-backed persistence, persisted queues, or distributed rate
22
+ * limiting in Node.js
23
+ * - Configure connection, TLS, database, retry, and reconnect behavior with
24
+ * standard `ioredis` options
25
+ * - Run custom Redis commands through {@link NodeRedis} when the generic
26
+ * `Redis` service does not cover the command shape you need
27
+ *
28
+ * **Gotchas**
29
+ *
30
+ * - Install the layer at the lifetime you want for the connection; a short
31
+ * scope opens and closes a Redis client for that scope
32
+ * - Persistence and rate limiter stores create their own keys and Lua scripts
33
+ * on top of this service, so choose stable prefixes and store ids
34
+ * - Persisted values may fail to decode after schema changes; plan migrations
35
+ * or cleanup for long-lived Redis data
36
+ * - Avoid unbounded high-cardinality rate-limit keys unless another process or
37
+ * key policy bounds their lifetime
38
+ *
39
+ * @since 4.0.0
3
40
  */
4
41
  import * as Config from "effect/Config"
42
+ import * as Context from "effect/Context"
5
43
  import * as Effect from "effect/Effect"
6
44
  import * as Fn from "effect/Function"
7
45
  import * as Layer from "effect/Layer"
8
46
  import * as Scope from "effect/Scope"
9
- import * as ServiceMap from "effect/ServiceMap"
10
47
  import * as Redis from "effect/unstable/persistence/Redis"
11
48
  import * as IoRedis from "ioredis"
12
49
 
13
50
  /**
14
- * @since 1.0.0
15
- * @category Service
51
+ * Service tag for the Node Redis integration, exposing the underlying
52
+ * `ioredis` client and a `use` helper that maps client failures to
53
+ * `RedisError`.
54
+ *
55
+ * @category services
56
+ * @since 4.0.0
16
57
  */
17
- export class NodeRedis extends ServiceMap.Service<NodeRedis, {
58
+ export class NodeRedis extends Context.Service<NodeRedis, {
18
59
  readonly client: IoRedis.Redis
19
60
  readonly use: <A>(f: (client: IoRedis.Redis) => Promise<A>) => Effect.Effect<A, Redis.RedisError>
20
61
  }>()("@effect/platform-node/NodeRedis") {}
@@ -45,30 +86,36 @@ const make = Effect.fnUntraced(function*(
45
86
  use
46
87
  })
47
88
 
48
- return ServiceMap.make(NodeRedis, nodeRedis).pipe(
49
- ServiceMap.add(Redis.Redis, redis)
89
+ return Context.make(NodeRedis, nodeRedis).pipe(
90
+ Context.add(Redis.Redis, redis)
50
91
  )
51
92
  })
52
93
 
53
94
  /**
54
- * @since 1.0.0
55
- * @category Layers
95
+ * Provides `Redis` and `NodeRedis` services backed by an `ioredis` client
96
+ * created with the supplied options and closed when the layer scope ends.
97
+ *
98
+ * @category layers
99
+ * @since 4.0.0
56
100
  */
57
101
  export const layer = (
58
102
  options?: IoRedis.RedisOptions | undefined
59
- ): Layer.Layer<Redis.Redis | NodeRedis> => Layer.effectServices(make(options))
103
+ ): Layer.Layer<Redis.Redis | NodeRedis> => Layer.effectContext(make(options))
60
104
 
61
105
  /**
62
- * @since 1.0.0
63
- * @category Layers
106
+ * Provides `Redis` and `NodeRedis` services from `Config`-backed ioredis
107
+ * options, closing the client when the layer scope ends.
108
+ *
109
+ * @category layers
110
+ * @since 4.0.0
64
111
  */
65
112
  export const layerConfig: (
66
113
  options: Config.Wrap<IoRedis.RedisOptions>
67
114
  ) => Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> = (
68
115
  options: Config.Wrap<IoRedis.RedisOptions>
69
116
  ): Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> =>
70
- Layer.effectServices(
71
- Config.unwrap(options).asEffect().pipe(
117
+ Layer.effectContext(
118
+ Config.unwrap(options).pipe(
72
119
  Effect.flatMap(make)
73
120
  )
74
121
  )