UNPKG

@gati-framework/runtime

Version:

Gati runtime execution engine for running handler-based applications

231 lines 7.69 kB
/** * @module runtime/global-context * @description Global context implementation for cross-request shared state */ import { createModuleLoader } from './module-loader.js'; import { LifecycleManager } from './lifecycle-manager.js'; import { VersionRegistry } from './timescape/registry.js'; import { SQLiteTimelineStore, JSONTimelineStore } from './timescape/timeline-store.js'; import { createModuleClient, getGlobalConnectionPool, } from './module-rpc.js'; import { RuntimeMetricsClient } from './metrics-client.js'; import { createMetricsClient } from './observability-factory.js'; /** * Creates a global context instance * * @param options - Configuration options for the global context * @returns GlobalContext instance * * @example * ```typescript * const gctx = createGlobalContext({ * modules: { db: databaseModule }, * config: { port: 3000 }, * }); * ``` */ export function createGlobalContext(options = {}) { const moduleLoader = options.moduleLoader || createModuleLoader(); const lifecycleManager = new LifecycleManager(options.coordinator); const metricsClient = options.metricsClient || createMetricsClient(options.observability) || new RuntimeMetricsClient(); const moduleLoaderSymbol = Symbol.for('gati:moduleLoader'); const lifecycleSymbol = Symbol.for('gati:lifecycle'); const metricsSymbol = Symbol.for('gati:metrics'); const gctx = { instance: { id: options.instance?.id || 'unknown', region: options.instance?.region || 'unknown', zone: options.instance?.zone || 'unknown', startedAt: Date.now(), }, modules: options.modules || {}, services: options.services || {}, config: options.config || {}, state: options.state || {}, lifecycle: { onStartup: ((...args) => { lifecycleManager.onStartup(args[0], args[1], args[2]); }), onHealthCheck: (name, fn) => { lifecycleManager.onHealthCheck(name, fn); }, onShutdown: ((...args) => { lifecycleManager.onShutdown(args[0], args[1], args[2]); }), onPreShutdown: ((...args) => { lifecycleManager.onPreShutdown(args[0], args[1]); }), onConfigReload: (name, fn) => { lifecycleManager.onConfigReload(name, fn); }, onMemoryPressure: (name, fn) => { lifecycleManager.onMemoryPressure(name, fn); }, onCircuitBreakerChange: (name, fn) => { lifecycleManager.onCircuitBreakerChange(name, fn); }, executeStartup: () => lifecycleManager.executeStartup(), executeHealthChecks: () => lifecycleManager.executeHealthChecks(), executeShutdown: () => lifecycleManager.executeShutdown(), isShuttingDown: () => lifecycleManager.isShuttingDown(), coordinator: options.coordinator, }, metrics: metricsClient, timescape: { registry: new VersionRegistry(), timeline: (() => { try { return new SQLiteTimelineStore('.gati/timeline.db'); } catch (e) { console.warn('SQLiteTimelineStore failed to initialize, falling back to JSONTimelineStore:', e); return new JSONTimelineStore('.gati/timeline.json'); } })(), }, }; // Store module loader, lifecycle manager, and metrics client for later access gctx[moduleLoaderSymbol] = moduleLoader; gctx[lifecycleSymbol] = lifecycleManager; gctx[metricsSymbol] = metricsClient; return gctx; } /** * Registers a module in the global context * * @param gctx - Global context instance * @param name - Module name * @param module - Module instance * @param options - Optional RPC options * @throws {Error} If module with the same name already exists * * @example * ```typescript * registerModule(gctx, 'db', databaseModule); * ``` */ export function registerModule(gctx, name, module, options) { if (name in gctx.modules) { throw new Error(`Module "${name}" is already registered`); } // Wrap module with RPC client if enabled const enableRPC = options?.enableRPC ?? true; if (enableRPC && typeof module === 'object' && module !== null) { const pool = options?.connectionPool || getGlobalConnectionPool(); gctx.modules[name] = createModuleClient(name, module, pool, options?.rpcOptions); } else { gctx.modules[name] = module; } } /** * Retrieves a module from the global context * * @param gctx - Global context instance * @param name - Module name * @returns Module instance or undefined if not found * * @example * ```typescript * const db = getModule<DatabaseModule>(gctx, 'db'); * ``` */ export function getModule(gctx, name) { return gctx.modules[name]; } /** * Shuts down the global context, calling all registered shutdown hooks * * @param gctx - Global context instance * @returns Promise that resolves when all shutdown hooks complete * * @example * ```typescript * await shutdownGlobalContext(gctx); * ``` */ export async function shutdownGlobalContext(gctx) { // Execute shutdown hooks via lifecycle manager await gctx.lifecycle.executeShutdown(); // Shutdown module loader const moduleLoaderSymbol = Symbol.for('gati:moduleLoader'); const moduleLoader = gctx[moduleLoaderSymbol]; if (moduleLoader) { await moduleLoader.shutdownAll(); } } /** * Get the module loader from global context * * @param gctx - Global context instance * @returns ModuleLoader instance * * @example * ```typescript * const loader = getModuleLoader(gctx); * await loader.register(myModule, gctx); * ``` */ export function getModuleLoader(gctx) { const moduleLoaderSymbol = Symbol.for('gati:moduleLoader'); const moduleLoader = gctx[moduleLoaderSymbol]; if (!moduleLoader) { throw new Error('Module loader not found in global context'); } return moduleLoader; } /** * Wrap all modules in global context with RPC clients * * @param gctx - Global context instance * @param options - Optional RPC options * * @example * ```typescript * wrapModulesWithRPC(gctx, { timeout: 10000 }); * ``` */ export function wrapModulesWithRPC(gctx, options) { const pool = options?.connectionPool || getGlobalConnectionPool(); for (const [name, module] of Object.entries(gctx.modules)) { if (typeof module === 'object' && module !== null) { gctx.modules[name] = createModuleClient(name, module, pool, options?.rpcOptions); } } } /** * Get the connection pool from global context * * @param gctx - Global context instance * @returns ConnectionPool instance * * @example * ```typescript * const pool = getConnectionPool(gctx); * const stats = pool.getStatistics(); * ``` */ export function getConnectionPool(gctx) { const poolSymbol = Symbol.for('gati:connectionPool'); let pool = gctx[poolSymbol]; if (!pool) { pool = getGlobalConnectionPool(); gctx[poolSymbol] = pool; } return pool; } /** * Get the metrics client from global context * * @param gctx - Global context instance * @returns MetricsClient instance * * @example * ```typescript * const metrics = getMetricsClient(gctx); * metrics.incrementCounter('requests_total', { method: 'GET' }); * ``` */ export function getMetricsClient(gctx) { return gctx.metrics; } //# sourceMappingURL=global-context.js.map