initial
This commit is contained in:
161
ts/adapters/adapter.bun.ts
Normal file
161
ts/adapters/adapter.bun.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
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<ISmartServeInstance> {
|
||||
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<void> {
|
||||
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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user