Skip to content

feat: support Bun Fullstack Dev Server #4602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/swift-parents-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/platform-bun": minor
---

Support Bun Fullstack Dev Server Feature
7 changes: 6 additions & 1 deletion packages/platform-bun/examples/http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { BunHttpServer, BunRuntime } from "@effect/platform-bun"
import { Layer } from "effect"

const HttpLive = HttpServer.serve(HttpServerResponse.text("Hello World")).pipe(
Layer.provide(BunHttpServer.layer({ port: 3000 }))
Layer.provide(BunHttpServer.layer({
port: 3000,
routes: {
"/hello": new Response("hi ")
}
}))
)

BunRuntime.runMain(Layer.launch(HttpLive))
2 changes: 1 addition & 1 deletion packages/platform-bun/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@effect/platform": "workspace:^",
"@effect/rpc": "workspace:^",
"@effect/sql": "workspace:^",
"@types/bun": "^1.2.2",
"@types/bun": "^1.2.5",
"effect": "workspace:^"
},
"effect": {
Expand Down
33 changes: 24 additions & 9 deletions packages/platform-bun/src/BunHttpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type * as HttpClient from "@effect/platform/HttpClient"
import type * as Platform from "@effect/platform/HttpPlatform"
import type * as Server from "@effect/platform/HttpServer"
import type * as HttpServerError from "@effect/platform/HttpServerError"
import type { ServeOptions } from "bun"
import type * as Config from "effect/Config"
import type * as ConfigError from "effect/ConfigError"
import type * as Effect from "effect/Effect"
Expand All @@ -15,27 +14,43 @@ import type * as Scope from "effect/Scope"
import type * as BunContext from "./BunContext.js"
import * as internal from "./internal/httpServer.js"

/**
* @since 1.0.0
* @category models
*/
export type BunServerRoutes<R> = internal.BunServerRoutes<R>

/**
* @since 1.0.0
* @category models
*/
export type BunServerOptions<R extends internal.BunServerRoutes<R>> = Omit<
internal.BunServerOptions<R>,
"fetch" | "error" | "unix"
>

/**
* @since 1.0.0
* @category constructors
*/
export const make: (
options: Omit<ServeOptions, "fetch" | "error">
export const make: <R extends BunServerRoutes<R>>(
options: BunServerOptions<R>
) => Effect.Effect<Server.HttpServer, never, Scope.Scope> = internal.make

/**
* @since 1.0.0
* @category layers
*/
export const layerServer: (options: Omit<ServeOptions, "fetch" | "error">) => Layer.Layer<Server.HttpServer> =
internal.layerServer
export const layerServer: <R extends internal.BunServerRoutes<R>>(
options: BunServerOptions<R>
) => Layer.Layer<Server.HttpServer> = internal.layerServer

/**
* @since 1.0.0
* @category layers
*/
export const layer: (
options: Omit<ServeOptions, "fetch" | "error">
export const layer: <R extends internal.BunServerRoutes<R>>(
options: BunServerOptions<R>
) => Layer.Layer<Server.HttpServer | Platform.HttpPlatform | Etag.Generator | BunContext.BunContext> = internal.layer

/**
Expand All @@ -58,8 +73,8 @@ export const layerTest: Layer.Layer<
* @since 1.0.0
* @category layers
*/
export const layerConfig: (
options: Config.Config.Wrap<Omit<ServeOptions, "fetch" | "error">>
export const layerConfig: <R extends internal.BunServerRoutes<R>>(
options: Config.Config.Wrap<BunServerOptions<R>>
) => Layer.Layer<
Server.HttpServer | Platform.HttpPlatform | Etag.Generator | BunContext.BunContext,
ConfigError.ConfigError
Expand Down
51 changes: 36 additions & 15 deletions packages/platform-bun/src/internal/httpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type * as Multipart from "@effect/platform/Multipart"
import type * as Path from "@effect/platform/Path"
import * as Socket from "@effect/platform/Socket"
import * as UrlParams from "@effect/platform/UrlParams"
import type { ServeOptions, Server as BunServer, ServerWebSocket } from "bun"
import type { RouterTypes, ServeOptions, Server as BunServer, ServerWebSocket } from "bun"
import * as Config from "effect/Config"
import * as Deferred from "effect/Deferred"
import * as Effect from "effect/Effect"
Expand All @@ -31,27 +31,48 @@ import * as BunContext from "../BunContext.js"
import * as Platform from "../BunHttpPlatform.js"
import * as MultipartBun from "./multipart.js"

export type BunServerRoutes<R> = { [K in keyof R]: RouterTypes.RouteValue<K & string> }

export type BunServerOptions<R extends BunServerRoutes<R>> =
| (ServeOptions & {
routes: R
fetch?: (
this: BunServer,
request: Request,
server: BunServer
) => Response | Promise<Response>
})
| (ServeOptions & {
routes?: never
fetch?: (
this: BunServer,
request: Request,
server: BunServer
) => Response | Promise<Response>
})

/** @internal */
export const make = (
options: Omit<ServeOptions, "fetch" | "error">
export const make = <R extends BunServerRoutes<R>>(
options: Omit<BunServerOptions<R>, "fetch" | "error" | "unix">
): Effect.Effect<Server.HttpServer, never, Scope.Scope> =>
Effect.gen(function*() {
const handlerStack: Array<(request: Request, server: BunServer) => Response | Promise<Response>> = [
function(_request, _server) {
return new Response("not found", { status: 404 })
}
]
const server = Bun.serve<WebSocketContext>({

const server = Bun.serve<WebSocketContext, R>({
...options,
fetch: handlerStack[0],
websocket: {
open(ws) {
open(ws: ServerWebSocket<WebSocketContext>) {
Deferred.unsafeDone(ws.data.deferred, Exit.succeed(ws))
},
message(ws, message) {
message(ws: ServerWebSocket<WebSocketContext>, message: string | Buffer) {
ws.data.run(message)
},
close(ws, code, closeReason) {
close(ws: ServerWebSocket<WebSocketContext>, code: number, closeReason: string) {
Deferred.unsafeDone(
ws.data.closeDeferred,
Socket.defaultCloseCodeIsError(code)
Expand Down Expand Up @@ -95,12 +116,12 @@ export const make = (
yield* Effect.acquireRelease(
Effect.sync(() => {
handlerStack.push(handler)
server.reload({ fetch: handler } as ServeOptions)
server.reload({ ...options, fetch: handler } as BunServerOptions<R>)
}),
() =>
Effect.sync(() => {
handlerStack.pop()
server.reload({ fetch: handlerStack[handlerStack.length - 1] } as ServeOptions)
server.reload({ ...options, fetch: handlerStack[handlerStack.length - 1] } as BunServerOptions<R>)
})
)
})
Expand Down Expand Up @@ -157,8 +178,8 @@ const makeResponse = (
}

/** @internal */
export const layerServer = (
options: Omit<ServeOptions, "fetch" | "error">
export const layerServer = <R extends BunServerRoutes<R>>(
options: Omit<BunServerOptions<R>, "fetch" | "error" | "unix">
) => Layer.scoped(Server.HttpServer, make(options))

/** @internal */
Expand All @@ -169,8 +190,8 @@ export const layerContext = Layer.mergeAll(
)

/** @internal */
export const layer = (
options: Omit<ServeOptions, "fetch" | "error">
export const layer = <R extends BunServerRoutes<R>>(
options: Omit<BunServerOptions<R>, "fetch" | "error" | "unix">
) =>
Layer.mergeAll(
Layer.scoped(Server.HttpServer, make(options)),
Expand All @@ -186,8 +207,8 @@ export const layerTest = Server.layerTestClient.pipe(
)

/** @internal */
export const layerConfig = (
options: Config.Config.Wrap<Omit<ServeOptions, "fetch" | "error">>
export const layerConfig = <R extends BunServerRoutes<R>>(
options: Config.Config.Wrap<Omit<BunServerOptions<R>, "fetch" | "error" | "unix">>
) =>
Layer.mergeAll(
Layer.scoped(Server.HttpServer, Effect.flatMap(Config.unwrap(options), make)),
Expand Down
Loading