@effect/platform-node 4.0.0-beta.99 → 4.0.0-rc.109

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 (144) hide show
  1. package/AGENTS.md +395 -0
  2. package/CLAUDE.md +395 -0
  3. package/README.md +10 -3
  4. package/ai-docs/README.md +44 -0
  5. package/ai-docs/package.json +36 -0
  6. package/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +30 -0
  7. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +39 -0
  8. package/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +74 -0
  9. package/ai-docs/src/01_effect/01_basics/index.md +5 -0
  10. package/ai-docs/src/01_effect/02_schema/10_schema-basics.ts +43 -0
  11. package/ai-docs/src/01_effect/02_schema/index.md +7 -0
  12. package/ai-docs/src/01_effect/03_services/01_service.ts +45 -0
  13. package/ai-docs/src/01_effect/03_services/10_reference.ts +10 -0
  14. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +70 -0
  15. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +66 -0
  16. package/ai-docs/src/01_effect/03_services/index.md +5 -0
  17. package/ai-docs/src/01_effect/04_errors/01_error-handling.ts +30 -0
  18. package/ai-docs/src/01_effect/04_errors/10_catch-tags.ts +24 -0
  19. package/ai-docs/src/01_effect/04_errors/20_reason-errors.ts +64 -0
  20. package/ai-docs/src/01_effect/04_errors/index.md +1 -0
  21. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +105 -0
  22. package/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts +31 -0
  23. package/ai-docs/src/01_effect/05_resources/30_layer-map.ts +86 -0
  24. package/ai-docs/src/01_effect/05_resources/index.md +3 -0
  25. package/ai-docs/src/01_effect/06_running/10_run-main.ts +30 -0
  26. package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +27 -0
  27. package/ai-docs/src/01_effect/06_running/index.md +1 -0
  28. package/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts +56 -0
  29. package/ai-docs/src/01_effect/07_pubsub/index.md +3 -0
  30. package/ai-docs/src/03_stream/10_creating-streams.ts +103 -0
  31. package/ai-docs/src/03_stream/20_consuming-streams.ts +137 -0
  32. package/ai-docs/src/03_stream/30_encoding.ts +165 -0
  33. package/ai-docs/src/03_stream/index.md +4 -0
  34. package/ai-docs/src/04_integration/10_managed-runtime.ts +129 -0
  35. package/ai-docs/src/04_integration/index.md +5 -0
  36. package/ai-docs/src/05_batching/10_request-resolver.ts +89 -0
  37. package/ai-docs/src/05_batching/index.md +3 -0
  38. package/ai-docs/src/06_schedule/10_schedules.ts +110 -0
  39. package/ai-docs/src/06_schedule/index.md +3 -0
  40. package/ai-docs/src/07_datetime/10_creating-and-formatting.ts +30 -0
  41. package/ai-docs/src/07_datetime/20_time-zones.ts +44 -0
  42. package/ai-docs/src/07_datetime/index.md +5 -0
  43. package/ai-docs/src/08_observability/10_logging.ts +66 -0
  44. package/ai-docs/src/08_observability/20_otlp-tracing.ts +95 -0
  45. package/ai-docs/src/08_observability/index.md +7 -0
  46. package/ai-docs/src/09_testing/10_effect-tests.ts +55 -0
  47. package/ai-docs/src/09_testing/20_layer-tests.ts +138 -0
  48. package/ai-docs/src/09_testing/index.md +1 -0
  49. package/ai-docs/src/10_predicate/01_basics.ts +14 -0
  50. package/ai-docs/src/10_predicate/index.md +9 -0
  51. package/ai-docs/src/40_sql/10_basics.ts +175 -0
  52. package/ai-docs/src/40_sql/index.md +6 -0
  53. package/ai-docs/src/50_http-client/10_basics.ts +102 -0
  54. package/ai-docs/src/50_http-client/index.md +3 -0
  55. package/ai-docs/src/51_http-server/10_basics.ts +118 -0
  56. package/ai-docs/src/51_http-server/20_testing.ts +100 -0
  57. package/ai-docs/src/51_http-server/fixtures/api/Api.ts +14 -0
  58. package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +36 -0
  59. package/ai-docs/src/51_http-server/fixtures/api/System.ts +10 -0
  60. package/ai-docs/src/51_http-server/fixtures/api/Users.ts +107 -0
  61. package/ai-docs/src/51_http-server/fixtures/domain/User.ts +21 -0
  62. package/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +22 -0
  63. package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +37 -0
  64. package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +80 -0
  65. package/ai-docs/src/51_http-server/fixtures/server/Users.ts +183 -0
  66. package/ai-docs/src/51_http-server/index.md +3 -0
  67. package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +117 -0
  68. package/ai-docs/src/60_child-process/index.md +3 -0
  69. package/ai-docs/src/70_cli/10_basics.ts +160 -0
  70. package/ai-docs/src/70_cli/index.md +5 -0
  71. package/ai-docs/src/71_ai/10_language-model.ts +156 -0
  72. package/ai-docs/src/71_ai/20_tools.ts +226 -0
  73. package/ai-docs/src/71_ai/30_chat.ts +158 -0
  74. package/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts +9 -0
  75. package/ai-docs/src/71_ai/index.md +5 -0
  76. package/ai-docs/src/80_cluster/10_entities.ts +97 -0
  77. package/ai-docs/src/80_cluster/index.md +4 -0
  78. package/ai-docs/src/index.md +10 -0
  79. package/ai-docs/tsconfig.json +24 -0
  80. package/dist/Mime.js.map +1 -1
  81. package/dist/NodeClusterHttp.d.ts +1 -0
  82. package/dist/NodeClusterHttp.d.ts.map +1 -1
  83. package/dist/NodeClusterHttp.js +6 -1
  84. package/dist/NodeClusterHttp.js.map +1 -1
  85. package/dist/NodeClusterSocket.d.ts +1 -0
  86. package/dist/NodeClusterSocket.d.ts.map +1 -1
  87. package/dist/NodeClusterSocket.js +10 -2
  88. package/dist/NodeClusterSocket.js.map +1 -1
  89. package/dist/NodeCrypto.js.map +1 -1
  90. package/dist/NodeFileSystem.js.map +1 -1
  91. package/dist/NodeHttpClient.d.ts +18 -18
  92. package/dist/NodeHttpClient.d.ts.map +1 -1
  93. package/dist/NodeHttpClient.js +26 -20
  94. package/dist/NodeHttpClient.js.map +1 -1
  95. package/dist/NodeHttpIncomingMessage.js.map +1 -1
  96. package/dist/NodeHttpPlatform.d.ts +8 -4
  97. package/dist/NodeHttpPlatform.d.ts.map +1 -1
  98. package/dist/NodeHttpPlatform.js +38 -0
  99. package/dist/NodeHttpPlatform.js.map +1 -1
  100. package/dist/NodeHttpServer.d.ts +21 -16
  101. package/dist/NodeHttpServer.d.ts.map +1 -1
  102. package/dist/NodeHttpServer.js +29 -21
  103. package/dist/NodeHttpServer.js.map +1 -1
  104. package/dist/NodeHttpServerRequest.js.map +1 -1
  105. package/dist/NodeMultipart.d.ts.map +1 -1
  106. package/dist/NodeMultipart.js +3 -2
  107. package/dist/NodeMultipart.js.map +1 -1
  108. package/dist/NodeMultipartParser.d.ts +97 -0
  109. package/dist/NodeMultipartParser.d.ts.map +1 -0
  110. package/dist/NodeMultipartParser.js +108 -0
  111. package/dist/NodeMultipartParser.js.map +1 -0
  112. package/dist/NodePath.js.map +1 -1
  113. package/dist/NodeRedis.d.ts +40 -15
  114. package/dist/NodeRedis.d.ts.map +1 -1
  115. package/dist/NodeRedis.js +66 -16
  116. package/dist/NodeRedis.js.map +1 -1
  117. package/dist/NodeRuntime.js.map +1 -1
  118. package/dist/NodeServices.js.map +1 -1
  119. package/dist/NodeSocket.js.map +1 -1
  120. package/dist/NodeStdio.js.map +1 -1
  121. package/dist/NodeTerminal.js.map +1 -1
  122. package/dist/NodeWorker.js.map +1 -1
  123. package/dist/NodeWorkerRunner.d.ts.map +1 -1
  124. package/dist/NodeWorkerRunner.js +30 -18
  125. package/dist/NodeWorkerRunner.js.map +1 -1
  126. package/dist/Undici.d.ts +2 -2
  127. package/dist/Undici.js +2 -2
  128. package/dist/Undici.js.map +1 -1
  129. package/dist/index.d.ts +4 -0
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +4 -0
  132. package/dist/index.js.map +1 -1
  133. package/package.json +17 -14
  134. package/src/NodeClusterHttp.ts +6 -2
  135. package/src/NodeClusterSocket.ts +8 -2
  136. package/src/NodeHttpClient.ts +27 -20
  137. package/src/NodeHttpPlatform.ts +49 -0
  138. package/src/NodeHttpServer.ts +61 -40
  139. package/src/NodeMultipart.ts +5 -4
  140. package/src/NodeMultipartParser.ts +184 -0
  141. package/src/NodeRedis.ts +86 -26
  142. package/src/NodeWorkerRunner.ts +39 -23
  143. package/src/Undici.ts +2 -2
  144. package/src/index.ts +5 -0
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@effect/platform-node",
3
3
  "type": "module",
4
- "version": "4.0.0-beta.99",
4
+ "version": "4.0.0-rc.109",
5
5
  "license": "MIT",
6
6
  "description": "Platform specific implementations for the Node.js runtime",
7
7
  "homepage": "https://effect.website",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Effect-TS/effect.git",
11
- "directory": "packages/platform-node"
11
+ "directory": "packages/platform/node"
12
12
  },
13
13
  "bugs": {
14
14
  "url": "https://github.com/Effect-TS/effect/issues"
@@ -34,6 +34,7 @@
34
34
  ".": "./dist/index.js",
35
35
  "./*": "./dist/*.js",
36
36
  "./internal/*": null,
37
+ "./index": null,
37
38
  "./*/index": null
38
39
  },
39
40
  "files": [
@@ -41,7 +42,10 @@
41
42
  "dist/**/*.js",
42
43
  "dist/**/*.js.map",
43
44
  "dist/**/*.d.ts",
44
- "dist/**/*.d.ts.map"
45
+ "dist/**/*.d.ts.map",
46
+ "AGENTS.md",
47
+ "CLAUDE.md",
48
+ "ai-docs/**/*"
45
49
  ],
46
50
  "publishConfig": {
47
51
  "access": "public",
@@ -50,25 +54,24 @@
50
54
  "dependencies": {
51
55
  "mime": "^4.1.0",
52
56
  "undici": "^8.7.0",
53
- "@effect/platform-node-shared": "^4.0.0-beta.99"
57
+ "@effect/platform-node-shared": "^4.0.0-rc.109"
54
58
  },
55
59
  "peerDependencies": {
56
- "ioredis": "^5.7.0",
57
- "effect": "^4.0.0-beta.99"
60
+ "redis": ">=5.0.0 <7.0.0",
61
+ "effect": "^4.0.0-rc.109"
58
62
  },
59
63
  "devDependencies": {
60
- "@testcontainers/mysql": "^11.14.0",
61
- "@testcontainers/postgresql": "^11.14.0",
62
- "@testcontainers/redis": "^11.14.0",
63
- "@types/node": "^26.1.1",
64
- "effect": "^4.0.0-beta.99"
64
+ "@testcontainers/mysql": "^12.0.4",
65
+ "@testcontainers/postgresql": "^12.0.4",
66
+ "@testcontainers/redis": "^12.0.4",
67
+ "@types/node": "^26.1.2",
68
+ "redis": "^6.2.1",
69
+ "effect": "^4.0.0-rc.109"
65
70
  },
66
71
  "scripts": {
67
72
  "codegen": "effect-utils codegen",
68
73
  "build": "tsc -b tsconfig.json && pnpm babel",
69
74
  "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps",
70
- "check": "tsc -b tsconfig.json",
71
- "test": "vitest",
72
- "coverage": "vitest --coverage"
75
+ "check": "tsc -b tsconfig.json"
73
76
  }
74
77
  }
@@ -31,6 +31,7 @@ import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization"
31
31
  import type { SqlClient } from "effect/unstable/sql/SqlClient"
32
32
  import { createServer } from "node:http"
33
33
  import { layerK8sHttpClient } from "./NodeClusterSocket.ts"
34
+ import * as NodeCrypto from "./NodeCrypto.ts"
34
35
  import * as NodeHttpClient from "./NodeHttpClient.ts"
35
36
  import * as NodeHttpServer from "./NodeHttpServer.ts"
36
37
  import type { NodeServices } from "./NodeServices.ts"
@@ -60,6 +61,7 @@ export const layer = <
60
61
  >(options: {
61
62
  readonly transport: "http" | "websocket"
62
63
  readonly serialization?: "msgpack" | "ndjson" | undefined
64
+ readonly serializationMaxBufferSize?: number | "unbounded" | undefined
63
65
  readonly clientOnly?: ClientOnly | undefined
64
66
  readonly storage?: Storage | undefined
65
67
  readonly runnerHealth?: "ping" | "k8s" | undefined
@@ -115,7 +117,7 @@ export const layer = <
115
117
  ? MessageStorage.layerNoop
116
118
  : options?.storage === "byo"
117
119
  ? Layer.empty
118
- : Layer.orDie(SqlMessageStorage.layer)
120
+ : Layer.orDie(SqlMessageStorage.layer).pipe(Layer.provide(NodeCrypto.layer))
119
121
  ),
120
122
  Layer.provide(
121
123
  options?.storage === "local"
@@ -126,7 +128,9 @@ export const layer = <
126
128
  ),
127
129
  Layer.provide(ShardingConfig.layerFromEnv(options?.shardingConfig)),
128
130
  Layer.provide(
129
- options?.serialization === "ndjson" ? RpcSerialization.layerNdjson : RpcSerialization.layerMsgPack
131
+ options?.serialization === "ndjson"
132
+ ? RpcSerialization.layerNdjsonWith({ maxBufferSize: options.serializationMaxBufferSize })
133
+ : RpcSerialization.layerMsgPackWith({ maxBufferSize: options.serializationMaxBufferSize })
130
134
  )
131
135
  ) as any
132
136
  }
@@ -28,6 +28,7 @@ import * as SqlRunnerStorage from "effect/unstable/cluster/SqlRunnerStorage"
28
28
  import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization"
29
29
  import type * as SocketServer from "effect/unstable/socket/SocketServer"
30
30
  import type { SqlClient } from "effect/unstable/sql/SqlClient"
31
+ import * as NodeCrypto from "./NodeCrypto.ts"
31
32
  import * as NodeFileSystem from "./NodeFileSystem.ts"
32
33
  import * as NodeHttpClient from "./NodeHttpClient.ts"
33
34
  import * as Undici from "./Undici.ts"
@@ -65,6 +66,7 @@ export const layer = <
65
66
  >(
66
67
  options?: {
67
68
  readonly serialization?: "msgpack" | "ndjson" | undefined
69
+ readonly serializationMaxBufferSize?: number | "unbounded" | undefined
68
70
  readonly clientOnly?: ClientOnly | undefined
69
71
  readonly storage?: Storage | undefined
70
72
  readonly runnerHealth?: "ping" | "k8s" | undefined
@@ -113,7 +115,7 @@ export const layer = <
113
115
  ? MessageStorage.layerNoop
114
116
  : options?.storage === "byo"
115
117
  ? Layer.empty
116
- : Layer.orDie(SqlMessageStorage.layer)
118
+ : Layer.orDie(SqlMessageStorage.layer).pipe(Layer.provide(NodeCrypto.layer))
117
119
  ),
118
120
  Layer.provide(
119
121
  options?.storage === "local"
@@ -124,7 +126,9 @@ export const layer = <
124
126
  ),
125
127
  Layer.provide(ShardingConfig.layerFromEnv(options?.shardingConfig)),
126
128
  Layer.provide(
127
- options?.serialization === "ndjson" ? RpcSerialization.layerNdjson : RpcSerialization.layerMsgPack
129
+ options?.serialization === "ndjson"
130
+ ? RpcSerialization.layerNdjsonWith({ maxBufferSize: options?.serializationMaxBufferSize })
131
+ : RpcSerialization.layerMsgPackWith({ maxBufferSize: options?.serializationMaxBufferSize })
128
132
  )
129
133
  ) as any
130
134
  }
@@ -146,6 +150,8 @@ export const layerDispatcherK8s: Layer.Layer<NodeHttpClient.Dispatcher> = Layer.
146
150
  if (caCertOption._tag === "Some") {
147
151
  return yield* Effect.acquireRelease(
148
152
  Effect.sync(() =>
153
+ // oxlint cannot resolve values re-exported through the local Undici facade.
154
+ // oxlint-disable-next-line import/namespace
149
155
  new Undici.Agent({
150
156
  connect: {
151
157
  ca: caCertOption.value
@@ -51,14 +51,14 @@ export {
51
51
  * Use to access or override the fetch implementation used by the Node
52
52
  * fetch-based HTTP client.
53
53
  *
54
- * @category fetch
54
+ * @category services
55
55
  * @since 4.0.0
56
56
  */
57
57
  Fetch,
58
58
  /**
59
59
  * Layer that provides the fetch-based HTTP client implementation.
60
60
  *
61
- * @category fetch
61
+ * @category layers
62
62
  * @since 4.0.0
63
63
  */
64
64
  layer as layerFetch,
@@ -69,7 +69,7 @@ export {
69
69
  *
70
70
  * Use to provide default fetch request options for Node HTTP requests.
71
71
  *
72
- * @category fetch
72
+ * @category services
73
73
  * @since 4.0.0
74
74
  */
75
75
  RequestInit
@@ -83,7 +83,7 @@ export {
83
83
  * Service tag for the Undici `Dispatcher` used by the Undici-backed HTTP
84
84
  * client.
85
85
  *
86
- * @category Dispatcher
86
+ * @category services
87
87
  * @since 4.0.0
88
88
  */
89
89
  export class Dispatcher extends Context.Service<Dispatcher, Undici.Dispatcher>()(
@@ -94,10 +94,12 @@ export class Dispatcher extends Context.Service<Dispatcher, Undici.Dispatcher>()
94
94
  * Acquires a new Undici `Agent` dispatcher and destroys it when the enclosing
95
95
  * scope is finalized.
96
96
  *
97
- * @category Dispatcher
97
+ * @category resource management
98
98
  * @since 4.0.0
99
99
  */
100
100
  export const makeDispatcher: Effect.Effect<Undici.Dispatcher, never, Scope.Scope> = Effect.acquireRelease(
101
+ // oxlint cannot resolve values re-exported through the local Undici facade.
102
+ // oxlint-disable-next-line import/namespace
101
103
  Effect.sync(() => new Undici.Agent()),
102
104
  (dispatcher) => Effect.promise(() => dispatcher.destroy())
103
105
  )
@@ -105,7 +107,7 @@ export const makeDispatcher: Effect.Effect<Undici.Dispatcher, never, Scope.Scope
105
107
  /**
106
108
  * Provides the `Dispatcher` service using a scoped Undici `Agent`.
107
109
  *
108
- * @category Dispatcher
110
+ * @category layers
109
111
  * @since 4.0.0
110
112
  */
111
113
  export const layerDispatcher: Layer.Layer<Dispatcher> = Layer.effect(Dispatcher)(makeDispatcher)
@@ -114,16 +116,18 @@ export const layerDispatcher: Layer.Layer<Dispatcher> = Layer.effect(Dispatcher)
114
116
  * Provides the `Dispatcher` service from Undici's process-global dispatcher,
115
117
  * without creating or owning a new agent.
116
118
  *
117
- * @category Dispatcher
119
+ * @category layers
118
120
  * @since 4.0.0
119
121
  */
122
+ // oxlint cannot resolve values re-exported through the local Undici facade.
123
+ // oxlint-disable-next-line import/namespace
120
124
  export const dispatcherLayerGlobal: Layer.Layer<Dispatcher> = Layer.sync(Dispatcher)(() => Undici.getGlobalDispatcher())
121
125
 
122
126
  /**
123
127
  * Fiber reference containing default Undici request options applied to requests
124
128
  * sent by `makeUndici`.
125
129
  *
126
- * @category Undici
130
+ * @category services
127
131
  * @since 4.0.0
128
132
  */
129
133
  export const UndiciOptions = Context.Reference<Partial<Undici.Dispatcher.RequestOptions>>(
@@ -136,7 +140,7 @@ export const UndiciOptions = Context.Reference<Partial<Undici.Dispatcher.Request
136
140
  * `Dispatcher`, converts Effect HTTP bodies to Undici bodies, and maps
137
141
  * transport and decode failures to `HttpClientError`.
138
142
  *
139
- * @category Undici
143
+ * @category constructors
140
144
  * @since 4.0.0
141
145
  */
142
146
  export const makeUndici = Effect.gen(function*() {
@@ -353,7 +357,7 @@ class UndiciResponse extends Inspectable.Class implements HttpClientResponse, Pi
353
357
  * Provides an Undici-backed `HttpClient` using the current `Dispatcher`
354
358
  * service.
355
359
  *
356
- * @category Undici
360
+ * @category layers
357
361
  * @since 4.0.0
358
362
  */
359
363
  export const layerUndiciNoDispatcher: Layer.Layer<
@@ -366,7 +370,7 @@ export const layerUndiciNoDispatcher: Layer.Layer<
366
370
  * Provides an Undici-backed `HttpClient` together with a scoped default
367
371
  * Undici `Agent` dispatcher.
368
372
  *
369
- * @category Undici
373
+ * @category layers
370
374
  * @since 4.0.0
371
375
  */
372
376
  export const layerUndici: Layer.Layer<Client.HttpClient> = Layer.provide(layerUndiciNoDispatcher, layerDispatcher)
@@ -379,7 +383,7 @@ export const layerUndici: Layer.Layer<Client.HttpClient> = Layer.provide(layerUn
379
383
  * Service tag for the paired Node `http` and `https` agents used by the
380
384
  * node:http-backed HTTP client.
381
385
  *
382
- * @category HttpAgent
386
+ * @category services
383
387
  * @since 4.0.0
384
388
  */
385
389
  export class HttpAgent extends Context.Service<HttpAgent, {
@@ -391,7 +395,7 @@ export class HttpAgent extends Context.Service<HttpAgent, {
391
395
  * Acquires Node `http` and `https` agents with the supplied options and
392
396
  * destroys both agents when the enclosing scope is finalized.
393
397
  *
394
- * @category HttpAgent
398
+ * @category resource management
395
399
  * @since 4.0.0
396
400
  */
397
401
  export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<HttpAgent["Service"], never, Scope.Scope> =>
@@ -411,7 +415,7 @@ export const makeAgent = (options?: Https.AgentOptions): Effect.Effect<HttpAgent
411
415
  * Provides the `HttpAgent` service using scoped Node `http` and `https`
412
416
  * agents configured with the supplied options.
413
417
  *
414
- * @category HttpAgent
418
+ * @category layers
415
419
  * @since 4.0.0
416
420
  */
417
421
  export const layerAgentOptions: (options?: Https.AgentOptions | undefined) => Layer.Layer<
@@ -422,7 +426,7 @@ export const layerAgentOptions: (options?: Https.AgentOptions | undefined) => La
422
426
  * Provides the `HttpAgent` service using default scoped Node `http` and
423
427
  * `https` agents.
424
428
  *
425
- * @category HttpAgent
429
+ * @category layers
426
430
  * @since 4.0.0
427
431
  */
428
432
  export const layerAgent: Layer.Layer<HttpAgent> = layerAgentOptions()
@@ -432,7 +436,7 @@ export const layerAgent: Layer.Layer<HttpAgent> = layerAgentOptions()
432
436
  * current `HttpAgent`, streaming request bodies, and wrapping Node responses
433
437
  * as `HttpClientResponse` values.
434
438
  *
435
- * @category node:http
439
+ * @category constructors
436
440
  * @since 4.0.0
437
441
  */
438
442
  export const makeNodeHttp = Effect.gen(function*() {
@@ -451,8 +455,11 @@ export const makeNodeHttp = Effect.gen(function*() {
451
455
  headers: request.headers,
452
456
  signal
453
457
  })
454
- return Effect.forkChild(sendBody(nodeRequest, request, request.body)).pipe(
455
- Effect.flatMap(() => waitForResponse(nodeRequest, request)),
458
+ return Effect.raceFirst(
459
+ waitForResponse(nodeRequest, request),
460
+ sendBody(nodeRequest, request, request.body).pipe(Effect.andThen(Effect.never))
461
+ ).pipe(
462
+ Effect.onError(() => Effect.sync(() => nodeRequest.destroy())),
456
463
  Effect.map((_) => new NodeHttpResponse(request, _))
457
464
  )
458
465
  })
@@ -645,7 +652,7 @@ class NodeHttpResponse extends NodeHttpIncomingMessage<Error.HttpClientError> im
645
652
  * Provides a node:http-backed `HttpClient` using the current `HttpAgent`
646
653
  * service.
647
654
  *
648
- * @category node:http
655
+ * @category layers
649
656
  * @since 4.0.0
650
657
  */
651
658
  export const layerNodeHttpNoAgent: Layer.Layer<
@@ -658,7 +665,7 @@ export const layerNodeHttpNoAgent: Layer.Layer<
658
665
  * Provides a node:http-backed `HttpClient` together with default scoped Node
659
666
  * `http` and `https` agents.
660
667
  *
661
- * @category node:http
668
+ * @category layers
662
669
  * @since 4.0.0
663
670
  */
664
671
  export const layerNodeHttp: Layer.Layer<Client.HttpClient> = Layer.provide(layerNodeHttpNoAgent, layerAgent)
@@ -8,16 +8,63 @@
8
8
  *
9
9
  * @since 4.0.0
10
10
  */
11
+ import * as NodeHttpCompression from "@effect/platform-node-shared/NodeHttpCompression"
12
+ import * as Effect from "effect/Effect"
11
13
  import { pipe } from "effect/Function"
12
14
  import * as Layer from "effect/Layer"
13
15
  import * as EtagImpl from "effect/unstable/http/Etag"
14
16
  import * as Headers from "effect/unstable/http/Headers"
17
+ import * as HttpBody from "effect/unstable/http/HttpBody"
15
18
  import * as Platform from "effect/unstable/http/HttpPlatform"
16
19
  import * as ServerResponse from "effect/unstable/http/HttpServerResponse"
17
20
  import * as Fs from "node:fs"
18
21
  import { Readable } from "node:stream"
19
22
  import Mime from "./Mime.ts"
20
23
  import * as NodeFileSystem from "./NodeFileSystem.ts"
24
+ import * as NodeStream from "./NodeStream.ts"
25
+
26
+ // replaces the response body while keeping every other field, dropping the
27
+ // now-stale Content-Length header
28
+ const compressedBody = (
29
+ response: ServerResponse.HttpServerResponse,
30
+ body: HttpBody.HttpBody
31
+ ): ServerResponse.HttpServerResponse =>
32
+ ServerResponse.removeHeader(ServerResponse.setBody(response, body), "content-length")
33
+
34
+ const compression = NodeHttpCompression.make({
35
+ algorithms: NodeHttpCompression.algorithms,
36
+ compressResponse(response, algorithm, options) {
37
+ const body = response.body
38
+ switch (body._tag) {
39
+ case "Stream": {
40
+ return Effect.succeed(compressedBody(
41
+ response,
42
+ HttpBody.stream(
43
+ NodeStream.pipeThroughDuplex(body.stream, {
44
+ evaluate: () => NodeHttpCompression.compressTransform(algorithm, options)
45
+ }),
46
+ body.contentType
47
+ )
48
+ ))
49
+ }
50
+ case "Raw": {
51
+ const readable = body.body instanceof Readable
52
+ ? body.body
53
+ : Readable.fromWeb(new Response(body.body as BodyInit).body as any)
54
+ const transform = NodeHttpCompression.compressTransform(algorithm, options)
55
+ readable.on("error", (cause) => transform.destroy(cause))
56
+ transform.on("error", (cause) => readable.destroy(cause))
57
+ transform.on("close", () => readable.destroy())
58
+ return Effect.succeed(
59
+ compressedBody(response, HttpBody.raw(readable.pipe(transform), { contentType: body.contentType }))
60
+ )
61
+ }
62
+ default: {
63
+ return Effect.succeed(response)
64
+ }
65
+ }
66
+ }
67
+ })
21
68
 
22
69
  /**
23
70
  * Creates the Node `HttpPlatform`, serving file responses from Node readable
@@ -27,6 +74,8 @@ import * as NodeFileSystem from "./NodeFileSystem.ts"
27
74
  * @since 4.0.0
28
75
  */
29
76
  export const make = Platform.make({
77
+ platform: "node",
78
+ compression,
30
79
  fileResponse(path, status, statusText, headers, start, end, contentLength) {
31
80
  const stream = contentLength === 0
32
81
  ? Readable.from([])
@@ -62,6 +62,26 @@ import * as NodeMultipart from "./NodeMultipart.ts"
62
62
  import * as NodeServices from "./NodeServices.ts"
63
63
  import { NodeWS } from "./NodeSocket.ts"
64
64
 
65
+ /**
66
+ * Options accepted by the Node `HttpServer` constructors and layers.
67
+ *
68
+ * @category options
69
+ * @since 4.0.0
70
+ */
71
+ export interface Options extends Net.ListenOptions {
72
+ readonly disablePreemptiveShutdown?: boolean | undefined
73
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
74
+ /**
75
+ * Options forwarded to the underlying `ws` `WebSocketServer`, minus the
76
+ * wiring options the server manages itself. Use this to enable
77
+ * `permessage-deflate` compression or tune payload limits, e.g.
78
+ * `websocket: { perMessageDeflate: true }`.
79
+ */
80
+ readonly websocket?:
81
+ | Omit<NodeWS.ServerOptions, "noServer" | "server" | "host" | "port" | "path">
82
+ | undefined
83
+ }
84
+
65
85
  /**
66
86
  * Creates a scoped `HttpServer` from a Node `http.Server`, starts listening
67
87
  * with the supplied options, registers request and upgrade handling, and closes
@@ -72,10 +92,7 @@ import { NodeWS } from "./NodeSocket.ts"
72
92
  */
73
93
  export const make = Effect.fnUntraced(function*(
74
94
  evaluate: LazyArg<Http.Server>,
75
- options: Net.ListenOptions & {
76
- readonly disablePreemptiveShutdown?: boolean | undefined
77
- readonly gracefulShutdownTimeout?: Duration.Input | undefined
78
- }
95
+ options: Options
79
96
  ) {
80
97
  const scope = yield* Effect.scope
81
98
  const server = evaluate()
@@ -116,7 +133,7 @@ export const make = Effect.fnUntraced(function*(
116
133
  const address = server.address()!
117
134
 
118
135
  const wss = yield* Effect.acquireRelease(
119
- Effect.sync(() => new NodeWS.WebSocketServer({ noServer: true })),
136
+ Effect.sync(() => new NodeWS.WebSocketServer({ ...options.websocket, noServer: true })),
120
137
  (wss) =>
121
138
  Effect.callback<void>((resume) => {
122
139
  wss.close(() => resume(Effect.void))
@@ -189,9 +206,8 @@ export const makeHandler = <
189
206
  nodeRequest: Http.IncomingMessage,
190
207
  nodeResponse: Http.ServerResponse
191
208
  ) {
192
- const map = new Map(services.mapUnsafe)
193
- map.set(HttpServerRequest.key, new ServerRequestImpl(nodeRequest, nodeResponse))
194
- const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe<any>(map))(handled), options.scope)
209
+ const context = Context.add(services, HttpServerRequest, new ServerRequestImpl(nodeRequest, nodeResponse))
210
+ const fiber = Fiber.runIn(Effect.runForkWith(context as Context.Context<any>)(handled), options.scope)
195
211
  nodeResponse.on("close", () => {
196
212
  if (!nodeResponse.writableEnded) {
197
213
  fiber.interruptUnsafe(parent.id, ClientAbort.annotation)
@@ -256,9 +272,13 @@ export const makeUpgradeHandler = <
256
272
  (ws) => Effect.sync(() => ws.close())
257
273
  )
258
274
  ))
259
- const map = new Map(services.mapUnsafe)
260
- map.set(HttpServerRequest.key, new ServerRequestImpl(nodeRequest, nodeResponse, upgradeEffect))
261
- const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe<any>(map))(handledApp), options.scope)
275
+ const context = Context.add(
276
+ services,
277
+ HttpServerRequest,
278
+ new ServerRequestImpl(nodeRequest, nodeResponse, upgradeEffect)
279
+ )
280
+ const fiber = Fiber.runIn(Effect.runForkWith(context as Context.Context<any>)(handledApp), options.scope)
281
+ socket.on("error", () => {})
262
282
  socket.on("close", () => {
263
283
  if (!socket.writableEnded) {
264
284
  fiber.interruptUnsafe(parent.id, ClientAbort.annotation)
@@ -330,8 +350,9 @@ class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> impleme
330
350
  return this.source.url!
331
351
  }
332
352
 
353
+ private cachedMethod: HttpMethod | undefined
333
354
  get method(): HttpMethod {
334
- return this.source.method!.toUpperCase() as HttpMethod
355
+ return this.cachedMethod ??= this.source.method!.toUpperCase() as HttpMethod
335
356
  }
336
357
 
337
358
  override get headers(): Headers.Headers {
@@ -397,10 +418,7 @@ class ServerRequestImpl extends NodeHttpIncomingMessage<HttpServerError> impleme
397
418
  */
398
419
  export const layerServer: (
399
420
  evaluate: LazyArg<Http.Server<typeof Http.IncomingMessage, typeof Http.ServerResponse>>,
400
- options: Net.ListenOptions & {
401
- readonly disablePreemptiveShutdown?: boolean | undefined
402
- readonly gracefulShutdownTimeout?: Duration.Input | undefined
403
- }
421
+ options: Options
404
422
  ) => Layer.Layer<HttpServer.HttpServer, ServeError> = flow(make, Layer.effect(HttpServer.HttpServer))
405
423
 
406
424
  /**
@@ -427,10 +445,7 @@ export const layerHttpServices: Layer.Layer<
427
445
  */
428
446
  export const layer = (
429
447
  evaluate: LazyArg<Http.Server>,
430
- options: Net.ListenOptions & {
431
- readonly disablePreemptiveShutdown?: boolean | undefined
432
- readonly gracefulShutdownTimeout?: Duration.Input | undefined
433
- }
448
+ options: Options
434
449
  ): Layer.Layer<
435
450
  HttpServer.HttpServer | NodeServices.NodeServices | HttpPlatform.HttpPlatform | Etag.Generator,
436
451
  ServeError
@@ -450,12 +465,7 @@ export const layer = (
450
465
  */
451
466
  export const layerConfig = (
452
467
  evaluate: LazyArg<Http.Server>,
453
- options: Config.Wrap<
454
- Net.ListenOptions & {
455
- readonly disablePreemptiveShutdown?: boolean | undefined
456
- readonly gracefulShutdownTimeout?: Duration.Input | undefined
457
- }
458
- >
468
+ options: Config.Wrap<Options>
459
469
  ): Layer.Layer<
460
470
  HttpServer.HttpServer | NodeServices.NodeServices | HttpPlatform.HttpPlatform | Etag.Generator,
461
471
  ServeError | Config.ConfigError
@@ -517,9 +527,20 @@ const handleResponse = (
517
527
 
518
528
  if (request.method === "HEAD") {
519
529
  nodeResponse.writeHead(response.status, headers)
520
- return Effect.callback<void>((resume) => {
521
- nodeResponse.end(() => resume(Effect.void))
522
- })
530
+ return Effect.andThen(
531
+ cancelResponseBody(response.body),
532
+ Effect.callback<void>((resume) => {
533
+ let completed = false
534
+ const done = () => {
535
+ if (completed) return
536
+ completed = true
537
+ nodeResponse.off("close", done)
538
+ resume(Effect.void)
539
+ }
540
+ nodeResponse.once("close", done)
541
+ nodeResponse.end(done)
542
+ })
543
+ )
523
544
  }
524
545
  const body = response.body
525
546
  switch (body._tag) {
@@ -603,17 +624,9 @@ const handleResponse = (
603
624
  return body.stream.pipe(
604
625
  Stream.orDie,
605
626
  Stream.runForEachArray((array) => {
606
- let needDrain = false
607
- for (let i = 0; i < array.length; i++) {
608
- const written = nodeResponse.write(array[i])
609
- if (!written && !needDrain) {
610
- needDrain = true
611
- drainLatch.closeUnsafe()
612
- } else if (written && needDrain) {
613
- needDrain = false
614
- }
615
- }
616
- if (!needDrain) return Effect.void
627
+ const chunk = array.length > 1 ? Buffer.concat(array) : array[0]
628
+ if (nodeResponse.write(chunk)) return Effect.void
629
+ drainLatch.closeUnsafe()
617
630
  return drainLatch.await
618
631
  }),
619
632
  Effect.interruptible,
@@ -626,6 +639,14 @@ const handleResponse = (
626
639
  }
627
640
  }
628
641
 
642
+ const cancelResponseBody = (body: HttpServerResponse["body"]): Effect.Effect<void> => {
643
+ const stream = body._tag === "Raw" ? body.body : undefined
644
+ if (stream instanceof Readable) {
645
+ return Effect.sync(() => stream.destroy())
646
+ }
647
+ return Effect.void
648
+ }
649
+
629
650
  const handleCause = (
630
651
  nodeResponse: Http.ServerResponse,
631
652
  originalResponse: HttpServerResponse
@@ -17,11 +17,12 @@ import type * as Path from "effect/Path"
17
17
  import type * as Scope from "effect/Scope"
18
18
  import * as Stream from "effect/Stream"
19
19
  import * as Multipart from "effect/unstable/http/Multipart"
20
- import * as MP from "effect/unstable/http/Multipasta/Node"
20
+ import * as MultipartParser from "effect/unstable/http/MultipartParser"
21
21
  import * as NFS from "node:fs"
22
22
  import type { IncomingHttpHeaders } from "node:http"
23
23
  import type { Readable } from "node:stream"
24
24
  import * as NodeStreamP from "node:stream/promises"
25
+ import * as MP from "./NodeMultipartParser.ts"
25
26
  import * as NodeStream from "./NodeStream.ts"
26
27
 
27
28
  /**
@@ -103,13 +104,13 @@ class FieldImpl extends PartBase implements Multipart.Field {
103
104
  readonly value: string
104
105
 
105
106
  constructor(
106
- info: MP.PartInfo,
107
+ info: MultipartParser.PartInfo,
107
108
  value: Uint8Array
108
109
  ) {
109
110
  super()
110
111
  this.key = info.name
111
112
  this.contentType = info.contentType
112
- this.value = MP.decodeField(info, value)
113
+ this.value = MultipartParser.decodeField(info, value)
113
114
  }
114
115
 
115
116
  toJSON(): unknown {
@@ -158,7 +159,7 @@ class FileImpl extends PartBase implements Multipart.File {
158
159
  }
159
160
  }
160
161
 
161
- function convertError(cause: MP.MultipartError): Multipart.MultipartError {
162
+ function convertError(cause: MultipartParser.MultipartError): Multipart.MultipartError {
162
163
  switch (cause._tag) {
163
164
  case "ReachedLimit": {
164
165
  switch (cause.limit) {