142 lines
4.4 KiB
TypeScript
142 lines
4.4 KiB
TypeScript
|
|
import type { ISmartServeInstance, IConnectionInfo } from '../core/smartserve.interfaces.js';
|
||
|
|
import { BaseAdapter, type IAdapterCharacteristics, type TRequestHandler } from './adapter.base.js';
|
||
|
|
|
||
|
|
// Deno types (for type checking without requiring Deno runtime)
|
||
|
|
declare const Deno: {
|
||
|
|
serve(options: any, handler?: any): { shutdown(): Promise<void>; finished: Promise<void> };
|
||
|
|
upgradeWebSocket(request: Request): { socket: WebSocket; response: Response };
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Deno adapter - zero overhead, native Request/Response
|
||
|
|
*/
|
||
|
|
export class DenoAdapter extends BaseAdapter {
|
||
|
|
private server: { shutdown(): Promise<void>; finished: Promise<void> } | null = null;
|
||
|
|
|
||
|
|
get name(): 'deno' {
|
||
|
|
return 'deno';
|
||
|
|
}
|
||
|
|
|
||
|
|
get characteristics(): IAdapterCharacteristics {
|
||
|
|
return {
|
||
|
|
zeroCopyStreaming: true,
|
||
|
|
http2Support: true,
|
||
|
|
maxConnections: 'unlimited',
|
||
|
|
nativeWebSocket: true,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
isSupported(): boolean {
|
||
|
|
return typeof (globalThis as any).Deno !== 'undefined';
|
||
|
|
}
|
||
|
|
|
||
|
|
async start(handler: TRequestHandler): Promise<ISmartServeInstance> {
|
||
|
|
if (!this.isSupported()) {
|
||
|
|
throw new Error('Deno 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',
|
||
|
|
};
|
||
|
|
|
||
|
|
// Add TLS if configured
|
||
|
|
if (this.options.tls) {
|
||
|
|
serveOptions.cert = this.options.tls.cert;
|
||
|
|
serveOptions.key = this.options.tls.key;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.server = Deno.serve(serveOptions, async (request: Request, info: any) => {
|
||
|
|
this.stats.requestsTotal++;
|
||
|
|
this.stats.requestsActive++;
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Handle WebSocket upgrade
|
||
|
|
if (this.options.websocket && request.headers.get('upgrade') === 'websocket') {
|
||
|
|
const { socket, response } = Deno.upgradeWebSocket(request);
|
||
|
|
this.attachWebSocketHooks(socket, request);
|
||
|
|
return response;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create connection info
|
||
|
|
const connectionInfo: IConnectionInfo = {
|
||
|
|
remoteAddr: info?.remoteAddr?.hostname ?? 'unknown',
|
||
|
|
remotePort: info?.remoteAddr?.port ?? 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--;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return this.createInstance();
|
||
|
|
}
|
||
|
|
|
||
|
|
async stop(): Promise<void> {
|
||
|
|
if (this.server) {
|
||
|
|
await this.server.shutdown();
|
||
|
|
await this.server.finished;
|
||
|
|
this.server = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private attachWebSocketHooks(socket: WebSocket, request: Request): void {
|
||
|
|
const hooks = this.options.websocket;
|
||
|
|
if (!hooks) return;
|
||
|
|
|
||
|
|
const peer = this.createWebSocketPeer(socket, request);
|
||
|
|
|
||
|
|
socket.onopen = () => {
|
||
|
|
hooks.onOpen?.(peer);
|
||
|
|
};
|
||
|
|
|
||
|
|
socket.onmessage = (event) => {
|
||
|
|
const message = {
|
||
|
|
type: typeof event.data === 'string' ? 'text' as const : 'binary' as const,
|
||
|
|
text: typeof event.data === 'string' ? event.data : undefined,
|
||
|
|
data: event.data instanceof Uint8Array ? event.data : undefined,
|
||
|
|
size: typeof event.data === 'string' ? event.data.length : (event.data as ArrayBuffer).byteLength,
|
||
|
|
};
|
||
|
|
hooks.onMessage?.(peer, message);
|
||
|
|
};
|
||
|
|
|
||
|
|
socket.onclose = (event) => {
|
||
|
|
hooks.onClose?.(peer, event.code, event.reason);
|
||
|
|
};
|
||
|
|
|
||
|
|
socket.onerror = (event) => {
|
||
|
|
hooks.onError?.(peer, new Error('WebSocket error'));
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
private createWebSocketPeer(socket: WebSocket, request: Request): any {
|
||
|
|
const url = this.parseUrl(request);
|
||
|
|
return {
|
||
|
|
id: crypto.randomUUID(),
|
||
|
|
url: url.pathname,
|
||
|
|
get readyState() { return socket.readyState; },
|
||
|
|
protocol: socket.protocol,
|
||
|
|
extensions: socket.extensions,
|
||
|
|
send: (data: string) => socket.send(data),
|
||
|
|
sendBinary: (data: Uint8Array | ArrayBuffer) => socket.send(data),
|
||
|
|
close: (code?: number, reason?: string) => socket.close(code, reason),
|
||
|
|
ping: () => { /* Deno handles ping/pong automatically */ },
|
||
|
|
terminate: () => socket.close(),
|
||
|
|
context: {} as any, // Will be populated with IRequestContext
|
||
|
|
data: new Map(),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|