@gati-framework/runtime
Version:
Gati runtime execution engine for running handler-based applications
208 lines • 6.9 kB
JavaScript
/**
* @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