@effect/platform-bun 4.0.0-beta.1 → 4.0.0-beta.100

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 (109) hide show
  1. package/README.md +1 -1
  2. package/dist/BunChildProcessSpawner.d.ts +2 -2
  3. package/dist/BunChildProcessSpawner.js +2 -2
  4. package/dist/BunClusterHttp.d.ts +44 -7
  5. package/dist/BunClusterHttp.d.ts.map +1 -1
  6. package/dist/BunClusterHttp.js +41 -10
  7. package/dist/BunClusterHttp.js.map +1 -1
  8. package/dist/BunClusterSocket.d.ts +27 -9
  9. package/dist/BunClusterSocket.d.ts.map +1 -1
  10. package/dist/BunClusterSocket.js +27 -9
  11. package/dist/BunClusterSocket.js.map +1 -1
  12. package/dist/BunCrypto.d.ts +10 -0
  13. package/dist/BunCrypto.d.ts.map +1 -0
  14. package/dist/BunCrypto.js +22 -0
  15. package/dist/BunCrypto.js.map +1 -0
  16. package/dist/BunFileSystem.d.ts +4 -2
  17. package/dist/BunFileSystem.d.ts.map +1 -1
  18. package/dist/BunFileSystem.js +11 -3
  19. package/dist/BunFileSystem.js.map +1 -1
  20. package/dist/BunHttpClient.d.ts +2 -2
  21. package/dist/BunHttpClient.js +2 -2
  22. package/dist/BunHttpPlatform.d.ts +4 -2
  23. package/dist/BunHttpPlatform.d.ts.map +1 -1
  24. package/dist/BunHttpPlatform.js +5 -3
  25. package/dist/BunHttpPlatform.js.map +1 -1
  26. package/dist/BunHttpServer.d.ts +58 -21
  27. package/dist/BunHttpServer.d.ts.map +1 -1
  28. package/dist/BunHttpServer.js +65 -37
  29. package/dist/BunHttpServer.js.map +1 -1
  30. package/dist/BunHttpServerRequest.d.ts +11 -3
  31. package/dist/BunHttpServerRequest.d.ts.map +1 -1
  32. package/dist/BunHttpServerRequest.js +4 -2
  33. package/dist/BunHttpServerRequest.js.map +1 -1
  34. package/dist/BunMultipart.d.ts +16 -5
  35. package/dist/BunMultipart.d.ts.map +1 -1
  36. package/dist/BunMultipart.js +15 -5
  37. package/dist/BunMultipart.js.map +1 -1
  38. package/dist/BunPath.d.ts +12 -6
  39. package/dist/BunPath.d.ts.map +1 -1
  40. package/dist/BunPath.js +20 -7
  41. package/dist/BunPath.js.map +1 -1
  42. package/dist/BunRedis.d.ts +23 -9
  43. package/dist/BunRedis.d.ts.map +1 -1
  44. package/dist/BunRedis.js +26 -12
  45. package/dist/BunRedis.js.map +1 -1
  46. package/dist/BunRuntime.d.ts +21 -30
  47. package/dist/BunRuntime.d.ts.map +1 -1
  48. package/dist/BunRuntime.js +14 -11
  49. package/dist/BunRuntime.js.map +1 -1
  50. package/dist/BunServices.d.ts +19 -5
  51. package/dist/BunServices.d.ts.map +1 -1
  52. package/dist/BunServices.js +7 -3
  53. package/dist/BunServices.js.map +1 -1
  54. package/dist/BunSink.d.ts +2 -2
  55. package/dist/BunSink.js +2 -2
  56. package/dist/BunSocket.d.ts +19 -5
  57. package/dist/BunSocket.d.ts.map +1 -1
  58. package/dist/BunSocket.js +10 -3
  59. package/dist/BunSocket.js.map +1 -1
  60. package/dist/BunSocketServer.d.ts +2 -2
  61. package/dist/BunSocketServer.js +2 -2
  62. package/dist/BunStdio.d.ts +5 -2
  63. package/dist/BunStdio.d.ts.map +1 -1
  64. package/dist/BunStdio.js +13 -3
  65. package/dist/BunStdio.js.map +1 -1
  66. package/dist/BunStream.d.ts +5 -4
  67. package/dist/BunStream.d.ts.map +1 -1
  68. package/dist/BunStream.js +28 -20
  69. package/dist/BunStream.js.map +1 -1
  70. package/dist/BunTerminal.d.ts +8 -2
  71. package/dist/BunTerminal.d.ts.map +1 -1
  72. package/dist/BunTerminal.js +15 -3
  73. package/dist/BunTerminal.js.map +1 -1
  74. package/dist/BunWorker.d.ts +9 -2
  75. package/dist/BunWorker.d.ts.map +1 -1
  76. package/dist/BunWorker.js +20 -4
  77. package/dist/BunWorker.js.map +1 -1
  78. package/dist/BunWorkerRunner.d.ts +5 -1
  79. package/dist/BunWorkerRunner.d.ts.map +1 -1
  80. package/dist/BunWorkerRunner.js +17 -5
  81. package/dist/BunWorkerRunner.js.map +1 -1
  82. package/dist/index.d.ts +25 -23
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +25 -23
  85. package/dist/index.js.map +1 -1
  86. package/package.json +7 -8
  87. package/src/BunChildProcessSpawner.ts +2 -2
  88. package/src/BunClusterHttp.ts +49 -11
  89. package/src/BunClusterSocket.ts +27 -9
  90. package/src/BunCrypto.ts +24 -0
  91. package/src/BunFileSystem.ts +11 -3
  92. package/src/BunHttpClient.ts +2 -2
  93. package/src/BunHttpPlatform.ts +14 -4
  94. package/src/BunHttpServer.ts +110 -58
  95. package/src/BunHttpServerRequest.ts +11 -3
  96. package/src/BunMultipart.ts +24 -6
  97. package/src/BunPath.ts +20 -7
  98. package/src/BunRedis.ts +28 -14
  99. package/src/BunRuntime.ts +28 -31
  100. package/src/BunServices.ts +21 -5
  101. package/src/BunSink.ts +2 -2
  102. package/src/BunSocket.ts +19 -5
  103. package/src/BunSocketServer.ts +2 -2
  104. package/src/BunStdio.ts +13 -3
  105. package/src/BunStream.ts +31 -23
  106. package/src/BunTerminal.ts +15 -3
  107. package/src/BunWorker.ts +20 -4
  108. package/src/BunWorkerRunner.ts +17 -5
  109. package/src/index.ts +26 -23
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@effect/platform-bun",
3
3
  "type": "module",
4
- "version": "4.0.0-beta.1",
4
+ "version": "4.0.0-beta.100",
5
5
  "license": "MIT",
6
6
  "description": "Platform specific implementations for the Bun runtime",
7
7
  "homepage": "https://effect.website",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/Effect-TS/effect-smol.git",
10
+ "url": "https://github.com/Effect-TS/effect.git",
11
11
  "directory": "packages/platform-bun"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://github.com/Effect-TS/effect-smol/issues"
14
+ "url": "https://github.com/Effect-TS/effect/issues"
15
15
  },
16
16
  "tags": [
17
17
  "bun",
@@ -45,19 +45,18 @@
45
45
  "provenance": true
46
46
  },
47
47
  "peerDependencies": {
48
- "effect": "^4.0.0-beta.1"
48
+ "effect": "^4.0.0-beta.100"
49
49
  },
50
50
  "dependencies": {
51
- "@effect/platform-node-shared": "^4.0.0-beta.1"
51
+ "@effect/platform-node-shared": "^4.0.0-beta.100"
52
52
  },
53
53
  "devDependencies": {
54
- "@types/bun": "^1.3.8",
55
- "effect": "^4.0.0-beta.1"
54
+ "@types/bun": "^1.3.14",
55
+ "effect": "^4.0.0-beta.100"
56
56
  },
57
57
  "scripts": {
58
58
  "codegen": "effect-utils codegen",
59
59
  "build": "tsc -b tsconfig.json && pnpm babel",
60
- "build:tsgo": "tsgo -b tsconfig.json && pnpm babel",
61
60
  "babel": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps",
62
61
  "check": "tsc -b tsconfig.json",
63
62
  "test": "vitest",
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Node.js implementation of `ChildProcessSpawner`.
2
+ * Bun implementation of `ChildProcessSpawner`.
3
3
  *
4
- * @since 1.0.0
4
+ * @since 4.0.0
5
5
  */
6
6
  export * from "@effect/platform-node-shared/NodeChildProcessSpawner"
@@ -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,10 +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"
19
- import * as ServiceMap from "effect/ServiceMap"
31
+ import type * as Schema from "effect/Schema"
32
+ import * as Scope from "effect/Scope"
33
+ import * as Semaphore from "effect/Semaphore"
20
34
  import * as Stream from "effect/Stream"
21
35
  import * as Cookies from "effect/unstable/http/Cookies"
22
36
  import * as Etag from "effect/unstable/http/Etag"
@@ -40,8 +54,10 @@ import * as BunServices from "./BunServices.ts"
40
54
  import * as BunStream from "./BunStream.ts"
41
55
 
42
56
  /**
43
- * @since 1.0.0
44
- * @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
45
61
  */
46
62
  export type ServeOptions<R extends string> =
47
63
  & (
@@ -51,13 +67,19 @@ export type ServeOptions<R extends string> =
51
67
  & { readonly routes?: Bun.Serve.Routes<WebSocketContext, R> }
52
68
 
53
69
  /**
54
- * @since 1.0.0
55
- * @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
56
74
  */
57
75
  export const make = Effect.fnUntraced(
58
76
  function*<R extends string>(
59
- options: ServeOptions<R>
77
+ options: ServeOptions<R> & {
78
+ readonly disablePreemptiveShutdown?: boolean | undefined
79
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
80
+ }
60
81
  ) {
82
+ const scope = yield* Effect.scope
61
83
  const handlerStack: Array<(request: Request, server: BunServer<WebSocketContext>) => Response | Promise<Response>> =
62
84
  [
63
85
  function(_request, _server) {
@@ -75,6 +97,7 @@ export const make = Effect.fnUntraced(
75
97
  ws.data.run(message)
76
98
  },
77
99
  close(ws, code, closeReason) {
100
+ code = typeof code === "number" ? code : 1001
78
101
  Deferred.doneUnsafe(
79
102
  ws.data.closeDeferred,
80
103
  Socket.defaultCloseCodeIsError(code)
@@ -89,13 +112,24 @@ export const make = Effect.fnUntraced(
89
112
  }
90
113
  })
91
114
 
92
- 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)
93
124
 
94
125
  return Server.make({
95
126
  address: { _tag: "TcpAddress", port: server.port!, hostname: server.hostname! },
96
127
  serve: Effect.fnUntraced(function*(httpApp, middleware) {
97
- const scope = yield* Effect.scope
98
- 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
+
99
133
  const httpEffect = HttpEffect.toHandled(httpApp, (request, response) =>
100
134
  Effect.sync(() => {
101
135
  ;(request as BunServerRequest).resolve(makeResponse(request, response, services, scope))
@@ -108,24 +142,20 @@ export const make = Effect.fnUntraced(
108
142
  ServerRequest.HttpServerRequest.key,
109
143
  new BunServerRequest(request, resolve, removeHost(request.url), server)
110
144
  )
111
- 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)
112
146
  request.signal.addEventListener("abort", () => {
113
- fiber.interruptUnsafe(Error.clientAbortFiberId)
147
+ fiber.interruptUnsafe(parent.id, Error.ClientAbort.annotation)
114
148
  }, { once: true })
115
149
  })
116
150
  }
117
151
 
118
- yield* Effect.acquireRelease(
119
- Effect.sync(() => {
120
- handlerStack.push(handler)
121
- server.reload({ fetch: handler })
122
- }),
123
- () =>
124
- Effect.sync(() => {
125
- handlerStack.pop()
126
- server.reload({ fetch: handlerStack[handlerStack.length - 1] })
127
- })
128
- )
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 })
129
159
  })
130
160
  })
131
161
  }
@@ -134,7 +164,7 @@ export const make = Effect.fnUntraced(
134
164
  const makeResponse = (
135
165
  request: ServerRequest.HttpServerRequest,
136
166
  response: ServerResponse.HttpServerResponse,
137
- services: ServiceMap.ServiceMap<never>,
167
+ context: Context.Context<never>,
138
168
  scope: Scope.Scope
139
169
  ): Response => {
140
170
  const fields: {
@@ -185,7 +215,7 @@ const makeResponse = (
185
215
  Fiber.runIn(fiber, scope)
186
216
  return Effect.succeed(body.stream)
187
217
  })),
188
- services
218
+ context
189
219
  ),
190
220
  fields
191
221
  )
@@ -194,16 +224,23 @@ const makeResponse = (
194
224
  }
195
225
 
196
226
  /**
197
- * @since 1.0.0
198
- * @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
199
231
  */
200
232
  export const layerServer: <R extends string>(
201
- options: ServeOptions<R>
233
+ options: ServeOptions<R> & {
234
+ readonly disablePreemptiveShutdown?: boolean | undefined
235
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
236
+ }
202
237
  ) => Layer.Layer<Server.HttpServer> = flow(make, Layer.effect(Server.HttpServer)) as any
203
238
 
204
239
  /**
205
- * @since 1.0.0
206
- * @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
207
244
  */
208
245
  export const layerHttpServices: Layer.Layer<
209
246
  | HttpPlatform
@@ -216,11 +253,16 @@ export const layerHttpServices: Layer.Layer<
216
253
  )
217
254
 
218
255
  /**
219
- * @since 1.0.0
220
- * @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
221
260
  */
222
261
  export const layer = <R extends string>(
223
- options: ServeOptions<R>
262
+ options: ServeOptions<R> & {
263
+ readonly disablePreemptiveShutdown?: boolean | undefined
264
+ readonly gracefulShutdownTimeout?: Duration.Input | undefined
265
+ }
224
266
  ): Layer.Layer<
225
267
  | Server.HttpServer
226
268
  | HttpPlatform
@@ -229,8 +271,10 @@ export const layer = <R extends string>(
229
271
  > => Layer.mergeAll(layerServer(options), layerHttpServices)
230
272
 
231
273
  /**
232
- * @since 1.0.0
233
- * @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
234
278
  */
235
279
  export const layerTest: Layer.Layer<
236
280
  Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path | HttpClient
@@ -242,17 +286,24 @@ export const layerTest: Layer.Layer<
242
286
  )
243
287
 
244
288
  /**
245
- * @since 1.0.0
246
- * @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
247
293
  */
248
294
  export const layerConfig = <R extends string>(
249
- 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
+ >
250
301
  ): Layer.Layer<
251
302
  Server.HttpServer | HttpPlatform | FileSystem.FileSystem | Etag.Generator | Path.Path,
252
303
  ConfigError
253
304
  > =>
254
305
  Layer.mergeAll(
255
- Layer.effect(Server.HttpServer)(Effect.flatMap(Config.unwrap(options).asEffect(), make)),
306
+ Layer.effect(Server.HttpServer)(Effect.flatMap(Config.unwrap(options), make)),
256
307
  layerHttpServices
257
308
  )
258
309
 
@@ -279,7 +330,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
279
330
  readonly url: string
280
331
  private bunServer: BunServer<WebSocketContext>
281
332
  public headersOverride?: Headers.Headers | undefined
282
- private remoteAddressOverride?: string | undefined
333
+ private remoteAddressOverride?: Option.Option<string> | undefined
283
334
 
284
335
  constructor(
285
336
  source: Request,
@@ -287,7 +338,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
287
338
  url: string,
288
339
  bunServer: BunServer<WebSocketContext>,
289
340
  headersOverride?: Headers.Headers,
290
- remoteAddressOverride?: string
341
+ remoteAddressOverride?: Option.Option<string>
291
342
  ) {
292
343
  super()
293
344
  this[ServerRequest.TypeId] = ServerRequest.TypeId
@@ -310,7 +361,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
310
361
  options: {
311
362
  readonly url?: string | undefined
312
363
  readonly headers?: Headers.Headers | undefined
313
- readonly remoteAddress?: string | undefined
364
+ readonly remoteAddress?: Option.Option<string> | undefined
314
365
  }
315
366
  ) {
316
367
  return new BunServerRequest(
@@ -319,7 +370,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
319
370
  options.url ?? this.url,
320
371
  this.bunServer,
321
372
  options.headers ?? this.headersOverride,
322
- options.remoteAddress ?? this.remoteAddressOverride
373
+ "remoteAddress" in options ? options.remoteAddress : this.remoteAddressOverride
323
374
  )
324
375
  }
325
376
  get method(): HttpMethod {
@@ -328,8 +379,8 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
328
379
  get originalUrl() {
329
380
  return this.source.url
330
381
  }
331
- get remoteAddress(): string | undefined {
332
- 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)
333
384
  }
334
385
  get headers(): Headers.Headers {
335
386
  this.headersOverride ??= Headers.fromInput(this.source.headers)
@@ -347,7 +398,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
347
398
  get stream(): Stream.Stream<Uint8Array, Error.HttpServerError> {
348
399
  return this.source.body
349
400
  ? BunStream.fromReadableStream({
350
- evaluate: () => this.source.body as any,
401
+ evaluate: () => this.source.body ?? emptyReadbleStream,
351
402
  onError: (cause) =>
352
403
  new Error.HttpServerError({
353
404
  reason: new Error.RequestParseError({
@@ -386,10 +437,10 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
386
437
  return this.textEffect
387
438
  }
388
439
 
389
- get json(): Effect.Effect<unknown, Error.HttpServerError> {
440
+ get json(): Effect.Effect<Schema.Json, Error.HttpServerError> {
390
441
  return Effect.flatMap(this.text, (_) =>
391
442
  Effect.try({
392
- try: () => JSON.parse(_) as unknown,
443
+ try: () => JSON.parse(_) as Schema.Json,
393
444
  catch: (cause) =>
394
445
  new Error.HttpServerError({
395
446
  reason: new Error.RequestParseError({
@@ -456,6 +507,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
456
507
  })
457
508
  })
458
509
  ))
510
+ this.textEffect = Effect.map(this.arrayBufferEffect, (_) => new TextDecoder().decode(_))
459
511
  return this.arrayBufferEffect
460
512
  }
461
513
 
@@ -463,7 +515,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
463
515
  return Effect.callback<Socket.Socket, Error.HttpServerError>((resume) => {
464
516
  const deferred = Deferred.makeUnsafe<ServerWebSocket<WebSocketContext>>()
465
517
  const closeDeferred = Deferred.makeUnsafe<void, Socket.SocketError>()
466
- const semaphore = Effect.makeSemaphoreUnsafe(1)
518
+ const semaphore = Semaphore.makeUnsafe(1)
467
519
 
468
520
  const success = this.bunServer.upgrade(this.source, {
469
521
  data: {
@@ -523,14 +575,7 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
523
575
  semaphore.withPermits(1)
524
576
  )
525
577
 
526
- const encoder = new TextEncoder()
527
- const run = <R, E, _>(handler: (_: Uint8Array) => Effect.Effect<_, E, R> | void, opts?: {
528
- readonly onOpen?: Effect.Effect<void> | undefined
529
- }) => runRaw((data) => typeof data === "string" ? handler(encoder.encode(data)) : handler(data), opts)
530
-
531
- return Socket.Socket.of({
532
- [Socket.TypeId]: Socket.TypeId as typeof Socket.TypeId,
533
- run,
578
+ return Socket.make({
534
579
  runRaw,
535
580
  writer
536
581
  })
@@ -539,6 +584,13 @@ class BunServerRequest extends Inspectable.Class implements ServerRequest.HttpSe
539
584
  }
540
585
  }
541
586
 
587
+ const emptyReadbleStream = new ReadableStream({
588
+ start(controller) {
589
+ controller.enqueue(new Uint8Array())
590
+ controller.close()
591
+ }
592
+ })
593
+
542
594
  const removeHost = (url: string) => {
543
595
  if (url[0] === "/") {
544
596
  return url