bunshine
Version:
A Bun HTTP & WebSocket server that is a little ray of sunshine.
312 lines (308 loc) • 14.8 kB
TypeScript
// Generated by dts-bundle-generator v9.5.1
import { Server, ServerWebSocket, ServerWebSocketSendStatus, TLSServeOptions, ZlibCompressionOptions } from 'bun';
import { LRUCache } from 'lru-cache';
import { BrotliOptions } from 'node:zlib';
import { RequireAtLeastOne, TypedArray } from 'type-fest';
export type Registration<T> = {
matcher: (subject: string) => null | Record<string, string>;
pattern: string;
regex: RegExp;
methodFilter: null | ((subject: string) => boolean);
target: T;
};
export type Result<T> = Array<[
T,
Record<string, string>
]>;
declare class RouteMatcher<Target extends any> {
registered: Registration<Target>[];
match(method: string, subject: string, fallbacks?: Target[]): Result<Target>;
add(method: string, pattern: string | RegExp, target: Target): this;
detectPotentialDos(detector: any, config?: any): void;
}
declare class MatcherWithCache<Target = any> extends RouteMatcher<Target> {
cache: LRUCache<string, any>;
constructor(size?: number);
match(method: string, subject: string, fallbacks?: Target[]): any;
}
declare class SocketContext<UpgradeShape = any, ParamsShape = Record<string, any>> {
ws?: ServerWebSocket<WsDataShape<UpgradeShape, ParamsShape>>;
server: Server;
url: URL;
params: ParamsShape;
data: UpgradeShape;
type: SocketEventName;
constructor(server: Server, url: URL, params: ParamsShape, data: UpgradeShape);
get remoteAddress(): string;
get readyState(): Bun.WebSocketReadyState;
set binaryType(type: "nodebuffer" | "arraybuffer" | "uint8array");
get binaryType(): "nodebuffer" | "arraybuffer" | "uint8array" | undefined;
send(message: any, compress?: boolean): number;
close(status?: number, reason?: string): this;
terminate(): this;
subscribe(topic: string): this;
unsubscribe(topic: string): this;
isSubscribed(topic: string): boolean;
cork(callback: (ws: ServerWebSocket<WsDataShape<UpgradeShape, ParamsShape>>) => WsDataShape<UpgradeShape, ParamsShape>): WsDataShape<UpgradeShape, ParamsShape>;
publish(topic: string, message: any, compress?: boolean): number;
ping(data?: string | Bun.BufferSource): ServerWebSocketSendStatus;
pong(data?: string | Bun.BufferSource): ServerWebSocketSendStatus;
}
export type SocketEventName = "upgrade" | "open" | "message" | "close" | "drain" | "ping" | "pong" | "error";
declare class SocketMessage<T extends SocketEventName> {
readonly type: T;
private readonly _rawMessage;
constructor(type: T, rawMessage: string | Buffer);
raw(): string | Buffer<ArrayBufferLike>;
text(encoding?: BufferEncoding): string;
toString(encoding?: BufferEncoding): string;
buffer(): Buffer<ArrayBufferLike>;
arrayBuffer(): ArrayBufferLike;
readableStream(chunkSize?: number): ReadableStream<Uint8Array<ArrayBufferLike>>;
json(): any;
}
export type WsDataShape<U = any, P = Record<string, any>> = {
sc: SocketContext<U, P>;
};
export type SocketUpgradeHandler<U, P extends Record<string, any> = Record<string, any>> = (context: Context<P>, next: NextFunction) => U | Promise<U>;
export type SocketPlainHandler<U, P> = (context: SocketContext<U, P>) => void;
export type SocketMessageHandler<U, P, T extends SocketEventType> = (context: SocketContext<U, P>, message: SocketMessage<T>) => void;
export type SocketErrorHandler<U, P> = (context: SocketContext<U, P>, error: Error) => void;
export type SocketCloseHandler<U, P> = (context: SocketContext<U, P>, status: number, reason: string) => void;
export type BunshineHandlers<U, P extends Record<string, string> = Record<string, string>> = RequireAtLeastOne<{
upgrade: SocketUpgradeHandler<U, P>;
error: SocketErrorHandler<U, P>;
open: SocketPlainHandler<U, P>;
message: SocketMessageHandler<U, P, "message">;
close: SocketCloseHandler<U, P>;
drain: SocketPlainHandler<U, P>;
ping: SocketMessageHandler<U, P, "ping">;
pong: SocketMessageHandler<U, P, "pong">;
}>;
export type BunHandlers = {
open: (ws: ServerWebSocket<WsDataShape>) => void;
message: (ws: ServerWebSocket<WsDataShape>, data: any) => void;
close: (ws: ServerWebSocket<WsDataShape>, code: number, reason: string) => void;
drain: (ws: ServerWebSocket<WsDataShape>) => void;
ping: (ws: ServerWebSocket<WsDataShape>, data: any) => void;
pong: (ws: ServerWebSocket<WsDataShape>, data: any) => void;
};
export type SocketEventType = "open" | "message" | "close" | "drain" | "ping" | "pong";
export declare class SocketRouter {
httpRouter: HttpRouter;
routeMatcher: RouteMatcher<BunshineHandlers<any>>;
handlers: BunHandlers;
constructor(router: HttpRouter);
at: <P extends Record<string, string> = Record<string, string>, U = any>(path: string, handlers: BunshineHandlers<U, P>) => this;
private _fallbackError;
private _createHandler;
}
export type NextFunction = () => Promise<Response>;
export type SingleHandler<ParamsShape extends Record<string, string> = Record<string, string>> = (context: Context<ParamsShape>, next: NextFunction) => Response | void | Promise<Response | void>;
export type Handler<ParamsShape extends Record<string, string> = Record<string, string>> = SingleHandler<ParamsShape> | Handler<ParamsShape>[];
export type Middleware<ParamsShape extends Record<string, string> = Record<string, string>> = SingleHandler<ParamsShape> | Handler<ParamsShape>[];
export type ListenOptions = Omit<TLSServeOptions, "fetch" | "websocket"> | number;
export type HttpMethods = "ALL" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "TRACE";
export type HttpRouterOptions = {
cacheSize?: number;
};
export type EmitUrlOptions = {
verbose?: boolean;
to?: (message: string) => void;
date?: boolean;
};
export declare class HttpRouter {
version: string;
locals: Record<string, any>;
server: Server | undefined;
routeMatcher: MatcherWithCache<SingleHandler>;
_wsRouter?: SocketRouter;
onNotFound: (...handlers: Handler[]) => HttpRouter;
onError: (...handlers: Handler[]) => HttpRouter;
private _on404Handlers;
private _on500Handlers;
constructor(options?: HttpRouterOptions);
listen(portOrOptions?: ListenOptions): Server;
emitUrl({ verbose, to, date, }?: EmitUrlOptions): void;
getExport(options?: Omit<TLSServeOptions, "fetch" | "websocket">): TLSServeOptions;
get socket(): SocketRouter;
on<ParamsShape extends Record<string, string> = Record<string, string>>(verbOrVerbs: HttpMethods | HttpMethods[], path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
all<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
get<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
put<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
head<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
post<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
patch<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
trace<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
delete<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
options<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
headGet<ParamsShape extends Record<string, string> = Record<string, string>>(path: string | RegExp, ...handlers: Handler<ParamsShape>[]): this;
use: (...handlers: Handler[]) => this;
on404: (...handlers: Handler[]) => this;
on500: (...handlers: Handler[]) => this;
fetch: (request: Request, server: Server) => Promise<Response>;
}
export type FileLike = string | Blob | Uint8Array | ArrayBuffer;
export type FileResponseOptions = {
chunkSize?: number;
disposition?: "inline" | "attachment" | "form-data";
acceptRanges?: boolean;
sendLastModified?: boolean;
headers?: HeadersInit;
};
export type SseSend = (eventName: string, data?: string | object, id?: string, retry?: number) => void | Promise<void>;
export type SseClose = () => void | Promise<void>;
export type SseSetupFunction = (send: SseSend, close: SseClose) => void | (() => void);
export declare class Context<ParamsShape extends Record<string, string> = Record<string, string>> {
/** The raw request object */
request: Request;
/** Alias for `request` */
req: Request;
/** The Bun server instance */
server: Server;
/** The HttpRouter instance */
app: HttpRouter;
/** The request params from URL placeholders */
params: ParamsShape;
/** A place to persist data between handlers for the duration of the request */
locals: Record<string, any>;
/** A URL object constructed with `new URL(request.url)` */
url: URL;
/** The date the request was received */
date: Date;
/** The milliseconds between server start and this request, as float (from performance.now()) */
now: number;
/** If an error has been thrown, the error Object */
error: Error | null;
constructor(request: Request, server: Server, app: HttpRouter);
/** Get the IP address info of the client */
get ip(): {
address: string;
family: string;
port: number;
} | null;
/** A shorthand for `new Response(text, { headers: { 'Content-type': 'text/plain' } })` */
text: (text: string, init?: ResponseInit) => Response;
/** A shorthand for `new Response(js, { headers: { 'Content-type': 'text/javascript' } })` */
js: (js: string, init?: ResponseInit) => Response;
/** A shorthand for `new Response(html, { headers: { 'Content-type': 'text/html' } })` */
html: (html: string, init?: ResponseInit) => Response;
/** A shorthand for `new Response(html, { headers: { 'Content-type': 'text/css' } })` */
css: (css: string, init?: ResponseInit) => Response;
/** A shorthand for `new Response(xml, { headers: { 'Content-type': 'text/xml' } })` */
xml: (xml: string, init?: ResponseInit) => Response;
/** A shorthand for `new Response(JSON.stringify(data), { headers: { 'Content-type': 'application/json' } })` */
json: (data: any, init?: ResponseInit) => Response;
/** A shorthand for `new Response(null, { headers: { Location: url }, status: 301 })` */
redirect: (url: string, status?: number) => Response;
/** A shorthand for `new Response(bunFile, fileHeaders)` plus range features */
file: (pathOrData: FileLike, fileOptions?: FileResponseOptions) => Promise<Response>;
/** A shorthand for `new Response({ headers: { 'Content-type': 'text/event-stream' } })` */
sse: (setup: SseSetupFunction, init?: ResponseInit) => Response;
}
export type ApplyHandlerIfArgs = {
requestCondition?: (c: Context) => Promise<boolean> | boolean;
responseCondition?: (c: Context, resp: Response) => Promise<boolean> | boolean;
handler: Handler;
};
export declare function applyHandlerIf(conditions: ApplyHandlerIfArgs): Middleware;
export type CompressionOptions = {
prefer: "br" | "gzip" | "none";
br: BrotliOptions;
gzip: ZlibCompressionOptions;
minSize: number;
maxSize: number;
exceptWhen: (context: Context, response: Response) => boolean | Promise<boolean>;
};
export declare const compressionDefaults: {
prefer: "gzip";
br: BrotliOptions;
gzip: ZlibCompressionOptions;
minSize: number;
maxSize: number;
exceptWhen: () => boolean;
};
export declare function compression(options?: Partial<CompressionOptions>): Middleware;
export type CorsOptions = {
origin?: string | RegExp | Array<string | RegExp> | boolean | ((incomingOrigin: string, context: Context) => string | string[] | boolean | undefined | null);
allowMethods?: string[];
allowHeaders?: string[];
maxAge?: number;
credentials?: boolean;
exposeHeaders?: string[];
exceptWhen?: (context: Context, response: Response) => boolean | Promise<boolean>;
};
export declare const corsDefaults: {
origin: string;
allowMethods: string[];
maxAge: undefined;
credentials: undefined;
allowHeaders: never[];
exposeHeaders: never[];
exceptWhen: () => boolean;
};
export declare function cors(options?: CorsOptions): Middleware;
export type LoggerOptions = {
writer?: (msg: string) => void;
exceptWhen?: (context: Context, response: Response | null) => boolean | Promise<boolean>;
};
export declare function devLogger(options?: LoggerOptions): Middleware;
export type EtagHashCalculator = (context: Context, response: Response) => Promise<{
buffer: ArrayBuffer | TypedArray | Buffer;
hash: string;
}>;
export type EtagOptions = {
calculator?: EtagHashCalculator;
maxSize?: number;
exceptWhen?: (context: Context, response: Response) => boolean;
};
export declare function etags({ calculator, maxSize, // 2GB
exceptWhen, }?: EtagOptions): Middleware;
export declare function defaultEtagsCalculator(_: Context, resp: Response): Promise<{
buffer: ArrayBuffer;
hash: string;
}>;
export type HeaderValue = string | ((c: Context, resp: Response) => string | null | Promise<string | null>);
export type HeaderValues = Record<string, HeaderValue>;
export type HeaderCondition = (c: Context, resp: Response) => boolean | Promise<boolean>;
export declare function headers(headers: HeaderValues, condition?: HeaderCondition): Middleware;
export declare function performanceHeader(headerName?: string): Middleware;
export declare function prodLogger(options?: LoggerOptions): Middleware;
export type ServeFilesOptions = {
acceptRanges?: boolean;
dotfiles?: "allow" | "deny" | "ignore";
extensions?: string[];
fallthrough?: boolean;
immutable?: boolean;
index?: string[];
lastModified?: boolean;
maxAge?: number | string;
exceptWhen?: (context: Context, response: Response | null) => boolean;
};
export declare function serveFiles(directory: string, { acceptRanges, dotfiles, extensions, fallthrough, immutable, index, lastModified, maxAge, exceptWhen, }?: ServeFilesOptions): Middleware;
export declare function trailingSlashes(mode: "add" | "remove"): Middleware;
export function ms(expression: string | number): number;
export type RangeInformation = {
rangeHeader?: string | null | false;
totalFileSize: number;
defaultChunkSize?: number;
};
export function parseRangeHeader({ rangeHeader, totalFileSize, defaultChunkSize, }: RangeInformation): {
slice: null;
contentLength: number;
status: number;
} | {
slice: null;
contentLength: null;
status: number;
} | {
slice: {
start: number;
end: number;
};
contentLength: number;
status: number;
};
export type Factory = (body: string, init?: ResponseInit) => Response;
export function factory(contentType: string): Factory;
export {};