@gati-framework/runtime
Version:
Gati runtime execution engine for running handler-based applications
187 lines • 5.93 kB
JavaScript
/**
* @module runtime/handler-worker
* @description Handler Worker execution engine for stateless handler invocation
*/
import { HookOrchestrator } from './hook-orchestrator.js';
import { createLocalContext, cleanupLocalContext } from './local-context.js';
/**
* Handler Worker - Stateless execution engine for handlers
*
* @example
* ```typescript
* const worker = new HandlerWorker(gctx);
*
* worker.registerHandler('getUser', async (req, res, gctx, lctx) => {
* const user = await gctx.modules['db'].users.findById(req.params.id);
* res.json({ user });
* });
*
* await worker.executeHandler('getUser', req, res);
* ```
*/
export class HandlerWorker {
handlers = new Map();
orchestrator;
gctx;
config;
startTime;
requestCount = 0;
errorCount = 0;
constructor(gctx, config = {}) {
this.gctx = gctx;
this.config = {
defaultTimeout: config.defaultTimeout ?? 30000,
enableMetrics: config.enableMetrics ?? true,
enableHealthCheck: config.enableHealthCheck ?? true,
orchestratorConfig: config.orchestratorConfig ?? {},
};
this.orchestrator = new HookOrchestrator(this.config.orchestratorConfig);
this.startTime = Date.now();
}
/**
* Register a handler
*
* @param id - Unique handler identifier
* @param handler - Handler function with signature (req, res, gctx, lctx)
* @throws {Error} If handler ID is invalid, handler is not a function, or has wrong parameter count
*
* @example
* ```typescript
* worker.registerHandler('getUser', (req, res, gctx, lctx) => {
* res.json({ id: req.params.id });
* });
* ```
*/
registerHandler(id, handler) {
if (!id || typeof id !== 'string') {
throw new Error('Handler ID must be a non-empty string');
}
if (typeof handler !== 'function') {
throw new Error('Handler must be a function');
}
if (handler.length !== 4) {
throw new Error('Handler must accept exactly 4 parameters (req, res, gctx, lctx)');
}
if (this.handlers.has(id)) {
throw new Error(`Handler with ID "${id}" already registered`);
}
this.handlers.set(id, handler);
}
/**
* Unregister a handler
*
* @param id - Handler identifier
* @returns true if handler was removed, false if not found
*/
unregisterHandler(id) {
return this.handlers.delete(id);
}
/**
* Get number of registered handlers
*
* @returns Handler count
*/
getHandlerCount() {
return this.handlers.size;
}
/**
* Execute a handler with stateless execution
*
* @param handlerId - Handler identifier
* @param req - Request object
* @param res - Response object
* @throws {Error} If handler not found or execution fails
*
* @example
* ```typescript
* await worker.executeHandler('getUser', req, res);
* ```
*/
async executeHandler(handlerId, req, res) {
const handler = this.handlers.get(handlerId);
if (!handler) {
throw new Error(`Handler "${handlerId}" not found`);
}
if (this.config.enableMetrics) {
this.requestCount++;
}
const lctx = createLocalContext({
meta: {
timestamp: Date.now(),
instanceId: this.gctx.instance.id,
region: this.gctx.instance.region,
method: req.method,
path: req.path,
},
});
try {
await this.orchestrator.executeBefore(lctx, this.gctx);
await Promise.resolve(handler(req, res, this.gctx, lctx));
await this.orchestrator.executeAfter(lctx, this.gctx);
}
catch (error) {
if (this.config.enableMetrics) {
this.errorCount++;
}
const err = error instanceof Error ? error : new Error(String(error));
await this.orchestrator.executeCatch(err, lctx, this.gctx);
throw error;
}
finally {
await cleanupLocalContext(lctx);
}
}
/**
* Get health status
*
* @returns Health status with checks
*
* @example
* ```typescript
* const status = worker.getHealthStatus();
* console.log(status.status); // 'healthy' | 'degraded' | 'unhealthy'
* ```
*/
getHealthStatus() {
const uptime = Date.now() - this.startTime;
const errorRate = this.requestCount > 0
? (this.errorCount / this.requestCount) * 100
: 0;
const checks = {
handlers: {
status: this.handlers.size > 0 ? 'pass' : 'fail',
message: `${this.handlers.size} handler(s) registered`,
},
globalContext: {
status: 'pass',
message: 'Global context available',
},
uptime: {
status: 'pass',
message: `${uptime}ms`,
duration: uptime,
},
requests: {
status: 'pass',
message: `${this.requestCount} requests processed`,
},
errors: {
status: errorRate > 50 ? 'fail' : errorRate > 10 ? 'warn' : 'pass',
message: `${this.errorCount} errors (${errorRate.toFixed(2)}% error rate)`,
},
};
let status = 'healthy';
if (this.handlers.size === 0 || errorRate > 50) {
status = 'unhealthy';
}
else if (errorRate > 10) {
status = 'degraded';
}
return {
status,
checks,
timestamp: Date.now(),
};
}
}
//# sourceMappingURL=handler-worker.js.map