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