npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@mfdlabs/sentinels

v1.0.0

Published

A Node.js package that provides a set of circuit breakers and sentinels for your application.

Downloads

2

Readme

@mfdlabs/sentinels

This is Node.js package that provides circuit breaker and sentinel functionality.

Examples

import { ExecutionCircuitBreaker } from '@mfdlabs/sentinels';

const breaker = new ExecutionCircuitBreaker(
  'test',
  (ex) => true,
  () => 5000,
);

try {
  breaker.execute(() => {
    throw new Error('test');
  });
} catch (err) {
  console.log(err); // Error: test
}

try {
  breaker.execute(() => {
    throw new Error('test');
  });
} catch (err) {
  console.log(err); // [CircuitBreaker Error]: 'test' has been tripped for xxx seconds.
}

// It also works with async functions
try {
  await breaker.executeAsync(async () => {
    throw new Error('test');
  });
} catch (err) {
  console.log(err); // [CircuitBreaker Error]: 'test' has been tripped for xxx seconds.
}

Exports

The package exports the following:

/**
 * The jitter enum for the exponential backoff algorithm.
 */
export enum Jitter {
  /**
   * No jitter.
   */
  None = 0,
  /**
   * Full jitter.
   */
  Full = 1,
  /**
   * Equal jitter.
   */
  Equal = 2,
}

/**
 * Error thrown when the circuit breaker is tripped.
 */
export class CircuitBreakerError extends Error {
  /**
   * Construct a new instance of the CircuitBreakerError class.
   * @param {string} message The error message.
   */
  constructor(message: string);
}

/**
 * Base class for circuit breakers.
 */
export abstract class CircuitBreakerBase implements ICircuitBreaker {
  /**
   * The name of the circuit breaker.
   */
  protected abstract get name(): string;

  /**
   * The date when the circuit breaker was tripped.
   * If the circuit breaker is not tripped, this value is undefined.
   */
  protected trippedAt: Date | undefined;

  /**
   * Gets the current date time.
   */
  protected get now(): Date;

  /**
   * Is the circuit breaker tripped?
   */
  get isTripped(): boolean;

  /**
   * Resets the circuit breaker.
   * @returns {boolean} True if the circuit breaker was reset, false otherwise.
   */
  reset(): boolean;

  /**
   * Tests the circuit breaker.
   * @throws {Error} If the circuit breaker is tripped.
   */
  test(): void;

  /**
   * Trips the circuit breaker.
   * @returns {boolean} True if the circuit breaker was tripped, false otherwise.
   */
  trip(): boolean;

  /**
   * Get the trip error.
   * @returns {Error} The trip error.
   */
  getTripError(): Error;
}

/**
 * Base class for circuit breaker policies.
 * @template TExecutionContext The type of the execution context.
 */
export abstract class CircuitBreakerPolicyBase<TExecutionContext> implements ICircuitBreakerPolicy<TExecutionContext> {
  /**
   * Construct a new instance of the CircuitBreakerPolicyBase class.
   * @param {ITripReasonAuthority<TExecutionContext>} tripReasonAuthority The trip reason authority.
   */
  protected constructor(tripReasonAuthority: ITripReasonAuthority<TExecutionContext>);

  /**
   * An action to perform when the circuit breaker is terminating a request.
   */
  onTerminatingRequest: OnTerminatingRequest | undefined;

  /**
   * An action to perform when a request to open the circuit breaker is made.
   */
  onRequestToOpen: OnRequestToOpen | undefined;

  /**
   * Notify the policy that a request has finished.
   * @param {TExecutionContext} executionContext The execution context.
   * @param {Error} error The error that occurred.
   */
  notifyRequestFinished(executionContext: TExecutionContext, error: Error): void;

  /**
   * Throw if the circuit breaker is tripped.
   * @param {TExecutionContext} executionContext The execution context.
   */
  throwIfTripped(executionContext: TExecutionContext): void;

  /**
   * Determine if the circuit breaker is openened.
   * @param {TExecutionContext} executionContext The execution context.
   * @returns {[boolean, Error]} A tuple containing a boolean indicating if the circuit breaker is openened and the error that caused the circuit breaker to open.
   */
  protected abstract isCircuitBreakerOpen(executionContext: TExecutionContext): [boolean, Error];

  /**
   * Try to trip the circuit breaker.
   * @param {TExecutionContext} executionContext The execution context.
   * @returns {boolean} True if the circuit breaker was tripped, otherwise false.
   */
  protected abstract tryToTripCircuitBreaker(executionContext: TExecutionContext): boolean;

  /**
   * An action to perform when a request has finished successfully.
   * @param {TExecutionContext} executionContext The execution context.
   * @returns {void}
   */
  protected abstract onSuccessfulRequest(executionContext: TExecutionContext): void;

  /**
   * An action to perform when the policy is notified.
   * @param {TExecutionContext} executionContext The execution context.
   * @returns {void}
   */
  protected abstract onNotified(executionContext: TExecutionContext): void;
}

/**
 * Represents a function that can be executed by an execution circuit breaker.
 */
export type Action = () => void;

/**
 * Represents a function that can be executed by an execution circuit breaker asynchronously.
 * @returns {Promise<void>} A promise that resolves when the function has completed.
 */
export type AsyncAction = () => Promise<void>;

/**
 * Base class for execution circuit breakers.
 */
export abstract class ExecutionCircuitBreakerBase extends CircuitBreakerBase {
  /**
   * The retry interval for the circuit breaker.
   */
  protected abstract get retryInterval(): number;

  /**
   * Should this circuit breaker trip?
   * @param {Error} error The error that was thrown.
   * @returns {boolean} True if the circuit breaker should trip, false otherwise.
   */
  protected abstract shouldTrip(error: Error): boolean;

  /**
   * Execute the given circuit action.
   * @param {Action} action The action to execute.
   */
  execute(action: Action): void;

  /**
   * Execute the given circuit action asynchronously.
   * @param {AsyncAction} action The action to execute.
   * @returns {Promise<void>} A promise that resolves when the action is executed.
   */
  executeAsync(action: AsyncAction): Promise<void>;

  /**
   * Reset the circuit breaker.
   * @returns {boolean} True if the circuit breaker was reset, false otherwise.
   */
  reset(): boolean;
}

/**
 * Base class for trip reason authorities.
 * @template TExecutionContext The type of the execution context.
 */
export abstract class TripReasonAuthorityBase<TExecutionContext> implements ITripReasonAuthority<TExecutionContext> {
  /**
   * Is a reason for tripping the circuit breaker.
   * @param {TExecutionContext} executionContext The execution context.
   * @param {Error} error The error that occurred.
   * @returns {boolean} True if the error is a reason for tripping the circuit breaker, otherwise false.
   */
  abstract isReasonForTrip(executionContext: TExecutionContext, error: Error): boolean;
}

/**
 * Simple circuit breaker. Provides an implementation for the name property.
 */
export class CircuitBreaker extends CircuitBreakerBase {
  /**
   * Construct a new instance of the CircuitBreaker class.
   * @param {string} name The name of the circuit breaker.
   */
  constructor(name: string);

  /**
   * The name of the circuit breaker.
   * @returns {string} The name of the circuit breaker.
   * @override
   */
  protected get name(): string;
}

/**
 * Represents a function to determine if an error is a failure.
 * @param {Error} error The error to check.
 * @returns {boolean} True if the error is a failure, otherwise false.
 */
export type FailureDetector = (error: Error) => boolean;

/**
 * Represents a function to calculate the retry interval.
 * @returns {number} The retry interval.
 */
export type RetryIntervalCalculator = () => number;

/**
 * Simple execution circuit breaker.
 */
export class ExecutionCircuitBreaker extends ExecutionCircuitBreakerBase {
  /**
   * Construct a new instance of the ExecutionCircuitBreaker class.
   * @param {string} name The name of the circuit breaker.
   * @param {FailureDetector} failureDetector The function to determine if an error is a failure.
   * @param {RetryIntervalCalculator} retryIntervalCalculator The function to calculate the retry interval.
   */
  constructor(name: string, failureDetector: FailureDetector, retryIntervalCalculator: RetryIntervalCalculator);

  /**
   * The name of the circuit breaker.
   * @returns {string} The name of the circuit breaker.
   * @override
   */
  protected get name(): string;

  /**
   * The function to determine if this circuit breaker should be tripped.
   * @param {Error} error The error to check.
   * @returns {boolean} True if the circuit breaker should be tripped, otherwise false.
   * @override
   */
  protected shouldTrip(error: Error): boolean;

  /**
   * Get the retry interval.
   * @returns {number} The retry interval.
   * @override
   */
  protected get retryInterval(): number;
}

/**
 * Represents a function to get the error count for tripping the circuit breaker.
 * @returns {number} The error count for tripping the circuit breaker.
 */
export type ErrorCountGetter = () => number;

/**
 * Represents a function to get the error interval for tripping the circuit breaker.
 * @returns {number} The error interval for tripping the circuit breaker.
 * @remarks The error interval is the time window in which the error count is calculated.
 */
export type ErrorIntervalGetter = () => number;

/**
 * Execution circuit breaker with an error threshold.
 */
export class ThresholdExecutionCircuitBreaker extends ExecutionCircuitBreakerBase {
  /**
   * Construct a new instance of the ThresholdExecutionCircuitBreaker class.
   * @param {string} name The name of the circuit breaker.
   * @param {FailureDetector} failureDetector The function to determine if an error is a failure.
   * @param {RetryIntervalCalculator} retryIntervalCalculator The function to calculate the retry interval.
   * @param {ErrorCountGetter} errorCountGetter The function to get the error count for tripping the circuit breaker.
   * @param {ErrorIntervalGetter} errorIntervalGetter The function to get the error interval for tripping the circuit breaker.
   */
  constructor(
    name: string,
    failureDetector: FailureDetector,
    retryIntervalCalculator: RetryIntervalCalculator,
    errorCountGetter: ErrorCountGetter,
    errorIntervalGetter: ErrorIntervalGetter,
  );

  /**
   * The name of the circuit breaker.
   * @returns {string} The name of the circuit breaker.
   * @override
   */
  protected get name(): string;

  /**
   * Should the circuit breaker be tripped.
   * @param {Error} error The error.
   * @returns {boolean} True if the circuit breaker should be tripped, otherwise false.
   */
  protected shouldTrip(error: Error): boolean;

  /**
   * Get the retry interval.
   * @returns {number} The retry interval.
   * @override
   */
  protected get retryInterval(): number;
}

/**
 * Default circuit breaker policy.
 * @template TExecutionContext The type of the execution context.
 */
export class DefaultCircuitBreakerPolicy<TExecutionContext> extends CircuitBreakerPolicyBase<TExecutionContext> {
  /**
   * The circuit breaker policy configuration.
   */
  protected readonly config: IDefaultCircuitBreakerPolicyConfig;

  /**
   * Construct a new instance of the DefaultCircuitBreakerPolicy class.
   * @param {string} circuitBreakerIdentifier The identifier of the circuit breaker.
   * @param {IDefaultCircuitBreakerPolicyConfig} config The circuit breaker policy configuration.
   * @param {ITripReasonAuthority<TExecutionContext>} tripReasonAuthority The trip reason authority.
   */
  constructor(
    circuitBreakerIdentifier: string,
    config: IDefaultCircuitBreakerPolicyConfig,
    tripReasonAuthority: ITripReasonAuthority<TExecutionContext>,
  );

  /**
   * Is the circuit breaker open?
   * @param {TExecutionContext} executionContext The execution context.
   * @returns {[boolean, Error]} A tuple containing a boolean indicating whether the circuit breaker is open and an error if the circuit breaker is open.
   * @override
   */
  protected isCircuitBreakerOpen(executionContext: TExecutionContext): [boolean, Error];

  /**
   * On successful reequst.
   * @param {TExecutionContext} executionContext The execution context.
   * @override
   */
  protected onSuccessfulRequest(executionContext: TExecutionContext): void;

  /**
   * On notified.
   * @param {TExecutionContext} executionContext The execution context.
   * @override
   */
  protected onNotified(executionContext: TExecutionContext): void;

  /**
   * Try to trip the circuit breaker.
   * @param {TExecutionContext} executionContext The execution context.
   * @returns {boolean} A boolean indicating whether the circuit breaker was tripped.
   * @override
   */
  protected tryToTripCircuitBreaker(executionContext: TExecutionContext): boolean;
}

/**
 * Configuration for the default circuit breaker policy.
 */
export class DefaultCircuitBreakerPolicyConfig implements IDefaultCircuitBreakerPolicyConfig {
  /**
   * Retry interval in milliseconds.
   * @returns {number} Retry interval in milliseconds.
   */
  get retryInterval(): number;

  /**
   * Retry interval in milliseconds.
   * @param {number} value Retry interval in milliseconds.
   */
  set retryInterval(value: number);

  /**
   * Failures allowed before trip.
   * @returns {number} Failures allowed before trip.
   */
  get failuresAllowedBeforeTrip(): number;

  /**
   * Failures allowed before trip.
   * @param {number} value Failures allowed before trip.
   * @throws {Error} Failures allowed before trip is out of range.
   */
  set failuresAllowedBeforeTrip(value: number);
}

/**
 * Represents a function to determine if the service is healthy.
 * @returns {boolean} True if the service is healthy, otherwise false.
 */
export type HealthCheck = () => boolean;

/**
 * Represents a function to get the monitor interval.
 * @returns {number} The monitor interval.
 */
export type MonitorIntervalGetter = () => number;

/**
 * Simple sentinel for a health check service.
 */
export class ServiceSentinel implements ISentinel {
  /**
   * The function to get the monitor interval.
   */
  protected readonly monitorIntervalGetter: MonitorIntervalGetter;

  /**
   * The timer that monitors the service.
   */
  protected monitorTimer: NodeJS.Timeout;

  /**
   * Construct a new instance of the ServiceSentinel class.
   * @param {HealthCheck} healthCheck The function to determine if the service is healthy.
   * @param {MonitorIntervalGetter} monitorIntervalGetter The function to get the monitor interval.
   * @param {boolean} isHealthy The initial health status.
   */
  constructor(healthCheck: HealthCheck, monitorIntervalGetter: MonitorIntervalGetter, isHealthy: boolean);

  /**
   * Monitor the service. Protected so that classes that use monitorTimer can put the argument in the callback.
   * @returns {void}
   */
  protected monitor(): void;

  /**
   * Get the health status of the service.
   * @returns {boolean} True if the service is healthy, otherwise false.
   */
  get isHealthy(): boolean;

  /**
   * Dispose the sentinel.
   * @param {boolean} disposing True if disposing, otherwise false.
   */
  dispose(disposing: boolean): void;
}

/**
 * Service sentinel that can be toggled.
 */
export class TogglableServiceSentinel extends ServiceSentinel {
  /**
   * Construct a new instance of the TogglableServiceSentinel class.
   * @param {HealthCheck} healthCheck The function to determine if the service is healthy.
   * @param {MonitorIntervalGetter} monitorIntervalGetter The function to get the monitor interval.
   * @param {boolean} isHealthy The initial health status.
   */
  constructor(healthCheck: HealthCheck, monitorIntervalGetter: MonitorIntervalGetter, isHealthy: boolean);

  /**
   * Start the sentinel.
   * @returns {void}
   */
  start(): void;

  /**
   * Stop the sentinel.
   * @returns {void}
   */
  stop(): void;
}

/**
 * A class that calculates the exponential backoff time.
 */
export class ExponentialBackoff {
  /**
   * The ceiling for the maximum number of attempts.
   */
  static readonly ceilingForMaxAttempts = 10;

  /**
   * Calculates the exponential backoff time.
   * @param {number} attempt The attempt number.
   * @param {number} maxAttempts The maximum number of attempts.
   * @param {number} baseDelay The base delay.
   * @param {number} maxDelay The maximum delay.
   * @param {Jitter} jitter The jitter.
   * @returns {number} The exponential backoff time.
   * @throws {Error} The max attempts must be between 0 and 10.
   */
  static calculateBackoff(
    attempt: number,
    maxAttempts: number,
    baseDelay: number,
    maxDelay: number,
    jitter: Jitter,
  ): number;
}

/**
 * Interface for the sentinel.
 */
export interface ISentinel {
  /**
   * Is the sentinel healthy.
   * @returns {boolean} Is the sentinel healthy.
   */
  get isHealthy(): boolean;
}

/**
 * Interface for the circuit breaker.
 */
export interface ICircuitBreaker {
  /**
   * Is the circuit breaker tripped.
   * @returns {boolean} Is the circuit breaker tripped.
   */
  get isTripped(): boolean;

  /**
   * Reset the circuit breaker.
   * @returns {boolean} Is the circuit breaker tripped.
   */
  reset(): boolean;

  /**
   * Test the circuit breaker.
   * @returns {void}
   * @throws {CircuitBreakerError} Circuit breaker is tripped.
   */
  test(): void;

  /**
   * Trip the circuit breaker.
   * @returns {boolean} Is the circuit breaker tripped.
   */
  trip(): boolean;
}

/**
 * Interface for the trip reason authority.
 * @template TExecutionContext The execution context type.
 */
export interface ITripReasonAuthority<in TExecutionContext> {
  /**
   * Is this a reason to trip the circuit breaker?
   * @param {TExecutionContext} executionContext The execution context.
   * @param {Error} error The error.
   * @returns {boolean} Is this a reason to trip the circuit breaker?
   */
  isReasonForTrip(executionContext: TExecutionContext, error: Error): boolean;
}

/**
 * Function that is called when the circuit breaker is terminating a request.
 */
export type OnTerminatingRequest = () => void;

/**
 * Function that is called when a request to open the circuit breaker is made.
 */
export type OnRequestToOpen = () => void;

/**
 * Interface for the circuit breaker policy.
 * @template TExecutionContext The execution context type.
 */
export interface ICircuitBreakerPolicy<in TExecutionContext> {
  /**
   * Invoked when the circuit breaker terminates the request.
   */
  onTerminatingRequest: OnTerminatingRequest | undefined;

  /**
   * Invoked when a request to open the circuit breaker is made.
   */
  onRequestToOpen: OnRequestToOpen | undefined;

  /**
   * Notify the policy that a request has finished.
   * @param {TExecutionContext} executionContext The execution context.
   * @param {Error?} error The error that occurred.
   */
  notifyRequestFinished(executionContext: TExecutionContext, error?: Error): void;

  /**
   * Throw if the circuit breaker is tripped.
   * @param {TExecutionContext} executionContext The execution context.
   * @throws {CircuitBreakerError} Thrown if the circuit breaker is tripped.
   * @returns {void}
   */
  throwIfTripped(executionContext: TExecutionContext): void;
}

/**
 * Interface for the circuit breaker policy config.
 */
export interface IDefaultCircuitBreakerPolicyConfig {
  /**
   * The retry interval.
   * @returns {number} The retry interval.
   */
  get retryInterval(): number;

  /**
   * The retry interval.
   * @param {number} value The retry interval.
   */
  set retryInterval(value: number);

  /**
   * The failures allowed before tripping the circuit breaker.
   * @returns {number} The failures allowed before tripping the circuit breaker.
   */
  get failuresAllowedBeforeTrip(): number;

  /**
   * The failures allowed before tripping the circuit breaker.
   * @param {number} value The failures allowed before tripping the circuit breaker.
   */
  set failuresAllowedBeforeTrip(value: number);
}