UNPKG

durable-execution

Version:

A durable execution engine for running tasks durably and resiliently

1,188 lines (1,135 loc) 35.3 kB
import type { Effect } from 'effect' import type { DurableExecutionErrorStorageValue } from './errors' /** * The type of task. * * @category Task */ export type TaskType = 'task' | 'sleepingTask' | 'parentTask' | 'sequentialTasks' /** * Represents a durable task that can be executed with automatic retry, timeout and failure handling * capabilities. * * Tasks are the fundamental unit of work in the durable execution system. They encapsulate business * logic that needs to run reliably despite failures. * * ## Key Properties * * - **id**: Unique identifier for the task type * - **retryOptions**: Configuration for automatic retry behavior * - **sleepMsBeforeRun**: Optional delay before execution starts * - **timeoutMs**: Maximum execution time before timeout * * @example * ```ts * const emailTask: Task<{to: string, subject: string}, {messageId: string}> = { * id: 'sendEmail', * retryOptions: { maxAttempts: 3, baseDelayMs: 1000 }, * sleepMsBeforeRun: 0, * timeoutMs: 30000 * } * ``` * * See the [task examples](https://gpahal.github.io/durable-execution/index.html#task-examples) for * more patterns and use cases. * * @category Task */ export type Task< // eslint-disable-next-line @typescript-eslint/no-unused-vars TInput, // eslint-disable-next-line @typescript-eslint/no-unused-vars TOutput, TTaskType extends TaskType = TaskType, > = { readonly taskType: TTaskType readonly id: string readonly retryOptions: TaskRetryOptions readonly sleepMsBeforeRun: number readonly timeoutMs: number } /** * Type alias for a task with unknown input and output. * * @category Task */ export type AnyTask = Task<unknown, unknown> /** * Type-safe record of available tasks. * * @category Task */ export type AnyTasks = Record<string, AnyTask> /** * TypeScript utility type to extract the input type from a Task. * * @example * ```ts * type EmailInput = InferTaskInput<typeof emailTask> * // Result: { to: string, subject: string } * ``` * * @category Task */ export type InferTaskInput<TTask extends AnyTask> = TTask extends Task<infer I, unknown> ? I : never /** * TypeScript utility type to extract the output type from a Task. * * @example * ```ts * type EmailOutput = InferTaskOutput<typeof emailTask> * // Result: { messageId: string } * ``` * * @category Task */ export type InferTaskOutput<TTask extends AnyTask> = TTask extends Task<unknown, infer O> ? O : never /** * TypeScript utility type to extract the task type from a Task. * * @example * ```ts * type EmailTaskType = InferTaskType<typeof emailTask> * // Result: 'task' * ``` * * @category Task */ export type InferTaskType<TTask extends AnyTask> = TTask extends Task<unknown, unknown, infer T> ? T : never /** * Common options for a task. These options are used by both {@link TaskOptions} and * {@link ParentTaskOptions}. * * @category Task */ export type CommonTaskOptions = { /** * A unique identifier for the task. Can only contain alphanumeric characters and underscores. * The identifier must be unique among all the tasks in the same executor. */ id: string /** * The options for retrying the task. */ retryOptions?: TaskRetryOptions /** * The delay before running the task run function. If the value is undefined, it will be treated * as 0. */ sleepMsBeforeRun?: number /** * The timeout for the task run function. */ timeoutMs: number } /** * Configuration for automatic task retry behavior with exponential backoff. * * ## Retry Delay Calculation * * The delay between retries follows an exponential backoff pattern: * ``` * delay = min(baseDelayMs * (delayMultiplier ^ attemptNumber), maxDelayMs) * ``` * * @example * ```ts * // Retry up to 5 times with exponential backoff * const retryOptions: TaskRetryOptions = { * maxAttempts: 5, * baseDelayMs: 1000, // Start with 1 second * delayMultiplier: 2, // Double each time * maxDelayMs: 30000 // Cap at 30 seconds * } * // Results in delays: 1s, 2s, 4s, 8s, 16s (capped) * * // Constant delay (no backoff) * const constantRetry: TaskRetryOptions = { * maxAttempts: 3, * baseDelayMs: 5000, // Always wait 5 seconds * delayMultiplier: 1, // No increase * } * // Results in delays: 5s, 5s, 5s * * // Immediate retry (no delay) * const immediateRetry: TaskRetryOptions = { * maxAttempts: 2, * baseDelayMs: 0, // No delay between retries * } * // Results in delays: 0s, 0s * ``` * * @category Task */ export type TaskRetryOptions = { /** * The maximum number of times the task can be retried. */ maxAttempts: number /** * The base delay before each retry in milliseconds. Defaults to 0 (immediate retry). When set to * 0, retries happen immediately without delay. */ baseDelayMs?: number /** * The multiplier for the delay before each retry. Defaults to 1 (constant delay). Values > 1 * create exponential backoff, values < 1 create decreasing delays. */ delayMultiplier?: number /** * The maximum delay before each retry in milliseconds. When specified, delays are capped at this * value regardless of the exponential calculation. */ maxDelayMs?: number } /** * Options for a task that can be run using a durable executor. A task is resilient to task * failures, process failures, network connectivity issues, and other transient errors. The task * should be idempotent as it may be run multiple times if there is a process failure or if the * task is retried. * * When enqueued with an executor, a task execution handle is returned. It supports getting the * execution status, waiting for the task to complete, and cancelling the task. * * The output of the `run` function is the output of the task. * * The input and output are serialized and deserialized using the serializer passed to the durable * executor. * * Make sure the id is unique among all the tasks in the same durable executor. If two tasks are * registered with the same id, an error will be thrown. * * The tasks can be added to an executor using the {@link DurableExecutor.task} method. If the * input to a task needs to be validated, it can be done using the * {@link DurableExecutor.validateInput} or {@link DurableExecutor.inputSchema} methods. * * See the [task examples](https://gpahal.github.io/durable-execution/index.html#task-examples) * section for more details on creating tasks. * * @example * ```ts * const extractFileTitle = executor * .inputSchema(Schema.Struct({ filePath: Schema.String })) * .task({ * id: 'extractFileTitle', * timeoutMs: 30_000, // 30 seconds * run: async (ctx, input) => { * // ... extract the file title * return { * title: 'File Title', * } * }, * }) * ``` * * @category Task */ export type TaskOptions<TInput = undefined, TOutput = unknown> = CommonTaskOptions & { /** * The task run logic. It returns the output. * * Behavior on throwing errors: * - If the task throws an error, it will be marked as failed * - If the task throws a `{@link DurableExecutionTimedOutError}`, it will be marked as timed out * - If the task throws a `{@link DurableExecutionCancelledError}`, it will be marked as cancelled * - Failed and timed out tasks might be retried based on the task's retry configuration * - Cancelled tasks will not be retried * * @param ctx - The context object to the task. * @param input - The input to the task. * @returns The output of the task. */ run: ( ctx: TaskRunContext, input: TInput, ) => TOutput | Promise<TOutput> | Effect.Effect<TOutput, unknown, never> } /** * Options for a task that is asleep until it is marked completed or failed or timed out or * cancelled. It is similar to {@link TaskOptions} but it does not have a `run` function. It * just sleeps until it is marked completed or failed or timed out or cancelled. * * It is not very useful by itself but when combined with {@link ParentTaskOptions}, it can be used * to create a task that is a parent task that runs a child task that is asleep until it is marked * completed by a webhook or some other event. * * See the [task examples](https://gpahal.github.io/durable-execution/index.html#task-examples) * section for more details on creating tasks. * * @example * ```ts * const waitForWebhook = executor * .sleepingTask<{ webhookId: string }>({ * id: 'waitForWebhook', * timeoutMs: 24 * 60 * 60 * 1000, // 24 hours * }) * * // Enqueue the sleeping task * const handle = await executor.enqueueTask(waitForWebhook, 'uniqueId') * * // Wakeup using the unique id and executor (or execution client) * await executor.wakeupSleepingTaskExecution(waitForWebhook, 'uniqueId', { * status: 'completed', * output: { * webhookId: '123', * }, * }) * * // Or, wakeup in a webhook or event handler asynchronously using the unique id and executor * // (or execution client) * await executor.wakeupSleepingTaskExecution(waitForWebhook, 'uniqueId', { * status: 'completed', * output: { * webhookId: '123', * }, * }) * ``` * * @template TOutput - The type of the output when the sleeping task is woken up * * @category Task */ // eslint-disable-next-line @typescript-eslint/no-unused-vars export type SleepingTaskOptions<TOutput = unknown> = Pick<CommonTaskOptions, 'id' | 'timeoutMs'> /** * Options for a parent task that can be run using a durable executor. It is similar to * {@link TaskOptions} but it returns children tasks to be run in parallel after the run * function completes, along with the output of the parent task. * * The `runParent` function is similar to the `run` function in {@link TaskOptions}, but the * output is of the form `{ output: TRunOutput, children: ReadonlyArray<ChildTask> }` where the * children are the tasks to be run in parallel after the run function completes. * * The `finalize` function or task is run after the runParent function and all the children tasks * finish. It is useful for combining the output of the runParent function and children tasks. It * is called even if the children tasks fail. Its input has the following properties: * * - `output`: The output of the runParent function * - `children`: The finished children task executions (includes both successful and failed * children) * * **Important**: The `finalize` function or task receives outputs from ALL children, including * those that have failed. This behaves similar to `Promise.allSettled()` - you get the results * regardless of individual child success or failure. This allows you to implement custom error * handling logic. * * If `finalize` is provided, the output of the whole task is the output of the `finalize` function * or task. If it is not provided, the output of the whole task is of the form * `{ output: TRunOutput, children: ReadonlyArray<FinishedChildTaskExecution> }`. * * See the [task examples](https://gpahal.github.io/durable-execution/index.html#task-examples) * section for more details on creating tasks. * * @example * ```ts * const extractFileTitle = executor * .inputSchema(Schema.Struct({ filePath: Schema.String })) * .task({ * id: 'extractFileTitle', * timeoutMs: 30_000, // 30 seconds * run: async (ctx, input) => { * // ... extract the file title * return { * title: 'File Title', * } * }, * }) * * const summarizeFile = executor * .validateInput(async (input: { filePath: string }) => { * // Example validation function - implement your own validation logic * if (!isValidFilePath(input.filePath)) { * throw new Error('Invalid file path') * } * return { * filePath: input.filePath, * } * }) * .task({ * id: 'summarizeFile', * timeoutMs: 30_000, // 30 seconds * run: async (ctx, input) => { * // ... summarize the file * return { * summary: 'File summary', * } * }, * }) * * const uploadFile = executor * .inputSchema(Schema.Struct({ filePath: Schema.String, uploadUrl: Schema.String })) * .parentTask({ * id: 'uploadFile', * timeoutMs: 60_000, // 1 minute * runParent: async (ctx, input) => { * // ... upload file to the given uploadUrl * // Extract the file title and summarize the file in parallel * return { * output: { * filePath: input.filePath, * uploadUrl: input.uploadUrl, * fileSize: 100, * }, * children: [ * childTask(extractFileTitle, { filePath: input.filePath }), * childTask(summarizeFile, { filePath: input.filePath }), * ], * } * }, * finalize: { * id: 'uploadFileFinalize', * timeoutMs: 60_000, // 1 minute * run: async (ctx, { output, children }) => { * // ... combine the output of the run function and children tasks * return { * filePath: output.filePath, * uploadUrl: output.uploadUrl, * fileSize: 100, * title: 'File Title', * summary: 'File summary', * } * } * }, * }) * ``` * * @category Task */ export type ParentTaskOptions< TInput = undefined, TRunOutput = unknown, TOutput = DefaultParentTaskOutput<TRunOutput>, TFinalizeTaskRunOutput = unknown, > = CommonTaskOptions & { /** * The task run logic. It is similar to the `run` function in {@link TaskOptions} but it * returns the output and children tasks to be run in parallel after the run function completes. * * @param ctx - The context object to the task. * @param input - The input of the task. * @returns The output of the task and children tasks to be run in parallel after the run * function completes. */ runParent: ( ctx: TaskRunContext, input: TInput, ) => | { output: TRunOutput children?: ReadonlyArray<ChildTask> } | Promise<{ output: TRunOutput children?: ReadonlyArray<ChildTask> }> | Effect.Effect< { output: TRunOutput children?: ReadonlyArray<ChildTask> }, unknown, never > /** * Function or task to run after the runParent function and children tasks finish. This is useful * for combining the output of the run function and children tasks. It is called even if the * children tasks fail. * * If it is a function, it is called without any durability guarantees and retries. It should * not be a long running function. */ finalize?: | (( input: DefaultParentTaskOutput<TRunOutput>, ) => TOutput | Promise<TOutput> | Effect.Effect<TOutput, unknown, never>) | FinalizeTaskOptions<TRunOutput, TOutput, TFinalizeTaskRunOutput> } /** * Options for the `finalize` property in {@link ParentTaskOptions}. It is similar to * {@link TaskOptions} or {@link ParentTaskOptions} but the input is of the form: * * ```ts * { * output: TRunOutput, * children: ReadonlyArray<FinishedChildTaskExecution> * } * ``` * * **Critical**: The `finalize` function/task receives outputs from all children, including those * that have failed. This behaves similar to `Promise.allSettled()` - you get the results * regardless of individual child success or failure. This allows you to implement custom error * handling logic, such as failing the parent only if critical children fail, or providing partial * results. As a caveat, always check the status of child executions in the finalize function/task. * * No validation is done on the input and the output of the parent task is the output of the * `finalize` task. * * @template TRunOutput - The type of the parent task's run output * @template TOutput - The type of the finalize task's output (becomes the parent task's output) * @template TFinalizeTaskRunOutput - If finalize is a parent task, the type of its run output * * @category Task */ export type FinalizeTaskOptions< TRunOutput = unknown, TOutput = unknown, TFinalizeTaskRunOutput = unknown, > = | TaskOptions<DefaultParentTaskOutput<TRunOutput>, TOutput> | ParentTaskOptions<DefaultParentTaskOutput<TRunOutput>, TFinalizeTaskRunOutput, TOutput> /** * The default output type for a parent task when no `finalize` task is provided. * * @category Task */ export type DefaultParentTaskOutput<TRunOutput = unknown> = { output: TRunOutput children: ReadonlyArray<FinishedChildTaskExecution> } /** * Type predicate to check if a {@link FinalizeTaskOptions} is a {@link TaskOptions}. * * This utility function helps determine whether the finalize configuration is a simple task (with * a `run` function) or a parent task (with a `runParent` function). * * @template TRunOutput - The type of the parent task's run output. * @template TOutput - The type of the finalize task's output. * @template TFinalizeTaskRunOutput - If finalize is a parent task, the type of its run output. * * @param options - The finalize task options to check. * @returns `true` if the options represent a simple task, `false` otherwise. * * @category Task * @internal */ export function isFinalizeTaskOptionsTaskOptions< TRunOutput = unknown, TOutput = unknown, TFinalizeTaskRunOutput = unknown, >( options: FinalizeTaskOptions<TRunOutput, TOutput, TFinalizeTaskRunOutput>, ): options is TaskOptions<DefaultParentTaskOutput<TRunOutput>, TOutput> { return 'run' in options && !('runParent' in options) } /** * Type predicate to check if a {@link FinalizeTaskOptions} is a {@link ParentTaskOptions}. * * This utility function helps determine whether the finalize configuration is a parent task (with * a `runParent` function) or a simple task (with a `run` function). * * @template TRunOutput - The type of the parent task's run output. * @template TOutput - The type of the finalize task's output. * @template TFinalizeTaskRunOutput - If finalize is a parent task, the type of its run output. * * @param options - The finalize task options to check. * @returns `true` if the options represent a parent task, `false` otherwise. * * @category Task * @internal */ export function isFinalizeTaskOptionsParentTaskOptions< TRunOutput = unknown, TOutput = unknown, TFinalizeTaskRunOutput = unknown, >( options: FinalizeTaskOptions<TRunOutput, TOutput, TFinalizeTaskRunOutput>, ): options is ParentTaskOptions< DefaultParentTaskOutput<TRunOutput>, TFinalizeTaskRunOutput, TOutput > { return 'runParent' in options && !('run' in options) } /** * Runtime context provided to every task execution, containing metadata, signals and information * about the execution environment. * * ## Context Properties * * - **Identity**: `taskId`, `executionId` - Unique identifiers * - **Hierarchy**: `root`, `parent` - Task relationship information * - **Signals**: `shutdownSignal`, `abortSignal` - For graceful termination * - **Retry State**: `attempt`, `prevError` - Retry attempt information * * @example * ```ts * const task = executor.task({ * id: 'processData', * timeoutMs: 60000, * run: async (ctx, input) => { * console.log(`Execution ${ctx.executionId}, attempt ${ctx.attempt}`) * * // Check for executor shutdown or task cancellation * if (ctx.shutdownSignal.aborted || ctx.abortSignal.aborted) { * throw new DurableExecutionCancelledError() * } * * // Check previous error if retrying * if (ctx.prevError) { * console.log('Retrying after:', ctx.prevError.message) * } * * // Process data... * return result * } * }) * ``` * * @category Task */ export type TaskRunContext = { /** * The root task execution. */ root?: TaskExecutionSummary /** * The parent task execution. */ parent?: ParentTaskExecutionSummary /** * The task id. */ taskId: string /** * The task execution id. */ executionId: string /** * The shutdown signal of the task. It is set when the executor is shutting down. */ shutdownSignal: AbortSignal /** * The abort signal of the task. It is cancelled when the task gets cancelled or times out. */ abortSignal: AbortSignal /** * The attempt number of the task. The first attempt is 0, the second attempt is 1, etc. */ attempt: number /** * The error of the previous attempt. */ prevError?: DurableExecutionErrorStorageValue } /** * Represents the current state and metadata of a task execution. * * Task executions transition through various states during their lifecycle: * `ready` → `running` → `completed` (or `failed`/`timed_out`/`cancelled`) * * Parent tasks have additional states: * `running` → `waiting_for_children` → `waiting_for_finalize` → `completed` * * @see [Task execution](https://gpahal.github.io/durable-execution/index.html#task-execution) * for detailed state transitions. * * @category Task */ export type TaskExecution<TOutput = unknown> = | ReadyTaskExecution | RunningTaskExecution | FailedTaskExecution | TimedOutTaskExecution | WaitingForChildrenTaskExecution | WaitingForFinalizeTaskExecution | FinalizeFailedTaskExecution | CompletedTaskExecution<TOutput> | CancelledTaskExecution /** * Represents a task execution that has reached a terminal state. * * Terminal states include: * - `completed`: Successfully finished with output * - `failed`: Execution failed (may have been retried) * - `timed_out`: Exceeded timeout limit * - `finalize_failed`: Parent task's finalize function failed * - `cancelled`: Manually cancelled or parent failed * * Once in a terminal state, the task will not execute again. * * @category Task */ export type FinishedTaskExecution<TOutput = unknown> = | FailedTaskExecution | TimedOutTaskExecution | FinalizeFailedTaskExecution | CompletedTaskExecution<TOutput> | CancelledTaskExecution /** * A task execution that is ready to be run. * * @category Task */ export type ReadyTaskExecution = { root?: { taskId: string executionId: string } parent?: { taskId: string executionId: string } taskId: string executionId: string sleepingTaskUniqueId?: string retryOptions: TaskRetryOptions sleepMsBeforeRun: number timeoutMs: number areChildrenSequential: boolean input: unknown status: 'ready' error?: DurableExecutionErrorStorageValue retryAttempts: number startAt: Date createdAt: Date updatedAt: Date } /** * A task execution that is running. * * @category Task */ export type RunningTaskExecution = Omit<ReadyTaskExecution, 'status'> & { status: 'running' executorId: string startedAt: Date expiresAt: Date } /** * A task execution that failed while running. * * @category Task */ export type FailedTaskExecution = Omit<RunningTaskExecution, 'executorId' | 'status' | 'error'> & { status: 'failed' error: DurableExecutionErrorStorageValue finishedAt: Date } /** * A task execution that timed out while running. * * @category Task */ export type TimedOutTaskExecution = Omit< RunningTaskExecution, 'executorId' | 'status' | 'error' > & { status: 'timed_out' error: DurableExecutionErrorStorageValue finishedAt: Date } /** * A task execution that is waiting for children tasks to complete. * * @category Task */ export type WaitingForChildrenTaskExecution = Omit< RunningTaskExecution, 'executorId' | 'status' | 'error' > & { status: 'waiting_for_children' waitingForChildrenStartedAt: Date children: Array<TaskExecutionSummary> activeChildrenCount: number } /** * A task execution that is waiting for the finalize task to complete. * * @category Task */ export type WaitingForFinalizeTaskExecution = Omit< WaitingForChildrenTaskExecution, 'status' | 'error' | 'waitingForChildrenStartedAt' > & { status: 'waiting_for_finalize' waitingForFinalizeStartedAt: Date finalize: TaskExecutionSummary /** * The time the task execution waiting for children started. This is only present for tasks which * have children. */ waitingForChildrenStartedAt?: Date } /** * A task execution that failed while waiting for the finalize task to complete because the * finalize task execution failed. * * @category Task */ export type FinalizeFailedTaskExecution = Omit< WaitingForFinalizeTaskExecution, 'status' | 'error' > & { status: 'finalize_failed' error: DurableExecutionErrorStorageValue finishedAt: Date } /** * A task execution that completed successfully. * * @category Task */ export type CompletedTaskExecution<TOutput = unknown> = Omit< WaitingForChildrenTaskExecution, 'status' | 'output' | 'waitingForChildrenStartedAt' > & { status: 'completed' output: TOutput finishedAt: Date /** * The time the task execution waiting for children started. This is only present for tasks which * have children tasks and whose run method completed successfully. */ waitingForChildrenStartedAt?: Date /** * The time the task execution waiting for finalize started. This is only present for tasks which * have a finalize task and whose run method completed successfully and children tasks finished. */ waitingForFinalizeStartedAt?: Date /** * The finalize task execution. This is only present for tasks which have a finalize task and * whose run method completed successfully and children tasks finished. */ finalize?: TaskExecutionSummary } /** * A task execution that was cancelled. This can happen when a task is cancelled by using * the cancel method of the task handle or when the task is cancelled because it's parent task * failed. * * @category Task */ export type CancelledTaskExecution = Omit<ReadyTaskExecution, 'status' | 'error'> & { status: 'cancelled' error: DurableExecutionErrorStorageValue finishedAt: Date /** * The id of the executor. This is only present for tasks which were cancelled while running. */ executorId?: string /** * The time the task execution started. This is only present for tasks which started running. */ startedAt?: Date /** * The time the task execution expires. This is only present for tasks which started running. */ expiresAt?: Date /** * The time the task execution waiting for children started. This is only present for tasks which * have children tasks and whose run method completed successfully. */ waitingForChildrenStartedAt?: Date /** * The time the task execution waiting for finalize started. This is only present for tasks which * have a finalize task and whose run method completed successfully and children tasks finished. */ waitingForFinalizeStartedAt?: Date /** * The children task executions that were running when the task was cancelled. This is only present for * tasks whose run method completed successfully. */ children?: Array<TaskExecutionSummary> /** * The number of children task executions that are still active. This is only present for tasks * whose run method completed successfully. */ activeChildrenCount: number /** * The finalize task execution. This is only present for tasks which have a finalize task and * whose run method completed successfully and children tasks finished. */ finalize?: TaskExecutionSummary } /** * A child task of a task. * * @category Task */ export type ChildTask<TTask extends AnyTask = AnyTask> = undefined extends InferTaskInput<TTask> ? { task: TTask input?: InferTaskInput<TTask> options?: TaskEnqueueOptions<AnyTask> } : { task: TTask input: InferTaskInput<TTask> options?: TaskEnqueueOptions<AnyTask> } /** * Create a type-safe child task. Usually used alongside the `parentTask` function. See * {@link ParentTaskOptions} for more details. * * @param rest - The task, input and options. * @returns A child task. * * @category Task */ export function childTask<TTask extends AnyTask = AnyTask>( ...rest: undefined extends InferTaskInput<TTask> ? [task: TTask, input?: InferTaskInput<TTask>, options?: TaskEnqueueOptions<TTask>] : [task: TTask, input: InferTaskInput<TTask>, options?: TaskEnqueueOptions<TTask>] ): ChildTask<TTask> { return { task: rest[0], input: rest[1], options: rest[2], } as ChildTask<TTask> } /** * A finished child task execution. * * This type represents both successful and failed child task executions, similar to the result * of `Promise.allSettled()`. The finalize function receives all children regardless of their * completion status, allowing for custom error handling logic. * * @category Task */ export type FinishedChildTaskExecution<TOutput = unknown> = | CompletedChildTaskExecution<TOutput> | ErroredChildTaskExecution /** * A completed child task execution. * * @category Task */ export type CompletedChildTaskExecution<TOutput = unknown> = { taskId: string executionId: string status: 'completed' output: TOutput } /** * An errored child task execution. * * @category Task */ export type ErroredChildTaskExecution = { taskId: string executionId: string status: ErroredTaskExecutionStatus error: DurableExecutionErrorStorageValue } /** * A summary of a task execution. * * @category Task */ export type TaskExecutionSummary = { taskId: string executionId: string } /** * A summary of a parent task execution. * * @category Task */ export type ParentTaskExecutionSummary = TaskExecutionSummary & { indexInParentChildren: number isOnlyChildOfParent: boolean isFinalizeOfParent: boolean } /** * A status of a task execution. * * @category Task */ export type TaskExecutionStatus = | 'ready' | 'running' | 'failed' | 'timed_out' | 'waiting_for_children' | 'waiting_for_finalize' | 'finalize_failed' | 'completed' | 'cancelled' /** * The statuses of a task execution that are considered errored. * * @category Task */ export type ErroredTaskExecutionStatus = 'failed' | 'timed_out' | 'finalize_failed' | 'cancelled' /** * All possible statuses of a task execution. * * @category Task */ export const ALL_TASK_EXECUTION_STATUSES = [ 'ready', 'running', 'failed', 'timed_out', 'waiting_for_children', 'waiting_for_finalize', 'finalize_failed', 'completed', 'cancelled', ] as ReadonlyArray<TaskExecutionStatus> /** * All possible statuses of a task execution that are considered active. * * @category Task */ export const ACTIVE_TASK_EXECUTION_STATUSES = [ 'ready', 'running', 'waiting_for_children', 'waiting_for_finalize', ] as ReadonlyArray<TaskExecutionStatus> /** * All possible statuses of a task execution that are considered finished. * * @category Task */ export const FINISHED_TASK_EXECUTION_STATUSES = [ 'failed', 'timed_out', 'finalize_failed', 'completed', 'cancelled', ] as ReadonlyArray<TaskExecutionStatus> /** * All possible statuses of a task execution that are considered errored. * * @category Task */ export const ERRORED_TASK_EXECUTION_STATUSES = [ 'failed', 'timed_out', 'finalize_failed', 'cancelled', ] as ReadonlyArray<TaskExecutionStatus> /** * Runtime options for enqueuing a task that override the task's default configuration. * * These options allow you to customize task behavior at enqueue time without * changing the task definition. Useful for adjusting timeouts or retry behavior * based on runtime conditions. * * @example * ```ts * // Enqueue with custom timeout for urgent processing * const handle = await executor.enqueueTask(emailTask, input, { * timeoutMs: 60_000, // 1 minute instead of default 30s * retryOptions: { * maxAttempts: 1, // Don't retry urgent emails * } * }) * * // Enqueue with delay for rate limiting * const handle = await executor.enqueueTask(apiTask, input, { * sleepMsBeforeRun: 5000 // Wait 5 seconds before starting * }) * ``` * * @category Task */ export type TaskEnqueueOptions<TTask extends AnyTask = AnyTask> = { /** * Options for retrying the task. Has no effect on sleeping tasks. */ retryOptions?: InferTaskType<TTask> extends 'sleepingTask' ? never : TaskRetryOptions /** * The number of milliseconds to wait before running the task. Has no effect on sleeping tasks. */ sleepMsBeforeRun?: InferTaskType<TTask> extends 'sleepingTask' ? never : number /** * The number of milliseconds after which the task will be timed out. */ timeoutMs?: number } /** * Options for waking up a sleeping task execution. * * @category Task */ export type WakeupSleepingTaskExecutionOptions<TOutput = unknown> = | { status: 'completed' output: TOutput } | { status: 'failed' error?: unknown } /** * Type constraint for sequential task execution where each task's output becomes the next task's * input. * * This utility type ensures type safety in task pipelines by verifying that: * - The sequence is not empty * - Each task's output type matches the next task's input type * - The overall pipeline type is valid * * Used internally by {@link DurableExecutor.sequentialTasks} to provide compile-time type checking * for sequential task pipelines. * * @example * ```ts * // Valid sequential tasks * const validSequence: SequentialTasks<[ * Task<{name: string}, {id: number, name: string}>, * Task<{id: number, name: string}, {result: string}> * ]> = [fetchUser, processUser] * * // Invalid: output/input types don't match * const invalidSequence: SequentialTasks<[ * Task<{name: string}, {id: number}>, * Task<{email: string}, {result: string}> // Error: needs {id: number} * ]> = [fetchUser, processEmail] // TypeScript error * ``` * * @category Task */ export type SequentialTasks<T extends ReadonlyArray<AnyTask>> = T extends readonly [] ? never : SequentialTasksHelper<T> /** * Internal helper type for implementing sequential task type checking. * * This type recursively validates that each task in the sequence can accept the output from the * previous task as its input, ensuring type safety throughout the pipeline. * * @internal * @category Task */ export type SequentialTasksHelper<T extends ReadonlyArray<AnyTask>> = T extends readonly [] ? T : T extends readonly [Task<infer _I1, infer _O1>] ? T : T extends readonly [Task<infer I1, infer O1>, Task<infer I2, infer O2>, ...infer Rest] ? O1 extends I2 ? Rest extends ReadonlyArray<AnyTask> ? readonly [Task<I1, O1>, ...SequentialTasksHelper<readonly [Task<I2, O2>, ...Rest]>] : never : never : T /** * Utility type to extract the last task from a sequential task array. * * Used to determine the final output type of a sequential task pipeline. * * @example * ```ts * type LastTask = LastTaskElementInArray<[ * Task<string, number>, * Task<number, boolean>, * Task<boolean, string> * ]> * // Result: Task<boolean, string> * ``` * * @internal * @category Task */ export type LastTaskElementInArray<T extends ReadonlyArray<AnyTask>> = T extends readonly [ ...Array<AnyTask>, infer L, ] ? L : never