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