elysia
Version:
Ergonomic Framework for Human
323 lines (322 loc) • 10.3 kB
TypeScript
import { Serve as BunServe, type Server as BunServer } from 'bun';
import type { Equal, MaybePromise } from '../types';
export interface ErrorLike extends Error {
code?: string;
errno?: number;
syscall?: string;
}
export interface GenericServeOptions {
/**
* What URI should be used to make {@link Request.url} absolute?
*
* By default, looks at {@link hostname}, {@link port}, and whether or not SSL is enabled to generate one
*
* @example
* ```js
* "http://my-app.com"
* ```
*
* @example
* ```js
* "https://wongmjane.com/"
* ```
*
* This should be the public, absolute URL – include the protocol and {@link hostname}. If the port isn't 80 or 443, then include the {@link port} too.
*
* @example
* "http://localhost:3000"
*/
/**
* What is the maximum size of a request body? (in bytes)
* @default 1024 * 1024 * 128 // 128MB
*/
maxRequestBodySize?: number;
/**
* Render contextual errors? This enables bun's error page
* @default process.env.NODE_ENV !== 'production'
*/
development?: boolean;
error?: (this: Server, request: ErrorLike) => Response | Promise<Response> | undefined | Promise<undefined>;
/**
* Uniquely identify a server instance with an ID
*
* ### When bun is started with the `--hot` flag
*
* This string will be used to hot reload the server without interrupting
* pending requests or websockets. If not provided, a value will be
* generated. To disable hot reloading, set this value to `null`.
*
* ### When bun is not started with the `--hot` flag
*
* This string will currently do nothing. But in the future it could be useful for logs or metrics.
*/
id?: string | null;
}
export interface ServeOptions extends GenericServeOptions {
/**
* What port should the server listen on?
* @default process.env.PORT || "3000"
*/
port?: string | number;
/**
* If the `SO_REUSEPORT` flag should be set.
*
* This allows multiple processes to bind to the same port, which is useful for load balancing.
*
* @default false
*/
reusePort?: boolean;
/**
* What hostname should the server listen on?
*
* @default
* ```js
* "0.0.0.0" // listen on all interfaces
* ```
* @example
* ```js
* "127.0.0.1" // Only listen locally
* ```
* @example
* ```js
* "remix.run" // Only listen on remix.run
* ````
*
* note: hostname should not include a {@link port}
*/
hostname?: string;
/**
* If set, the HTTP server will listen on a unix socket instead of a port.
* (Cannot be used with hostname+port)
*/
unix?: never;
/**
* Handle HTTP requests
*
* Respond to {@link Request} objects with a {@link Response} object.
*/
fetch(this: Server, request: Request, server: Server): Response | Promise<Response>;
routes: Record<string, Function | Response | Record<string, Function | Response>>;
}
export type Serve = Equal<BunServe.Options<unknown>, unknown> extends false ? BunServe.Options<unknown> : ServeOptions;
export type Server = Equal<BunServer<unknown>, unknown> extends false ? BunServer<unknown> : ServerOptions;
export type ServerWebSocketSendStatus = number;
export interface SocketAddress {
/**
* The IP address of the client.
*/
address: string;
/**
* The port of the client.
*/
port: number;
/**
* The IP family ("IPv4" or "IPv6").
*/
family: 'IPv4' | 'IPv6';
}
export interface ServerOptions extends Disposable {
/**
* Stop listening to prevent new connections from being accepted.
*
* By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops.
*
* @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections.
* @default false
*/
stop(closeActiveConnections?: boolean): void;
/**
* Update the `fetch` and `error` handlers without restarting the server.
*
* This is useful if you want to change the behavior of your server without
* restarting it or for hot reloading.
*
* @example
*
* ```js
* // create the server
* const server = Bun.serve({
* fetch(request) {
* return new Response("Hello World v1")
* }
* });
*
* // Update the server to return a different response
* server.reload({
* fetch(request) {
* return new Response("Hello World v2")
* }
* });
* ```
*
* Passing other options such as `port` or `hostname` won't do anything.
*/
reload(options: Serve): void;
/**
* Mock the fetch handler for a running server.
*
* This feature is not fully implemented yet. It doesn't normalize URLs
* consistently in all cases and it doesn't yet call the `error` handler
* consistently. This needs to be fixed
*/
fetch(request: Request | string): Response | Promise<Response>;
/**
* Upgrade a {@link Request} to a {@link ServerWebSocket}
*
* @param request The {@link Request} to upgrade
* @param options Pass headers or attach data to the {@link ServerWebSocket}
*
* @returns `true` if the upgrade was successful and `false` if it failed
*
* @example
* ```js
* import { serve } from "bun";
* serve({
* websocket: {
* open: (ws) => {
* console.log("Client connected");
* },
* message: (ws, message) => {
* console.log("Client sent message", message);
* },
* close: (ws) => {
* console.log("Client disconnected");
* },
* },
* fetch(req, server) {
* const url = new URL(req.url);
* if (url.pathname === "/chat") {
* const upgraded = server.upgrade(req);
* if (!upgraded) {
* return new Response("Upgrade failed", { status: 400 });
* }
* }
* return new Response("Hello World");
* },
* });
* ```
* What you pass to `data` is available on the {@link ServerWebSocket.data} property
*/
upgrade<T = undefined>(request: Request, options?: {
/**
* Send any additional headers while upgrading, like cookies
*/
headers?: Bun.HeadersInit;
/**
* This value is passed to the {@link ServerWebSocket.data} property
*/
data?: T;
}): boolean;
/**
* Send a message to all connected {@link ServerWebSocket} subscribed to a topic
*
* @param topic The topic to publish to
* @param data The data to send
* @param compress Should the data be compressed? Ignored if the client does not support compression.
*
* @returns 0 if the message was dropped, -1 if backpressure was applied, or the number of bytes sent.
*
* @example
*
* ```js
* server.publish("chat", "Hello World");
* ```
*
* @example
* ```js
* server.publish("chat", new Uint8Array([1, 2, 3, 4]));
* ```
*
* @example
* ```js
* server.publish("chat", new ArrayBuffer(4), true);
* ```
*
* @example
* ```js
* server.publish("chat", new DataView(new ArrayBuffer(4)));
* ```
*/
publish(topic: string, data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, compress?: boolean): ServerWebSocketSendStatus;
/**
* Returns the client IP address and port of the given Request. If the request was closed or is a unix socket, returns null.
*
* @example
* ```js
* export default {
* async fetch(request, server) {
* return new Response(server.requestIP(request));
* }
* }
* ```
*/
requestIP(request: Request): SocketAddress | null;
/**
* Reset the idleTimeout of the given Request to the number in seconds. 0 means no timeout.
*
* @example
* ```js
* export default {
* async fetch(request, server) {
* server.timeout(request, 60);
* await Bun.sleep(30000);
* return new Response("30 seconds have passed");
* }
* }
* ```
*/
timeout(request: Request, seconds: number): void;
/**
* Undo a call to {@link Server.unref}
*
* If the Server has already been stopped, this does nothing.
*
* If {@link Server.ref} is called multiple times, this does nothing. Think of it as a boolean toggle.
*/
ref(): void;
/**
* Don't keep the process alive if this server is the only thing left.
* Active connections may continue to keep the process alive.
*
* By default, the server is ref'd.
*
* To prevent new connections from being accepted, use {@link Server.stop}
*/
unref(): void;
/**
* How many requests are in-flight right now?
*/
readonly pendingRequests: number;
/**
* How many {@link ServerWebSocket}s are in-flight right now?
*/
readonly pendingWebSockets: number;
readonly url: URL;
readonly port: number;
/**
* The hostname the server is listening on. Does not include the port
* @example
* ```js
* "localhost"
* ```
*/
readonly hostname: string;
/**
* Is the server running in development mode?
*
* In development mode, `Bun.serve()` returns rendered error messages with
* stack traces instead of a generic 500 error. This makes debugging easier,
* but development mode shouldn't be used in production or you will risk
* leaking sensitive information.
*/
readonly development: boolean;
/**
* An identifier of the server instance
*
* When bun is started with the `--hot` flag, this ID is used to hot reload the server without interrupting pending requests or websockets.
*
* When bun is not started with the `--hot` flag, this ID is currently unused.
*/
readonly id: string;
}
export type ListenCallback = (server: Server) => MaybePromise<void>;