durabull
Version:
A durable workflow engine built on top of BullMQ and Redis
57 lines (47 loc) • 1.7 kB
text/typescript
import { AsyncLocalStorage } from 'async_hooks';
import { Workflow } from '../Workflow';
import { WorkflowRecord, HistoryEvent } from './history';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface WorkflowExecutionContext {
workflowId: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
workflow: Workflow<any, any>;
record: WorkflowRecord;
history?: { events: HistoryEvent[]; cursor: number };
isResume: boolean;
clockCursor: number;
timerCursor: number;
sideEffectCursor: number;
activityCursor: number;
childWorkflowCursor: number;
// Index for O(1) event lookups
eventIndex?: Map<string, HistoryEvent>;
}
const workflowContext = new AsyncLocalStorage<WorkflowExecutionContext>();
export function getWorkflowContext(): WorkflowExecutionContext | undefined {
return workflowContext.getStore();
}
export function runInWorkflowContext<T>(ctx: WorkflowExecutionContext, fn: () => T): T {
return workflowContext.run(ctx, fn);
}
export const getVirtualTimestamp = (workflowId?: string): number => {
const ctx = workflowContext.getStore();
if (!ctx || !workflowId) {
return Date.now();
}
// Use clock cursor for deterministic replay
const cursor = ctx.clockCursor ?? 0;
const existing = ctx.record.clockEvents?.[cursor];
if (typeof existing === 'number' && !Number.isNaN(existing)) {
ctx.clockCursor = cursor + 1;
return existing;
}
// First execution - record timestamp
const timestamp = Date.now();
if (!ctx.record.clockEvents) {
ctx.record.clockEvents = [];
}
ctx.record.clockEvents[cursor] = timestamp;
ctx.clockCursor = cursor + 1;
return timestamp;
};