UNPKG

@gati-framework/runtime

Version:

Gati runtime execution engine for running handler-based applications

183 lines 5.95 kB
/** * @module runtime/request-replayer * @description Replays requests from snapshots */ import { computeDiff } from './diff-engine.js'; /** * Replays requests from stored traces */ export class RequestReplayer { storage; config; constructor(storage, config = {}) { this.storage = storage; this.config = config; this.config = { maxDuration: config.maxDuration ?? 30000, // 30 seconds enableComparison: config.enableComparison ?? true, }; } /** * Replay a request from a trace */ async replay(options) { const originalTrace = await this.storage.getTrace(options.traceId); if (!originalTrace) { throw new Error(`Trace not found: ${options.traceId}`); } const startTime = Date.now(); const newTraceId = `${options.traceId}_replay_${startTime}`; try { // Get snapshot to replay from const snapshot = this.getSnapshotForStage(originalTrace, options.fromStage); if (!snapshot) { throw new Error(`No snapshot found for stage: ${options.fromStage || 'start'}`); } // Prepare request (with modifications if provided) const request = this.prepareRequest(originalTrace.request, options.modifiedRequest); // Execute replay (simplified - actual execution would go through pipeline) const response = await this.executeReplay(request, snapshot); // Compare with original if requested const diff = options.compare && this.config.enableComparison ? this.compareResults(originalTrace, response) : undefined; return { traceId: newTraceId, originalTraceId: options.traceId, timestamp: startTime, response, diff, }; } catch (error) { return { traceId: newTraceId, originalTraceId: options.traceId, timestamp: startTime, error: { message: error.message, stack: error.stack, }, }; } } /** * Get snapshot for a specific stage */ getSnapshotForStage(trace, stage) { if (!stage) { // Return earliest snapshot const firstStage = trace.stages[0]; return firstStage ? trace.snapshots[firstStage.snapshotId] : null; } // Find stage and return its snapshot const targetStage = this.findStage(trace.stages, stage); return targetStage ? trace.snapshots[targetStage.snapshotId] : null; } /** * Find stage by name (recursive) */ findStage(stages, name) { for (const stage of stages) { if (stage.name === name) return stage; if (stage.children) { const found = this.findStage(stage.children, name); if (found) return found; } } return null; } /** * Prepare request with modifications */ prepareRequest(original, modifications) { if (!modifications) return original; return { ...original, ...modifications, headers: { ...original.headers, ...modifications.headers }, query: { ...original.query, ...modifications.query }, }; } /** * Execute replay (simplified) */ async executeReplay(request, snapshot) { // In a real implementation, this would: // 1. Restore LocalContext from snapshot // 2. Re-execute handler with restored context // 3. Capture new response // For now, return a mock response return { statusCode: 200, headers: {}, body: { replayed: true, timestamp: Date.now() }, }; } /** * Compare replay result with original */ compareResults(originalTrace, replayResponse) { if (!originalTrace.response) return undefined; // Create mock snapshots for comparison const originalSnapshot = { requestId: originalTrace.id, timestamp: originalTrace.timestamp, state: { response: originalTrace.response }, outstandingPromises: [], lastHookIndex: 0, phase: 'completed', traceId: originalTrace.id, clientId: 'original', }; const replaySnapshot = { ...originalSnapshot, state: { response: replayResponse }, clientId: 'replay', }; return computeDiff(originalSnapshot, replaySnapshot); } /** * Validate replay is possible */ async canReplay(traceId, fromStage) { const trace = await this.storage.getTrace(traceId); if (!trace) return false; const snapshot = this.getSnapshotForStage(trace, fromStage); return snapshot !== null; } /** * Get available replay stages for a trace */ async getReplayStages(traceId) { const trace = await this.storage.getTrace(traceId); if (!trace) return []; const stages = []; this.collectStageNames(trace.stages, stages); return stages; } /** * Collect stage names recursively */ collectStageNames(stages, result) { for (const stage of stages) { result.push(stage.name); if (stage.children) { this.collectStageNames(stage.children, result); } } } } /** * Create a request replayer instance */ export function createRequestReplayer(storage, config) { return new RequestReplayer(storage, config); } //# sourceMappingURL=request-replayer.js.map