UNPKG

@gati-framework/runtime

Version:

Gati runtime execution engine for running handler-based applications

208 lines 6.9 kB
/** * @module runtime/local-context * @description Local context manager for request-scoped data in Gati framework */ import { RequestPhase } from './types/context.js'; import { RequestLifecycleManager } from './lifecycle-manager.js'; import { ExecutionContextResolver } from './timescape/resolver.js'; import { VersionRegistry } from './timescape/registry.js'; /** * Generates a unique request ID * * @returns Unique request ID */ function generateRequestId() { return `req_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`; } /** * Generates a unique client ID * * @returns Unique client ID */ function generateClientId() { return `client_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`; } /** * Generates a unique trace ID for distributed tracing * * @returns Unique trace ID */ function generateTraceId() { return `trace_${Date.now()}_${Math.random().toString(36).slice(2, 16)}`; } /** * Creates a local context instance for a single request * * @param options - Configuration options for the local context * @param wsCoordinator - WebSocket coordinator for event handling * @returns LocalContext instance * * @example * ```typescript * const lctx = createLocalContext({ * requestId: 'custom-id', * state: { userId: '123' }, * }); * ``` */ export function createLocalContext(options = {}, wsCoordinator, registry) { const requestLifecycle = new RequestLifecycleManager(); const lifecycleSymbol = Symbol.for('gati:requestLifecycle'); // Use provided registry or create a new empty one (for tests/fallback) const versionRegistry = registry || new VersionRegistry(); const resolver = new ExecutionContextResolver(versionRegistry); const lctx = { requestId: options.requestId || generateRequestId(), timestamp: Date.now(), traceId: options.traceId || generateTraceId(), parentSpanId: options.parentSpanId, clientId: options.clientId || generateClientId(), refs: options.refs || {}, client: options.client || { ip: 'unknown', userAgent: 'unknown', region: 'unknown', }, meta: { timestamp: Date.now(), instanceId: 'unknown', region: 'unknown', method: 'GET', path: '/', phase: RequestPhase.RECEIVED, startTime: Date.now(), ...options.meta, }, state: options.state || {}, websocket: { waitForEvent: async (eventType, timeout) => { if (!wsCoordinator) { throw new Error('WebSocket coordinator not available'); } return wsCoordinator.waitForEvent(lctx.requestId, eventType, timeout); }, emitEvent: (eventType, data) => { if (!wsCoordinator) { throw new Error('WebSocket coordinator not available'); } wsCoordinator.emitEvent({ type: eventType, requestId: lctx.requestId, data, timestamp: Date.now(), }); }, }, lifecycle: { onCleanup: ((...args) => { requestLifecycle.onCleanup(args[0], args[1]); }), onTimeout: (fn) => { requestLifecycle.onTimeout(fn); }, onError: (fn) => { requestLifecycle.onError(fn); }, onPhaseChange: (fn) => { requestLifecycle.onPhaseChange(fn); }, setPhase: (phase) => { requestLifecycle.setPhase(phase); lctx.meta.phase = phase; }, executeCleanup: () => requestLifecycle.executeCleanup(), isCleaningUp: () => requestLifecycle.isCleaningUp(), isTimedOut: () => requestLifecycle.isTimedOut(), }, timescape: { resolver, resolvedState: undefined, // Will be populated by resolver if needed }, snapshot: { create: () => { // Capture current state const state = { ...lctx.state }; // TODO: Capture outstanding promises (requires promise tracking) const outstandingPromises = []; // TODO: Capture last hook index (requires hook orchestrator integration) const lastHookIndex = 0; return { requestId: lctx.requestId, timestamp: Date.now(), state, outstandingPromises, lastHookIndex, handlerVersion: undefined, // Will be set by handler engine phase: lctx.meta.phase, traceId: lctx.traceId, clientId: lctx.clientId, }; }, restore: (token) => { // Restore state lctx.state = { ...token.state }; // Restore metadata lctx.meta.phase = token.phase; // Note: Outstanding promises and hook index restoration // would require more complex promise tracking and hook orchestrator integration // This is a simplified implementation for the MVP }, }, }; // Store request lifecycle manager for later access lctx[lifecycleSymbol] = requestLifecycle; return lctx; } /** * Cleans up the local context, calling all registered cleanup hooks * * @param lctx - Local context instance * @param wsCoordinator - WebSocket coordinator for cleanup * @returns Promise that resolves when all cleanup hooks complete * * @example * ```typescript * await cleanupLocalContext(lctx); * ``` */ export async function cleanupLocalContext(lctx, wsCoordinator) { // Clean up WebSocket listeners if (wsCoordinator) { wsCoordinator.cleanup(lctx.requestId); } // Execute cleanup through lifecycle manager await lctx.lifecycle.executeCleanup(); // Clear state to prevent memory leaks lctx.state = {}; } /** * Sets a value in the local context state * * @param lctx - Local context instance * @param key - State key * @param value - State value * * @example * ```typescript * setState(lctx, 'userId', '123'); * ``` */ export function setState(lctx, key, value) { lctx.state[key] = value; } /** * Gets a value from the local context state * * @param lctx - Local context instance * @param key - State key * @returns State value or undefined if not found * * @example * ```typescript * const userId = getState<string>(lctx, 'userId'); * ``` */ export function getState(lctx, key) { return lctx.state[key]; } //# sourceMappingURL=local-context.js.map