@gati-framework/runtime
Version:
Gati runtime execution engine for running handler-based applications
121 lines • 3.93 kB
JavaScript
/**
* @module runtime/handler-engine
* @description Handler execution engine with error handling
*/
import { HandlerError } from './types/index.js';
import { logger } from './logger.js';
/**
* Default handler execution timeout (30 seconds)
*/
const DEFAULT_TIMEOUT = 30_000;
/**
* Execute a handler function with the provided context
*
* @param handler - The handler function to execute
* @param req - HTTP request object
* @param res - HTTP response object
* @param gctx - Global context (shared resources)
* @param lctx - Local context (request-scoped data)
* @param options - Execution options
* @returns Promise that resolves when handler completes
*
* @throws {HandlerError} If handler validation fails
* @throws {Error} If handler execution times out
* @throws {Error} If handler throws an error (when catchErrors is false)
*
* @example
* ```typescript
* const handler: Handler = (req, res) => res.json({ ok: true });
* await executeHandler(handler, req, res, gctx, lctx);
* ```
*/
export async function executeHandler(handler, req, res, gctx, lctx, options) {
// Validate handler
if (!isValidHandler(handler)) {
throw new HandlerError('Invalid handler: must be a function', 500);
}
// Extract options
const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
const catchErrors = options?.catchErrors ?? true;
try {
// Resolve Timescape version context
if (lctx.timescape) {
lctx.timescape.resolvedState = lctx.timescape.resolver.resolve({
requestId: lctx.requestId,
timestamp: lctx.timestamp,
actor: lctx.refs.userId || 'anonymous',
});
}
// Execute handler with timeout
await executeWithTimeout(() => handler(req, res, gctx, lctx), timeout, `Handler execution timed out after ${timeout}ms`);
}
catch (error) {
if (!catchErrors) {
throw error;
}
// Handle errors gracefully
handleExecutionError(error, res);
}
}
/**
* Validate that a handler is a function
*
* @param handler - The value to validate
* @returns true if handler is a valid function
*/
function isValidHandler(handler) {
return typeof handler === 'function';
}
/**
* Execute a function with a timeout
*
* @param fn - Function to execute
* @param timeoutMs - Timeout in milliseconds
* @param timeoutMessage - Error message if timeout occurs
* @returns Promise that resolves with function result or rejects on timeout
*/
async function executeWithTimeout(fn, timeoutMs, timeoutMessage) {
return Promise.race([
Promise.resolve(fn()),
new Promise((_, reject) => setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs)),
]);
}
/**
* Handle handler execution errors
*
* @param error - The error that occurred
* @param res - Response object to send error response
*/
function handleExecutionError(error, res) {
// Don't send error response if response already sent
if (res.isSent()) {
// Log error internally
logger.error({ error }, 'Handler error after response sent');
return;
}
const isDevelopment = process.env['NODE_ENV'] !== 'production';
// Handle HandlerError
if (error instanceof HandlerError) {
res.status(error.statusCode).json({
error: error.message,
...(isDevelopment && error.context ? { context: error.context } : {}),
});
return;
}
// Handle generic errors
if (error instanceof Error) {
res.status(500).json({
error: 'Internal server error',
...(isDevelopment && {
message: error.message,
stack: error.stack,
}),
});
return;
}
// Handle unknown errors
res.status(500).json({
error: 'Internal server error',
});
}
//# sourceMappingURL=handler-engine.js.map