@effect/platform-bun 4.0.0-beta.9 → 4.0.0-beta.90

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 +44 -7
  4. package/dist/BunClusterHttp.d.ts.map +1 -1
  5. package/dist/BunClusterHttp.js +41 -10
  6. package/dist/BunClusterHttp.js.map +1 -1
  7. package/dist/BunClusterSocket.d.ts +27 -9
  8. package/dist/BunClusterSocket.d.ts.map +1 -1
  9. package/dist/BunClusterSocket.js +27 -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 +22 -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 +11 -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 +57 -20
  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 +11 -3
  30. package/dist/BunHttpServerRequest.d.ts.map +1 -1
  31. package/dist/BunHttpServerRequest.js +4 -2
  32. package/dist/BunHttpServerRequest.js.map +1 -1
  33. package/dist/BunMultipart.d.ts +16 -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 +20 -7
  40. package/dist/BunPath.js.map +1 -1
  41. package/dist/BunRedis.d.ts +23 -9
  42. package/dist/BunRedis.d.ts.map +1 -1
  43. package/dist/BunRedis.js +26 -12
  44. package/dist/BunRedis.js.map +1 -1
  45. package/dist/BunRuntime.d.ts +21 -30
  46. package/dist/BunRuntime.d.ts.map +1 -1
  47. package/dist/BunRuntime.js +14 -11
  48. package/dist/BunRuntime.js.map +1 -1
  49. package/dist/BunServices.d.ts +19 -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 +18 -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 +13 -3
  64. package/dist/BunStdio.js.map +1 -1
  65. package/dist/BunStream.d.ts +5 -4
  66. package/dist/BunStream.d.ts.map +1 -1
  67. package/dist/BunStream.js +14 -5
  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 +15 -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 +20 -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 +17 -5
  80. package/dist/BunWorkerRunner.js.map +1 -1
  81. package/dist/index.d.ts +25 -23
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +25 -23
  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 +49 -11
  88. package/src/BunClusterSocket.ts +27 -9
  89. package/src/BunCrypto.ts +24 -0
  90. package/src/BunFileSystem.ts +11 -3
  91. package/src/BunHttpClient.ts +2 -2
  92. package/src/BunHttpPlatform.ts +14 -4
  93. package/src/BunHttpServer.ts +108 -57
  94. package/src/BunHttpServerRequest.ts +11 -3
  95. package/src/BunMultipart.ts +24 -6
  96. package/src/BunPath.ts +20 -7
  97. package/src/BunRedis.ts +28 -14
  98. package/src/BunRuntime.ts +28 -31
  99. package/src/BunServices.ts +21 -5
  100. package/src/BunSink.ts +2 -2
  101. package/src/BunSocket.ts +18 -4
  102. package/src/BunSocketServer.ts +2 -2
  103. package/src/BunStdio.ts +13 -3
  104. package/src/BunStream.ts +14 -5
  105. package/src/BunTerminal.ts +15 -3
  106. package/src/BunWorker.ts +20 -4
  107. package/src/BunWorkerRunner.ts +17 -5
  108. package/src/index.ts +26 -23
@@ -1,9 +1,17 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun HTTP and WebSocket layers for Effect Cluster runners.
3
+ *
4
+ * `layerHttpServer` provides the Bun HTTP server used by cluster runners. The
5
+ * main `layer` builds a sharding layer for HTTP or WebSocket transport,
6
+ * choosing serialization, runner health checks, runner storage, message
7
+ * storage, and optional client-only mode from the supplied options.
8
+ *
9
+ * @since 4.0.0
3
10
  */
4
11
  import type * as Config from "effect/Config"
5
12
  import * as Effect from "effect/Effect"
6
13
  import * as Layer from "effect/Layer"
14
+ import * as Option from "effect/Option"
7
15
  import * as HttpRunner from "effect/unstable/cluster/HttpRunner"
8
16
  import * as MessageStorage from "effect/unstable/cluster/MessageStorage"
9
17
  import * as RunnerHealth from "effect/unstable/cluster/RunnerHealth"
@@ -28,15 +36,19 @@ import * as BunSocket from "./BunSocket.ts"
28
36
 
29
37
  export {
30
38
  /**
31
- * @since 1.0.0
32
- * @category Re-exports
39
+ * Layer that provides a Kubernetes HTTP client for runner health checks.
40
+ *
41
+ * @category re-exports
42
+ * @since 4.0.0
33
43
  */
34
44
  layerK8sHttpClient
35
45
  }
36
46
 
37
47
  /**
38
- * @since 1.0.0
39
- * @category Layers
48
+ * Layer that provides a Bun HTTP server for cluster runners.
49
+ *
50
+ * @category layers
51
+ * @since 4.0.0
40
52
  */
41
53
  export const layerHttpServer: Layer.Layer<
42
54
  | HttpPlatform
@@ -47,16 +59,42 @@ export const layerHttpServer: Layer.Layer<
47
59
  ShardingConfig.ShardingConfig
48
60
  > = Effect.gen(function*() {
49
61
  const config = yield* ShardingConfig.ShardingConfig
50
- const listenAddress = config.runnerListenAddress ?? config.runnerAddress
51
- if (listenAddress === undefined) {
52
- return yield* Effect.die("BunClusterHttp.layerHttpServer: ShardingConfig.runnerAddress is undefined")
62
+ const listenAddress = Option.orElse(config.runnerListenAddress, () => config.runnerAddress)
63
+ if (Option.isNone(listenAddress)) {
64
+ return yield* Effect.die("BunClusterHttp.layerHttpServer: ShardingConfig.runnerAddress is None")
53
65
  }
54
- return BunHttpServer.layer(listenAddress)
66
+ return BunHttpServer.layer(listenAddress.value)
55
67
  }).pipe(Layer.unwrap)
56
68
 
57
69
  /**
58
- * @since 1.0.0
59
- * @category Layers
70
+ * Creates Bun cluster layers for HTTP or WebSocket transport, configuring serialization, storage, runner health, and optional client-only mode.
71
+ *
72
+ * **When to use**
73
+ *
74
+ * Use to install the complete Bun HTTP or WebSocket cluster layer, including
75
+ * client-only cluster access when a process should connect without serving
76
+ * runner RPCs.
77
+ *
78
+ * **Details**
79
+ *
80
+ * `serialization` defaults to MessagePack, `runnerHealth` defaults to ping
81
+ * checks, SQL-backed storage is used by default, and `shardingConfig` is
82
+ * overlaid on environment-loaded sharding configuration. `local` storage uses
83
+ * no-op message storage plus in-memory runner storage, while `byo` leaves both
84
+ * message and runner storage for the caller to provide.
85
+ *
86
+ * **Gotchas**
87
+ *
88
+ * `clientOnly` does not start the HTTP server or receive shard assignments.
89
+ * Non-client-only mode listens with `runnerListenAddress` when present, falling
90
+ * back to `runnerAddress`. HTTP and WebSocket runner RPCs use the default
91
+ * `HttpRunner` route.
92
+ *
93
+ * @see {@link layerHttpServer} for the server layer used by non-client-only transports
94
+ * @see {@link layerK8sHttpClient} for Kubernetes runner health support
95
+ *
96
+ * @category layers
97
+ * @since 4.0.0
60
98
  */
61
99
  export const layer = <
62
100
  const ClientOnly extends boolean = false,
@@ -1,5 +1,13 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun socket layers for Effect Cluster runners.
3
+ *
4
+ * The main `layer` builds a sharding layer for socket transport, choosing
5
+ * serialization, runner health checks, runner storage, message storage, and
6
+ * optional client-only mode from the supplied options. This module also
7
+ * re-exports the shared socket client and server protocol layers and provides
8
+ * `layerK8sHttpClient` for Kubernetes runner health checks.
9
+ *
10
+ * @since 4.0.0
3
11
  */
4
12
  import { layerClientProtocol, layerSocketServer } from "@effect/platform-node-shared/NodeClusterSocket"
5
13
  import type * as Config from "effect/Config"
@@ -24,20 +32,28 @@ import * as BunFileSystem from "./BunFileSystem.ts"
24
32
 
25
33
  export {
26
34
  /**
27
- * @since 1.0.0
28
- * @category Re-exports
35
+ * Provides the cluster `RpcClientProtocol` using the shared socket client
36
+ * implementation.
37
+ *
38
+ * @category re-exports
39
+ * @since 4.0.0
29
40
  */
30
41
  layerClientProtocol,
31
42
  /**
32
- * @since 1.0.0
33
- * @category Re-exports
43
+ * Provides the socket server used by Bun cluster runners through the shared
44
+ * socket server implementation.
45
+ *
46
+ * @category re-exports
47
+ * @since 4.0.0
34
48
  */
35
49
  layerSocketServer
36
50
  }
37
51
 
38
52
  /**
39
- * @since 1.0.0
40
- * @category Layers
53
+ * Creates Bun socket cluster layers, configuring serialization, storage, runner health, and optional client-only mode.
54
+ *
55
+ * @category layers
56
+ * @since 4.0.0
41
57
  */
42
58
  export const layer = <
43
59
  const ClientOnly extends boolean = false,
@@ -110,8 +126,10 @@ export const layer = <
110
126
  }
111
127
 
112
128
  /**
113
- * @since 1.0.0
114
- * @category Layers
129
+ * Layer that provides `K8sHttpClient`, using the Kubernetes service-account CA certificate when it is available.
130
+ *
131
+ * @category layers
132
+ * @since 4.0.0
115
133
  */
116
134
  export const layerK8sHttpClient: Layer.Layer<K8sHttpClient.K8sHttpClient> = K8sHttpClient.layer.pipe(
117
135
  Layer.provide(Layer.unwrap(Effect.gen(function*() {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The `BunCrypto` module provides Bun's `Crypto` service layer for Effect
3
+ * programs. Provide {@link layer} at the edge of a Bun app, CLI, script, or
4
+ * test to satisfy `effect/Crypto` with cryptographically secure random bytes,
5
+ * UUID generation, random values, and SHA digest operations.
6
+ *
7
+ * This adapter reuses the shared Node-compatible implementation, so randomness
8
+ * and digest behavior follow Bun's `node:crypto` compatibility layer. SHA-1 is
9
+ * present for interoperability with existing protocols, not for new
10
+ * security-sensitive designs.
11
+ *
12
+ * @since 1.0.0
13
+ */
14
+ import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
15
+ import type * as Crypto from "effect/Crypto"
16
+ import type * as Layer from "effect/Layer"
17
+
18
+ /**
19
+ * Layer that provides the Bun Crypto service implementation.
20
+ *
21
+ * @category layers
22
+ * @since 1.0.0
23
+ */
24
+ export const layer: Layer.Layer<Crypto.Crypto> = NodeCrypto.layer
@@ -1,12 +1,20 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun layer for Effect's `FileSystem` service.
3
+ *
4
+ * This module exposes one `layer` that provides `FileSystem` in Bun by using
5
+ * the shared Node file-system implementation. Once the layer is provided,
6
+ * programs use the standard `effect/FileSystem` service operations.
7
+ *
8
+ * @since 4.0.0
3
9
  */
4
10
  import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem"
5
11
  import type { FileSystem } from "effect/FileSystem"
6
12
  import type * as Layer from "effect/Layer"
7
13
 
8
14
  /**
9
- * @since 1.0.0
10
- * @category layer
15
+ * Layer that provides the `FileSystem` service for Bun using the shared Node file-system implementation.
16
+ *
17
+ * @category layers
18
+ * @since 4.0.0
11
19
  */
12
20
  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,13 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun implementation of the Effect HTTP platform service.
3
+ *
4
+ * This module provides one `layer` for `HttpPlatform`. It implements file
5
+ * responses with `Bun.file`, supports sliced file responses for byte ranges,
6
+ * and returns Web `File` values as raw HTTP server responses. The layer also
7
+ * provides the Bun file-system layer and ETag generator required by
8
+ * `HttpPlatform`.
9
+ *
10
+ * @since 4.0.0
3
11
  */
4
12
  import type { Effect } from "effect"
5
13
  import type { FileSystem } from "effect/FileSystem"
@@ -10,8 +18,8 @@ import * as Response from "effect/unstable/http/HttpServerResponse"
10
18
  import * as BunFileSystem from "./BunFileSystem.ts"
11
19
 
12
20
  /**
13
- * @since 1.0.0
14
21
  * @category constructors
22
+ * @since 4.0.0
15
23
  */
16
24
  const make: Effect.Effect<
17
25
  Platform.HttpPlatform["Service"],
@@ -31,8 +39,10 @@ const make: Effect.Effect<
31
39
  })
32
40
 
33
41
  /**
34
- * @since 1.0.0
35
- * @category Layers
42
+ * Layer that provides the Bun `HttpPlatform`, including file responses backed by `Bun.file`.
43
+ *
44
+ * @category layers
45
+ * @since 4.0.0
36
46
  */
37
47
  export const layer = Layer.effect(Platform.HttpPlatform)(make).pipe(
38
48
  Layer.provide(BunFileSystem.layer),
@@ -1,10 +1,22 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun implementation of the Effect `HttpServer`.
3
+ *
4
+ * `make` creates a scoped HTTP server from `Bun.serve`, converting Bun
5
+ * `Request` values into `HttpServerRequest` values and Effect
6
+ * `HttpServerResponse` values back into Web `Response` values. The server
7
+ * supports streaming bodies, multipart requests, file responses through
8
+ * `BunHttpPlatform`, and WebSocket upgrades. This module also provides layers
9
+ * for the server alone, the Bun HTTP support services, the combined server,
10
+ * configurable server options, and a test server with an HTTP client.
11
+ *
12
+ * @since 4.0.0
3
13
  */
4
14
  import type { Server as BunServer, ServerWebSocket } from "bun"
5
15
  import * as Config from "effect/Config"
6
16
  import type { ConfigError } from "effect/Config"
17
+ import * as Context from "effect/Context"
7
18
  import * as Deferred from "effect/Deferred"
19
+ import * as Duration from "effect/Duration"
8
20
  import * as Effect from "effect/Effect"
9
21
  import * as Exit from "effect/Exit"
10
22
  import * as Fiber from "effect/Fiber"
@@ -13,11 +25,12 @@ import type * as FileSystem from "effect/FileSystem"
13
25
  import { flow } from "effect/Function"
14
26
  import * as Inspectable from "effect/Inspectable"
15
27
  import * as Layer from "effect/Layer"
28
+ import * as Option from "effect/Option"
16
29
  import type * as Path from "effect/Path"
17
30
  import type * as Record from "effect/Record"
18
- import type * as Scope from "effect/Scope"
31
+ import type * as Schema from "effect/Schema"
32
+ import * as Scope from "effect/Scope"
19
33
  import * as Semaphore from "effect/Semaphore"
20
- import * as ServiceMap from "effect/ServiceMap"
21
34
  import * as Stream from "effect/Stream"
22
35
  import * as Cookies from "effect/unstable/http/Cookies"
23
36
  import * as Etag from "effect/unstable/http/Etag"
@@ -41,8 +54,10 @@ import * as BunServices from "./BunServices.ts"
41
54
  import * as BunStream from "./BunStream.ts"
42
55
 
43
56
  /**
44
- * @since 1.0.0
45
- * @category Options
57
+ * Bun serve options accepted by the HTTP server, extended with typed route definitions.
58
+ *
59
+ * @category options
60
+ * @since 4.0.0
46
61
  */
47
62
  export type ServeOptions<R extends string> =
48
63
  & (
@@ -52,13 +67,19 @@ export type ServeOptions<R extends string> =
52
67
  & { readonly routes?: Bun.Serve.Routes<WebSocketContext, R> }
53
68
 
54
69
  /**
55
- * @since 1.0.0
56
- * @category Constructors
70
+ * Creates a scoped Bun `HttpServer` from `Bun.serve` options, stopping the server on scope finalization with optional graceful shutdown settings.
71
+ *
72
+ * @category constructors
73
+ * @since 4.0.0
57
74
  */
58
75
  export const make = Effect.fnUntraced(
59
76
  function*<R extends string>(
60
- options: ServeOptions<R>
77
+ options: ServeOptions<R> & {
78
+ readonly disablePreemptiveShutdown?: boolean | undefined
79
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
80
+ }
61
81
  ) {
82
+ const scope = yield* Effect.scope
62
83
  const handlerStack: Array<(request: Request, server: BunServer<WebSocketContext>) => Response | Promise<Response>> =
63
84
  [
64
85
  function(_request, _server) {
@@ -76,6 +97,7 @@ export const make = Effect.fnUntraced(
76
97
  ws.data.run(message)
77
98
  },
78
99
  close(ws, code, closeReason) {
100
+ code = typeof code === "number" ? code : 1001
79
101
  Deferred.doneUnsafe(
80
102
  ws.data.closeDeferred,
81
103
  Socket.defaultCloseCodeIsError(code)
@@ -90,13 +112,24 @@ export const make = Effect.fnUntraced(
90
112
  }
91
113
  })
92
114
 
93
- yield* Effect.addFinalizer(() => Effect.promise(() => server.stop()))
115
+ const shutdown = yield* Effect.promise(() => server.stop()).pipe(
116
+ Effect.cached
117
+ )
118
+ const preemptiveShutdown = options.disablePreemptiveShutdown ? Effect.void : Effect.timeoutOrElse(shutdown, {
119
+ duration: options.gracefulShutdownTimeout ?? Duration.seconds(20),
120
+ orElse: () => Effect.void
121
+ })
122
+
123
+ yield* Scope.addFinalizer(scope, shutdown)
94
124
 
95
125
  return Server.make({
96
126
  address: { _tag: "TcpAddress", port: server.port!, hostname: server.hostname! },
97
127
  serve: Effect.fnUntraced(function*(httpApp, middleware) {
98
- const scope = yield* Effect.scope
99
- const services = yield* Effect.services<never>()
128
+ const parent = yield* Effect.fiber
129
+ const services = parent.context
130
+ const serveScope = Context.getUnsafe(services, Scope.Scope)
131
+ const scope = Scope.forkUnsafe(serveScope, "parallel")
132
+
100
133
  const httpEffect = HttpEffect.toHandled(httpApp, (request, response) =>
101
134
  Effect.sync(() => {
102
135
  ;(request as BunServerRequest).resolve(makeResponse(request, response, services, scope))
@@ -109,24 +142,20 @@ export const make = Effect.fnUntraced(
109
142
  ServerRequest.HttpServerRequest.key,
110
143
  new BunServerRequest(request, resolve, removeHost(request.url), server)
111
144
  )
112
- const fiber = Fiber.runIn(Effect.runForkWith(ServiceMap.makeUnsafe<any>(map))(httpEffect), scope)
145
+ const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe<any>(map))(httpEffect), scope)
113
146
  request.signal.addEventListener("abort", () => {
114
- fiber.interruptUnsafe(Error.clientAbortFiberId)
147
+ fiber.interruptUnsafe(parent.id, Error.ClientAbort.annotation)
115
148
  }, { once: true })
116
149
  })
117
150
  }
118
151
 
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
- )
152
+ yield* Scope.addFinalizerExit(serveScope, () => {
153
+ handlerStack.pop()
154
+ server.reload({ fetch: handlerStack[handlerStack.length - 1] })
155
+ return preemptiveShutdown
156
+ })
157
+ handlerStack.push(handler)
158
+ server.reload({ fetch: handler })
130
159
  })
131
160
  })
132
161
  }
@@ -135,7 +164,7 @@ export const make = Effect.fnUntraced(
135
164
  const makeResponse = (
136
165
  request: ServerRequest.HttpServerRequest,
137
166
  response: ServerResponse.HttpServerResponse,
138
- services: ServiceMap.ServiceMap<never>,
167
+ context: Context.Context<never>,
139
168
  scope: Scope.Scope
140
169
  ): Response => {
141
170
  const fields: {
@@ -186,7 +215,7 @@ const makeResponse = (
186
215
  Fiber.runIn(fiber, scope)
187
216
  return Effect.succeed(body.stream)
188
217
  })),
189
- services
218
+ context
190
219
  ),
191
220
  fields
192
221
  )
@@ -195,16 +224,23 @@ const makeResponse = (
195
224
  }
196
225
 
197
226
  /**
198
- * @since 1.0.0
199
- * @category Layers
227
+ * Layer that provides only `HttpServer` by constructing a scoped Bun server from the supplied serve options.
228
+ *
229
+ * @category layers
230
+ * @since 4.0.0
200
231
  */
201
232
  export const layerServer: <R extends string>(
202
- options: ServeOptions<R>
233
+ options: ServeOptions<R> & {
234
+ readonly disablePreemptiveShutdown?: boolean | undefined
235
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
236
+ }
203
237
  ) => Layer.Layer<Server.HttpServer> = flow(make, Layer.effect(Server.HttpServer)) as any
204
238
 
205
239
  /**
206
- * @since 1.0.0
207
- * @category Layers
240
+ * Layer that provides Bun HTTP support services: `HttpPlatform`, weak ETag generation, and `BunServices`.
241
+ *
242
+ * @category layers
243
+ * @since 4.0.0
208
244
  */
209
245
  export const layerHttpServices: Layer.Layer<
210
246
  | HttpPlatform
@@ -217,11 +253,16 @@ export const layerHttpServices: Layer.Layer<
217
253
  )
218
254
 
219
255
  /**
220
- * @since 1.0.0
221
- * @category Layers
256
+ * Layer that provides a Bun `HttpServer` together with the Bun HTTP platform, ETag generator, and Bun services.
257
+ *
258
+ * @category layers
259
+ * @since 4.0.0
222
260
  */
223
261
  export const layer = <R extends string>(
224
- options: ServeOptions<R>
262
+ options: ServeOptions<R> & {
263
+ readonly disablePreemptiveShutdown?: boolean | undefined
264
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
265
+ }
225
266
  ): Layer.Layer<
226
267
  | Server.HttpServer
227
268
  | HttpPlatform
@@ -230,8 +271,10 @@ export const layer = <R extends string>(
230
271
  > => Layer.mergeAll(layerServer(options), layerHttpServices)
231
272
 
232
273
  /**
233
- * @since 1.0.0
234
- * @category Layers
274
+ * Layer that starts a Bun HTTP server on an ephemeral port for tests.
275
+ *
276
+ * @category layers
277
+ * @since 4.0.0
235
278
  */
236
279
  export const layerTest: Layer.Layer<
237
280
  Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path | HttpClient
@@ -243,17 +286,24 @@ export const layerTest: Layer.Layer<
243
286
  )
244
287
 
245
288
  /**
246
- * @since 1.0.0
247
- * @category Layers
289
+ * Creates the Bun HTTP server and support-services layer from configurable serve options.
290
+ *
291
+ * @category layers
292
+ * @since 4.0.0
248
293
  */
249
294
  export const layerConfig = <R extends string>(
250
- options: Config.Wrap<ServeOptions<R>>
295
+ options: Config.Wrap<
296
+ ServeOptions<R> & {
297
+ readonly disablePreemptiveShutdown?: boolean | undefined
298
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
299
+ }
300
+ >
251
301
  ): Layer.Layer<
252
302
  Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path,
253
303
  ConfigError
254
304
  > =>
255
305
  Layer.mergeAll(
256
- Layer.effect(Server.HttpServer)(Effect.flatMap(Config.unwrap(options).asEffect(), make)),
306
+ Layer.effect(Server.HttpServer)(Effect.flatMap(Config.unwrap(options), make)),
257
307
  layerHttpServices
258
308
  )
259
309
 
@@ -280,7 +330,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
280
330
  readonly url: string
281
331
  private bunServer: BunServer<WebSocketContext>
282
332
  public headersOverride?: Headers.Headers | undefined
283
- private remoteAddressOverride?: string | undefined
333
+ private remoteAddressOverride?: Option.Option<string> | undefined
284
334
 
285
335
  constructor(
286
336
  source: Request,
@@ -288,7 +338,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
288
338
  url: string,
289
339
  bunServer: BunServer<WebSocketContext>,
290
340
  headersOverride?: Headers.Headers,
291
- remoteAddressOverride?: string
341
+ remoteAddressOverride?: Option.Option<string>
292
342
  ) {
293
343
  super()
294
344
  this[ServerRequest.TypeId] = ServerRequest.TypeId
@@ -311,7 +361,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
311
361
  options: {
312
362
  readonly url?: string | undefined
313
363
  readonly headers?: Headers.Headers | undefined
314
- readonly remoteAddress?: string | undefined
364
+ readonly remoteAddress?: Option.Option<string> | undefined
315
365
  }
316
366
  ) {
317
367
  return new BunServerRequest(
@@ -320,7 +370,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
320
370
  options.url ?? this.url,
321
371
  this.bunServer,
322
372
  options.headers ?? this.headersOverride,
323
- options.remoteAddress ?? this.remoteAddressOverride
373
+ "remoteAddress" in options ? options.remoteAddress : this.remoteAddressOverride
324
374
  )
325
375
  }
326
376
  get method(): HttpMethod {
@@ -329,8 +379,8 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
329
379
  get originalUrl() {
330
380
  return this.source.url
331
381
  }
332
- get remoteAddress(): string | undefined {
333
- return this.remoteAddressOverride ?? this.bunServer.requestIP(this.source)?.address
382
+ get remoteAddress(): Option.Option<string> {
383
+ return this.remoteAddressOverride ?? Option.fromNullishOr(this.bunServer.requestIP(this.source)?.address)
334
384
  }
335
385
  get headers(): Headers.Headers {
336
386
  this.headersOverride ??= Headers.fromInput(this.source.headers)
@@ -348,7 +398,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
348
398
  get stream(): Stream.Stream<Uint8Array, Error.HttpServerError> {
349
399
  return this.source.body
350
400
  ? BunStream.fromReadableStream({
351
- evaluate: () => this.source.body as any,
401
+ evaluate: () => this.source.body ?? emptyReadbleStream,
352
402
  onError: (cause) =>
353
403
  new Error.HttpServerError({
354
404
  reason: new Error.RequestParseError({
@@ -387,10 +437,10 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
387
437
  return this.textEffect
388
438
  }
389
439
 
390
- get json(): Effect.Effect<unknown, Error.HttpServerError> {
440
+ get json(): Effect.Effect<Schema.Json, Error.HttpServerError> {
391
441
  return Effect.flatMap(this.text, (_) =>
392
442
  Effect.try({
393
- try: () => JSON.parse(_) as unknown,
443
+ try: () => JSON.parse(_) as Schema.Json,
394
444
  catch: (cause) =>
395
445
  new Error.HttpServerError({
396
446
  reason: new Error.RequestParseError({
@@ -457,6 +507,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
457
507
  })
458
508
  })
459
509
  ))
510
+ this.textEffect = Effect.map(this.arrayBufferEffect, (_) => new TextDecoder().decode(_))
460
511
  return this.arrayBufferEffect
461
512
  }
462
513
 
@@ -524,14 +575,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
524
575
  semaphore.withPermits(1)
525
576
  )
526
577
 
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,
578
+ return Socket.make({
535
579
  runRaw,
536
580
  writer
537
581
  })
@@ -540,6 +584,13 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
540
584
  }
541
585
  }
542
586
 
587
+ const emptyReadbleStream = new ReadableStream({
588
+ start(controller) {
589
+ controller.enqueue(new Uint8Array())
590
+ controller.close()
591
+ }
592
+ })
593
+
543
594
  const removeHost = (url: string) => {
544
595
  if (url[0] === "/") {
545
596
  return url
@@ -1,11 +1,19 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Accessor for the Bun request behind an Effect HTTP server request.
3
+ *
4
+ * This module exports `toBunServerRequest`, which returns the underlying
5
+ * `Bun.BunRequest` stored inside a Bun-backed `HttpServerRequest`. It is meant
6
+ * for code that needs to interoperate with Bun-specific request APIs.
7
+ *
8
+ * @since 4.0.0
3
9
  */
4
10
  import type { HttpServerRequest } from "effect/unstable/http/HttpServerRequest"
5
11
 
6
12
  /**
7
- * @since 1.0.0
8
- * @category Accessors
13
+ * Returns the underlying `Bun.BunRequest` from an Effect `HttpServerRequest`.
14
+ *
15
+ * @category accessors
16
+ * @since 4.0.0
9
17
  */
10
18
  export const toBunServerRequest = <T extends string = string>(self: HttpServerRequest): Bun.BunRequest<T> =>
11
19
  (self as any).source
@@ -1,5 +1,12 @@
1
1
  /**
2
- * @since 1.0.0
2
+ * Bun-specific helpers for parsing HTTP `multipart/form-data` request bodies.
3
+ *
4
+ * This module adapts a Bun `Request` body and headers into the shared
5
+ * `Multipart` model. `stream` returns multipart parts as a `Stream`, while
6
+ * `persisted` collects the form and writes file parts to scoped temporary files
7
+ * through the current `FileSystem`, `Path`, and `Scope` services.
8
+ *
9
+ * @since 4.0.0
3
10
  */
4
11
  import type * as Effect from "effect/Effect"
5
12
  import type { FileSystem } from "effect/FileSystem"
@@ -10,20 +17,31 @@ import * as Multipart from "effect/unstable/http/Multipart"
10
17
  import * as BunStream from "./BunStream.ts"
11
18
 
12
19
  /**
13
- * @since 1.0.0
14
- * @category Constructors
20
+ * Parses a Bun `Request` body as multipart data and returns a stream of multipart parts.
21
+ *
22
+ * @category constructors
23
+ * @since 4.0.0
15
24
  */
16
25
  export const stream = (source: Request): Stream.Stream<Multipart.Part, Multipart.MultipartError> =>
17
26
  BunStream.fromReadableStream({
18
- evaluate: () => source.body!,
27
+ evaluate: () => source.body ?? emptyReadbleStream,
19
28
  onError: (cause) => Multipart.MultipartError.fromReason("InternalError", cause)
20
29
  }).pipe(
21
30
  Stream.pipeThroughChannel(Multipart.makeChannel(Object.fromEntries(source.headers)))
22
31
  )
23
32
 
33
+ const emptyReadbleStream = new ReadableStream({
34
+ start(controller) {
35
+ controller.enqueue(new Uint8Array())
36
+ controller.close()
37
+ }
38
+ })
39
+
24
40
  /**
25
- * @since 1.0.0
26
- * @category Constructors
41
+ * Parses and persists multipart data from a Bun `Request`, requiring file-system, path, and scope services.
42
+ *
43
+ * @category constructors
44
+ * @since 4.0.0
27
45
  */
28
46
  export const persisted = (
29
47
  source: Request