@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
297 lines • 11.6 kB
JavaScript
/**
* Common Middleware Components
* Utility middleware for common server operations
*/
import { getMetricsAggregator, SpanSerializer, SpanStatus, SpanType, } from "../../observability/index.js";
import { SpanStatusCode } from "@opentelemetry/api";
import { tracers } from "../../telemetry/tracers.js";
import { logger } from "../../utils/logger.js";
/**
* Create request timing middleware
* Adds timing information to responses
*
* @example
* ```typescript
* server.registerMiddleware(createTimingMiddleware());
* ```
*/
export function createTimingMiddleware() {
return {
name: "timing",
order: 0, // Run first
handler: async (ctx, next) => {
return tracers.middleware.startActiveSpan("neurolink.middleware.timing", async (otelSpan) => {
try {
const startTime = Date.now();
const startHrTime = process.hrtime.bigint();
// Store start time in metadata
ctx.metadata.requestStartTime = startTime;
const span = SpanSerializer.createSpan(SpanType.SERVER_REQUEST, "server.middleware.timing", {
"server.operation": "middleware",
"server.middleware": "timing",
});
try {
const result = await next();
// Calculate duration
const endHrTime = process.hrtime.bigint();
const durationNs = Number(endHrTime - startHrTime);
const durationMs = durationNs / 1_000_000;
// Add timing headers to responseHeaders (adapters read from here)
ctx.responseHeaders = ctx.responseHeaders || {};
ctx.responseHeaders["X-Response-Time"] =
`${durationMs.toFixed(2)}ms`;
ctx.responseHeaders["Server-Timing"] =
`total;dur=${durationMs.toFixed(2)}`;
span.durationMs = Date.now() - startTime;
const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
getMetricsAggregator().recordSpan(endedSpan);
return result;
}
catch (error) {
// Propagate error to OTel span so traces show ERROR status
otelSpan.recordException(error instanceof Error ? error : new Error(String(error)));
otelSpan.setStatus({
code: SpanStatusCode.ERROR,
message: error instanceof Error ? error.message : String(error),
});
span.durationMs = Date.now() - startTime;
const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
endedSpan.statusMessage =
error instanceof Error ? error.message : String(error);
getMetricsAggregator().recordSpan(endedSpan);
throw error;
}
}
finally {
otelSpan.end();
}
}); // end startActiveSpan
},
};
}
/**
* Create request ID middleware
* Ensures every request has a unique ID
*
* @example
* ```typescript
* server.registerMiddleware(createRequestIdMiddleware());
* ```
*/
export function createRequestIdMiddleware(options) {
const { headerName = "x-request-id", prefix = "req", generator = () => `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`, } = options ?? {};
return {
name: "request-id",
order: 0, // Run first
handler: async (ctx, next) => {
// Use existing ID or generate new one
ctx.requestId =
ctx.headers[headerName.toLowerCase()] || ctx.requestId || generator();
// Add request ID header to responseHeaders (adapters read from here)
ctx.responseHeaders = ctx.responseHeaders || {};
ctx.responseHeaders["X-Request-ID"] = ctx.requestId;
return next();
},
};
}
/**
* Create error handling middleware
* Catches errors and formats them consistently
*
* @example
* ```typescript
* server.registerMiddleware(createErrorHandlingMiddleware({
* includeStack: process.env.NODE_ENV === 'development',
* }));
* ```
*/
export function createErrorHandlingMiddleware(options) {
const { includeStack = false, onError, logErrors = true } = options ?? {};
return {
name: "error-handling",
order: 1, // Run very early to catch all errors
handler: async (ctx, next) => {
const span = SpanSerializer.createSpan(SpanType.SERVER_REQUEST, "server.middleware.errorHandling", {
"server.operation": "middleware",
"server.middleware": "error-handling",
});
const middlewareStartTime = Date.now();
try {
const result = await next();
span.durationMs = Date.now() - middlewareStartTime;
const endedSpan = SpanSerializer.endSpan(span, SpanStatus.OK);
getMetricsAggregator().recordSpan(endedSpan);
return result;
}
catch (error) {
const err = error;
if (logErrors) {
logger.error("[ErrorMiddleware]", {
requestId: ctx.requestId,
error: err.message,
stack: err.stack,
});
}
span.durationMs = Date.now() - middlewareStartTime;
const endedSpan = SpanSerializer.endSpan(span, SpanStatus.ERROR);
endedSpan.statusMessage = err.message;
getMetricsAggregator().recordSpan(endedSpan);
// Use custom handler if provided
if (onError) {
return onError(err, ctx);
}
// Default error response
const statusCode = err.statusCode || 500;
const errorResponse = {
error: {
code: err.code || `HTTP_${statusCode}`,
message: statusCode === 500 ? "Internal server error" : err.message,
},
metadata: {
requestId: ctx.requestId,
timestamp: new Date().toISOString(),
},
};
// Include stack in development
if (includeStack && err.stack) {
errorResponse.error.stack = err.stack;
}
// Store error response in metadata
ctx.metadata.errorResponse = errorResponse;
ctx.metadata.errorStatusCode = statusCode;
// Re-throw for adapter to handle
throw error;
}
},
};
}
/**
* Create security headers middleware
* Adds common security headers to responses
*
* @example
* ```typescript
* server.registerMiddleware(createSecurityHeadersMiddleware());
* ```
*/
export function createSecurityHeadersMiddleware(options) {
const { contentSecurityPolicy, frameOptions = "DENY", contentTypeOptions = "nosniff", hstsMaxAge = 31536000, referrerPolicy = "strict-origin-when-cross-origin", customHeaders = {}, } = options ?? {};
// Build headers object
const securityHeaders = {};
if (contentSecurityPolicy) {
securityHeaders["Content-Security-Policy"] = contentSecurityPolicy;
}
if (frameOptions) {
securityHeaders["X-Frame-Options"] = frameOptions;
}
if (contentTypeOptions) {
securityHeaders["X-Content-Type-Options"] = contentTypeOptions;
}
if (hstsMaxAge !== false) {
securityHeaders["Strict-Transport-Security"] =
`max-age=${hstsMaxAge}; includeSubDomains`;
}
if (referrerPolicy) {
securityHeaders["Referrer-Policy"] = referrerPolicy;
}
// Add X-XSS-Protection for older browsers
securityHeaders["X-XSS-Protection"] = "1; mode=block";
// Add custom headers
Object.assign(securityHeaders, customHeaders);
return {
name: "security-headers",
order: 2,
handler: async (ctx, next) => {
// Add security headers to responseHeaders (adapters read from here)
ctx.responseHeaders = ctx.responseHeaders || {};
Object.assign(ctx.responseHeaders, securityHeaders);
return next();
},
};
}
/**
* Create request logging middleware
* Logs request and response information
*
* @example
* ```typescript
* server.registerMiddleware(createLoggingMiddleware({
* logBody: process.env.NODE_ENV === 'development',
* }));
* ```
*/
export function createLoggingMiddleware(options) {
const { logBody = false, logResponse = false, logger = console, skipPaths = ["/health", "/ready", "/metrics"], } = options ?? {};
return {
name: "logging",
order: 3,
excludePaths: skipPaths,
handler: async (ctx, next) => {
const startTime = Date.now();
// Log request
const requestLog = {
requestId: ctx.requestId,
method: ctx.method,
path: ctx.path,
query: Object.keys(ctx.query).length > 0 ? ctx.query : undefined,
};
if (logBody && ctx.body) {
requestLog.body = ctx.body;
}
logger.info(`[Request] ${ctx.method} ${ctx.path}`, requestLog);
try {
const result = await next();
const duration = Date.now() - startTime;
// Log response
const responseLog = {
requestId: ctx.requestId,
duration: `${duration}ms`,
status: 200,
};
if (logResponse && result) {
responseLog.response = result;
}
logger.info(`[Response] ${ctx.method} ${ctx.path}`, responseLog);
return result;
}
catch (error) {
const duration = Date.now() - startTime;
const err = error;
logger.error(`[Error] ${ctx.method} ${ctx.path}`, {
requestId: ctx.requestId,
duration: `${duration}ms`,
error: err.message,
status: err.statusCode || 500,
});
throw error;
}
},
};
}
/**
* Create compression preference middleware
* Signals compression preference to adapters
*/
export function createCompressionMiddleware(options) {
const { threshold = 1024, contentTypes = [
"text/",
"application/json",
"application/javascript",
"application/xml",
], } = options ?? {};
return {
name: "compression",
order: 5,
handler: async (ctx, next) => {
// Store compression preferences in metadata
ctx.metadata.compression = {
enabled: true,
threshold,
contentTypes,
acceptEncoding: ctx.headers["accept-encoding"] || "",
};
return next();
},
};
}
//# sourceMappingURL=common.js.map