Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/durabletask-js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export { OrchestrationStatus as ProtoOrchestrationStatus } from "./proto/orchest
export { getName, whenAll, whenAny } from "./task";
export { Task } from "./task/task";

// Retry policies and task options
export { RetryPolicy, RetryPolicyOptions } from "./task/retry";
export { TaskOptions, SubOrchestrationOptions, taskOptionsFromRetryPolicy, subOrchestrationOptionsFromRetryPolicy } from "./task/options";

// Types
export { TOrchestrator } from "./types/orchestrator.type";
export { TActivity } from "./types/activity.type";
Expand Down
21 changes: 13 additions & 8 deletions packages/durabletask-js/src/task/context/orchestration-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { TActivity } from "../../types/activity.type";
import { TOrchestrator } from "../../types/orchestrator.type";
import { TaskOptions, SubOrchestrationOptions } from "../options";
import { Task } from "../task";

export abstract class OrchestrationContext {
Expand Down Expand Up @@ -48,27 +49,31 @@ export abstract class OrchestrationContext {
/**
* Schedule an activity for execution.
*
* @param {Orchestrator} orchestrator The sub-orchestrator function to call.
* @param {TInput} input The JSON-serializable input value for the sub-orchestrator function.
* @param {string} instanceId The ID to use for the sub-orchestration instance. If not provided, a new GUID will be used.
* @param {TActivity} activity The activity function to call.
* @param {TInput} input The JSON-serializable input value for the activity function.
* @param {TaskOptions} options Optional options to control the behavior of the activity execution, including retry policies.
*
* @returns {Task<TOutput>} A Durable Task that completes when the sub-orchestrator function completes.
* @returns {Task<TOutput>} A Durable Task that completes when the activity function completes.
*/
abstract callActivity<TInput, TOutput>(activity: TActivity<TInput, TOutput> | string, input?: TInput): Task<TOutput>;
abstract callActivity<TInput, TOutput>(
activity: TActivity<TInput, TOutput> | string,
input?: TInput,
options?: TaskOptions,
): Task<TOutput>;

/**
* Schedule sub-orchestrator function for execution.
*
* @param orchestrator A reference to the orchestrator function call
* @param orchestrator A reference to the orchestrator function to call.
* @param input The JSON-serializable input value for the orchestrator function.
* @param instanceId A unique ID to use for the sub-orchestration instance. If not provided, a new GUID will be used.
* @param options Optional options to control the behavior of the sub-orchestration, including retry policies and instance ID.
*
* @returns {Task<TOutput>} A Durable Task that completes when the sub-orchestrator function completes.
*/
abstract callSubOrchestrator<TInput, TOutput>(
orchestrator: TOrchestrator | string,
input?: TInput,
instanceId?: string,
options?: SubOrchestrationOptions,
): Task<TOutput>;
Comment thread
YunchuWang marked this conversation as resolved.

/**
Expand Down
9 changes: 9 additions & 0 deletions packages/durabletask-js/src/task/options/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

export {
TaskOptions,
SubOrchestrationOptions,
taskOptionsFromRetryPolicy,
subOrchestrationOptionsFromRetryPolicy,
} from "./task-options";
71 changes: 71 additions & 0 deletions packages/durabletask-js/src/task/options/task-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { RetryPolicy } from "../retry/retry-policy";

/**
* Options that can be used to control the behavior of orchestrator task execution.
*/
export interface TaskOptions {
/**
* The retry policy for the task.
* Controls how many times a task is retried and the delay between retries.
*/
retry?: RetryPolicy;
}

/**
* Options that can be used to control the behavior of sub-orchestrator execution.
* Extends TaskOptions with additional options specific to sub-orchestrations.
*/
export interface SubOrchestrationOptions extends TaskOptions {
/**
* The unique ID to use for the sub-orchestration instance.
* If not specified, a deterministic ID will be generated based on the parent instance ID.
*/
instanceId?: string;
}

/**
* Creates a TaskOptions instance from a RetryPolicy.
*
* @param policy - The retry policy to use
* @returns A TaskOptions instance configured with the retry policy
*
* @example
* ```typescript
* const retryPolicy = new RetryPolicy({
* maxNumberOfAttempts: 3,
* firstRetryIntervalInMilliseconds: 1000
* });
*
* const options = taskOptionsFromRetryPolicy(retryPolicy);
* ```
*/
export function taskOptionsFromRetryPolicy(policy: RetryPolicy): TaskOptions {
return { retry: policy };
}

/**
* Creates a SubOrchestrationOptions instance from a RetryPolicy and optional instance ID.
*
* @param policy - The retry policy to use
* @param instanceId - Optional instance ID for the sub-orchestration
* @returns A SubOrchestrationOptions instance configured with the retry policy
*
* @example
* ```typescript
* const retryPolicy = new RetryPolicy({
* maxNumberOfAttempts: 3,
* firstRetryIntervalInMilliseconds: 1000
* });
*
* const options = subOrchestrationOptionsFromRetryPolicy(retryPolicy, "my-sub-orch-123");
* ```
*/
export function subOrchestrationOptionsFromRetryPolicy(
policy: RetryPolicy,
instanceId?: string,
): SubOrchestrationOptions {
return { retry: policy, instanceId };
}
31 changes: 31 additions & 0 deletions packages/durabletask-js/src/task/retry-timer-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { CompletableTask } from "./completable-task";
import { RetryableTask } from "./retryable-task";

/**
* A timer task that is associated with a retryable task for retry purposes.
*
* When this timer fires, the retryable task should be rescheduled for another attempt.
*/
export class RetryTimerTask<T> extends CompletableTask<T> {
private readonly _retryableParent: RetryableTask<any>;

/**
* Creates a new RetryTimerTask.
*
* @param retryableParent - The retryable task that this timer is associated with
*/
constructor(retryableParent: RetryableTask<any>) {
super();
this._retryableParent = retryableParent;
}

/**
* Gets the retryable task that this timer is associated with.
*/
get retryableParent(): RetryableTask<any> {
return this._retryableParent;
}
}
4 changes: 4 additions & 0 deletions packages/durabletask-js/src/task/retry/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

export { RetryPolicy, RetryPolicyOptions } from "./retry-policy";
159 changes: 159 additions & 0 deletions packages/durabletask-js/src/task/retry/retry-policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

/**
* A declarative retry policy that can be configured for activity or sub-orchestration calls.
*
* @remarks
* Retry policies control how many times a task is retried and the delay between retries.
* The delay between retries increases exponentially based on the backoffCoefficient.
*
* @example
* ```typescript
* const retryPolicy = new RetryPolicy({
* maxNumberOfAttempts: 5,
* firstRetryIntervalInMilliseconds: 1000,
* backoffCoefficient: 2.0,
* maxRetryIntervalInMilliseconds: 30000,
* retryTimeoutInMilliseconds: 300000
* });
* ```
*/
export class RetryPolicy {
private readonly _maxNumberOfAttempts: number;
private readonly _firstRetryIntervalInMilliseconds: number;
private readonly _backoffCoefficient: number;
private readonly _maxRetryIntervalInMilliseconds: number;
private readonly _retryTimeoutInMilliseconds: number;

/**
* Creates a new RetryPolicy instance.
*
* @param options - The retry policy options
* @throws Error if any of the validation constraints are violated
*/
constructor(options: RetryPolicyOptions) {
const {
maxNumberOfAttempts,
firstRetryIntervalInMilliseconds,
backoffCoefficient = 1.0,
maxRetryIntervalInMilliseconds,
retryTimeoutInMilliseconds,
} = options;

// Validation aligned with .NET SDK
if (maxNumberOfAttempts <= 0) {
throw new Error("maxNumberOfAttempts must be greater than zero");
}

if (firstRetryIntervalInMilliseconds <= 0) {
throw new Error("firstRetryIntervalInMilliseconds must be greater than zero");
}

if (backoffCoefficient < 1.0) {
throw new Error("backoffCoefficient must be greater than or equal to 1.0");
}

if (
maxRetryIntervalInMilliseconds !== undefined &&
maxRetryIntervalInMilliseconds !== -1 &&
maxRetryIntervalInMilliseconds < firstRetryIntervalInMilliseconds
) {
throw new Error("maxRetryIntervalInMilliseconds must be greater than or equal to firstRetryIntervalInMilliseconds");
}

if (
retryTimeoutInMilliseconds !== undefined &&
retryTimeoutInMilliseconds !== -1 &&
retryTimeoutInMilliseconds < firstRetryIntervalInMilliseconds
) {
throw new Error("retryTimeoutInMilliseconds must be greater than or equal to firstRetryIntervalInMilliseconds");
}

this._maxNumberOfAttempts = maxNumberOfAttempts;
this._firstRetryIntervalInMilliseconds = firstRetryIntervalInMilliseconds;
this._backoffCoefficient = backoffCoefficient;
// Default to 1 hour (3600000ms) if not specified, -1 means infinite
this._maxRetryIntervalInMilliseconds = maxRetryIntervalInMilliseconds ?? 3600000;
// Default to -1 (infinite) if not specified
this._retryTimeoutInMilliseconds = retryTimeoutInMilliseconds ?? -1;
}

/**
* Gets the max number of attempts for executing a given task.
*/
get maxNumberOfAttempts(): number {
return this._maxNumberOfAttempts;
}

/**
* Gets the amount of time in milliseconds to delay between the first and second attempt.
*/
get firstRetryIntervalInMilliseconds(): number {
return this._firstRetryIntervalInMilliseconds;
}

/**
* Gets the exponential back-off coefficient used to determine the delay between subsequent retries.
* @remarks Defaults to 1.0 for no back-off.
*/
get backoffCoefficient(): number {
return this._backoffCoefficient;
}

/**
* Gets the maximum time in milliseconds to delay between attempts.
* @remarks Defaults to 1 hour (3600000ms). Use -1 for infinite.
*/
get maxRetryIntervalInMilliseconds(): number {
return this._maxRetryIntervalInMilliseconds;
}

/**
* Gets the overall timeout for retries in milliseconds.
* No further attempts will be made after this timeout expires.
* @remarks Defaults to -1 (infinite).
*/
get retryTimeoutInMilliseconds(): number {
return this._retryTimeoutInMilliseconds;
}
}

/**
* Options for creating a RetryPolicy.
*/
export interface RetryPolicyOptions {
/**
* The maximum number of task invocation attempts. Must be 1 or greater.
*/
maxNumberOfAttempts: number;

/**
* The amount of time in milliseconds to delay between the first and second attempt.
* Must be greater than 0.
*/
firstRetryIntervalInMilliseconds: number;

/**
* The exponential back-off coefficient used to determine the delay between subsequent retries.
* Must be 1.0 or greater.
* @default 1.0
*/
backoffCoefficient?: number;

/**
* The maximum time in milliseconds to delay between attempts.
* Must be greater than or equal to firstRetryIntervalInMilliseconds.
* Use -1 for infinite (no maximum).
* @default 3600000 (1 hour)
*/
maxRetryIntervalInMilliseconds?: number;

/**
* The overall timeout for retries in milliseconds.
* No further attempts will be made after this timeout expires.
* Use -1 for infinite (no timeout).
* @default -1 (infinite)
*/
retryTimeoutInMilliseconds?: number;
}
Loading
Loading