@effect/platform-bun 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 (108) hide show
  1. package/dist/BunChildProcessSpawner.d.ts +1 -1
  2. package/dist/BunChildProcessSpawner.js +1 -1
  3. package/dist/BunClusterHttp.d.ts +46 -7
  4. package/dist/BunClusterHttp.d.ts.map +1 -1
  5. package/dist/BunClusterHttp.js +17 -10
  6. package/dist/BunClusterHttp.js.map +1 -1
  7. package/dist/BunClusterSocket.d.ts +54 -9
  8. package/dist/BunClusterSocket.d.ts.map +1 -1
  9. package/dist/BunClusterSocket.js +54 -9
  10. package/dist/BunClusterSocket.js.map +1 -1
  11. package/dist/BunCrypto.d.ts +10 -0
  12. package/dist/BunCrypto.d.ts.map +1 -0
  13. package/dist/BunCrypto.js +14 -0
  14. package/dist/BunCrypto.js.map +1 -0
  15. package/dist/BunFileSystem.d.ts +4 -2
  16. package/dist/BunFileSystem.d.ts.map +1 -1
  17. package/dist/BunFileSystem.js +27 -3
  18. package/dist/BunFileSystem.js.map +1 -1
  19. package/dist/BunHttpClient.d.ts +2 -2
  20. package/dist/BunHttpClient.js +2 -2
  21. package/dist/BunHttpPlatform.d.ts +4 -2
  22. package/dist/BunHttpPlatform.d.ts.map +1 -1
  23. package/dist/BunHttpPlatform.js +5 -3
  24. package/dist/BunHttpPlatform.js.map +1 -1
  25. package/dist/BunHttpServer.d.ts +77 -19
  26. package/dist/BunHttpServer.d.ts.map +1 -1
  27. package/dist/BunHttpServer.js +63 -36
  28. package/dist/BunHttpServer.js.map +1 -1
  29. package/dist/BunHttpServerRequest.d.ts +26 -2
  30. package/dist/BunHttpServerRequest.d.ts.map +1 -1
  31. package/dist/BunHttpServerRequest.js +3 -1
  32. package/dist/BunHttpServerRequest.js.map +1 -1
  33. package/dist/BunMultipart.d.ts +28 -5
  34. package/dist/BunMultipart.d.ts.map +1 -1
  35. package/dist/BunMultipart.js +15 -5
  36. package/dist/BunMultipart.js.map +1 -1
  37. package/dist/BunPath.d.ts +12 -6
  38. package/dist/BunPath.d.ts.map +1 -1
  39. package/dist/BunPath.js +30 -7
  40. package/dist/BunPath.js.map +1 -1
  41. package/dist/BunRedis.d.ts +39 -9
  42. package/dist/BunRedis.d.ts.map +1 -1
  43. package/dist/BunRedis.js +42 -12
  44. package/dist/BunRedis.js.map +1 -1
  45. package/dist/BunRuntime.d.ts +24 -30
  46. package/dist/BunRuntime.d.ts.map +1 -1
  47. package/dist/BunRuntime.js +25 -11
  48. package/dist/BunRuntime.js.map +1 -1
  49. package/dist/BunServices.d.ts +32 -5
  50. package/dist/BunServices.d.ts.map +1 -1
  51. package/dist/BunServices.js +7 -3
  52. package/dist/BunServices.js.map +1 -1
  53. package/dist/BunSink.d.ts +2 -2
  54. package/dist/BunSink.js +2 -2
  55. package/dist/BunSocket.d.ts +30 -4
  56. package/dist/BunSocket.d.ts.map +1 -1
  57. package/dist/BunSocket.js +10 -3
  58. package/dist/BunSocket.js.map +1 -1
  59. package/dist/BunSocketServer.d.ts +2 -2
  60. package/dist/BunSocketServer.js +2 -2
  61. package/dist/BunStdio.d.ts +5 -2
  62. package/dist/BunStdio.d.ts.map +1 -1
  63. package/dist/BunStdio.js +24 -3
  64. package/dist/BunStdio.js.map +1 -1
  65. package/dist/BunStream.d.ts +3 -2
  66. package/dist/BunStream.d.ts.map +1 -1
  67. package/dist/BunStream.js +25 -3
  68. package/dist/BunStream.js.map +1 -1
  69. package/dist/BunTerminal.d.ts +8 -2
  70. package/dist/BunTerminal.d.ts.map +1 -1
  71. package/dist/BunTerminal.js +23 -3
  72. package/dist/BunTerminal.js.map +1 -1
  73. package/dist/BunWorker.d.ts +9 -2
  74. package/dist/BunWorker.d.ts.map +1 -1
  75. package/dist/BunWorker.js +29 -4
  76. package/dist/BunWorker.js.map +1 -1
  77. package/dist/BunWorkerRunner.d.ts +5 -1
  78. package/dist/BunWorkerRunner.d.ts.map +1 -1
  79. package/dist/BunWorkerRunner.js +27 -5
  80. package/dist/BunWorkerRunner.js.map +1 -1
  81. package/dist/index.d.ts +396 -21
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +396 -21
  84. package/dist/index.js.map +1 -1
  85. package/package.json +5 -5
  86. package/src/BunChildProcessSpawner.ts +1 -1
  87. package/src/BunClusterHttp.ts +51 -11
  88. package/src/BunClusterSocket.ts +54 -9
  89. package/src/BunCrypto.ts +16 -0
  90. package/src/BunFileSystem.ts +27 -3
  91. package/src/BunHttpClient.ts +2 -2
  92. package/src/BunHttpPlatform.ts +28 -4
  93. package/src/BunHttpServer.ts +128 -56
  94. package/src/BunHttpServerRequest.ts +26 -2
  95. package/src/BunMultipart.ts +36 -6
  96. package/src/BunPath.ts +30 -7
  97. package/src/BunRedis.ts +44 -14
  98. package/src/BunRuntime.ts +41 -31
  99. package/src/BunServices.ts +34 -5
  100. package/src/BunSink.ts +2 -2
  101. package/src/BunSocket.ts +30 -4
  102. package/src/BunSocketServer.ts +2 -2
  103. package/src/BunStdio.ts +24 -3
  104. package/src/BunStream.ts +25 -3
  105. package/src/BunTerminal.ts +23 -3
  106. package/src/BunWorker.ts +29 -4
  107. package/src/BunWorkerRunner.ts +27 -5
  108. package/src/index.ts +397 -21
@@ -1,5 +1,40 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * The `BunClusterSocket` module provides the Bun socket transport for Effect
3
+ * Cluster runners. It wires `SocketRunner` to Bun-compatible TCP sockets,
4
+ * supplies RPC client and server protocol layers, and builds a complete
5
+ * sharding layer with serialization, runner health, runner storage, and message
6
+ * storage.
7
+ *
8
+ * **Common tasks**
9
+ *
10
+ * - Run a Bun process as a cluster runner over raw TCP sockets with
11
+ * {@link layer}
12
+ * - Connect a client-only process to an existing socket cluster without
13
+ * starting a runner server
14
+ * - Use SQL-backed storage for durable multi-process clusters, `local` storage
15
+ * for short-lived development, or `byo` storage when the deployment owns the
16
+ * persistence boundary
17
+ * - Check runner health with socket pings or Kubernetes pod readiness through
18
+ * {@link layerK8sHttpClient}
19
+ *
20
+ * **Gotchas**
21
+ *
22
+ * - `runnerAddress` is the host and port advertised to other runners; set
23
+ * `runnerListenAddress` when the local bind address differs from the
24
+ * externally reachable address
25
+ * - The socket transport is point-to-point RPC, not cluster gossip: runner
26
+ * membership, shard ownership, and persisted delivery are coordinated through
27
+ * `RunnerStorage`, `MessageStorage`, and `RunnerHealth`
28
+ * - `clientOnly` does not start a socket server or receive shard assignments
29
+ * - SQL storage is the default; `local` storage is in-memory/noop and `byo`
30
+ * requires the surrounding application to provide both runner and message
31
+ * storage services
32
+ * - Ping health checks use the same socket protocol, so unreachable ports,
33
+ * firewalls, or serialization mismatches can make a runner appear unhealthy
34
+ * - Kubernetes health checks use Bun's Fetch-backed HTTP client and the service
35
+ * account CA certificate when it is available
36
+ *
37
+ * @since 4.0.0
3
38
  */
4
39
  import { layerClientProtocol, layerSocketServer } from "@effect/platform-node-shared/NodeClusterSocket"
5
40
  import type * as Config from "effect/Config"
@@ -24,20 +59,28 @@ import * as BunFileSystem from "./BunFileSystem.ts"
24
59
 
25
60
  export {
26
61
  /**
27
- * @since 1.0.0
28
- * @category Re-exports
62
+ * Provides the cluster `RpcClientProtocol` using the shared socket client
63
+ * implementation.
64
+ *
65
+ * @category re-exports
66
+ * @since 4.0.0
29
67
  */
30
68
  layerClientProtocol,
31
69
  /**
32
- * @since 1.0.0
33
- * @category Re-exports
70
+ * Provides the socket server used by Bun cluster runners through the shared
71
+ * socket server implementation.
72
+ *
73
+ * @category re-exports
74
+ * @since 4.0.0
34
75
  */
35
76
  layerSocketServer
36
77
  }
37
78
 
38
79
  /**
39
- * @since 1.0.0
40
- * @category Layers
80
+ * Creates Bun socket cluster layers, configuring serialization, storage, runner health, and optional client-only mode.
81
+ *
82
+ * @category layers
83
+ * @since 4.0.0
41
84
  */
42
85
  export const layer = <
43
86
  const ClientOnly extends boolean = false,
@@ -110,8 +153,10 @@ export const layer = <
110
153
  }
111
154
 
112
155
  /**
113
- * @since 1.0.0
114
- * @category Layers
156
+ * Layer that provides `K8sHttpClient`, using the Kubernetes service-account CA certificate when it is available.
157
+ *
158
+ * @category layers
159
+ * @since 4.0.0
115
160
  */
116
161
  export const layerK8sHttpClient: Layer.Layer<K8sHttpClient.K8sHttpClient> = K8sHttpClient.layer.pipe(
117
162
  Layer.provide(Layer.unwrap(Effect.gen(function*() {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Bun platform Crypto service layer.
3
+ *
4
+ * @since 1.0.0
5
+ */
6
+ import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
7
+ import type * as Crypto from "effect/Crypto"
8
+ import type * as Layer from "effect/Layer"
9
+
10
+ /**
11
+ * A layer that provides the Bun Crypto service implementation.
12
+ *
13
+ * @category layers
14
+ * @since 1.0.0
15
+ */
16
+ export const layer: Layer.Layer<Crypto.Crypto> = NodeCrypto.layer
@@ -1,12 +1,36 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun layer for Effect's `FileSystem` service.
3
+ *
4
+ * Use this module at the edge of Bun applications, CLIs, scripts, and tests
5
+ * that need real local filesystem access through `effect/FileSystem`: reading
6
+ * and writing files, creating directories and temporary files, inspecting
7
+ * metadata, managing links, or watching paths for changes. It exposes only the
8
+ * Bun `FileSystem` layer; the operations themselves are accessed from the
9
+ * `FileSystem` service once the layer is provided, or from `BunServices.layer`
10
+ * when the program also needs the standard Bun path, stdio, terminal, and child
11
+ * process services.
12
+ *
13
+ * Bun supports Node-compatible filesystem APIs, so this layer reuses the shared
14
+ * Node filesystem implementation. Paths therefore follow the current process and
15
+ * host platform rules: relative paths are resolved from the current working
16
+ * directory, separators and drive/UNC behavior are platform-dependent, and
17
+ * request URLs should be decoded and validated before being mapped to local
18
+ * paths. The service works with bytes, scoped file handles, and Effect
19
+ * streams/sinks; use `FileSystem.stream` for large files instead of
20
+ * `readFile`, and remember that stream offsets and lengths are byte positions.
21
+ * Bun `File` and `Blob` values are not filesystem handles here; path-based HTTP
22
+ * file responses are handled by the Bun HTTP platform adapter with `Bun.file`.
23
+ *
24
+ * @since 4.0.0
3
25
  */
4
26
  import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem"
5
27
  import type { FileSystem } from "effect/FileSystem"
6
28
  import type * as Layer from "effect/Layer"
7
29
 
8
30
  /**
9
- * @since 1.0.0
10
- * @category layer
31
+ * Layer that provides the `FileSystem` service for Bun using the shared Node file-system implementation.
32
+ *
33
+ * @category layers
34
+ * @since 4.0.0
11
35
  */
12
36
  export const layer: Layer.Layer<FileSystem, never, never> = NodeFileSystem.layer
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * @since 4.0.0
3
3
  */
4
4
 
5
5
  /**
6
- * @since 1.0.0
7
6
  * @category re-exports
7
+ * @since 4.0.0
8
8
  */
9
9
  export * from "effect/unstable/http/FetchHttpClient"
@@ -1,5 +1,27 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun implementation of the Effect HTTP platform service.
3
+ *
4
+ * This module connects the portable `HttpPlatform` file response helpers to
5
+ * Bun's Web-compatible runtime. `BunHttpServer` provides this layer when
6
+ * applications serve local files, public assets, downloads, byte ranges, or
7
+ * Web `File` values from Effect `HttpServerResponse` constructors.
8
+ *
9
+ * Path-based responses are backed by `Bun.file`, and Web `File` responses are
10
+ * returned directly as raw response bodies. The shared `HttpPlatform` service
11
+ * still computes file metadata such as ETags and last-modified headers, while
12
+ * this adapter lets Bun's `Response` implementation handle the platform body.
13
+ *
14
+ * Because the Bun server adapter sits on top of Web `Request` and `Response`,
15
+ * request bodies follow the usual single-consumption rules: choose the
16
+ * streamed, text, URL-encoded, or multipart view that matches the route. For
17
+ * `FormData` responses, let the `Response` constructor create the multipart
18
+ * content type and boundary unless you intentionally override it. File
19
+ * responses take filesystem paths, not request URLs; Bun request URLs are
20
+ * absolute at the runtime edge, and route paths are normalized by
21
+ * `BunHttpServer`, so decode and validate URL pathnames before mapping them to
22
+ * files.
23
+ *
24
+ * @since 4.0.0
3
25
  */
4
26
  import type { Effect } from "effect"
5
27
  import type { FileSystem } from "effect/FileSystem"
@@ -10,8 +32,8 @@ import * as Response from "effect/unstable/http/HttpServerResponse"
10
32
  import * as BunFileSystem from "./BunFileSystem.ts"
11
33
 
12
34
  /**
13
- * @since 1.0.0
14
35
  * @category constructors
36
+ * @since 4.0.0
15
37
  */
16
38
  const make: Effect.Effect<
17
39
  Platform.HttpPlatform["Service"],
@@ -31,8 +53,10 @@ const make: Effect.Effect<
31
53
  })
32
54
 
33
55
  /**
34
- * @since 1.0.0
35
- * @category Layers
56
+ * Layer that provides the Bun `HttpPlatform`, including file responses backed by `Bun.file`.
57
+ *
58
+ * @category layers
59
+ * @since 4.0.0
36
60
  */
37
61
  export const layer = Layer.effect(Platform.HttpPlatform)(make).pipe(
38
62
  Layer.provide(BunFileSystem.layer),
@@ -1,10 +1,43 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun implementation of the Effect `HttpServer`.
3
+ *
4
+ * This module builds an Effect HTTP server from `Bun.serve`, translating Bun's
5
+ * Web `Request` objects into `HttpServerRequest` values and Effect
6
+ * `HttpServerResponse` values back into Web `Response` objects. It is the Bun
7
+ * runtime entry point for serving `HttpApp`s, streaming responses, file
8
+ * responses through `BunHttpPlatform`, multipart requests, and websocket
9
+ * endpoints through `HttpServerRequest.upgrade`.
10
+ *
11
+ * Common use cases include using {@link layer} or {@link layerConfig} to serve
12
+ * an application from Bun configuration, {@link layerServer} when only the
13
+ * `HttpServer` service is needed, and {@link layerTest} for tests that need an
14
+ * ephemeral Bun listener and fetch-compatible client.
15
+ *
16
+ * Bun supplies absolute request URLs and Web-standard request bodies. This
17
+ * adapter stores the normalized path-and-query URL on `HttpServerRequest.url`,
18
+ * while the underlying `Request` still follows Web body rules: pick the
19
+ * streamed, text, JSON, URL-encoded, or multipart view that matches the route
20
+ * instead of consuming the same body in incompatible ways. Because `Bun.serve`
21
+ * has a single active `fetch` handler, each `serve` call reloads that handler
22
+ * and restores the previous one when the serve scope finalizes.
23
+ *
24
+ * WebSocket upgrades must happen from the Bun request handler. The
25
+ * `HttpServerRequest.upgrade` effect calls `server.upgrade`, fails when Bun says
26
+ * the request is not upgradeable, buffers messages that arrive before the
27
+ * Effect socket handler is installed, and maps non-normal close codes into
28
+ * `Socket` errors. The server is stopped with `server.stop()` when its
29
+ * acquisition scope closes; unless preemptive shutdown is disabled, finalizing
30
+ * a serve scope also starts that stop with the configured graceful shutdown
31
+ * timeout or the default timeout.
32
+ *
33
+ * @since 4.0.0
3
34
  */
4
35
  import type { Server as BunServer, ServerWebSocket } from "bun"
5
36
  import * as Config from "effect/Config"
6
37
  import type { ConfigError } from "effect/Config"
38
+ import * as Context from "effect/Context"
7
39
  import * as Deferred from "effect/Deferred"
40
+ import * as Duration from "effect/Duration"
8
41
  import * as Effect from "effect/Effect"
9
42
  import * as Exit from "effect/Exit"
10
43
  import * as Fiber from "effect/Fiber"
@@ -13,11 +46,12 @@ import type * as FileSystem from "effect/FileSystem"
13
46
  import { flow } from "effect/Function"
14
47
  import * as Inspectable from "effect/Inspectable"
15
48
  import * as Layer from "effect/Layer"
49
+ import * as Option from "effect/Option"
16
50
  import type * as Path from "effect/Path"
17
51
  import type * as Record from "effect/Record"
18
- import type * as Scope from "effect/Scope"
52
+ import type * as Schema from "effect/Schema"
53
+ import * as Scope from "effect/Scope"
19
54
  import * as Semaphore from "effect/Semaphore"
20
- import * as ServiceMap from "effect/ServiceMap"
21
55
  import * as Stream from "effect/Stream"
22
56
  import * as Cookies from "effect/unstable/http/Cookies"
23
57
  import * as Etag from "effect/unstable/http/Etag"
@@ -41,8 +75,10 @@ import * as BunServices from "./BunServices.ts"
41
75
  import * as BunStream from "./BunStream.ts"
42
76
 
43
77
  /**
44
- * @since 1.0.0
78
+ * Bun serve options accepted by the HTTP server, extended with typed route definitions.
79
+ *
45
80
  * @category Options
81
+ * @since 4.0.0
46
82
  */
47
83
  export type ServeOptions<R extends string> =
48
84
  & (
@@ -52,13 +88,19 @@ export type ServeOptions<R extends string> =
52
88
  & { readonly routes?: Bun.Serve.Routes<WebSocketContext, R> }
53
89
 
54
90
  /**
55
- * @since 1.0.0
56
- * @category Constructors
91
+ * Creates a scoped Bun `HttpServer` from `Bun.serve` options, stopping the server on scope finalization with optional graceful shutdown settings.
92
+ *
93
+ * @category constructors
94
+ * @since 4.0.0
57
95
  */
58
96
  export const make = Effect.fnUntraced(
59
97
  function*<R extends string>(
60
- options: ServeOptions<R>
98
+ options: ServeOptions<R> & {
99
+ readonly disablePreemptiveShutdown?: boolean | undefined
100
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
101
+ }
61
102
  ) {
103
+ const scope = yield* Effect.scope
62
104
  const handlerStack: Array<(request: Request, server: BunServer<WebSocketContext>) => Response | Promise<Response>> =
63
105
  [
64
106
  function(_request, _server) {
@@ -76,6 +118,7 @@ export const make = Effect.fnUntraced(
76
118
  ws.data.run(message)
77
119
  },
78
120
  close(ws, code, closeReason) {
121
+ code = typeof code === "number" ? code : 1001
79
122
  Deferred.doneUnsafe(
80
123
  ws.data.closeDeferred,
81
124
  Socket.defaultCloseCodeIsError(code)
@@ -90,13 +133,24 @@ export const make = Effect.fnUntraced(
90
133
  }
91
134
  })
92
135
 
93
- yield* Effect.addFinalizer(() => Effect.promise(() => server.stop()))
136
+ const shutdown = yield* Effect.promise(() => server.stop()).pipe(
137
+ Effect.cached
138
+ )
139
+ const preemptiveShutdown = options.disablePreemptiveShutdown ? Effect.void : Effect.timeoutOrElse(shutdown, {
140
+ duration: options.gracefulShutdownTimeout ?? Duration.seconds(20),
141
+ orElse: () => Effect.void
142
+ })
143
+
144
+ yield* Scope.addFinalizer(scope, shutdown)
94
145
 
95
146
  return Server.make({
96
147
  address: { _tag: "TcpAddress", port: server.port!, hostname: server.hostname! },
97
148
  serve: Effect.fnUntraced(function*(httpApp, middleware) {
98
- const scope = yield* Effect.scope
99
- const services = yield* Effect.services<never>()
149
+ const parent = yield* Effect.fiber
150
+ const services = parent.context
151
+ const serveScope = Context.getUnsafe(services, Scope.Scope)
152
+ const scope = Scope.forkUnsafe(serveScope, "parallel")
153
+
100
154
  const httpEffect = HttpEffect.toHandled(httpApp, (request, response) =>
101
155
  Effect.sync(() => {
102
156
  ;(request as BunServerRequest).resolve(makeResponse(request, response, services, scope))
@@ -109,24 +163,20 @@ export const make = Effect.fnUntraced(
109
163
  ServerRequest.HttpServerRequest.key,
110
164
  new BunServerRequest(request, resolve, removeHost(request.url), server)
111
165
  )
112
- const fiber = Fiber.runIn(Effect.runForkWith(ServiceMap.makeUnsafe<any>(map))(httpEffect), scope)
166
+ const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe<any>(map))(httpEffect), scope)
113
167
  request.signal.addEventListener("abort", () => {
114
- fiber.interruptUnsafe(Error.clientAbortFiberId)
168
+ fiber.interruptUnsafe(parent.id, Error.ClientAbort.annotation)
115
169
  }, { once: true })
116
170
  })
117
171
  }
118
172
 
119
- yield* Effect.acquireRelease(
120
- Effect.sync(() => {
121
- handlerStack.push(handler)
122
- server.reload({ fetch: handler })
123
- }),
124
- () =>
125
- Effect.sync(() => {
126
- handlerStack.pop()
127
- server.reload({ fetch: handlerStack[handlerStack.length - 1] })
128
- })
129
- )
173
+ yield* Scope.addFinalizerExit(serveScope, () => {
174
+ handlerStack.pop()
175
+ server.reload({ fetch: handlerStack[handlerStack.length - 1] })
176
+ return preemptiveShutdown
177
+ })
178
+ handlerStack.push(handler)
179
+ server.reload({ fetch: handler })
130
180
  })
131
181
  })
132
182
  }
@@ -135,7 +185,7 @@ export const make = Effect.fnUntraced(
135
185
  const makeResponse = (
136
186
  request: ServerRequest.HttpServerRequest,
137
187
  response: ServerResponse.HttpServerResponse,
138
- services: ServiceMap.ServiceMap<never>,
188
+ context: Context.Context<never>,
139
189
  scope: Scope.Scope
140
190
  ): Response => {
141
191
  const fields: {
@@ -186,7 +236,7 @@ const makeResponse = (
186
236
  Fiber.runIn(fiber, scope)
187
237
  return Effect.succeed(body.stream)
188
238
  })),
189
- services
239
+ context
190
240
  ),
191
241
  fields
192
242
  )
@@ -195,16 +245,23 @@ const makeResponse = (
195
245
  }
196
246
 
197
247
  /**
198
- * @since 1.0.0
199
- * @category Layers
248
+ * Layer that provides only `HttpServer` by constructing a scoped Bun server from the supplied serve options.
249
+ *
250
+ * @category layers
251
+ * @since 4.0.0
200
252
  */
201
253
  export const layerServer: <R extends string>(
202
- options: ServeOptions<R>
254
+ options: ServeOptions<R> & {
255
+ readonly disablePreemptiveShutdown?: boolean | undefined
256
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
257
+ }
203
258
  ) => Layer.Layer<Server.HttpServer> = flow(make, Layer.effect(Server.HttpServer)) as any
204
259
 
205
260
  /**
206
- * @since 1.0.0
207
- * @category Layers
261
+ * Layer that provides Bun HTTP support services: `HttpPlatform`, weak ETag generation, and `BunServices`.
262
+ *
263
+ * @category layers
264
+ * @since 4.0.0
208
265
  */
209
266
  export const layerHttpServices: Layer.Layer<
210
267
  | HttpPlatform
@@ -217,11 +274,16 @@ export const layerHttpServices: Layer.Layer<
217
274
  )
218
275
 
219
276
  /**
220
- * @since 1.0.0
221
- * @category Layers
277
+ * Layer that provides a Bun `HttpServer` together with the Bun HTTP platform, ETag generator, and Bun services.
278
+ *
279
+ * @category layers
280
+ * @since 4.0.0
222
281
  */
223
282
  export const layer = <R extends string>(
224
- options: ServeOptions<R>
283
+ options: ServeOptions<R> & {
284
+ readonly disablePreemptiveShutdown?: boolean | undefined
285
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
286
+ }
225
287
  ): Layer.Layer<
226
288
  | Server.HttpServer
227
289
  | HttpPlatform
@@ -230,8 +292,10 @@ export const layer = <R extends string>(
230
292
  > => Layer.mergeAll(layerServer(options), layerHttpServices)
231
293
 
232
294
  /**
233
- * @since 1.0.0
234
- * @category Layers
295
+ * Test layer that starts a Bun HTTP server on an ephemeral port and provides the HTTP test client dependencies.
296
+ *
297
+ * @category layers
298
+ * @since 4.0.0
235
299
  */
236
300
  export const layerTest: Layer.Layer<
237
301
  Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path | HttpClient
@@ -243,17 +307,24 @@ export const layerTest: Layer.Layer<
243
307
  )
244
308
 
245
309
  /**
246
- * @since 1.0.0
247
- * @category Layers
310
+ * Creates the Bun HTTP server and support-services layer from configurable serve options.
311
+ *
312
+ * @category layers
313
+ * @since 4.0.0
248
314
  */
249
315
  export const layerConfig = <R extends string>(
250
- options: Config.Wrap<ServeOptions<R>>
316
+ options: Config.Wrap<
317
+ ServeOptions<R> & {
318
+ readonly disablePreemptiveShutdown?: boolean | undefined
319
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
320
+ }
321
+ >
251
322
  ): Layer.Layer<
252
323
  Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path,
253
324
  ConfigError
254
325
  > =>
255
326
  Layer.mergeAll(
256
- Layer.effect(Server.HttpServer)(Effect.flatMap(Config.unwrap(options).asEffect(), make)),
327
+ Layer.effect(Server.HttpServer)(Effect.flatMap(Config.unwrap(options), make)),
257
328
  layerHttpServices
258
329
  )
259
330
 
@@ -280,7 +351,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
280
351
  readonly url: string
281
352
  private bunServer: BunServer<WebSocketContext>
282
353
  public headersOverride?: Headers.Headers | undefined
283
- private remoteAddressOverride?: string | undefined
354
+ private remoteAddressOverride?: Option.Option<string> | undefined
284
355
 
285
356
  constructor(
286
357
  source: Request,
@@ -288,7 +359,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
288
359
  url: string,
289
360
  bunServer: BunServer<WebSocketContext>,
290
361
  headersOverride?: Headers.Headers,
291
- remoteAddressOverride?: string
362
+ remoteAddressOverride?: Option.Option<string>
292
363
  ) {
293
364
  super()
294
365
  this[ServerRequest.TypeId] = ServerRequest.TypeId
@@ -311,7 +382,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
311
382
  options: {
312
383
  readonly url?: string | undefined
313
384
  readonly headers?: Headers.Headers | undefined
314
- readonly remoteAddress?: string | undefined
385
+ readonly remoteAddress?: Option.Option<string> | undefined
315
386
  }
316
387
  ) {
317
388
  return new BunServerRequest(
@@ -320,7 +391,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
320
391
  options.url ?? this.url,
321
392
  this.bunServer,
322
393
  options.headers ?? this.headersOverride,
323
- options.remoteAddress ?? this.remoteAddressOverride
394
+ "remoteAddress" in options ? options.remoteAddress : this.remoteAddressOverride
324
395
  )
325
396
  }
326
397
  get method(): HttpMethod {
@@ -329,8 +400,8 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
329
400
  get originalUrl() {
330
401
  return this.source.url
331
402
  }
332
- get remoteAddress(): string | undefined {
333
- return this.remoteAddressOverride ?? this.bunServer.requestIP(this.source)?.address
403
+ get remoteAddress(): Option.Option<string> {
404
+ return this.remoteAddressOverride ?? Option.fromNullishOr(this.bunServer.requestIP(this.source)?.address)
334
405
  }
335
406
  get headers(): Headers.Headers {
336
407
  this.headersOverride ??= Headers.fromInput(this.source.headers)
@@ -348,7 +419,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
348
419
  get stream(): Stream.Stream<Uint8Array, Error.HttpServerError> {
349
420
  return this.source.body
350
421
  ? BunStream.fromReadableStream({
351
- evaluate: () => this.source.body as any,
422
+ evaluate: () => this.source.body ?? emptyReadbleStream,
352
423
  onError: (cause) =>
353
424
  new Error.HttpServerError({
354
425
  reason: new Error.RequestParseError({
@@ -387,10 +458,10 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
387
458
  return this.textEffect
388
459
  }
389
460
 
390
- get json(): Effect.Effect<unknown, Error.HttpServerError> {
461
+ get json(): Effect.Effect<Schema.Json, Error.HttpServerError> {
391
462
  return Effect.flatMap(this.text, (_) =>
392
463
  Effect.try({
393
- try: () => JSON.parse(_) as unknown,
464
+ try: () => JSON.parse(_) as Schema.Json,
394
465
  catch: (cause) =>
395
466
  new Error.HttpServerError({
396
467
  reason: new Error.RequestParseError({
@@ -457,6 +528,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
457
528
  })
458
529
  })
459
530
  ))
531
+ this.textEffect = Effect.map(this.arrayBufferEffect, (_) => new TextDecoder().decode(_))
460
532
  return this.arrayBufferEffect
461
533
  }
462
534
 
@@ -524,14 +596,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
524
596
  semaphore.withPermits(1)
525
597
  )
526
598
 
527
- const encoder = new TextEncoder()
528
- const run = <R, E, _>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void, opts?: {
529
- readonly onOpen?: Effect.Effect<void> | undefined
530
- }) => runRaw((data) => typeof data === "string" ? handler(encoder.encode(data)) : handler(data), opts)
531
-
532
- return Socket.Socket.of({
533
- [Socket.TypeId]: Socket.TypeId as typeof Socket.TypeId,
534
- run,
599
+ return Socket.make({
535
600
  runRaw,
536
601
  writer
537
602
  })
@@ -540,6 +605,13 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
540
605
  }
541
606
  }
542
607
 
608
+ const emptyReadbleStream = new ReadableStream({
609
+ start(controller) {
610
+ controller.enqueue(new Uint8Array())
611
+ controller.close()
612
+ }
613
+ })
614
+
543
615
  const removeHost = (url: string) => {
544
616
  if (url[0] === "/") {
545
617
  return url
@@ -1,11 +1,35 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Accessors for the Bun `Request` object backing a platform Bun
3
+ * `HttpServerRequest`.
4
+ *
5
+ * Use this module at interop boundaries when an Effect HTTP handler needs the
6
+ * original `Bun.BunRequest`, for example to read Bun route parameters, pass the
7
+ * request to Bun-specific APIs, inspect Web `Request` fields that are not
8
+ * exposed by the portable `HttpServerRequest` interface, or coordinate with code
9
+ * that already works directly with Bun's server request type.
10
+ *
11
+ * The returned request is the original Web request supplied by `Bun.serve`. 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
+ * the same one-shot Web `ReadableStream` used by the Effect body helpers, so
15
+ * calling `text`, `json`, `formData`, `arrayBuffer`, or reading `body` directly
16
+ * can disturb the request and conflict with Effect body, multipart, or stream
17
+ * helpers unless ownership of the body is clear.
18
+ *
19
+ * Bun stores client IP information on the server rather than on the request
20
+ * object. Prefer `HttpServerRequest.remoteAddress` when you need the address
21
+ * seen by Effect or middleware; the raw request returned here will not expose
22
+ * middleware-provided remote address overrides.
23
+ *
24
+ * @since 4.0.0
3
25
  */
4
26
  import type { HttpServerRequest } from "effect/unstable/http/HttpServerRequest"
5
27
 
6
28
  /**
7
- * @since 1.0.0
29
+ * Returns the underlying `Bun.BunRequest` from an Effect `HttpServerRequest`.
30
+ *
8
31
  * @category Accessors
32
+ * @since 4.0.0
9
33
  */
10
34
  export const toBunServerRequest = <T extends string = string>(self: HttpServerRequest): Bun.BunRequest<T> =>
11
35
  (self as any).source