๐Ÿ“ฆ nestjs / terminus

๐Ÿ“„ health-check-executor.service.ts ยท 119 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119import { Injectable, type BeforeApplicationShutdown } from '@nestjs/common';
import {
  type HealthCheckResult,
  type HealthCheckStatus,
} from './health-check-result.interface';
import { type HealthCheckError } from '../health-check/health-check.error';
import {
  type HealthIndicatorFunction,
  type HealthIndicatorResult,
} from '../health-indicator';
import { isHealthCheckError } from '../utils';

/**
 * Takes care of the execution of health indicators.
 *
 * @description
 * The HealthCheckExecutor is standalone, so it can be used for
 * the legacy TerminusBootstrapService and the HealthCheckService.
 *
 * On top of that, the HealthCheckExecutor uses the `BeforeApplicationShutdown`
 * hook, therefore it must implement the `beforeApplicationShutdown`
 * method as public. We do not want to expose that
 * to the end-user.
 *
 * @internal
 */
@Injectable()
export class HealthCheckExecutor implements BeforeApplicationShutdown {
  private isShuttingDown = false;

  /**
   * Executes the given health indicators.
   * Implementation for v6 compatibility.
   *
   * @throws {Error} All errors which are not inherited by the `HealthCheckError`-class
   *
   * @returns the result of given health indicators
   * @param healthIndicators The health indicators which should get executed
   */
  async execute(
    healthIndicators: HealthIndicatorFunction[],
  ): Promise<HealthCheckResult> {
    const { results, errors } =
      await this.executeHealthIndicators(healthIndicators);

    return this.getResult(results, errors);
  }

  /**
   * @internal
   */
  beforeApplicationShutdown(): void {
    this.isShuttingDown = true;
  }

  private async executeHealthIndicators(
    healthIndicators: HealthIndicatorFunction[],
  ) {
    const results: HealthIndicatorResult[] = [];
    const errors: HealthIndicatorResult[] = [];

    const result = await Promise.allSettled(
      healthIndicators.map(async (h) => h()),
    );

    result.forEach((res) => {
      if (res.status === 'fulfilled') {
        Object.entries(res.value).forEach(([key, value]) => {
          if (value.status === 'up') {
            results.push({ [key]: value });
          } else if (value.status === 'down') {
            errors.push({ [key]: value });
          }
        });
      } else {
        const error = res.reason;
        // Is not an expected error. Throw further!
        if (!isHealthCheckError(error)) {
          throw error;
        }

        // eslint-disable-next-line deprecation/deprecation
        errors.push((error as HealthCheckError).causes);
      }
    });

    return { results, errors };
  }

  private getSummary(results: HealthIndicatorResult[]): HealthIndicatorResult {
    return results.reduce(
      (previous: any, current: any) => Object.assign(previous, current),
      {},
    );
  }

  private getResult(
    results: HealthIndicatorResult[],
    errors: HealthIndicatorResult[],
  ): HealthCheckResult {
    const infoErrorCombined = results.concat(errors);

    const info = this.getSummary(results);
    const error = this.getSummary(errors);
    const details = this.getSummary(infoErrorCombined);

    let status: HealthCheckStatus = 'ok';
    status = errors.length > 0 ? 'error' : status;
    status = this.isShuttingDown ? 'shutting_down' : status;

    return {
      status,
      info,
      error,
      details,
    };
  }
}