@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
216 lines (215 loc) • 8.26 kB
TypeScript
/**
* Abstract Server Adapter
* Base class for all framework-specific server adapters
* Follows NeuroLink's composition and factory patterns
*/
import { EventEmitter } from "events";
import type { ExternalServerManager } from "../../mcp/externalServerManager.js";
import type { MCPToolRegistry } from "../../mcp/toolRegistry.js";
import type { NeuroLink } from "../../neurolink.js";
import type { MiddlewareDefinition, RedactionConfig, RequiredServerAdapterConfig, RequiredShutdownConfig, RouteDefinition, ServerAdapterConfig, ServerAdapterEvents, ServerContext, ServerLifecycleState, ServerStatus, TrackedConnection } from "../../types/index.js";
/**
* Abstract base class for server adapters
* Provides common functionality and defines the interface for framework-specific implementations
*/
export declare abstract class BaseServerAdapter extends EventEmitter {
protected readonly config: RequiredServerAdapterConfig;
protected readonly redactionConfig?: RedactionConfig;
protected readonly neurolink: NeuroLink;
protected readonly toolRegistry: MCPToolRegistry;
protected readonly externalServerManager?: ExternalServerManager;
protected routes: Map<string, RouteDefinition>;
protected middlewares: MiddlewareDefinition[];
protected isRunning: boolean;
protected startTime?: Date;
private frameworkReady;
private pendingMiddlewares;
private pendingRouteKeys;
protected lifecycleState: ServerLifecycleState;
protected activeConnections: Map<string, TrackedConnection>;
protected readonly shutdownConfig: RequiredShutdownConfig;
constructor(neurolink: NeuroLink, config?: ServerAdapterConfig);
/**
* Initialize the underlying server framework
* May be async: adapters that dynamically import their framework package
* (e.g. Hono) return a Promise; adapters with a synchronous no-op stub that
* defer real setup to their own overridden `initialize()` (Express/Fastify/
* Koa) still return void.
*/
protected abstract initializeFramework(): void | Promise<void>;
/**
* Register a route with the framework
*/
protected abstract registerFrameworkRoute(route: RouteDefinition): void;
/**
* Register middleware with the framework
*/
protected abstract registerFrameworkMiddleware(middleware: MiddlewareDefinition): void;
/**
* Start the server
*/
abstract start(): Promise<void>;
/**
* Stop the server
*/
abstract stop(): Promise<void>;
/**
* Get the underlying framework instance (for advanced usage)
*/
abstract getFrameworkInstance(): unknown;
/**
* The listening Node `http.Server`, once `start()` has run and the runtime
* is Node (Bun and Deno serve without one). A WebSocket upgrade handler
* such as `attachTranscribeWebSocket` attaches to it; `undefined` means
* there is nothing to attach to.
*/
getNativeServer(): unknown;
/**
* Stop accepting new connections
* Called during graceful shutdown to prevent new requests
*/
protected abstract stopAcceptingConnections(): Promise<void>;
/**
* Close the underlying server
* Called after connections are drained or timeout
*/
protected abstract closeServer(): Promise<void>;
/**
* Force close all active connections
* Called when drain timeout expires and forceClose is true
*/
protected abstract forceCloseConnections(): Promise<void>;
/**
* Import an optional framework dependency (express/fastify/koa and their
* plugins), converting a missing package into a {@link MissingDependencyError}
* instead of letting the framework's `Cannot find package` propagate raw.
*
* Delegates to {@link tryImport} for the actual resolution and message
* formatting, then re-wraps only the "package genuinely absent" case — the
* one `tryImport` signals by attaching the loader's own module-not-found
* error as `cause` — into the adapter-specific error type. Any other
* failure (installed but broken, non-package specifier) passes through
* unchanged, matching `tryImport`'s own contract.
*/
protected importFrameworkDependency<T>(pkg: string, framework: string): Promise<T>;
/**
* Initialize the server adapter
* Sets up routes, middleware, and framework
*/
initialize(): Promise<void>;
/**
* Register a custom route
*/
registerRoute(route: RouteDefinition): void;
/**
* Register multiple routes from a route group
*/
registerRouteGroup(group: {
prefix: string;
routes: RouteDefinition[];
middleware?: MiddlewareDefinition[];
}): void;
/**
* Normalize a path by removing duplicate slashes and ensuring leading slash
*/
private normalizePath;
/**
* Register custom middleware
*/
registerMiddleware(middleware: MiddlewareDefinition): void;
/**
* Log a failed request. A client error (a rejected API key, a failed
* validation) is routine traffic, so it is a one-line warning rather than
* an error with a stack trace that anyone could flood the logs with.
*/
protected logRequestError(source: string, requestId: string | undefined, error: Error): void;
/**
* Notify "error" listeners about a failed request. EventEmitter throws when
* "error" has no listener, which inside a framework's error handler turns
* every failure into an empty 500, so emit only when someone is listening.
*/
protected emitRequestError(payload: ServerAdapterEvents["error"]): void;
/**
* Hand queued registrations to the framework. A route re-registered under
* the same key before initialize() is replayed once, in its final form.
*/
private flushPendingRegistrations;
/**
* Create request context from incoming request
*/
protected createContext(options: {
requestId: string;
method: string;
path: string;
headers: Record<string, string>;
query?: Record<string, string>;
params?: Record<string, string>;
body?: unknown;
}): ServerContext;
/**
* Register built-in middleware
*/
protected registerBuiltInMiddleware(): void;
/**
* Register built-in routes
* Only registers health routes if disableBuiltInHealth is false (default)
*/
protected registerBuiltInRoutes(): Promise<void>;
/**
* Generate unique request ID
*/
protected generateRequestId(): string;
/**
* Get the current lifecycle state
*/
getLifecycleState(): ServerLifecycleState;
/**
* Track a new connection
* @param id Unique connection identifier
* @param socket Optional underlying socket object
* @param requestId Optional associated request ID
*/
protected trackConnection(id: string, socket?: unknown, requestId?: string): void;
/**
* Untrack a connection (when it's completed)
* @param id Connection identifier to remove
*/
protected untrackConnection(id: string): void;
/**
* Get the number of active connections
*/
getActiveConnectionCount(): number;
/**
* Perform graceful shutdown with connection draining
* This method handles the complete shutdown lifecycle
*/
protected gracefulShutdown(): Promise<void>;
/**
* Wait for all active connections to drain
* Resolves when activeConnections is empty
*/
protected drainConnections(): Promise<void>;
/**
* Reset server state for restart capability
* Call this after stop() completes to allow restart
*/
protected resetServerState(): void;
/**
* Validate lifecycle state transition
* @param operation The operation being performed
* @param allowedStates States that allow the operation
*/
protected validateLifecycleState(operation: string, allowedStates: ServerLifecycleState[]): void;
/**
* Get server status
*/
getStatus(): ServerStatus;
/**
* List all registered routes
*/
listRoutes(): RouteDefinition[];
/**
* Get configuration
*/
getConfig(): RequiredServerAdapterConfig;
}