diff --git a/README.md b/README.md index d082fc3..8d52327 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,6 @@ In spite of this, Durable Functions ensures reliable execution of orchestrations The `durable-functions` shim lets you express a workflow in code as a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators) wrapped by a call to the `orchestrator` method. `orchestrator` treats `yield`-ed calls to your function `context`'s `df` object, like `context.df.callActivity`, as points where you want to schedule an asynchronous unit of work and wait for it to complete. -These calls return a `Task` or `TaskSet` object signifying the outstanding work. The `orchestrator` method appends the action(s) of the `Task` or `TaskSet` object to a list which it passes back to the Functions runtime, plus whether the function is completed, and any output or errors. +These calls return a `Task` object signifying the outstanding work. The `orchestrator` method appends the action(s) of the `Task` object to a list which it passes back to the Functions runtime, plus whether the function is completed, and any output or errors. The Azure Functions extension schedules the desired actions. When the actions complete, the extension triggers the orchestrator function to replay up to the next incomplete asynchronous unit of work or its end, whichever comes first. diff --git a/src/classes.ts b/src/classes.ts index 89cd2b0..84b5a46 100644 --- a/src/classes.ts +++ b/src/classes.ts @@ -43,7 +43,7 @@ export { TimerCreatedEvent } from "./history/timercreatedevent"; export { TimerFiredEvent } from "./history/timerfiredevent"; export { ITaskMethods } from "./tasks/itaskmethods"; -export { Task } from "./tasks/task"; +export { SingleTask } from "./tasks/task"; export { TaskFactory } from "./tasks/taskfactory"; export { TaskFilter } from "./tasks/taskfilter"; export { TaskSet } from "./tasks/taskset"; diff --git a/src/durableorchestrationcontext.ts b/src/durableorchestrationcontext.ts index d661454..6b77d30 100644 --- a/src/durableorchestrationcontext.ts +++ b/src/durableorchestrationcontext.ts @@ -1,4 +1,4 @@ -import { EntityId, ITaskMethods, RetryOptions, Task, TimerTask } from "./classes"; +import { EntityId, ITaskMethods, RetryOptions, SingleTask, TimerTask } from "./classes"; import { TokenSource } from "./tokensource"; /** @@ -65,7 +65,7 @@ export class DurableOrchestrationContext { * @returns A Durable Task that completes when the called activity * function completes or fails. */ - public callActivity(name: string, input?: unknown): Task { + public callActivity(name: string, input?: unknown): SingleTask { throw new Error("This is a placeholder."); } @@ -78,7 +78,7 @@ export class DurableOrchestrationContext { * @param input The JSON-serializable input to pass to the activity * function. */ - public callActivityWithRetry(name: string, retryOptions: RetryOptions, input?: unknown): Task { + public callActivityWithRetry(name: string, retryOptions: RetryOptions, input?: unknown): SingleTask { throw new Error("This is a placeholder."); } @@ -90,7 +90,7 @@ export class DurableOrchestrationContext { * @param operationName The name of the operation. * @param operationInput The input for the operation. */ - public callEntity(entityId: EntityId, operationName: string, operationInput?: unknown): Task { + public callEntity(entityId: EntityId, operationName: string, operationInput?: unknown): SingleTask { throw new Error("This is a placeholder."); } @@ -104,7 +104,7 @@ export class DurableOrchestrationContext { * If `instanceId` is not specified, the extension will generate an id in * the format `:<#>` */ - public callSubOrchestrator(name: string, input?: unknown, instanceId?: string): Task { + public callSubOrchestrator(name: string, input?: unknown, instanceId?: string): SingleTask { throw new Error("This is a placeholder."); } @@ -123,7 +123,7 @@ export class DurableOrchestrationContext { retryOptions: RetryOptions, input?: unknown, instanceId?: string) - : Task { + : SingleTask { throw new Error("This is a placeholder."); } @@ -137,7 +137,7 @@ export class DurableOrchestrationContext { uri: string, content?: string | object, headers?: { [key: string]: string }, - tokenSource?: TokenSource): Task { + tokenSource?: TokenSource): SingleTask { throw new Error("This is a placeholder"); } @@ -146,7 +146,7 @@ export class DurableOrchestrationContext { * * @param The JSON-serializable data to re-initialize the instance with. */ - public continueAsNew(input: unknown): Task { + public continueAsNew(input: unknown): SingleTask { throw new Error("This is a placeholder."); } @@ -213,7 +213,7 @@ export class DurableOrchestrationContext { * External clients can raise events to a waiting orchestration instance * using [[raiseEvent]]. */ - public waitForExternalEvent(name: string): Task { + public waitForExternalEvent(name: string): SingleTask { throw new Error("This is a placeholder."); } } diff --git a/src/orchestrator.ts b/src/orchestrator.ts index d9aa63b..012d63c 100644 --- a/src/orchestrator.ts +++ b/src/orchestrator.ts @@ -5,14 +5,14 @@ import { CallActivityAction, CallActivityWithRetryAction, CallEntityAction, Call CreateTimerAction, DurableHttpRequest, DurableLock, DurableOrchestrationBindingInfo, EntityId, EventRaisedEvent, EventSentEvent, ExternalEventType, GuidManager, HistoryEvent, HistoryEventType, IAction, IOrchestrationFunctionContext, LockState, OrchestratorState, RequestMessage, ResponseMessage, - RetryOptions, SubOrchestrationInstanceCompletedEvent, SubOrchestrationInstanceCreatedEvent, - SubOrchestrationInstanceFailedEvent, Task, TaskCompletedEvent, TaskFactory, TaskFailedEvent, TaskFilter, + RetryOptions, SingleTask, SubOrchestrationInstanceCompletedEvent, SubOrchestrationInstanceCreatedEvent, + SubOrchestrationInstanceFailedEvent, TaskCompletedEvent, TaskFactory, TaskFailedEvent, TaskFilter, TaskScheduledEvent, TaskSet, TimerCreatedEvent, TimerFiredEvent, TimerTask, Utils, WaitForExternalEventAction, } from "./classes"; import { DurableError } from "./durableerror"; import { OrchestrationFailureError } from "./orchestrationfailureerror"; -import { CompletedTask, TaskBase } from "./tasks/taskinterfaces"; +import { CompletedTask, Task } from "./tasks/taskinterfaces"; import { TokenSource } from "./tokensource"; /** @hidden */ @@ -83,7 +83,7 @@ export class Orchestrator { // Setup const gen = this.fn(context); const actions: IAction[][] = []; - let partialResult: TaskBase; + let partialResult: Task; try { // First execution, we have not yet "yielded" any of the tasks. @@ -113,7 +113,7 @@ export class Orchestrator { } } - partialResult = g.value as TaskBase; + partialResult = g.value as Task; const newActions = partialResult.yieldNewActions(); if (newActions && newActions.length > 0) { actions.push(newActions); @@ -201,7 +201,7 @@ export class Orchestrator { } } - private callActivity(state: HistoryEvent[], name: string, input?: unknown): Task { + private callActivity(state: HistoryEvent[], name: string, input?: unknown): SingleTask { const newAction = new CallActivityAction(name, input); const taskScheduled = this.findTaskScheduled(state, name); @@ -240,7 +240,7 @@ export class Orchestrator { name: string, retryOptions: RetryOptions, input?: unknown) - : Task { + : SingleTask { const newAction = new CallActivityWithRetryAction(name, retryOptions, input); for (let attempt = 1; attempt <= retryOptions.maxNumberOfAttempts; attempt++) { @@ -274,7 +274,7 @@ export class Orchestrator { return TaskFactory.UncompletedTask(newAction); } - private callEntity(state: HistoryEvent[], entityId: EntityId, operationName: string, operationInput: unknown): Task { + private callEntity(state: HistoryEvent[], entityId: EntityId, operationName: string, operationInput: unknown): SingleTask { const newAction = new CallEntityAction(entityId, operationName, operationInput); const schedulerId = EntityId.getSchedulerIdFromEntityId(entityId); @@ -299,7 +299,7 @@ export class Orchestrator { ); } - private callSubOrchestrator(state: HistoryEvent[], name: string, input?: unknown, instanceId?: string): Task { + private callSubOrchestrator(state: HistoryEvent[], name: string, input?: unknown, instanceId?: string): SingleTask { if (!name) { throw new Error("A sub-orchestration function name must be provided when attempting to create a suborchestration"); } @@ -343,7 +343,7 @@ export class Orchestrator { retryOptions: RetryOptions, input?: unknown, instanceId?: string) - : Task { + : SingleTask { if (!name) { throw new Error("A sub-orchestration function name must be provided when attempting to create a suborchestration"); } @@ -441,7 +441,7 @@ export class Orchestrator { } } - private continueAsNew(state: HistoryEvent[], input: unknown): Task { + private continueAsNew(state: HistoryEvent[], input: unknown): SingleTask { const newAction = new ContinueAsNewAction(input); return TaskFactory.UncompletedTask( @@ -530,7 +530,7 @@ export class Orchestrator { this.customStatus = customStatusObject; } - private waitForExternalEvent(state: HistoryEvent[], name: string): Task { + private waitForExternalEvent(state: HistoryEvent[], name: string): SingleTask { const newAction = new WaitForExternalEventAction(name, ExternalEventType.ExternalEvent); const eventRaised = this.findEventRaised(state, name); @@ -550,7 +550,7 @@ export class Orchestrator { } } - private all(state: HistoryEvent[], tasks: TaskBase[]): TaskSet { + private all(state: HistoryEvent[], tasks: Task[]): TaskSet { let maxCompletionIndex: number | undefined; const errors: Error[] = []; const results: Array = []; @@ -583,7 +583,7 @@ export class Orchestrator { } } - private any(state: HistoryEvent[], tasks: TaskBase[]): TaskSet { + private any(state: HistoryEvent[], tasks: Task[]): TaskSet { if (!tasks || tasks.length === 0) { throw new Error("At least one yieldable task must be provided to wait for."); } diff --git a/src/tasks/itaskmethods.ts b/src/tasks/itaskmethods.ts index bd5cdf4..6602d23 100644 --- a/src/tasks/itaskmethods.ts +++ b/src/tasks/itaskmethods.ts @@ -1,4 +1,4 @@ -import { Task } from "./task"; +import { SingleTask } from "./task"; import { TaskSet } from "./taskset"; /** @@ -11,11 +11,11 @@ export interface ITaskMethods { * array containing the results of all [[Task]]s passed to it. It returns * when all of the [[Task]] instances have completed. */ - all: (tasks: Task[]) => TaskSet; + all: (tasks: SingleTask[]) => TaskSet; /** * Similar to Promise.race. When called with `yield` or `return`, returns * the first [[Task]] instance to complete. */ - any: (tasks: Task[]) => TaskSet; + any: (tasks: SingleTask[]) => TaskSet; } diff --git a/src/tasks/task.ts b/src/tasks/task.ts index b69e7b6..4df5176 100644 --- a/src/tasks/task.ts +++ b/src/tasks/task.ts @@ -1,5 +1,5 @@ import { IAction } from "../classes"; -import { TaskBase } from "./taskinterfaces"; +import { Task } from "./taskinterfaces"; /** * Represents some pending action. Similar to a native JavaScript promise in @@ -10,7 +10,7 @@ import { TaskBase } from "./taskinterfaces"; * [[DurableOrchestrationContext]] operation is not called with `yield`. They * are useful for parallelization and timeout operations in conjunction with * Task.all and Task.any. - * + * * We discourage the usage of `instanceof`-style guards on this type, * as it is subject to change in the future. * @@ -36,7 +36,7 @@ import { TaskBase } from "./taskinterfaces"; * return firstDone.result; * ``` */ -export class Task implements TaskBase { +export class SingleTask implements Task { /** * @hidden * Used to keep track of how many times the task has been yielded to avoid diff --git a/src/tasks/taskfactory.ts b/src/tasks/taskfactory.ts index 3d87a0e..434142b 100644 --- a/src/tasks/taskfactory.ts +++ b/src/tasks/taskfactory.ts @@ -1,17 +1,17 @@ import { CreateTimerAction, IAction } from "../classes"; -import { Task} from "./task"; -import { TaskBase} from "./taskinterfaces"; +import { SingleTask } from "./task"; +import { Task } from "./taskinterfaces"; import { TaskSet } from "./taskset"; import { TimerTask } from "./timertask"; /** @hidden */ export class TaskFactory { - public static UncompletedTask(action: IAction): Task { - return new Task(false, false, action); + public static UncompletedTask(action: IAction): SingleTask { + return new SingleTask(false, false, action); } - public static SuccessfulTask(action: IAction, result: unknown, timestamp: Date, id: number, completedHistoryEventIndex: number): Task { - return new Task( + public static SuccessfulTask(action: IAction, result: unknown, timestamp: Date, id: number, completedHistoryEventIndex: number): SingleTask { + return new SingleTask( true, false, action, @@ -23,8 +23,8 @@ export class TaskFactory { ); } - public static FailedTask(action: IAction, reason: string | undefined, timestamp: Date, id: number, completedHistoryEventIndex: number, exception: Error): Task { - return new Task( + public static FailedTask(action: IAction, reason: string | undefined, timestamp: Date, id: number, completedHistoryEventIndex: number, exception: Error): SingleTask { + return new SingleTask( true, true, action, @@ -44,7 +44,7 @@ export class TaskFactory { return new TimerTask(false, action); } - public static SuccessfulTaskSet(tasks: TaskBase[], completionIndex: number, result: unknown): TaskSet { + public static SuccessfulTaskSet(tasks: Task[], completionIndex: number, result: unknown): TaskSet { return new TaskSet( true, false, @@ -55,7 +55,7 @@ export class TaskFactory { ); } - public static FailedTaskSet(tasks: TaskBase[], completionIndex: number, exception: Error): TaskSet { + public static FailedTaskSet(tasks: Task[], completionIndex: number, exception: Error): TaskSet { return new TaskSet( true, true, @@ -66,7 +66,7 @@ export class TaskFactory { ); } - public static UncompletedTaskSet(tasks: TaskBase[]): TaskSet { + public static UncompletedTaskSet(tasks: Task[]): TaskSet { return new TaskSet( false, false, diff --git a/src/tasks/taskfilter.ts b/src/tasks/taskfilter.ts index 1e5a7b2..b5ec962 100644 --- a/src/tasks/taskfilter.ts +++ b/src/tasks/taskfilter.ts @@ -1,5 +1,5 @@ -import { Task } from "./task"; -import { CompletedTask, FailedTask, SuccessfulTask, TaskBase, UncompletedTask } from "./taskinterfaces"; +import { SingleTask } from "./task"; +import { CompletedTask, FailedTask, SuccessfulTask, Task, UncompletedTask } from "./taskinterfaces"; import { TaskSet } from "./taskset"; /** @hidden */ @@ -10,35 +10,35 @@ export class TaskFilter { return 0; } - public static isYieldable(task: any): task is TaskBase { - const taskBase = task as TaskBase; - return taskBase - && taskBase.isCompleted !== undefined - && taskBase.isFaulted !== undefined - && taskBase.yieldNewActions !== undefined; + public static isYieldable(task: any): task is Task { + const ATask = task as Task; + return ATask + && ATask.isCompleted !== undefined + && ATask.isFaulted !== undefined + && ATask.yieldNewActions !== undefined; } - public static isSingleTask(task: TaskBase): task is Task { - return (task instanceof Task); + public static isSingleTask(task: Task): task is SingleTask { + return (task instanceof SingleTask); } - public static isTaskSet(task: TaskBase): task is TaskSet { + public static isTaskSet(task: Task): task is TaskSet { return (task instanceof TaskSet); } - public static isCompletedTask(task: TaskBase): task is CompletedTask { + public static isCompletedTask(task: Task): task is CompletedTask { return task.isCompleted; } - public static isUncompletedTask(task: TaskBase): task is UncompletedTask { + public static isUncompletedTask(task: Task): task is UncompletedTask { return task.isCompleted === false; } - public static isSuccessfulTask(task: TaskBase): task is SuccessfulTask { + public static isSuccessfulTask(task: Task): task is SuccessfulTask { return task.isCompleted === true && task.isFaulted === false; } - public static isFailedTask(task: TaskBase): task is FailedTask { + public static isFailedTask(task: Task): task is FailedTask { return task.isCompleted === true && task.isFaulted === true; } } diff --git a/src/tasks/taskinterfaces.ts b/src/tasks/taskinterfaces.ts index e1f5ef5..f220215 100644 --- a/src/tasks/taskinterfaces.ts +++ b/src/tasks/taskinterfaces.ts @@ -2,20 +2,20 @@ import { IAction } from "../classes"; // Base interfaces /** @hidden */ -export interface TaskBase { +export interface Task { readonly isCompleted: boolean; readonly isFaulted: boolean; yieldNewActions(): IAction[]; } /** @hidden */ -export interface UncompletedTask extends TaskBase { +export interface UncompletedTask extends Task { readonly isCompleted: false; readonly isFaulted: false; } /** @hidden */ -export interface CompletedTask extends TaskBase { +export interface CompletedTask extends Task { readonly completionIndex: number; readonly isCompleted: true; readonly result: unknown | undefined; diff --git a/src/tasks/taskset.ts b/src/tasks/taskset.ts index 0acb98d..9ae099d 100644 --- a/src/tasks/taskset.ts +++ b/src/tasks/taskset.ts @@ -1,13 +1,13 @@ import { IAction } from "../classes"; -import { TaskBase } from "./taskinterfaces"; +import { Task } from "./taskinterfaces"; /** @hidden */ -export class TaskSet implements TaskBase { +export class TaskSet implements Task { constructor( public readonly isCompleted: boolean, public readonly isFaulted: boolean, - private readonly tasks: TaskBase[], + private readonly tasks: Task[], private readonly completionIndex?: number, public result?: unknown, public exception?: Error, diff --git a/src/tasks/timertask.ts b/src/tasks/timertask.ts index 4db4113..1168dbc 100644 --- a/src/tasks/timertask.ts +++ b/src/tasks/timertask.ts @@ -1,5 +1,5 @@ import { CreateTimerAction } from "../classes"; -import { Task } from "./task"; +import { SingleTask } from "./task"; /** * Returned from [[DurableOrchestrationClient]].[[createTimer]] if the call is @@ -40,7 +40,7 @@ import { Task } from "./task"; * } * ``` */ -export class TimerTask extends Task { +export class TimerTask extends SingleTask { /** @hidden */ constructor( isCompleted: boolean,