autotel
Version:
Write Once, Observe Anywhere
226 lines (222 loc) • 7.37 kB
TypeScript
import { Attributes, SpanContext } from '@opentelemetry/api';
import { T as TraceContext } from './trace-context-DbGKd1Rn.js';
/**
* Workflow and Saga tracing helpers
*
* Provides specialized tracing for multi-step workflows and sagas with
* automatic step linking, compensation tracking, and workflow correlation.
*
* @example Simple workflow
* ```typescript
* import { traceWorkflow, traceStep } from 'autotel/workflow';
*
* export const processOrder = traceWorkflow({
* name: 'OrderFulfillment',
* workflowId: (order) => order.id,
* })(ctx => async (order: Order) => {
* await validateOrder(order);
* await chargePayment(order);
* await shipOrder(order);
* });
* ```
*
* @example Saga with compensation
* ```typescript
* import { traceWorkflow, traceStep } from 'autotel/workflow';
*
* export const orderSaga = traceWorkflow({
* name: 'OrderSaga',
* workflowId: () => generateUUID(),
* })(ctx => async (order: Order) => {
*
* const reserveStep = traceStep({
* name: 'ReserveInventory',
* compensate: async () => {
* await releaseInventory(order.items);
* },
* })(async () => {
* await inventoryService.reserve(order.items);
* });
* await reserveStep();
*
* const paymentStep = traceStep({
* name: 'ProcessPayment',
* linkToPrevious: true,
* compensate: async () => {
* await refundPayment(order.paymentId);
* },
* })(async () => {
* await paymentService.charge(order);
* });
* await paymentStep();
* });
* ```
*
* @module
*/
/**
* Workflow status
*/
type WorkflowStatus = 'pending' | 'running' | 'completed' | 'failed' | 'compensating' | 'compensated' | 'compensation_failed';
/**
* Step status
*/
type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'compensated';
/**
* Configuration for workflow tracing
*/
interface WorkflowConfig<TArgs extends unknown[] = unknown[]> {
/** Workflow name (e.g., 'OrderFulfillment', 'UserOnboarding') */
name: string;
/**
* Function to extract or generate workflow ID
* Can be static string, function of args, or generator
*/
workflowId: string | ((...args: TArgs) => string);
/** Optional workflow version */
version?: string;
/** Additional attributes */
attributes?: Attributes;
/** Callback on workflow completion */
onComplete?: (ctx: WorkflowContext, result: unknown) => void;
/** Callback on workflow failure */
onFailed?: (ctx: WorkflowContext, error: Error) => void;
/** Callback on compensation start */
onCompensating?: (ctx: WorkflowContext) => void;
}
/**
* Configuration for workflow step tracing
*/
interface StepConfig {
/** Step name */
name: string;
/** Optional step description */
description?: string;
/** Step index (auto-assigned if not provided) */
index?: number;
/** Link to previous step span */
linkToPrevious?: boolean;
/** Link to specific step(s) by name */
linkTo?: string | string[];
/** Additional attributes */
attributes?: Attributes;
/** Compensation handler for saga rollback */
compensate?: (error: Error) => Promise<void> | void;
/** Whether this step is idempotent */
idempotent?: boolean;
/** Retry configuration */
retry?: {
maxAttempts: number;
backoffMs?: number;
};
/** Callback on step completion */
onComplete?: (ctx: StepContext) => void;
/** Callback on step failure */
onFailed?: (ctx: StepContext, error: Error) => void;
}
/**
* Extended trace context for workflows
*/
interface WorkflowContext extends TraceContext {
/** Get the workflow ID */
getWorkflowId(): string;
/** Get workflow name */
getWorkflowName(): string;
/** Get current workflow status */
getStatus(): WorkflowStatus;
/** Mark step as completed and store for linking */
completeStep(stepName: string): void;
/** Get previous step's span context for linking */
getPreviousStep(stepName?: string): SpanContext | null;
/** Get all completed steps */
getCompletedSteps(): string[];
/** Register a compensation handler */
registerCompensation(stepName: string, handler: (error: Error) => Promise<void> | void): void;
/** Trigger compensation for all registered steps */
compensate(error: Error): Promise<void>;
/** Record compensation result */
recordCompensation(stepName: string, success: boolean, error?: Error): void;
/** Set workflow status */
setWorkflowStatus(status: WorkflowStatus): void;
}
/**
* Extended trace context for workflow steps
*/
interface StepContext extends TraceContext {
/** Get step name */
getStepName(): string;
/** Get step index */
getStepIndex(): number;
/** Mark this step as completed */
complete(): void;
/** Skip this step */
skip(reason?: string): void;
/** Get workflow context */
getWorkflowContext(): WorkflowContext | null;
}
/**
* Create a traced workflow function
*
* Wraps business logic in a workflow span with automatic step tracking,
* correlation via workflow ID, and compensation support.
*
* @param config - Workflow configuration
* @returns Factory function that wraps your workflow logic
*
* @example Order fulfillment workflow
* ```typescript
* export const fulfillOrder = traceWorkflow({
* name: 'OrderFulfillment',
* workflowId: (order) => order.id,
* version: '2.0',
* })(ctx => async (order: Order) => {
* ctx.setAttribute('order.total', order.total);
*
* await validateOrder(order);
* await processPayment(order);
* await fulfillItems(order);
* await notifyCustomer(order);
*
* return { success: true, orderId: order.id };
* });
* ```
*/
declare function traceWorkflow<TArgs extends unknown[], TReturn>(config: WorkflowConfig<TArgs>): (fnFactory: (ctx: WorkflowContext) => (...args: TArgs) => Promise<TReturn>) => ((...args: TArgs) => Promise<TReturn>);
/**
* Create a traced workflow step
*
* Wraps step logic with automatic linking to previous steps,
* compensation registration, and status tracking.
*
* @param config - Step configuration
* @returns Factory function that wraps your step logic
*
* @example Step with compensation
* ```typescript
* const chargePayment = traceStep({
* name: 'ChargePayment',
* linkToPrevious: true,
* compensate: async (error) => {
* await paymentService.refund(paymentId);
* },
* })(async (amount: number) => {
* return await paymentService.charge(amount);
* });
* ```
*/
declare function traceStep<TArgs extends unknown[], TReturn>(config: StepConfig): (fn: (...args: TArgs) => Promise<TReturn>) => ((...args: TArgs) => Promise<TReturn>);
/**
* Get current workflow context (if inside a workflow)
*
* Uses AsyncLocalStorage to ensure async-safety when multiple
* workflows are running concurrently.
*/
declare function getCurrentWorkflowContext(): WorkflowContext | null;
/**
* Check if currently executing inside a workflow
*
* Uses AsyncLocalStorage to ensure async-safety when multiple
* workflows are running concurrently.
*/
declare function isInWorkflow(): boolean;
export { type StepConfig, type StepContext, type StepStatus, type WorkflowConfig, type WorkflowContext, type WorkflowStatus, getCurrentWorkflowContext, isInWorkflow, traceStep, traceWorkflow };