@effect/platform-bun 4.0.0-beta.7 → 4.0.0-beta.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BunChildProcessSpawner.d.ts +1 -1
- package/dist/BunChildProcessSpawner.js +1 -1
- package/dist/BunClusterHttp.d.ts +70 -7
- package/dist/BunClusterHttp.d.ts.map +1 -1
- package/dist/BunClusterHttp.js +41 -10
- package/dist/BunClusterHttp.js.map +1 -1
- package/dist/BunClusterSocket.d.ts +54 -9
- package/dist/BunClusterSocket.d.ts.map +1 -1
- package/dist/BunClusterSocket.js +54 -9
- package/dist/BunClusterSocket.js.map +1 -1
- package/dist/BunCrypto.d.ts +10 -0
- package/dist/BunCrypto.d.ts.map +1 -0
- package/dist/BunCrypto.js +22 -0
- package/dist/BunCrypto.js.map +1 -0
- package/dist/BunFileSystem.d.ts +4 -2
- package/dist/BunFileSystem.d.ts.map +1 -1
- package/dist/BunFileSystem.js +27 -3
- package/dist/BunFileSystem.js.map +1 -1
- package/dist/BunHttpClient.d.ts +2 -2
- package/dist/BunHttpClient.js +2 -2
- package/dist/BunHttpPlatform.d.ts +4 -2
- package/dist/BunHttpPlatform.d.ts.map +1 -1
- package/dist/BunHttpPlatform.js +5 -3
- package/dist/BunHttpPlatform.js.map +1 -1
- package/dist/BunHttpServer.d.ts +77 -19
- package/dist/BunHttpServer.d.ts.map +1 -1
- package/dist/BunHttpServer.js +63 -36
- package/dist/BunHttpServer.js.map +1 -1
- package/dist/BunHttpServerRequest.d.ts +26 -2
- package/dist/BunHttpServerRequest.d.ts.map +1 -1
- package/dist/BunHttpServerRequest.js +3 -1
- package/dist/BunHttpServerRequest.js.map +1 -1
- package/dist/BunMultipart.d.ts +28 -5
- package/dist/BunMultipart.d.ts.map +1 -1
- package/dist/BunMultipart.js +15 -5
- package/dist/BunMultipart.js.map +1 -1
- package/dist/BunPath.d.ts +12 -6
- package/dist/BunPath.d.ts.map +1 -1
- package/dist/BunPath.js +42 -7
- package/dist/BunPath.js.map +1 -1
- package/dist/BunRedis.d.ts +48 -9
- package/dist/BunRedis.d.ts.map +1 -1
- package/dist/BunRedis.js +51 -12
- package/dist/BunRedis.js.map +1 -1
- package/dist/BunRuntime.d.ts +24 -30
- package/dist/BunRuntime.d.ts.map +1 -1
- package/dist/BunRuntime.js +38 -11
- package/dist/BunRuntime.js.map +1 -1
- package/dist/BunServices.d.ts +32 -5
- package/dist/BunServices.d.ts.map +1 -1
- package/dist/BunServices.js +7 -3
- package/dist/BunServices.js.map +1 -1
- package/dist/BunSink.d.ts +2 -2
- package/dist/BunSink.js +2 -2
- package/dist/BunSocket.d.ts +30 -4
- package/dist/BunSocket.d.ts.map +1 -1
- package/dist/BunSocket.js +10 -3
- package/dist/BunSocket.js.map +1 -1
- package/dist/BunSocketServer.d.ts +2 -2
- package/dist/BunSocketServer.js +2 -2
- package/dist/BunStdio.d.ts +5 -2
- package/dist/BunStdio.d.ts.map +1 -1
- package/dist/BunStdio.js +36 -3
- package/dist/BunStdio.js.map +1 -1
- package/dist/BunStream.d.ts +3 -2
- package/dist/BunStream.d.ts.map +1 -1
- package/dist/BunStream.js +34 -3
- package/dist/BunStream.js.map +1 -1
- package/dist/BunTerminal.d.ts +8 -2
- package/dist/BunTerminal.d.ts.map +1 -1
- package/dist/BunTerminal.js +41 -3
- package/dist/BunTerminal.js.map +1 -1
- package/dist/BunWorker.d.ts +9 -2
- package/dist/BunWorker.d.ts.map +1 -1
- package/dist/BunWorker.js +46 -4
- package/dist/BunWorker.js.map +1 -1
- package/dist/BunWorkerRunner.d.ts +5 -1
- package/dist/BunWorkerRunner.d.ts.map +1 -1
- package/dist/BunWorkerRunner.js +41 -5
- package/dist/BunWorkerRunner.js.map +1 -1
- package/dist/index.d.ts +25 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -23
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/BunChildProcessSpawner.ts +1 -1
- package/src/BunClusterHttp.ts +75 -11
- package/src/BunClusterSocket.ts +54 -9
- package/src/BunCrypto.ts +24 -0
- package/src/BunFileSystem.ts +27 -3
- package/src/BunHttpClient.ts +2 -2
- package/src/BunHttpPlatform.ts +28 -4
- package/src/BunHttpServer.ts +128 -56
- package/src/BunHttpServerRequest.ts +26 -2
- package/src/BunMultipart.ts +36 -6
- package/src/BunPath.ts +42 -7
- package/src/BunRedis.ts +53 -14
- package/src/BunRuntime.ts +54 -31
- package/src/BunServices.ts +34 -5
- package/src/BunSink.ts +2 -2
- package/src/BunSocket.ts +30 -4
- package/src/BunSocketServer.ts +2 -2
- package/src/BunStdio.ts +36 -3
- package/src/BunStream.ts +34 -3
- package/src/BunTerminal.ts +41 -3
- package/src/BunWorker.ts +46 -4
- package/src/BunWorkerRunner.ts +41 -5
- package/src/index.ts +26 -23
package/src/BunHttpServer.ts
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
56
|
-
*
|
|
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.
|
|
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
|
|
99
|
-
const services =
|
|
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(
|
|
166
|
+
const fiber = Fiber.runIn(Effect.runForkWith(Context.makeUnsafe<any>(map))(httpEffect), scope)
|
|
113
167
|
request.signal.addEventListener("abort", () => {
|
|
114
|
-
fiber.interruptUnsafe(Error.
|
|
168
|
+
fiber.interruptUnsafe(parent.id, Error.ClientAbort.annotation)
|
|
115
169
|
}, { once: true })
|
|
116
170
|
})
|
|
117
171
|
}
|
|
118
172
|
|
|
119
|
-
yield*
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
239
|
+
context
|
|
190
240
|
),
|
|
191
241
|
fields
|
|
192
242
|
)
|
|
@@ -195,16 +245,23 @@ const makeResponse = (
|
|
|
195
245
|
}
|
|
196
246
|
|
|
197
247
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
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
|
-
*
|
|
207
|
-
*
|
|
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
|
-
*
|
|
221
|
-
*
|
|
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
|
-
*
|
|
234
|
-
*
|
|
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
|
-
*
|
|
247
|
-
*
|
|
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<
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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<
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
package/src/BunMultipart.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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. Use `stream` from Bun HTTP handlers when form fields and
|
|
6
|
+
* uploaded files should be consumed incrementally, for example validating text
|
|
7
|
+
* fields while piping large file parts to storage. Use `persisted` when the
|
|
8
|
+
* whole form should be collected into a record and file parts should be written
|
|
9
|
+
* to scoped temporary files through the current `FileSystem`, `Path`, and
|
|
10
|
+
* `Scope` services.
|
|
11
|
+
*
|
|
12
|
+
* Bun requests expose one-shot web streams, so choose one body reader and do
|
|
13
|
+
* not call `formData`, `text`, `json`, or `arrayBuffer` before using this
|
|
14
|
+
* module. Incoming `FormData` uploads must include a `multipart/form-data`
|
|
15
|
+
* content type with the boundary generated by the client; when constructing
|
|
16
|
+
* `FormData` requests, let the runtime set that header. Stream file content for
|
|
17
|
+
* large uploads, reserve `contentEffect` for small files, and treat client
|
|
18
|
+
* filenames as metadata rather than trusted filesystem paths. Persisted file
|
|
19
|
+
* paths remain valid only for the surrounding scope.
|
|
20
|
+
*
|
|
21
|
+
* @since 4.0.0
|
|
3
22
|
*/
|
|
4
23
|
import type * as Effect from "effect/Effect"
|
|
5
24
|
import type { FileSystem } from "effect/FileSystem"
|
|
@@ -10,20 +29,31 @@ import * as Multipart from "effect/unstable/http/Multipart"
|
|
|
10
29
|
import * as BunStream from "./BunStream.ts"
|
|
11
30
|
|
|
12
31
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
32
|
+
* Parses a Bun `Request` body as multipart data and returns a stream of multipart parts.
|
|
33
|
+
*
|
|
34
|
+
* @category constructors
|
|
35
|
+
* @since 4.0.0
|
|
15
36
|
*/
|
|
16
37
|
export const stream = (source: Request): Stream.Stream<Multipart.Part, Multipart.MultipartError> =>
|
|
17
38
|
BunStream.fromReadableStream({
|
|
18
|
-
evaluate: () => source.body
|
|
39
|
+
evaluate: () => source.body ?? emptyReadbleStream,
|
|
19
40
|
onError: (cause) => Multipart.MultipartError.fromReason("InternalError", cause)
|
|
20
41
|
}).pipe(
|
|
21
42
|
Stream.pipeThroughChannel(Multipart.makeChannel(Object.fromEntries(source.headers)))
|
|
22
43
|
)
|
|
23
44
|
|
|
45
|
+
const emptyReadbleStream = new ReadableStream({
|
|
46
|
+
start(controller) {
|
|
47
|
+
controller.enqueue(new Uint8Array())
|
|
48
|
+
controller.close()
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
24
52
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
53
|
+
* Parses and persists multipart data from a Bun `Request`, requiring file-system, path, and scope services.
|
|
54
|
+
*
|
|
55
|
+
* @category constructors
|
|
56
|
+
* @since 4.0.0
|
|
27
57
|
*/
|
|
28
58
|
export const persisted = (
|
|
29
59
|
source: Request
|
package/src/BunPath.ts
CHANGED
|
@@ -1,24 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* Bun-backed layers for Effect's {@link Path} service.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the `Path` service for Bun programs by reusing the
|
|
5
|
+
* shared Node-compatible path implementation. Provide one of these layers when
|
|
6
|
+
* Bun code should receive path operations from the Effect environment instead
|
|
7
|
+
* of importing runtime path helpers directly.
|
|
8
|
+
*
|
|
9
|
+
* **Mental model**
|
|
10
|
+
*
|
|
11
|
+
* Bun exposes Node-compatible path and file URL behavior, so the default
|
|
12
|
+
* {@link layer} follows the host operating system's rules. The
|
|
13
|
+
* {@link layerPosix} and {@link layerWin32} variants pin parsing and formatting
|
|
14
|
+
* to POSIX or Windows semantics for portable tests, generated paths, and
|
|
15
|
+
* cross-platform tooling.
|
|
16
|
+
*
|
|
17
|
+
* **Common tasks**
|
|
18
|
+
*
|
|
19
|
+
* Use {@link layer} for normal Bun applications and CLIs. Use
|
|
20
|
+
* {@link layerPosix} or {@link layerWin32} when code must produce stable path
|
|
21
|
+
* syntax regardless of the machine running Bun. `BunServices.layer` already
|
|
22
|
+
* includes {@link layer}, so import this module directly when only `Path` or a
|
|
23
|
+
* fixed platform variant is needed.
|
|
24
|
+
*
|
|
25
|
+
* **Gotchas**
|
|
26
|
+
*
|
|
27
|
+
* Path operations are syntactic. They normalize and convert strings and
|
|
28
|
+
* `file:` URLs, but they do not read the filesystem, check permissions, confirm
|
|
29
|
+
* that a path exists, or make request URLs safe to use as local paths.
|
|
30
|
+
*
|
|
31
|
+
* @since 4.0.0
|
|
3
32
|
*/
|
|
4
33
|
import * as NodePath from "@effect/platform-node-shared/NodePath"
|
|
5
34
|
import type * as Layer from "effect/Layer"
|
|
6
35
|
import type { Path } from "effect/Path"
|
|
7
36
|
|
|
8
37
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
38
|
+
* Layer that provides the default `Path` service for Bun using the shared Node path implementation.
|
|
39
|
+
*
|
|
40
|
+
* @category layers
|
|
41
|
+
* @since 4.0.0
|
|
11
42
|
*/
|
|
12
43
|
export const layer: Layer.Layer<Path> = NodePath.layer
|
|
13
44
|
|
|
14
45
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
46
|
+
* Layer that provides the POSIX `Path` service for Bun using the shared Node path implementation.
|
|
47
|
+
*
|
|
48
|
+
* @category layers
|
|
49
|
+
* @since 4.0.0
|
|
17
50
|
*/
|
|
18
51
|
export const layerPosix: Layer.Layer<Path> = NodePath.layerPosix
|
|
19
52
|
|
|
20
53
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
54
|
+
* Layer that provides the Win32 `Path` service for Bun using the shared Node path implementation.
|
|
55
|
+
*
|
|
56
|
+
* @category layers
|
|
57
|
+
* @since 4.0.0
|
|
23
58
|
*/
|
|
24
59
|
export const layerWin32: Layer.Layer<Path> = NodePath.layerWin32
|
package/src/BunRedis.ts
CHANGED
|
@@ -1,20 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Bun Redis integration backed by Bun's built-in `RedisClient`.
|
|
3
|
+
*
|
|
4
|
+
* This module creates scoped Bun `RedisClient` connections and exposes them as
|
|
5
|
+
* both the portable `Redis` service used by Effect persistence modules and the
|
|
6
|
+
* Bun-specific `BunRedis` service for direct access to the raw client. Use it in
|
|
7
|
+
* Bun applications that need Redis-backed persistence, persisted queues,
|
|
8
|
+
* distributed rate limiting, custom Redis commands, or Bun Redis features such
|
|
9
|
+
* as pub/sub.
|
|
10
|
+
*
|
|
11
|
+
* **Mental model**
|
|
12
|
+
*
|
|
13
|
+
* `layer` and `layerConfig` acquire one `RedisClient` for the layer scope. The
|
|
14
|
+
* same client backs the portable `Redis.Redis` command interface and the
|
|
15
|
+
* `BunRedis` service, and it is closed with `close` when the scope finalizes.
|
|
16
|
+
* Install the layer at the lifetime you want for that connection.
|
|
17
|
+
*
|
|
18
|
+
* **Common tasks**
|
|
19
|
+
*
|
|
20
|
+
* Pass a Redis URL or Bun `RedisOptions` to `layer` for direct configuration,
|
|
21
|
+
* or use `layerConfig` when connection settings should come from Effect
|
|
22
|
+
* configuration. Depend on `Redis.Redis` for persistence and rate limiter
|
|
23
|
+
* stores. Depend on `BunRedis` when you need `RedisClient` features that are
|
|
24
|
+
* not exposed by the portable service, including custom commands and pub/sub.
|
|
25
|
+
*
|
|
26
|
+
* **Gotchas**
|
|
27
|
+
*
|
|
28
|
+
* Pub/sub should normally use a separately scoped client so a subscription does
|
|
29
|
+
* not interfere with ordinary command traffic. Persistence and rate limiter
|
|
30
|
+
* stores build keys and Lua scripts on top of this service, so choose stable
|
|
31
|
+
* prefixes and store ids, account for persisted values that may fail to decode
|
|
32
|
+
* after schema changes, and avoid unbounded high-cardinality rate-limit keys
|
|
33
|
+
* without a cleanup or bounding strategy.
|
|
34
|
+
*
|
|
35
|
+
* @since 4.0.0
|
|
3
36
|
*/
|
|
4
37
|
import { RedisClient, type RedisOptions } from "bun"
|
|
5
38
|
import * as Config from "effect/Config"
|
|
39
|
+
import * as Context from "effect/Context"
|
|
6
40
|
import * as Effect from "effect/Effect"
|
|
7
41
|
import * as Fn from "effect/Function"
|
|
8
42
|
import * as Layer from "effect/Layer"
|
|
9
43
|
import * as Scope from "effect/Scope"
|
|
10
|
-
import * as ServiceMap from "effect/ServiceMap"
|
|
11
44
|
import * as Redis from "effect/unstable/persistence/Redis"
|
|
12
45
|
|
|
13
46
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
47
|
+
* Service tag for Bun Redis integration, exposing the raw `RedisClient` and a `use` helper that maps client promise failures to `RedisError`.
|
|
48
|
+
*
|
|
49
|
+
* @category services
|
|
50
|
+
* @since 4.0.0
|
|
16
51
|
*/
|
|
17
|
-
export class BunRedis extends
|
|
52
|
+
export class BunRedis extends Context.Service<BunRedis, {
|
|
18
53
|
readonly client: RedisClient
|
|
19
54
|
readonly use: <A>(f: (client: RedisClient) => Promise<A>) => Effect.Effect<A, Redis.RedisError>
|
|
20
55
|
}>()("@effect/platform-bun/BunRedis") {}
|
|
@@ -47,28 +82,32 @@ const make = Effect.fnUntraced(function*(
|
|
|
47
82
|
use
|
|
48
83
|
})
|
|
49
84
|
|
|
50
|
-
return
|
|
51
|
-
|
|
85
|
+
return Context.make(BunRedis, bunRedis).pipe(
|
|
86
|
+
Context.add(Redis.Redis, redis)
|
|
52
87
|
)
|
|
53
88
|
})
|
|
54
89
|
|
|
55
90
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
91
|
+
* Creates scoped Bun Redis layers for `Redis.Redis` and `BunRedis`, closing the underlying client when the scope finalizes.
|
|
92
|
+
*
|
|
93
|
+
* @category layers
|
|
94
|
+
* @since 4.0.0
|
|
58
95
|
*/
|
|
59
96
|
export const layer = (
|
|
60
97
|
options?: ({ readonly url?: string } & RedisOptions) | undefined
|
|
61
|
-
): Layer.Layer<Redis.Redis | BunRedis> => Layer.
|
|
98
|
+
): Layer.Layer<Redis.Redis | BunRedis> => Layer.effectContext(make(options))
|
|
62
99
|
|
|
63
100
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
101
|
+
* Creates scoped Bun Redis layers from configurable Redis options, closing the underlying client when the scope finalizes.
|
|
102
|
+
*
|
|
103
|
+
* @category layers
|
|
104
|
+
* @since 4.0.0
|
|
66
105
|
*/
|
|
67
106
|
export const layerConfig = (
|
|
68
107
|
options: Config.Wrap<{ readonly url?: string } & RedisOptions>
|
|
69
108
|
): Layer.Layer<Redis.Redis | BunRedis, Config.ConfigError> =>
|
|
70
|
-
Layer.
|
|
71
|
-
Config.unwrap(options).
|
|
109
|
+
Layer.effectContext(
|
|
110
|
+
Config.unwrap(options).pipe(
|
|
72
111
|
Effect.flatMap(make)
|
|
73
112
|
)
|
|
74
113
|
)
|