Files
smartserve/ts/adapters/adapter.deno.ts

142 lines
4.4 KiB
TypeScript
Raw Normal View History

2025-11-29 15:24:00 +00:00
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(),
};
}
}