@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
511 lines (510 loc) • 19.3 kB
JavaScript
/**
* Koa Server Adapter
* Server adapter implementation using Koa framework
* Koa is known for its elegant middleware composition using async/await
*/
import { logger } from "../../utils/logger.js";
import { AlreadyRunningError, ServerStopError, wrapError } from "../errors.js";
import { BaseServerAdapter } from "../abstract/baseServerAdapter.js";
import { isErrorResponse } from "../utils/validation.js";
/**
* Koa-specific server adapter
* Leverages Koa's middleware composition for clean request handling
*/
export class KoaServerAdapter extends BaseServerAdapter {
app;
router;
server;
frameworkInitialized = false;
rateLimitStore = new Map();
rateLimitCleanupInterval;
sockets = new Set();
constructor(neurolink, config = {}) {
super(neurolink, config);
}
/**
* Initialize Koa framework
* Called by base class but actual initialization happens in initializeFrameworkAsync
*/
initializeFramework() {
// Framework will be initialized asynchronously in initializeFrameworkAsync
// This is called by the base class constructor, but we need async imports
}
/**
* Initialize Koa framework with async imports
*/
async initializeFrameworkAsync() {
if (this.frameworkInitialized) {
return;
}
// Dynamic imports to avoid loading if not used
const KoaModule = await import("koa");
const RouterModule = await import("@koa/router");
const Koa = KoaModule.default;
const Router = RouterModule.default;
this.app = new Koa();
this.router = new Router();
// Request ID middleware (first)
this.app.use(async (ctx, next) => {
ctx.state.requestId =
ctx.get("x-request-id") || this.generateRequestId();
ctx.set("X-Request-ID", ctx.state.requestId);
await next();
});
// Error handling middleware (should be early to catch all errors)
this.app.use(async (ctx, next) => {
try {
await next();
}
catch (error) {
const err = error;
const requestId = ctx.state.requestId;
logger.error("[KoaAdapter] Request error", {
requestId,
error: err.message,
stack: err.stack,
});
this.emit("error", {
requestId,
error: err,
timestamp: new Date(),
});
const statusCode = err.status || err.statusCode || 500;
ctx.status = statusCode;
ctx.body = {
error: {
code: statusCode === 500 ? "INTERNAL_ERROR" : `HTTP_${statusCode}`,
message: statusCode === 500 ? "An internal error occurred" : err.message,
},
metadata: {
requestId,
timestamp: new Date().toISOString(),
},
};
}
});
// CORS middleware
if (this.config.cors.enabled) {
const corsModule = await import("@koa/cors");
const cors = corsModule.default;
this.app.use(cors({
origin: (ctx) => {
const origin = ctx.request.headers.origin || "*";
if (this.config.cors.origins.includes("*")) {
return origin;
}
return this.config.cors.origins.includes(origin) ? origin : "";
},
allowMethods: this.config.cors.methods?.join(","),
allowHeaders: this.config.cors.headers?.join(","),
credentials: this.config.cors.credentials,
maxAge: this.config.cors.maxAge,
}));
}
// Body parsing middleware
if (this.config.bodyParser.enabled) {
const bodyParserModule = await import("koa-bodyparser");
const bodyParser = bodyParserModule.default;
this.app.use(bodyParser({
jsonLimit: this.config.bodyParser.jsonLimit,
enableTypes: ["json", "form", "text"],
}));
}
// Rate limiting middleware
if (this.config.rateLimit.enabled) {
this.app.use(this.createRateLimiter());
// Schedule periodic cleanup of expired rate limit entries
this.rateLimitCleanupInterval = setInterval(() => this.cleanupRateLimitStore(), 60000);
}
// Logging middleware
if (this.config.logging.enabled) {
this.app.use(async (ctx, next) => {
const startTime = Date.now();
logger.info(`[KoaAdapter] ${ctx.method} ${ctx.path}`, {
requestId: ctx.state.requestId,
});
await next();
logger.info(`[KoaAdapter] ${ctx.method} ${ctx.path} ${ctx.status}`, {
requestId: ctx.state.requestId,
duration: Date.now() - startTime,
});
});
}
// Mount router
this.app.use(this.router.routes());
this.app.use(this.router.allowedMethods());
this.frameworkInitialized = true;
}
/**
* Create rate limiter middleware for Koa
*/
createRateLimiter() {
return async (ctx, next) => {
// Skip rate limiting for excluded paths
if (this.config.rateLimit.skipPaths) {
for (const skipPath of this.config.rateLimit.skipPaths) {
if (ctx.path.startsWith(skipPath)) {
await next();
return;
}
}
}
const key = ctx.get("x-forwarded-for") ||
ctx.get("x-real-ip") ||
ctx.ip ||
"unknown";
const now = Date.now();
const windowMs = this.config.rateLimit.windowMs;
const maxRequests = this.config.rateLimit.maxRequests;
let record = this.rateLimitStore.get(key);
if (!record || now > record.resetAt) {
record = { count: 0, resetAt: now + windowMs };
this.rateLimitStore.set(key, record);
}
record.count++;
// Set rate limit headers
ctx.set("X-RateLimit-Limit", String(maxRequests));
ctx.set("X-RateLimit-Remaining", String(Math.max(0, maxRequests - record.count)));
ctx.set("X-RateLimit-Reset", String(Math.ceil(record.resetAt / 1000)));
if (record.count > maxRequests) {
const retryAfter = Math.ceil((record.resetAt - now) / 1000);
ctx.set("Retry-After", String(retryAfter));
ctx.status = 429;
ctx.body = {
error: {
code: "RATE_LIMIT_EXCEEDED",
message: this.config.rateLimit.message,
},
metadata: {
timestamp: new Date().toISOString(),
retryAfter,
},
};
return;
}
await next();
};
}
/**
* Periodically clean up expired rate limit entries
*/
cleanupRateLimitStore() {
const now = Date.now();
for (const [key, entry] of this.rateLimitStore.entries()) {
if (now > entry.resetAt) {
this.rateLimitStore.delete(key);
}
}
}
/**
* Override initialize to ensure async framework setup
*/
async initialize() {
// Initialize Koa asynchronously first
await this.initializeFrameworkAsync();
// Then call base class initialize
await super.initialize();
}
/**
* Register route with Koa
*/
registerFrameworkRoute(route) {
const method = route.method.toLowerCase();
this.router[method](route.path, async (ctx) => {
const requestId = ctx.state.requestId;
const startTime = Date.now();
// Create server context
const serverCtx = this.createContext({
requestId,
method: ctx.method,
path: ctx.path,
headers: ctx.headers,
query: ctx.query,
params: ctx.params,
body: ctx.request.body,
});
// Copy response headers from middleware (stored in ctx.state by middleware)
if (ctx.state.responseHeaders) {
serverCtx.responseHeaders = { ...ctx.state.responseHeaders };
}
// Emit request event
this.emit("request", {
requestId,
method: serverCtx.method,
path: serverCtx.path,
timestamp: new Date(),
});
// Handle streaming if configured
if (route.streaming?.enabled) {
return this.handleStreamingResponse(ctx, serverCtx, route);
}
// Execute handler
const result = await route.handler(serverCtx);
const duration = Date.now() - startTime;
// Check if result is an error response
if (isErrorResponse(result)) {
const statusCode = result.httpStatus ?? 500;
// Emit response event with error status
this.emit("response", {
requestId,
statusCode,
duration,
timestamp: new Date(),
});
// Apply response headers from middleware and handler
if (serverCtx.responseHeaders) {
for (const [key, value] of Object.entries(serverCtx.responseHeaders)) {
ctx.set(key, value);
}
}
// Return error response with proper status code
ctx.status = statusCode;
ctx.body = {
error: result.error,
metadata: {
...result.metadata,
requestId,
timestamp: new Date().toISOString(),
duration,
},
};
return;
}
// Emit response event
this.emit("response", {
requestId,
statusCode: 200,
duration,
timestamp: new Date(),
});
// Apply response headers from middleware and handler
if (serverCtx.responseHeaders) {
for (const [key, value] of Object.entries(serverCtx.responseHeaders)) {
ctx.set(key, value);
}
}
// Return formatted response
ctx.body = {
data: result,
metadata: {
requestId,
timestamp: new Date().toISOString(),
duration,
},
};
});
}
/**
* Handle streaming response using Server-Sent Events
*/
async handleStreamingResponse(ctx, serverCtx, route) {
// Set SSE headers
ctx.set("Content-Type", "text/event-stream");
ctx.set("Cache-Control", "no-cache");
ctx.set("Connection", "keep-alive");
ctx.set("X-Accel-Buffering", "no"); // Disable nginx buffering
ctx.status = 200;
// Use raw response for streaming
const stream = ctx.res;
try {
const result = await route.handler(serverCtx);
// Check if result is an async iterable
if (result &&
typeof result === "object" &&
Symbol.asyncIterator in result) {
for await (const chunk of result) {
stream.write(`event: message\n`);
stream.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
}
else {
// Single result, send as complete event
stream.write(`event: complete\n`);
stream.write(`data: ${JSON.stringify(result)}\n\n`);
}
// Send done event
stream.write(`event: done\n`);
stream.write(`data: \n\n`);
stream.end();
}
catch (error) {
stream.write(`event: error\n`);
stream.write(`data: ${JSON.stringify({
error: error instanceof Error ? error.message : "Stream error",
})}\n\n`);
stream.end();
}
}
/**
* Register middleware with Koa
*/
registerFrameworkMiddleware(middleware) {
this.app.use(async (ctx, next) => {
// Skip excluded paths
if (middleware.excludePaths?.some((p) => ctx.path.startsWith(p))) {
return next();
}
// Check if path matches
const paths = middleware.paths || ["/"];
const matches = paths.some((p) => ctx.path.startsWith(p) || p === "*");
if (!matches) {
return next();
}
// Initialize response headers storage in ctx.state if not present
if (!ctx.state.responseHeaders) {
ctx.state.responseHeaders = {};
}
// Create context with existing response headers from previous middleware
const serverCtx = this.createContext({
requestId: ctx.state.requestId,
method: ctx.method,
path: ctx.path,
headers: ctx.headers,
query: ctx.query,
params: ctx.params,
body: ctx.request.body,
});
// Copy existing response headers to context
serverCtx.responseHeaders = { ...ctx.state.responseHeaders };
// Execute middleware
await middleware.handler(serverCtx, async () => {
// After middleware execution, merge response headers back to ctx.state
if (serverCtx.responseHeaders) {
Object.assign(ctx.state.responseHeaders, serverCtx.responseHeaders);
}
return next();
});
// Also merge headers after handler returns (for middleware that set headers after next())
if (serverCtx.responseHeaders) {
Object.assign(ctx.state.responseHeaders, serverCtx.responseHeaders);
}
});
}
/**
* Start the Koa server
*/
async start() {
// Validate lifecycle state
this.validateLifecycleState("start", ["initialized", "stopped"]);
if (this.isRunning) {
throw new AlreadyRunningError(this.config.port, this.config.host);
}
this.lifecycleState = "starting";
const { port, host } = this.config;
return new Promise((resolve, reject) => {
try {
this.server = this.app.listen(port, host, () => {
this.isRunning = true;
this.startTime = new Date();
this.lifecycleState = "running";
// Track connections for graceful shutdown
this.server?.on("connection", (socket) => {
const connectionId = `conn-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
this.sockets.add(socket);
this.trackConnection(connectionId, socket);
socket.on("close", () => {
this.sockets.delete(socket);
this.untrackConnection(connectionId);
});
});
logger.info(`[KoaAdapter] Server started on ${host}:${port}`);
this.emit("started", {
port,
host,
timestamp: this.startTime,
});
resolve();
});
this.server?.on("error", (error) => {
this.lifecycleState = "error";
reject(error);
});
}
catch (error) {
this.lifecycleState = "error";
reject(error);
}
});
}
/**
* Stop the Koa server with graceful shutdown
*/
async stop() {
if (!this.isRunning || !this.server) {
return; // Already stopped, return gracefully (idempotent)
}
const uptime = this.startTime ? Date.now() - this.startTime.getTime() : 0;
try {
// Use graceful shutdown from base class
await this.gracefulShutdown();
// Clean up rate limit cleanup interval
if (this.rateLimitCleanupInterval) {
clearInterval(this.rateLimitCleanupInterval);
this.rateLimitCleanupInterval = undefined;
}
logger.info("[KoaAdapter] Server stopped", { uptime });
this.emit("stopped", {
uptime,
timestamp: new Date(),
});
// Reset state for restart capability
this.resetServerState();
this.server = undefined;
this.sockets.clear();
this.rateLimitStore.clear();
this.frameworkInitialized = false;
}
catch (error) {
const wrappedError = wrapError(error);
throw new ServerStopError(wrappedError.message, wrappedError);
}
}
// ============================================
// Lifecycle Methods (Framework-Specific)
// ============================================
/**
* Stop accepting new connections
*/
async stopAcceptingConnections() {
// For Koa/Node.js HTTP server, close() stops accepting new connections
logger.debug("[KoaAdapter] Stopping acceptance of new connections");
}
/**
* Close the underlying server
*/
async closeServer() {
return new Promise((resolve, reject) => {
if (!this.server) {
resolve();
return;
}
this.server.close((err) => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
}
/**
* Force close all active connections
*/
async forceCloseConnections() {
logger.info("[KoaAdapter] Force closing connections", {
count: this.sockets.size,
});
for (const socket of this.sockets) {
socket.destroy();
}
this.sockets.clear();
this.activeConnections.clear();
}
/**
* Get the Koa app instance
*/
getFrameworkInstance() {
return this.app;
}
}