import type { ISmartServeInstance, IConnectionInfo } from '../core/smartserve.interfaces.js'; import { BaseAdapter, type IAdapterCharacteristics, type TRequestHandler } from './adapter.base.js'; // Bun types (for type checking without requiring Bun runtime) declare const Bun: { serve(options: any): { stop(): void; port: number; hostname: string }; file(path: string): any; }; /** * Bun adapter - zero overhead, native Request/Response */ export class BunAdapter extends BaseAdapter { private server: { stop(): void; port: number; hostname: string } | null = null; get name(): 'bun' { return 'bun'; } get characteristics(): IAdapterCharacteristics { return { zeroCopyStreaming: true, http2Support: false, // Bun currently HTTP/1.1 only maxConnections: 'unlimited', nativeWebSocket: true, }; } isSupported(): boolean { return typeof (globalThis as any).Bun !== 'undefined'; } async start(handler: TRequestHandler): Promise { if (!this.isSupported()) { throw new Error('Bun runtime is not available'); } this.handler = handler; this.startTime = Date.now(); const serveOptions: any = { port: this.options.port, hostname: this.options.hostname ?? '0.0.0.0', fetch: async (request: Request, server: any) => { this.stats.requestsTotal++; this.stats.requestsActive++; try { // Handle WebSocket upgrade if (this.options.websocket && request.headers.get('upgrade') === 'websocket') { const upgraded = server.upgrade(request); if (upgraded) { return undefined; // Bun handles the upgrade } } // Create connection info const connectionInfo: IConnectionInfo = { remoteAddr: server.requestIP(request)?.address ?? 'unknown', remotePort: 0, localAddr: this.options.hostname ?? '0.0.0.0', localPort: this.options.port, encrypted: !!this.options.tls, }; return await handler(request, connectionInfo); } catch (error) { if (this.options.onError) { return this.options.onError(error as Error, request); } return new Response('Internal Server Error', { status: 500 }); } finally { this.stats.requestsActive--; } }, }; // Add TLS if configured if (this.options.tls) { serveOptions.tls = { cert: typeof this.options.tls.cert === 'string' ? Bun.file(this.options.tls.cert) : this.options.tls.cert, key: typeof this.options.tls.key === 'string' ? Bun.file(this.options.tls.key) : this.options.tls.key, passphrase: this.options.tls.passphrase, }; } // Add WebSocket handlers if configured if (this.options.websocket) { serveOptions.websocket = this.createWebSocketHandler(); } this.server = Bun.serve(serveOptions); return this.createInstance(); } async stop(): Promise { if (this.server) { this.server.stop(); this.server = null; } } private createWebSocketHandler(): any { const hooks = this.options.websocket; if (!hooks) return undefined; return { open: (ws: any) => { const peer = this.wrapBunWebSocket(ws); hooks.onOpen?.(peer); }, message: (ws: any, message: string | ArrayBuffer) => { const peer = this.wrapBunWebSocket(ws); const msg = { type: typeof message === 'string' ? 'text' as const : 'binary' as const, text: typeof message === 'string' ? message : undefined, data: message instanceof ArrayBuffer ? new Uint8Array(message) : undefined, size: typeof message === 'string' ? message.length : (message as ArrayBuffer).byteLength, }; hooks.onMessage?.(peer, msg); }, close: (ws: any, code: number, reason: string) => { const peer = this.wrapBunWebSocket(ws); hooks.onClose?.(peer, code, reason); }, error: (ws: any, error: Error) => { const peer = this.wrapBunWebSocket(ws); hooks.onError?.(peer, error); }, ping: (ws: any, data: ArrayBuffer) => { const peer = this.wrapBunWebSocket(ws); hooks.onPing?.(peer, new Uint8Array(data)); }, pong: (ws: any, data: ArrayBuffer) => { const peer = this.wrapBunWebSocket(ws); hooks.onPong?.(peer, new Uint8Array(data)); }, }; } private wrapBunWebSocket(ws: any): any { return { id: ws.data?.id ?? crypto.randomUUID(), url: ws.data?.url ?? '', get readyState() { return ws.readyState; }, protocol: ws.protocol ?? '', extensions: ws.extensions ?? '', send: (data: string) => ws.send(data), sendBinary: (data: Uint8Array | ArrayBuffer) => ws.send(data), close: (code?: number, reason?: string) => ws.close(code, reason), ping: (data?: Uint8Array) => ws.ping(data), terminate: () => ws.terminate(), context: {} as any, data: ws.data?.customData ?? new Map(), }; } }