@loopback/health 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,25 @@
1
+ Copyright (c) IBM Corp. 2019.
2
+ Node module: @loopback/health
3
+ This project is licensed under the MIT License, full text below.
4
+
5
+ --------
6
+
7
+ MIT license
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in
17
+ all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @loopback/health
2
+
3
+ This module contains a component to report health status using
4
+ [@cloudnative/health](https://github.com/CloudNativeJS/cloud-health).
5
+
6
+ ## Installation
7
+
8
+ ```sh
9
+ npm install --save @loopback/health
10
+ ```
11
+
12
+ ## Basic use
13
+
14
+ The component should be loaded in the constructor of your custom Application
15
+ class.
16
+
17
+ Start by importing the component class:
18
+
19
+ ```ts
20
+ import {HealthComponent} from '@loopback/health';
21
+ ```
22
+
23
+ In the constructor, add the component to your application:
24
+
25
+ ```ts
26
+ this.component(HealthComponent);
27
+ ```
28
+
29
+ By default, three routes are exposed at:
30
+
31
+ - `/health` - overall health status
32
+ - `/live` - liveness status
33
+ - `/ready` - readiness status
34
+
35
+ The paths can be customized via Health configuration as follows:
36
+
37
+ ```ts
38
+ this.configure(HealthBindings.COMPONENT).to({
39
+ healthPath: '/health',
40
+ livePath: '/live',
41
+ readyPath: '/ready',
42
+ });
43
+ ```
44
+
45
+ {% include note.html content="*this.configure()* must be called before
46
+ *this.component()* to take effect. This is a
47
+ [known limitation](https://github.com/loopbackio/loopback-next/issues/4289#issuecomment-564617263)
48
+ ." %}
49
+
50
+ http://localhost:3000/health returns health in JSON format, such as:
51
+
52
+ ```json
53
+ {
54
+ "status": "UP",
55
+ "checks": [
56
+ {"name": "readiness", "state": "UP", "data": {"reason": ""}},
57
+ {"name": "liveness", "state": "UP", "data": {"reason": ""}}
58
+ ]
59
+ }
60
+ ```
61
+
62
+ It also has to be noted, that by default the OpenAPI spec is disabled and
63
+ therefore the endpoints will not be visible in the API explorer. The spec can be
64
+ enabled by setting `openApiSpec` to `true`.
65
+
66
+ ```ts
67
+ this.configure(HealthBindings.COMPONENT).to({
68
+ openApiSpec: true,
69
+ });
70
+ ```
71
+
72
+ ## Add custom `live` and `ready` checks
73
+
74
+ The health component allows extra
75
+ [`live` and `ready` checks](https://github.com/CloudNativeJS/cloud-health#readiness-vs-liveness)
76
+ to be added.
77
+
78
+ _Liveness probes_ are used to know when to restart a container. For example, in
79
+ case of a deadlock due to a multi-threading defect which might not crash the
80
+ container but keep the application unresponsive. A custom liveness probe would
81
+ detect this failure and restart the container.
82
+
83
+ _Readiness probes_ are used to decide when the container is available for
84
+ accepting traffic. It is important to note, that readiness probes are
85
+ periodically checked and not only at startup.
86
+
87
+ **Important:** It is recommended to avoid checking dependencies in liveness
88
+ probes. Liveness probes should be inexpensive and have response times with
89
+ minimal variance.
90
+
91
+ ```ts
92
+ import {LiveCheck, ReadyCheck, HealthTags} from '@loopback/health';
93
+
94
+ const myLiveCheck: LiveCheck = () => {
95
+ return Promise.resolve();
96
+ };
97
+ app.bind('health.MyLiveCheck').to(myLiveCheck).tag(HealthTags.LIVE_CHECK);
98
+
99
+ // Define a provider to check the health of a datasource
100
+ class DBHealthCheckProvider implements Provider<ReadyCheck> {
101
+ constructor(@inject('datasources.db') private ds: DataSource) {}
102
+
103
+ value() {
104
+ return () => this.ds.ping();
105
+ }
106
+ }
107
+
108
+ app
109
+ .bind('health.MyDBCheck')
110
+ .toProvider(DBHealthCheckProvider)
111
+ .tag(HealthTags.READY_CHECK);
112
+
113
+ const myReadyCheck: ReadyCheck = () => {
114
+ return Promise.resolve();
115
+ };
116
+ app.bind('health.MyReadyCheck').to(myReadyCheck).tag(HealthTags.READY_CHECK);
117
+ ```
118
+
119
+ ## Contributions
120
+
121
+ - [Guidelines](https://github.com/loopbackio/loopback-next/blob/master/docs/CONTRIBUTING.md)
122
+ - [Join the team](https://github.com/loopbackio/loopback-next/issues/110)
123
+
124
+ ## Tests
125
+
126
+ Run `npm test` from the root folder.
127
+
128
+ ## Contributors
129
+
130
+ See
131
+ [all contributors](https://github.com/loopbackio/loopback-next/graphs/contributors).
132
+
133
+ ## License
134
+
135
+ MIT
@@ -0,0 +1,10 @@
1
+ import { Constructor } from '@loopback/core';
2
+ import { HealthOptions } from '../types';
3
+ /**
4
+ * A factory function to create a controller class for health endpoints. This
5
+ * makes it possible to customize decorations such as `@get` with a dynamic
6
+ * path value not known at compile time.
7
+ *
8
+ * @param options - Options for health endpoints
9
+ */
10
+ export declare function createHealthController(options?: HealthOptions): Constructor<unknown>;
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019. All Rights Reserved.
3
+ // Node module: @loopback/health
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.createHealthController = void 0;
8
+ const tslib_1 = require("tslib");
9
+ const health_1 = require("@cloudnative/health");
10
+ const core_1 = require("@loopback/core");
11
+ const rest_1 = require("@loopback/rest");
12
+ const keys_1 = require("../keys");
13
+ const types_1 = require("../types");
14
+ function getHealthResponseObject() {
15
+ /**
16
+ * OpenAPI definition of health response schema
17
+ */
18
+ const HEALTH_RESPONSE_SCHEMA = {
19
+ type: 'object',
20
+ properties: {
21
+ status: { type: 'string' },
22
+ checks: {
23
+ type: 'array',
24
+ items: {
25
+ type: 'object',
26
+ properties: {
27
+ name: { type: 'string' },
28
+ state: { type: 'string' },
29
+ data: {
30
+ type: 'object',
31
+ properties: {
32
+ reason: { type: 'string' },
33
+ },
34
+ },
35
+ },
36
+ },
37
+ },
38
+ },
39
+ };
40
+ /**
41
+ * OpenAPI definition of health response
42
+ */
43
+ const HEALTH_RESPONSE = {
44
+ description: 'Health Response',
45
+ content: {
46
+ 'application/json': {
47
+ schema: HEALTH_RESPONSE_SCHEMA,
48
+ },
49
+ },
50
+ };
51
+ return HEALTH_RESPONSE;
52
+ }
53
+ /**
54
+ * OpenAPI spec for health endpoints
55
+ */
56
+ const HEALTH_SPEC = {
57
+ // response object needs to be cloned because the oas-validator throws an
58
+ // error if the same object is referenced twice
59
+ responses: {
60
+ '200': getHealthResponseObject(),
61
+ '500': getHealthResponseObject(),
62
+ '503': getHealthResponseObject(),
63
+ },
64
+ };
65
+ /**
66
+ * OpenAPI spec to hide endpoints
67
+ */
68
+ const HIDDEN_SPEC = {
69
+ responses: {},
70
+ 'x-visibility': 'undocumented',
71
+ };
72
+ /**
73
+ * A factory function to create a controller class for health endpoints. This
74
+ * makes it possible to customize decorations such as `@get` with a dynamic
75
+ * path value not known at compile time.
76
+ *
77
+ * @param options - Options for health endpoints
78
+ */
79
+ function createHealthController(options = types_1.DEFAULT_HEALTH_OPTIONS) {
80
+ const spec = options.openApiSpec ? HEALTH_SPEC : HIDDEN_SPEC;
81
+ /**
82
+ * Controller for health endpoints
83
+ */
84
+ let HealthController = class HealthController {
85
+ constructor(healthChecker) {
86
+ this.healthChecker = healthChecker;
87
+ }
88
+ async health(response) {
89
+ const status = await this.healthChecker.getStatus();
90
+ return handleStatus(response, status);
91
+ }
92
+ async ready(response) {
93
+ const status = await this.healthChecker.getReadinessStatus();
94
+ return handleStatus(response, status, 503);
95
+ }
96
+ async live(response) {
97
+ const status = await this.healthChecker.getLivenessStatus();
98
+ return handleStatus(response, status, 500);
99
+ }
100
+ };
101
+ tslib_1.__decorate([
102
+ rest_1.get(options.healthPath, spec),
103
+ tslib_1.__param(0, core_1.inject(rest_1.RestBindings.Http.RESPONSE)),
104
+ tslib_1.__metadata("design:type", Function),
105
+ tslib_1.__metadata("design:paramtypes", [Object]),
106
+ tslib_1.__metadata("design:returntype", Promise)
107
+ ], HealthController.prototype, "health", null);
108
+ tslib_1.__decorate([
109
+ rest_1.get(options.readyPath, spec),
110
+ tslib_1.__param(0, core_1.inject(rest_1.RestBindings.Http.RESPONSE)),
111
+ tslib_1.__metadata("design:type", Function),
112
+ tslib_1.__metadata("design:paramtypes", [Object]),
113
+ tslib_1.__metadata("design:returntype", Promise)
114
+ ], HealthController.prototype, "ready", null);
115
+ tslib_1.__decorate([
116
+ rest_1.get(options.livePath, spec),
117
+ tslib_1.__param(0, core_1.inject(rest_1.RestBindings.Http.RESPONSE)),
118
+ tslib_1.__metadata("design:type", Function),
119
+ tslib_1.__metadata("design:paramtypes", [Object]),
120
+ tslib_1.__metadata("design:returntype", Promise)
121
+ ], HealthController.prototype, "live", null);
122
+ HealthController = tslib_1.__decorate([
123
+ core_1.injectable({ scope: core_1.BindingScope.SINGLETON }),
124
+ tslib_1.__param(0, core_1.inject(keys_1.HealthBindings.HEALTH_CHECKER)),
125
+ tslib_1.__metadata("design:paramtypes", [health_1.HealthChecker])
126
+ ], HealthController);
127
+ return HealthController;
128
+ }
129
+ exports.createHealthController = createHealthController;
130
+ /**
131
+ * Create response for the given status
132
+ * @param response - Http response
133
+ * @param status - Health status
134
+ * @param failingCode - Status code for `DOWN`
135
+ */
136
+ function handleStatus(response, status, failingCode = 503) {
137
+ let statusCode = 200;
138
+ switch (status.status) {
139
+ case health_1.State.STARTING:
140
+ statusCode = 503;
141
+ break;
142
+ case health_1.State.UP:
143
+ statusCode = 200;
144
+ break;
145
+ case health_1.State.DOWN:
146
+ statusCode = failingCode;
147
+ break;
148
+ case health_1.State.STOPPING:
149
+ statusCode = 503;
150
+ break;
151
+ case health_1.State.STOPPED:
152
+ statusCode = 503;
153
+ break;
154
+ }
155
+ response.status(statusCode).send(status);
156
+ return response;
157
+ }
158
+ //# sourceMappingURL=health.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.controller.js","sourceRoot":"","sources":["../../src/controllers/health.controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export * from './health.controller';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019. All Rights Reserved.
3
+ // Node module: @loopback/health
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const tslib_1 = require("tslib");
8
+ tslib_1.__exportStar(require("./health.controller"), exports);
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/controllers/index.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,gCAAgC;AAChC,+CAA+C;AAC/C,gEAAgE;;;AAEhE,8DAAoC"}
@@ -0,0 +1,9 @@
1
+ import { Application, Component } from '@loopback/core';
2
+ import { HealthConfig } from './types';
3
+ /**
4
+ * A component providing health status
5
+ */
6
+ export declare class HealthComponent implements Component {
7
+ private application;
8
+ constructor(application: Application, healthConfig?: HealthConfig);
9
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019. All Rights Reserved.
3
+ // Node module: @loopback/health
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.HealthComponent = void 0;
8
+ const tslib_1 = require("tslib");
9
+ const health_1 = require("@cloudnative/health");
10
+ const core_1 = require("@loopback/core");
11
+ const controllers_1 = require("./controllers");
12
+ const keys_1 = require("./keys");
13
+ const observers_1 = require("./observers");
14
+ const types_1 = require("./types");
15
+ /**
16
+ * A component providing health status
17
+ */
18
+ let HealthComponent = class HealthComponent {
19
+ constructor(application, healthConfig = {}) {
20
+ this.application = application;
21
+ // Bind the HealthCheck service
22
+ this.application
23
+ .bind(keys_1.HealthBindings.HEALTH_CHECKER)
24
+ .toClass(health_1.HealthChecker)
25
+ .inScope(core_1.BindingScope.SINGLETON);
26
+ // Bind the health observer
27
+ this.application.lifeCycleObserver(observers_1.HealthObserver);
28
+ const options = {
29
+ ...types_1.DEFAULT_HEALTH_OPTIONS,
30
+ ...healthConfig,
31
+ };
32
+ if (!options.disabled) {
33
+ this.application.controller(controllers_1.createHealthController(options));
34
+ }
35
+ }
36
+ };
37
+ HealthComponent = tslib_1.__decorate([
38
+ core_1.injectable({ tags: { [core_1.ContextTags.KEY]: keys_1.HealthBindings.COMPONENT } }),
39
+ tslib_1.__param(0, core_1.inject(core_1.CoreBindings.APPLICATION_INSTANCE)),
40
+ tslib_1.__param(1, core_1.config()),
41
+ tslib_1.__metadata("design:paramtypes", [core_1.Application, Object])
42
+ ], HealthComponent);
43
+ exports.HealthComponent = HealthComponent;
44
+ //# sourceMappingURL=health.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.component.js","sourceRoot":"","sources":["../src/health.component.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,gCAAgC;AAChC,+CAA+C;AAC/C,gEAAgE;;;;AAEhE,gDAAkD;AAClD,yCASwB;AACxB,+CAAqD;AACrD,iCAAsC;AACtC,2CAA2C;AAC3C,mCAA4E;AAE5E;;GAEG;AAEH,IAAa,eAAe,GAA5B,MAAa,eAAe;IAC1B,YAEU,WAAwB,EAEhC,eAA6B,EAAE;QAFvB,gBAAW,GAAX,WAAW,CAAa;QAIhC,+BAA+B;QAC/B,IAAI,CAAC,WAAW;aACb,IAAI,CAAC,qBAAc,CAAC,cAAc,CAAC;aACnC,OAAO,CAAC,sBAAa,CAAC;aACtB,OAAO,CAAC,mBAAY,CAAC,SAAS,CAAC,CAAC;QAEnC,2BAA2B;QAC3B,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,0BAAc,CAAC,CAAC;QAEnD,MAAM,OAAO,GAAkB;YAC7B,GAAG,8BAAsB;YACzB,GAAG,YAAY;SAChB,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;YACrB,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,oCAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;SAC9D;IACH,CAAC;CACF,CAAA;AAxBY,eAAe;IAD3B,iBAAU,CAAC,EAAC,IAAI,EAAE,EAAC,CAAC,kBAAW,CAAC,GAAG,CAAC,EAAE,qBAAc,CAAC,SAAS,EAAC,EAAC,CAAC;IAG7D,mBAAA,aAAM,CAAC,mBAAY,CAAC,oBAAoB,CAAC,CAAA;IAEzC,mBAAA,aAAM,EAAE,CAAA;6CADY,kBAAW;GAHvB,eAAe,CAwB3B;AAxBY,0CAAe"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A component to report health status using
3
+ * {@link https://github.com/CloudNativeJS/cloud-health | @cloudnative/health }.
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ export * from './health.component';
8
+ export * from './keys';
9
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
3
+ // Node module: @loopback/health
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const tslib_1 = require("tslib");
8
+ /**
9
+ * A component to report health status using
10
+ * {@link https://github.com/CloudNativeJS/cloud-health | @cloudnative/health }.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ tslib_1.__exportStar(require("./health.component"), exports);
15
+ tslib_1.__exportStar(require("./keys"), exports);
16
+ tslib_1.__exportStar(require("./types"), exports);
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,gCAAgC;AAChC,+CAA+C;AAC/C,gEAAgE;;;AAEhE;;;;;GAKG;AAEH,6DAAmC;AACnC,iDAAuB;AACvB,kDAAwB"}
package/dist/keys.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { HealthChecker } from '@cloudnative/health';
2
+ import { BindingAddress, BindingKey } from '@loopback/core';
3
+ import { HealthComponent } from './health.component';
4
+ import { HealthConfig } from './types';
5
+ /**
6
+ * Binding keys used by this component.
7
+ */
8
+ export declare namespace HealthBindings {
9
+ const COMPONENT: BindingKey<HealthComponent>;
10
+ const CONFIG: BindingAddress<HealthConfig>;
11
+ const HEALTH_CHECKER: BindingKey<HealthChecker>;
12
+ }
13
+ /**
14
+ * Binding tags for health related services
15
+ */
16
+ export declare namespace HealthTags {
17
+ /**
18
+ * Binding tag for liveness check functions
19
+ */
20
+ const LIVE_CHECK = "health.liveCheck";
21
+ /**
22
+ * Binding tag for readiness check functions
23
+ */
24
+ const READY_CHECK = "health.readyCheck";
25
+ }
package/dist/keys.js ADDED
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
3
+ // Node module: @loopback/health
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.HealthTags = exports.HealthBindings = void 0;
8
+ const core_1 = require("@loopback/core");
9
+ /**
10
+ * Binding keys used by this component.
11
+ */
12
+ var HealthBindings;
13
+ (function (HealthBindings) {
14
+ HealthBindings.COMPONENT = core_1.BindingKey.create('components.HealthComponent');
15
+ HealthBindings.CONFIG = core_1.BindingKey.buildKeyForConfig(HealthBindings.COMPONENT.key);
16
+ HealthBindings.HEALTH_CHECKER = core_1.BindingKey.create('health.HeathChecker');
17
+ })(HealthBindings = exports.HealthBindings || (exports.HealthBindings = {}));
18
+ /**
19
+ * Binding tags for health related services
20
+ */
21
+ var HealthTags;
22
+ (function (HealthTags) {
23
+ /**
24
+ * Binding tag for liveness check functions
25
+ */
26
+ HealthTags.LIVE_CHECK = 'health.liveCheck';
27
+ /**
28
+ * Binding tag for readiness check functions
29
+ */
30
+ HealthTags.READY_CHECK = 'health.readyCheck';
31
+ })(HealthTags = exports.HealthTags || (exports.HealthTags = {}));
32
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":";AAAA,sDAAsD;AACtD,gCAAgC;AAChC,+CAA+C;AAC/C,gEAAgE;;;AAGhE,yCAA0D;AAI1D;;GAEG;AACH,IAAiB,cAAc,CAW9B;AAXD,WAAiB,cAAc;IAChB,wBAAS,GAAG,iBAAU,CAAC,MAAM,CACxC,4BAA4B,CAC7B,CAAC;IAEW,qBAAM,GACjB,iBAAU,CAAC,iBAAiB,CAAe,eAAA,SAAS,CAAC,GAAG,CAAC,CAAC;IAE/C,6BAAc,GAAG,iBAAU,CAAC,MAAM,CAC7C,qBAAqB,CACtB,CAAC;AACJ,CAAC,EAXgB,cAAc,GAAd,sBAAc,KAAd,sBAAc,QAW9B;AAED;;GAEG;AACH,IAAiB,UAAU,CAS1B;AATD,WAAiB,UAAU;IACzB;;OAEG;IACU,qBAAU,GAAG,kBAAkB,CAAC;IAC7C;;OAEG;IACU,sBAAW,GAAG,mBAAmB,CAAC;AACjD,CAAC,EATgB,UAAU,GAAV,kBAAU,KAAV,kBAAU,QAS1B"}
@@ -0,0 +1,14 @@
1
+ import { HealthChecker } from '@cloudnative/health';
2
+ import { ContextView, LifeCycleObserver } from '@loopback/core';
3
+ import { LiveCheck, ReadyCheck } from '../types';
4
+ export declare class HealthObserver implements LifeCycleObserver {
5
+ private healthChecker;
6
+ private liveChecks;
7
+ private readyChecks;
8
+ private eventEmitter;
9
+ private startupCheck;
10
+ private shutdownCheck;
11
+ constructor(healthChecker: HealthChecker, liveChecks: ContextView<LiveCheck>, readyChecks: ContextView<ReadyCheck>);
12
+ start(): Promise<void>;
13
+ stop(): void;
14
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
3
+ // Node module: @loopback/health
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.HealthObserver = void 0;
8
+ const tslib_1 = require("tslib");
9
+ const health_1 = require("@cloudnative/health");
10
+ const core_1 = require("@loopback/core");
11
+ const events_1 = require("events");
12
+ const keys_1 = require("../keys");
13
+ let HealthObserver = class HealthObserver {
14
+ constructor(healthChecker, liveChecks, readyChecks) {
15
+ this.healthChecker = healthChecker;
16
+ this.liveChecks = liveChecks;
17
+ this.readyChecks = readyChecks;
18
+ this.eventEmitter = new events_1.EventEmitter();
19
+ const startup = once(this.eventEmitter, 'startup');
20
+ const startupCheck = new health_1.StartupCheck('startup', () => startup);
21
+ this.startupCheck = this.healthChecker.registerStartupCheck(startupCheck);
22
+ const shutdown = once(this.eventEmitter, 'shutdown');
23
+ this.shutdownCheck = new health_1.ShutdownCheck('shutdown', () => shutdown);
24
+ }
25
+ async start() {
26
+ this.healthChecker.registerShutdownCheck(this.shutdownCheck);
27
+ const liveChecks = await this.liveChecks.values();
28
+ const liveCheckBindings = this.liveChecks.bindings;
29
+ let index = 0;
30
+ for (const lc of liveChecks) {
31
+ const name = liveCheckBindings[index].key;
32
+ const check = new health_1.LivenessCheck(name, lc);
33
+ this.healthChecker.registerLivenessCheck(check);
34
+ index++;
35
+ }
36
+ const readyChecks = await this.readyChecks.values();
37
+ const readyCheckBindings = this.readyChecks.bindings;
38
+ index = 0;
39
+ for (const rc of readyChecks) {
40
+ const name = readyCheckBindings[index].key;
41
+ const check = new health_1.ReadinessCheck(name, rc);
42
+ this.healthChecker.registerReadinessCheck(check);
43
+ index++;
44
+ }
45
+ this.eventEmitter.emit('startup');
46
+ await this.startupCheck;
47
+ }
48
+ stop() {
49
+ this.eventEmitter.emit('shutdown');
50
+ // Fix a potential memory leak caused by
51
+ // https://github.com/CloudNativeJS/cloud-health/blob/2.1.2/src/healthcheck/HealthChecker.ts#L118
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ const onShutdownRequest = this.healthChecker.onShutdownRequest;
54
+ if (onShutdownRequest != null) {
55
+ // Remove the listener from the current process
56
+ process.removeListener('SIGTERM', onShutdownRequest);
57
+ }
58
+ }
59
+ };
60
+ HealthObserver = tslib_1.__decorate([
61
+ tslib_1.__param(0, core_1.inject(keys_1.HealthBindings.HEALTH_CHECKER)),
62
+ tslib_1.__param(1, core_1.inject.view(core_1.filterByTag(keys_1.HealthTags.LIVE_CHECK))),
63
+ tslib_1.__param(2, core_1.inject.view(core_1.filterByTag(keys_1.HealthTags.READY_CHECK))),
64
+ tslib_1.__metadata("design:paramtypes", [health_1.HealthChecker,
65
+ core_1.ContextView,
66
+ core_1.ContextView])
67
+ ], HealthObserver);
68
+ exports.HealthObserver = HealthObserver;
69
+ function once(emitter, event) {
70
+ return events_1.once(emitter, event);
71
+ }
72
+ //# sourceMappingURL=health.observer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.observer.js","sourceRoot":"","sources":["../../src/observers/health.observer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export * from './health.observer';
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019. All Rights Reserved.
3
+ // Node module: @loopback/health
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const tslib_1 = require("tslib");
8
+ tslib_1.__exportStar(require("./health.observer"), exports);
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/observers/index.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,gCAAgC;AAChC,+CAA+C;AAC/C,gEAAgE;;;AAEhE,4DAAkC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Options for health component
3
+ */
4
+ export declare type HealthOptions = {
5
+ disabled?: boolean;
6
+ healthPath: string;
7
+ readyPath: string;
8
+ livePath: string;
9
+ openApiSpec?: boolean;
10
+ };
11
+ /**
12
+ * Configuration for health component with optional properties
13
+ */
14
+ export declare type HealthConfig = Partial<HealthOptions>;
15
+ export declare const DEFAULT_HEALTH_OPTIONS: HealthOptions;
16
+ /**
17
+ * Functions for liveness check
18
+ */
19
+ export declare type LiveCheck = () => Promise<void>;
20
+ /**
21
+ * Functions for readiness check
22
+ */
23
+ export declare type ReadyCheck = () => Promise<void>;
package/dist/types.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ // Copyright IBM Corp. 2019. All Rights Reserved.
3
+ // Node module: @loopback/health
4
+ // This file is licensed under the MIT License.
5
+ // License text available at https://opensource.org/licenses/MIT
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.DEFAULT_HEALTH_OPTIONS = void 0;
8
+ exports.DEFAULT_HEALTH_OPTIONS = {
9
+ healthPath: '/health',
10
+ readyPath: '/ready',
11
+ livePath: '/live',
12
+ openApiSpec: false,
13
+ };
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";AAAA,iDAAiD;AACjD,gCAAgC;AAChC,+CAA+C;AAC/C,gEAAgE;;;AAkBnD,QAAA,sBAAsB,GAAkB;IACnD,UAAU,EAAE,SAAS;IACrB,SAAS,EAAE,QAAQ;IACnB,QAAQ,EAAE,OAAO;IACjB,WAAW,EAAE,KAAK;CACnB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@loopback/health",
3
+ "description": "An extension exposes health check related endpoints with LoopBack 4",
4
+ "version": "0.8.3",
5
+ "keywords": [
6
+ "LoopBack",
7
+ "Cloud Native",
8
+ "Health"
9
+ ],
10
+ "license": "MIT",
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "author": "IBM Corp.",
14
+ "copyright.owner": "IBM Corp.",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/loopbackio/loopback-next.git",
18
+ "directory": "extensions/health"
19
+ },
20
+ "engines": {
21
+ "node": "^10.16 || 12 || 14 || 16"
22
+ },
23
+ "scripts": {
24
+ "build": "lb-tsc",
25
+ "clean": "lb-clean loopback-extension-health*.tgz dist *.tsbuildinfo package",
26
+ "pretest": "npm run build",
27
+ "test": "lb-mocha \"dist/__tests__/**/*.js\"",
28
+ "verify": "npm pack && tar xf loopback-extension-health*.tgz && tree package && npm run clean",
29
+ "demo": "./src/__examples__/demo.sh"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "files": [
35
+ "README.md",
36
+ "dist",
37
+ "src",
38
+ "!*/__tests__"
39
+ ],
40
+ "peerDependencies": {
41
+ "@loopback/core": "^2.17.0",
42
+ "@loopback/rest": "^10.0.1"
43
+ },
44
+ "dependencies": {
45
+ "@cloudnative/health": "^2.1.2",
46
+ "tslib": "^2.3.1"
47
+ },
48
+ "devDependencies": {
49
+ "@loopback/build": "^7.0.1",
50
+ "@loopback/core": "^2.17.0",
51
+ "@loopback/eslint-config": "^11.0.1",
52
+ "@loopback/rest": "^10.0.1",
53
+ "@loopback/testlab": "^3.4.3",
54
+ "@types/node": "^10.17.60"
55
+ },
56
+ "gitHead": "1df36bb1ee2e513d9e197bd6010c4cfb296d50b8"
57
+ }
@@ -0,0 +1,157 @@
1
+ // Copyright IBM Corp. 2019. All Rights Reserved.
2
+ // Node module: @loopback/health
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {HealthChecker, HealthStatus, State} from '@cloudnative/health';
7
+ import {BindingScope, Constructor, inject, injectable} from '@loopback/core';
8
+ import {
9
+ get,
10
+ OperationObject,
11
+ Response,
12
+ ResponseObject,
13
+ RestBindings,
14
+ SchemaObject,
15
+ } from '@loopback/rest';
16
+ import {HealthBindings} from '../keys';
17
+ import {DEFAULT_HEALTH_OPTIONS, HealthOptions} from '../types';
18
+
19
+ function getHealthResponseObject() {
20
+ /**
21
+ * OpenAPI definition of health response schema
22
+ */
23
+ const HEALTH_RESPONSE_SCHEMA: SchemaObject = {
24
+ type: 'object',
25
+ properties: {
26
+ status: {type: 'string'},
27
+ checks: {
28
+ type: 'array',
29
+ items: {
30
+ type: 'object',
31
+ properties: {
32
+ name: {type: 'string'},
33
+ state: {type: 'string'},
34
+ data: {
35
+ type: 'object',
36
+ properties: {
37
+ reason: {type: 'string'},
38
+ },
39
+ },
40
+ },
41
+ },
42
+ },
43
+ },
44
+ };
45
+
46
+ /**
47
+ * OpenAPI definition of health response
48
+ */
49
+ const HEALTH_RESPONSE: ResponseObject = {
50
+ description: 'Health Response',
51
+ content: {
52
+ 'application/json': {
53
+ schema: HEALTH_RESPONSE_SCHEMA,
54
+ },
55
+ },
56
+ };
57
+
58
+ return HEALTH_RESPONSE;
59
+ }
60
+
61
+ /**
62
+ * OpenAPI spec for health endpoints
63
+ */
64
+ const HEALTH_SPEC: OperationObject = {
65
+ // response object needs to be cloned because the oas-validator throws an
66
+ // error if the same object is referenced twice
67
+ responses: {
68
+ '200': getHealthResponseObject(),
69
+ '500': getHealthResponseObject(),
70
+ '503': getHealthResponseObject(),
71
+ },
72
+ };
73
+
74
+ /**
75
+ * OpenAPI spec to hide endpoints
76
+ */
77
+ const HIDDEN_SPEC: OperationObject = {
78
+ responses: {},
79
+ 'x-visibility': 'undocumented',
80
+ };
81
+
82
+ /**
83
+ * A factory function to create a controller class for health endpoints. This
84
+ * makes it possible to customize decorations such as `@get` with a dynamic
85
+ * path value not known at compile time.
86
+ *
87
+ * @param options - Options for health endpoints
88
+ */
89
+ export function createHealthController(
90
+ options: HealthOptions = DEFAULT_HEALTH_OPTIONS,
91
+ ): Constructor<unknown> {
92
+ const spec = options.openApiSpec ? HEALTH_SPEC : HIDDEN_SPEC;
93
+
94
+ /**
95
+ * Controller for health endpoints
96
+ */
97
+ @injectable({scope: BindingScope.SINGLETON})
98
+ class HealthController {
99
+ constructor(
100
+ @inject(HealthBindings.HEALTH_CHECKER)
101
+ private healthChecker: HealthChecker,
102
+ ) {}
103
+
104
+ @get(options.healthPath, spec)
105
+ async health(@inject(RestBindings.Http.RESPONSE) response: Response) {
106
+ const status = await this.healthChecker.getStatus();
107
+ return handleStatus(response, status);
108
+ }
109
+
110
+ @get(options.readyPath, spec)
111
+ async ready(@inject(RestBindings.Http.RESPONSE) response: Response) {
112
+ const status = await this.healthChecker.getReadinessStatus();
113
+ return handleStatus(response, status, 503);
114
+ }
115
+
116
+ @get(options.livePath, spec)
117
+ async live(@inject(RestBindings.Http.RESPONSE) response: Response) {
118
+ const status = await this.healthChecker.getLivenessStatus();
119
+ return handleStatus(response, status, 500);
120
+ }
121
+ }
122
+
123
+ return HealthController;
124
+ }
125
+
126
+ /**
127
+ * Create response for the given status
128
+ * @param response - Http response
129
+ * @param status - Health status
130
+ * @param failingCode - Status code for `DOWN`
131
+ */
132
+ function handleStatus(
133
+ response: Response,
134
+ status: HealthStatus,
135
+ failingCode: 500 | 503 = 503,
136
+ ) {
137
+ let statusCode = 200;
138
+ switch (status.status) {
139
+ case State.STARTING:
140
+ statusCode = 503;
141
+ break;
142
+ case State.UP:
143
+ statusCode = 200;
144
+ break;
145
+ case State.DOWN:
146
+ statusCode = failingCode;
147
+ break;
148
+ case State.STOPPING:
149
+ statusCode = 503;
150
+ break;
151
+ case State.STOPPED:
152
+ statusCode = 503;
153
+ break;
154
+ }
155
+ response.status(statusCode).send(status);
156
+ return response;
157
+ }
@@ -0,0 +1,6 @@
1
+ // Copyright IBM Corp. 2019. All Rights Reserved.
2
+ // Node module: @loopback/health
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ export * from './health.controller';
@@ -0,0 +1,50 @@
1
+ // Copyright IBM Corp. 2019. All Rights Reserved.
2
+ // Node module: @loopback/health
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {HealthChecker} from '@cloudnative/health';
7
+ import {
8
+ Application,
9
+ BindingScope,
10
+ Component,
11
+ config,
12
+ ContextTags,
13
+ CoreBindings,
14
+ inject,
15
+ injectable,
16
+ } from '@loopback/core';
17
+ import {createHealthController} from './controllers';
18
+ import {HealthBindings} from './keys';
19
+ import {HealthObserver} from './observers';
20
+ import {DEFAULT_HEALTH_OPTIONS, HealthConfig, HealthOptions} from './types';
21
+
22
+ /**
23
+ * A component providing health status
24
+ */
25
+ @injectable({tags: {[ContextTags.KEY]: HealthBindings.COMPONENT}})
26
+ export class HealthComponent implements Component {
27
+ constructor(
28
+ @inject(CoreBindings.APPLICATION_INSTANCE)
29
+ private application: Application,
30
+ @config()
31
+ healthConfig: HealthConfig = {},
32
+ ) {
33
+ // Bind the HealthCheck service
34
+ this.application
35
+ .bind(HealthBindings.HEALTH_CHECKER)
36
+ .toClass(HealthChecker)
37
+ .inScope(BindingScope.SINGLETON);
38
+
39
+ // Bind the health observer
40
+ this.application.lifeCycleObserver(HealthObserver);
41
+
42
+ const options: HealthOptions = {
43
+ ...DEFAULT_HEALTH_OPTIONS,
44
+ ...healthConfig,
45
+ };
46
+ if (!options.disabled) {
47
+ this.application.controller(createHealthController(options));
48
+ }
49
+ }
50
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
2
+ // Node module: @loopback/health
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ /**
7
+ * A component to report health status using
8
+ * {@link https://github.com/CloudNativeJS/cloud-health | @cloudnative/health }.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+
13
+ export * from './health.component';
14
+ export * from './keys';
15
+ export * from './types';
package/src/keys.ts ADDED
@@ -0,0 +1,39 @@
1
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
2
+ // Node module: @loopback/health
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {HealthChecker} from '@cloudnative/health';
7
+ import {BindingAddress, BindingKey} from '@loopback/core';
8
+ import {HealthComponent} from './health.component';
9
+ import {HealthConfig} from './types';
10
+
11
+ /**
12
+ * Binding keys used by this component.
13
+ */
14
+ export namespace HealthBindings {
15
+ export const COMPONENT = BindingKey.create<HealthComponent>(
16
+ 'components.HealthComponent',
17
+ );
18
+
19
+ export const CONFIG: BindingAddress<HealthConfig> =
20
+ BindingKey.buildKeyForConfig<HealthConfig>(COMPONENT.key);
21
+
22
+ export const HEALTH_CHECKER = BindingKey.create<HealthChecker>(
23
+ 'health.HeathChecker',
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Binding tags for health related services
29
+ */
30
+ export namespace HealthTags {
31
+ /**
32
+ * Binding tag for liveness check functions
33
+ */
34
+ export const LIVE_CHECK = 'health.liveCheck';
35
+ /**
36
+ * Binding tag for readiness check functions
37
+ */
38
+ export const READY_CHECK = 'health.readyCheck';
39
+ }
@@ -0,0 +1,88 @@
1
+ // Copyright IBM Corp. 2019,2020. All Rights Reserved.
2
+ // Node module: @loopback/health
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ import {
7
+ HealthChecker,
8
+ LivenessCheck,
9
+ ReadinessCheck,
10
+ ShutdownCheck,
11
+ StartupCheck,
12
+ } from '@cloudnative/health';
13
+ import {
14
+ ContextView,
15
+ filterByTag,
16
+ inject,
17
+ LifeCycleObserver,
18
+ } from '@loopback/core';
19
+ import {EventEmitter, once as onceGeneric} from 'events';
20
+ import {HealthBindings, HealthTags} from '../keys';
21
+ import {LiveCheck, ReadyCheck} from '../types';
22
+
23
+ export class HealthObserver implements LifeCycleObserver {
24
+ private eventEmitter = new EventEmitter();
25
+ private startupCheck: Promise<void>;
26
+ private shutdownCheck: ShutdownCheck;
27
+
28
+ constructor(
29
+ @inject(HealthBindings.HEALTH_CHECKER) private healthChecker: HealthChecker,
30
+
31
+ @inject.view(filterByTag(HealthTags.LIVE_CHECK))
32
+ private liveChecks: ContextView<LiveCheck>,
33
+
34
+ @inject.view(filterByTag(HealthTags.READY_CHECK))
35
+ private readyChecks: ContextView<ReadyCheck>,
36
+ ) {
37
+ const startup = once(
38
+ this.eventEmitter,
39
+ 'startup',
40
+ ) as unknown as Promise<void>;
41
+ const startupCheck = new StartupCheck('startup', () => startup);
42
+ this.startupCheck = this.healthChecker.registerStartupCheck(startupCheck);
43
+ const shutdown = once(this.eventEmitter, 'shutdown');
44
+ this.shutdownCheck = new ShutdownCheck('shutdown', () => shutdown);
45
+ }
46
+
47
+ async start() {
48
+ this.healthChecker.registerShutdownCheck(this.shutdownCheck);
49
+ const liveChecks = await this.liveChecks.values();
50
+ const liveCheckBindings = this.liveChecks.bindings;
51
+ let index = 0;
52
+ for (const lc of liveChecks) {
53
+ const name = liveCheckBindings[index].key;
54
+ const check = new LivenessCheck(name, lc);
55
+ this.healthChecker.registerLivenessCheck(check);
56
+ index++;
57
+ }
58
+
59
+ const readyChecks = await this.readyChecks.values();
60
+ const readyCheckBindings = this.readyChecks.bindings;
61
+ index = 0;
62
+ for (const rc of readyChecks) {
63
+ const name = readyCheckBindings[index].key;
64
+ const check = new ReadinessCheck(name, rc);
65
+ this.healthChecker.registerReadinessCheck(check);
66
+ index++;
67
+ }
68
+
69
+ this.eventEmitter.emit('startup');
70
+ await this.startupCheck;
71
+ }
72
+
73
+ stop() {
74
+ this.eventEmitter.emit('shutdown');
75
+ // Fix a potential memory leak caused by
76
+ // https://github.com/CloudNativeJS/cloud-health/blob/2.1.2/src/healthcheck/HealthChecker.ts#L118
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ const onShutdownRequest = (this.healthChecker as any).onShutdownRequest;
79
+ if (onShutdownRequest != null) {
80
+ // Remove the listener from the current process
81
+ process.removeListener('SIGTERM', onShutdownRequest);
82
+ }
83
+ }
84
+ }
85
+
86
+ function once(emitter: EventEmitter, event: string | symbol): Promise<void> {
87
+ return onceGeneric(emitter, event) as unknown as Promise<void>;
88
+ }
@@ -0,0 +1,6 @@
1
+ // Copyright IBM Corp. 2019. All Rights Reserved.
2
+ // Node module: @loopback/health
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ export * from './health.observer';
package/src/types.ts ADDED
@@ -0,0 +1,37 @@
1
+ // Copyright IBM Corp. 2019. All Rights Reserved.
2
+ // Node module: @loopback/health
3
+ // This file is licensed under the MIT License.
4
+ // License text available at https://opensource.org/licenses/MIT
5
+
6
+ /**
7
+ * Options for health component
8
+ */
9
+ export type HealthOptions = {
10
+ disabled?: boolean;
11
+ healthPath: string;
12
+ readyPath: string;
13
+ livePath: string;
14
+ openApiSpec?: boolean;
15
+ };
16
+
17
+ /**
18
+ * Configuration for health component with optional properties
19
+ */
20
+ export type HealthConfig = Partial<HealthOptions>;
21
+
22
+ export const DEFAULT_HEALTH_OPTIONS: HealthOptions = {
23
+ healthPath: '/health',
24
+ readyPath: '/ready',
25
+ livePath: '/live',
26
+ openApiSpec: false,
27
+ };
28
+
29
+ /**
30
+ * Functions for liveness check
31
+ */
32
+ export type LiveCheck = () => Promise<void>;
33
+
34
+ /**
35
+ * Functions for readiness check
36
+ */
37
+ export type ReadyCheck = () => Promise<void>;