cockatiel
Version:
A resilience and transient-fault-handling library that allows developers to express policies such as Backoff, Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. Inspired by .NET Polly.
323 lines (322 loc) • 12.4 kB
TypeScript
import { IBackoffFactory } from './backoff/Backoff';
import { BulkheadPolicy } from './BulkheadPolicy';
import { CircuitBreakerPolicy, ICircuitBreakerOptions } from './CircuitBreakerPolicy';
import { Event } from './common/Event';
import { FallbackPolicy } from './FallbackPolicy';
import { NoopPolicy } from './NoopPolicy';
import { IRetryBackoffContext, RetryPolicy } from './RetryPolicy';
import { TimeoutPolicy, TimeoutStrategy } from './TimeoutPolicy';
type Constructor<T> = new (...args: any) => T;
export interface IBasePolicyOptions {
errorFilter: (error: Error) => boolean;
resultFilter: (result: unknown) => boolean;
}
/**
* The reason for a call failure. Either an error, or the a value that was
* marked as a failure (when using result filtering).
*/
export type FailureReason<ReturnType> = {
error: Error;
} | {
value: ReturnType;
};
/**
* Event emitted on the `onFailure` calls.
*/
export interface IFailureEvent {
/**
* Call duration, in milliseconds (with nanosecond precision, as the OS allows).
*/
duration: number;
/**
* Whether the error was handled by the policy.
*/
handled: boolean;
/**
* The reason for the error.
*/
reason: FailureReason<unknown>;
}
/**
* Event emitted on the `onSuccess` calls.
*/
export interface ISuccessEvent {
/**
* Call duration, in milliseconds (with nanosecond precision, as the OS allows).
*/
duration: number;
}
export interface IDefaultPolicyContext {
/**
* Abort signal for the operation. This is propagated through multiple
* retry policies.
*/
signal: AbortSignal;
}
/**
* IPolicy is the type of all policies that Cockatiel provides. It describes
* an execute() function which takes a generic argument.
*/
export interface IPolicy<ContextType extends IDefaultPolicyContext = IDefaultPolicyContext, AltReturn = never> {
/**
* Virtual property only used for TypeScript--will not actually be defined.
* @deprecated This property does not exist
*/
readonly _altReturn: AltReturn;
/**
* Fires on the policy when a request successfully completes and some
* successful value will be returned. In a retry policy, this is fired once
* even if the request took multiple retries to succeed.
*/
readonly onSuccess: Event<ISuccessEvent>;
/**
* Fires on the policy when a request fails *due to a handled reason* fails
* and will give rejection to the called.
*/
readonly onFailure: Event<IFailureEvent>;
/**
* Runs the function through behavior specified by the policy.
*/
execute<T>(fn: (context: ContextType) => PromiseLike<T> | T, signal?: AbortSignal): Promise<T | AltReturn>;
}
export interface IMergedPolicy<A extends IDefaultPolicyContext, B, W extends IPolicy<any, any>[]> extends IPolicy<A, B> {
readonly wrapped: W;
}
type MergePolicies<A, B> = A extends IPolicy<infer A1, any> ? B extends IPolicy<infer B1, any> ? IMergedPolicy<A1 & B1, A['_altReturn'] | B['_altReturn'], B extends IMergedPolicy<any, any, infer W> ? [A, ...W] : [A, B]> : never : never;
export declare class Policy {
readonly options: Readonly<IBasePolicyOptions>;
/**
* Factory that builds a base set of filters that can be used in circuit
* breakers, retries, etc.
*/
constructor(options: Readonly<IBasePolicyOptions>);
/**
* Allows the policy to additionally handles errors of the given type.
*
* @param cls Class constructor to check that the error is an instance of.
* @param predicate If provided, a function to be called with the error
* which should return "true" if we want to handle this error.
* @example
* ```js
* // retry both network errors and response errors with a 503 status code
* new Policy()
* .orType(NetworkError)
* .orType(ResponseError, err => err.statusCode === 503)
* .retry()
* .attempts(3)
* .execute(() => getJsonFrom('https://example.com'));
* ```
*/
orType<T>(cls: Constructor<T>, predicate?: (error: T) => boolean): Policy;
/**
* Allows the policy to additionally handles errors that pass the given
* predicate function.
*
* @param predicate Takes any thrown error, and returns true if it should
* be retried by this policy.
* @example
* ```js
* // only retry if the error has a "shouldBeRetried" property set
* new Policy()
* .orWhen(err => err.shouldBeRetried === true)
* .retry()
* .attempts(3)
* .execute(() => getJsonFrom('https://example.com'));
* ```
*/
orWhen(predicate: (error: Error) => boolean): Policy;
/**
* Adds handling for return values. The predicate will be called with
* the return value of the executed function,
*
* @param predicate Takes the returned value, and returns true if it
* should be retried by this policy.
* @example
* ```js
* // retry when the response status code is a 5xx
* new Policy()
* .orResultWhen(res => res.statusCode >= 500)
* .retry()
* .attempts(3)
* .execute(() => getJsonFrom('https://example.com'));
* ```
*/
orWhenResult(predicate: (r: unknown) => boolean): Policy;
/**
* Adds handling for return values. The predicate will be called with
* the return value of the executed function,
*
* @param predicate Takes the returned value, and returns true if it
* should be retried by this policy.
* @example
* ```js
* // retry when the response status code is a 5xx
* new Policy()
* .orResultType(res => res.statusCode >= 500)
* .retry()
* .attempts(3)
* .execute(() => getJsonFrom('https://example.com'));
* ```
*/
orResultType<T>(cls: Constructor<T>, predicate?: (error: T) => boolean): Policy;
}
export declare const noop: NoopPolicy;
/**
* A policy that handles all errors.
*/
export declare const handleAll: Policy;
/**
* See {@link Policy.orType} for usage.
*/
export declare function handleType<T>(cls: Constructor<T>, predicate?: (error: T) => boolean): Policy;
/**
* See {@link Policy.orWhen} for usage.
*/
export declare function handleWhen(predicate: (error: Error) => boolean): Policy;
/**
* See {@link Policy.orResultType} for usage.
*/
export declare function handleResultType<T>(cls: Constructor<T>, predicate?: (error: T) => boolean): Policy;
/**
* See {@link Policy.orWhenResult} for usage.
*/
export declare function handleWhenResult(predicate: (error: unknown) => boolean): Policy;
/**
* Creates a bulkhead--a policy that limits the number of concurrent calls.
*/
export declare function bulkhead(limit: number, queue?: number): BulkheadPolicy;
/**
* A decorator that can be used to wrap class methods and apply the given
* policy to them. It also adds the last argument normally given in
* {@link Policy.execute} as the last argument in the function call.
* For example:
*
* ```ts
* import { usePolicy, retry, handleAll } from 'cockatiel';
*
* const retry = retry(handleAll, { maxAttempts: 3 });
*
* class Database {
* @usePolicy(retry)
* public getUserInfo(userId, context, cancellationToken) {
* console.log('Retry attempt number', context.attempt);
* // implementation here
* }
* }
*
* const db = new Database();
* db.getUserInfo(3).then(info => console.log('User 3 info:', info))
* ```
*
* Note that it will force the return type to be a Promise, since that's
* what policies return.
*/
export declare function usePolicy(policy: IPolicy<IDefaultPolicyContext, never>): (_target: unknown, _key: string, descriptor: PropertyDescriptor) => void;
/**
* Creates a timeout policy.
* @param duration - How long to wait before timing out execute()'d functions
* @param strategy - Strategy for timeouts, "Cooperative" or "Aggressive".
* A {@link CancellationToken} will be pass to any executed function, and in
* cooperative timeouts we'll simply wait for that function to return or
* throw. In aggressive timeouts, we'll immediately throw a
* {@link TaskCancelledError} when the timeout is reached, in addition to
* marking the passed token as failed.
*/
export declare function timeout(duration: number, strategyOrOpts: TimeoutStrategy | {
strategy: TimeoutStrategy;
abortOnReturn: boolean;
}): TimeoutPolicy;
/**
* Wraps the given set of policies into a single policy. For instance, this:
*
* ```js
* retry.execute(() =>
* breaker.execute(() =>
* timeout.execute(({ cancellationToken }) => getData(cancellationToken))))
* ```
*
* Is the equivalent to:
*
* ```js
* Policy
* .wrap(retry, breaker, timeout)
* .execute(({ cancellationToken }) => getData(cancellationToken)));
* ```
*
* The `context` argument passed to the executed function is the merged object
* of all previous policies.
*
*/
export declare function wrap<A extends IPolicy<IDefaultPolicyContext, unknown>>(p1: A): A;
export declare function wrap<A extends IPolicy<IDefaultPolicyContext, unknown>, B extends IPolicy<IDefaultPolicyContext, unknown>>(p1: A, p2: B): MergePolicies<A, B>;
export declare function wrap<A extends IPolicy<IDefaultPolicyContext, unknown>, B extends IPolicy<IDefaultPolicyContext, unknown>, C extends IPolicy<IDefaultPolicyContext, unknown>>(p1: A, p2: B, p3: C): MergePolicies<C, MergePolicies<A, B>>;
export declare function wrap<A extends IPolicy<IDefaultPolicyContext, unknown>, B extends IPolicy<IDefaultPolicyContext, unknown>, C extends IPolicy<IDefaultPolicyContext, unknown>, D extends IPolicy<IDefaultPolicyContext, unknown>>(p1: A, p2: B, p3: C, p4: D): MergePolicies<D, MergePolicies<C, MergePolicies<A, B>>>;
export declare function wrap<A extends IPolicy<IDefaultPolicyContext, unknown>, B extends IPolicy<IDefaultPolicyContext, unknown>, C extends IPolicy<IDefaultPolicyContext, unknown>, D extends IPolicy<IDefaultPolicyContext, unknown>, E extends IPolicy<IDefaultPolicyContext, unknown>>(p1: A, p2: B, p3: C, p4: D, p5: E): MergePolicies<E, MergePolicies<D, MergePolicies<C, MergePolicies<A, B>>>>;
export declare function wrap<C extends IDefaultPolicyContext, A>(...p: Array<IPolicy<C, A>>): IPolicy<C, A>;
/**
* Creates a retry policy. The options should contain the backoff strategy to
* use. Included strategies are:
* - {@link ConstantBackoff}
* - {@link ExponentialBackoff}
* - {@link IterableBackoff}
* - {@link DelegateBackoff} (advanced)
*
* For example:
*
* ```
* import { handleAll, retry } from 'cockatiel';
*
* const policy = retry(handleAll, { backoff: new ExponentialBackoff() });
* ```
*
* You can optionally pass in the `attempts` to limit the maximum number of
* retry attempts per call.
*/
export declare function retry(policy: Policy, opts: {
maxAttempts?: number;
backoff?: IBackoffFactory<IRetryBackoffContext<unknown>>;
}): RetryPolicy;
/**
* Returns a circuit breaker for the policy. **Important**: you should share
* your circuit breaker between executions of whatever function you're
* wrapping for it to function!
*
* ```ts
* import { SamplingBreaker, Policy } from 'cockatiel';
*
* // Break if more than 20% of requests fail in a 30 second time window:
* const breaker = Policy
* .handleAll()
* .circuitBreaker(10_000, new SamplingBreaker(0.2, 30 * 1000));
*
* export function handleRequest() {
* return breaker.execute(() => getInfoFromDatabase());
* }
* ```
*
* @param halfOpenAfter Time after failures to try to open the circuit
* breaker again.
* @param breaker The circuit breaker to use. This package exports
* ConsecutiveBreaker and SamplingBreakers for you to use.
*/
export declare function circuitBreaker(policy: Policy, opts: ICircuitBreakerOptions): CircuitBreakerPolicy;
/**
* Falls back to the given value in the event of an error.
*
* ```ts
* import { Policy } from 'cockatiel';
*
* const fallback = Policy
* .handleType(DatabaseError)
* .fallback(() => getStaleData());
*
* export function handleRequest() {
* return fallback.execute(() => getInfoFromDatabase());
* }
* ```
*
* @param toValue Value to fall back to, or a function that creates the
* value to return (any may return a promise)
*/
export declare function fallback<R>(policy: Policy, valueOrFactory: (() => Promise<R> | R) | R): FallbackPolicy<R>;
export {};