@zerooneit/expressive-tea 1.3.0-beta.5 → 2.0.0
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/.gitattributes +4 -0
- package/.swcrc +61 -0
- package/README.md +564 -174
- package/classes/Boot.d.ts +94 -3
- package/classes/Boot.js +171 -51
- package/classes/Engine.d.ts +59 -10
- package/classes/Engine.js +72 -11
- package/classes/EngineRegistry.d.ts +154 -0
- package/classes/EngineRegistry.js +247 -0
- package/classes/LoadBalancer.js +2 -5
- package/classes/ProxyRoute.d.ts +3 -3
- package/classes/ProxyRoute.js +5 -5
- package/classes/Settings.d.ts +31 -2
- package/classes/Settings.js +64 -11
- package/decorators/annotations.d.ts +1 -1
- package/decorators/annotations.js +17 -17
- package/decorators/env.d.ts +145 -0
- package/decorators/env.js +177 -0
- package/decorators/health.d.ts +115 -0
- package/decorators/health.js +124 -0
- package/decorators/module.d.ts +15 -15
- package/decorators/module.js +14 -23
- package/decorators/proxy.d.ts +26 -11
- package/decorators/proxy.js +35 -45
- package/decorators/router.d.ts +17 -16
- package/decorators/router.js +32 -52
- package/decorators/server.d.ts +8 -8
- package/decorators/server.js +48 -50
- package/engines/health/index.d.ts +120 -0
- package/engines/health/index.js +179 -0
- package/engines/http/index.d.ts +6 -7
- package/engines/http/index.js +22 -17
- package/engines/index.d.ts +32 -0
- package/engines/index.js +112 -0
- package/engines/socketio/index.d.ts +2 -1
- package/engines/socketio/index.js +16 -6
- package/engines/teacup/index.d.ts +13 -0
- package/engines/teacup/index.js +61 -11
- package/engines/teapot/index.d.ts +15 -2
- package/engines/teapot/index.js +61 -13
- package/engines/websocket/index.d.ts +4 -1
- package/engines/websocket/index.js +10 -2
- package/eslint.config.mjs +138 -0
- package/exceptions/RequestExceptions.d.ts +3 -3
- package/helpers/boot-helper.d.ts +6 -6
- package/helpers/boot-helper.js +30 -24
- package/helpers/decorators.js +7 -6
- package/helpers/promise-helper.d.ts +1 -1
- package/helpers/promise-helper.js +1 -2
- package/helpers/server.d.ts +32 -6
- package/helpers/server.js +101 -61
- package/helpers/teapot-helper.d.ts +5 -8
- package/helpers/teapot-helper.js +39 -11
- package/helpers/websocket-helper.d.ts +3 -5
- package/helpers/websocket-helper.js +3 -3
- package/interfaces/index.d.ts +1 -1
- package/inversify.config.d.ts +4 -4
- package/inversify.config.js +1 -1
- package/libs/utilities.d.ts +21910 -0
- package/libs/utilities.js +420 -0
- package/mixins/module.d.ts +45 -0
- package/mixins/module.js +71 -0
- package/mixins/proxy.d.ts +46 -0
- package/mixins/proxy.js +86 -0
- package/mixins/route.d.ts +48 -0
- package/mixins/route.js +96 -0
- package/package.json +91 -69
- package/services/DependencyInjection.d.ts +95 -7
- package/services/DependencyInjection.js +123 -5
- package/services/WebsocketService.d.ts +4 -6
- package/services/WebsocketService.js +5 -3
- package/types/core.d.ts +14 -0
- package/types/core.js +2 -0
- package/types/injection-types.d.ts +6 -0
- package/types/injection-types.js +10 -0
- package/types/inversify.d.ts +5 -0
- package/types/inversify.js +3 -0
package/decorators/server.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
exports.Plug = Plug;
|
|
4
|
+
exports.Pour = Pour;
|
|
5
|
+
exports.ServerSettings = ServerSettings;
|
|
6
|
+
exports.Static = Static;
|
|
7
|
+
exports.ExpressDirective = ExpressDirective;
|
|
8
|
+
exports.Setting = Setting;
|
|
9
|
+
exports.Modules = Modules;
|
|
10
|
+
exports.Proxies = Proxies;
|
|
11
|
+
exports.RegisterModule = RegisterModule;
|
|
12
|
+
exports.Teapot = Teapot;
|
|
13
|
+
exports.Teacup = Teacup;
|
|
14
|
+
const utilities_1 = require("../libs/utilities");
|
|
15
|
+
const commons_1 = require("@expressive-tea/commons");
|
|
6
16
|
const Settings_1 = require("../classes/Settings");
|
|
7
|
-
const
|
|
17
|
+
const commons_2 = require("@expressive-tea/commons");
|
|
18
|
+
const DependencyInjection_1 = require("../services/DependencyInjection");
|
|
8
19
|
/**
|
|
9
20
|
* Define the Main Plugins Properties.
|
|
10
21
|
* @typedef {Object} ExpressiveTeaPluginProps
|
|
@@ -46,10 +57,10 @@ const constants_1 = require("@expressive-tea/commons/constants");
|
|
|
46
57
|
* @module Decorators/Server
|
|
47
58
|
*/
|
|
48
59
|
function getStages(target) {
|
|
49
|
-
return
|
|
60
|
+
return commons_1.Metadata.get(commons_2.BOOT_STAGES_KEY, target) || {};
|
|
50
61
|
}
|
|
51
62
|
function getRegisteredPlugins(target) {
|
|
52
|
-
return
|
|
63
|
+
return commons_1.Metadata.get(commons_2.PLUGINS_KEY, target) || [];
|
|
53
64
|
}
|
|
54
65
|
function getStage(stage, target) {
|
|
55
66
|
const stages = getStages(target);
|
|
@@ -61,10 +72,10 @@ function getStage(stage, target) {
|
|
|
61
72
|
function setStage(stage, value, target) {
|
|
62
73
|
const stages = getStages(target);
|
|
63
74
|
stages[stage] = value;
|
|
64
|
-
|
|
75
|
+
commons_1.Metadata.set(commons_2.BOOT_STAGES_KEY, stages, target);
|
|
65
76
|
}
|
|
66
77
|
function setPlugins(plugins, target) {
|
|
67
|
-
|
|
78
|
+
commons_1.Metadata.set(commons_2.PLUGINS_KEY, plugins, target);
|
|
68
79
|
}
|
|
69
80
|
/**
|
|
70
81
|
* Plug Class Decorator create a simple plugin to execute in one of the public stages defined on BOOT_STAGES, might
|
|
@@ -88,7 +99,6 @@ function Plug(stage, name, method, required = false) {
|
|
|
88
99
|
setStage(stage, selectedStage, target);
|
|
89
100
|
};
|
|
90
101
|
}
|
|
91
|
-
exports.Plug = Plug;
|
|
92
102
|
/**
|
|
93
103
|
* Since version 1.1.0 Expressive Tea allow to use external plugins using the node
|
|
94
104
|
* package @expressive-tea/plugin. This plugin engine allows to create more complex plugin configuration and provision
|
|
@@ -104,15 +114,17 @@ exports.Plug = Plug;
|
|
|
104
114
|
function Pour(Plugin, ...pluginArgs) {
|
|
105
115
|
return (target) => {
|
|
106
116
|
const stages = getStages(target);
|
|
107
|
-
|
|
117
|
+
DependencyInjection_1.default.Container.bind(Plugin)
|
|
118
|
+
.toDynamicValue(() => new Plugin(...pluginArgs))
|
|
119
|
+
.inSingletonScope();
|
|
120
|
+
const instance = DependencyInjection_1.default.Container.get(Plugin);
|
|
108
121
|
const plugins = instance.register(Settings_1.default.getInstance(target).getOptions(), getRegisteredPlugins(target));
|
|
109
|
-
|
|
122
|
+
commons_2.BOOT_STAGES_LIST.forEach(STAGE => {
|
|
110
123
|
setStage(STAGE, (stages[STAGE] || []).concat(instance.getRegisteredStage(STAGE)), target);
|
|
111
124
|
});
|
|
112
|
-
setPlugins((0,
|
|
125
|
+
setPlugins((0, utilities_1.orderBy)(plugins, ['priority'], ['asc']), target);
|
|
113
126
|
};
|
|
114
127
|
}
|
|
115
|
-
exports.Pour = Pour;
|
|
116
128
|
/**
|
|
117
129
|
* Server Settings Singleton Class Decorator this Provide the Configuration to the server or another component on
|
|
118
130
|
* the projects,is working as a container to store user and library settings.
|
|
@@ -121,12 +133,11 @@ exports.Pour = Pour;
|
|
|
121
133
|
* @param {ExpressiveTeaModuleProps} options
|
|
122
134
|
*/
|
|
123
135
|
function ServerSettings(options = {}) {
|
|
124
|
-
return target => {
|
|
136
|
+
return (target) => {
|
|
125
137
|
Settings_1.default.getInstance(target).merge(options);
|
|
126
138
|
return target;
|
|
127
139
|
};
|
|
128
140
|
}
|
|
129
|
-
exports.ServerSettings = ServerSettings;
|
|
130
141
|
/**
|
|
131
142
|
* Create a new middleware function to serve files from within a given root directory. The file to serve will be
|
|
132
143
|
* determined by combining req.url with the provided root directory. When a file is not found, instead of sending a 404
|
|
@@ -141,16 +152,15 @@ exports.ServerSettings = ServerSettings;
|
|
|
141
152
|
* with virtual path if defined.
|
|
142
153
|
*/
|
|
143
154
|
function Static(root, virtual = null, options = {}) {
|
|
144
|
-
return target => {
|
|
145
|
-
if ((0,
|
|
155
|
+
return (target) => {
|
|
156
|
+
if ((0, utilities_1.isNil)(root)) {
|
|
146
157
|
throw new Error('Root must be defined');
|
|
147
158
|
}
|
|
148
|
-
const registeredStatics =
|
|
159
|
+
const registeredStatics = commons_1.Metadata.get(commons_2.REGISTERED_STATIC_KEY, target) || [];
|
|
149
160
|
registeredStatics.unshift({ root, options, virtual });
|
|
150
|
-
|
|
161
|
+
commons_1.Metadata.set(commons_2.REGISTERED_STATIC_KEY, registeredStatics, target);
|
|
151
162
|
};
|
|
152
163
|
}
|
|
153
|
-
exports.Static = Static;
|
|
154
164
|
/**
|
|
155
165
|
* Set or Update Express application settings, and allow to change the behavior of the server where is listed on the
|
|
156
166
|
* next link {@link http://expressjs.com/en/4x/api.html#app.settings.table Express Settings} as this is using the same
|
|
@@ -161,24 +171,22 @@ exports.Static = Static;
|
|
|
161
171
|
* @decorator {ClassDecorator} ExpressDirective - Set a Express App Setting.
|
|
162
172
|
*/
|
|
163
173
|
function ExpressDirective(name, ...settings) {
|
|
164
|
-
return target => {
|
|
165
|
-
if (!
|
|
174
|
+
return (target) => {
|
|
175
|
+
if (!commons_2.EXPRESS_DIRECTIVES.includes(name)) {
|
|
166
176
|
throw new Error(`Directive Name ${name} is not valid express behavior setting`);
|
|
167
177
|
}
|
|
168
|
-
const registeredDirectives =
|
|
178
|
+
const registeredDirectives = commons_1.Metadata.get(commons_2.REGISTERED_DIRECTIVES_KEY, target) || [];
|
|
169
179
|
registeredDirectives.unshift({ name, settings });
|
|
170
|
-
|
|
180
|
+
commons_1.Metadata.set(commons_2.REGISTERED_DIRECTIVES_KEY, registeredDirectives, target);
|
|
171
181
|
};
|
|
172
182
|
}
|
|
173
|
-
exports.ExpressDirective = ExpressDirective;
|
|
174
183
|
/**
|
|
175
184
|
* Setting Property Decorator Automatically assign a settings declared on Settings Service into the decorated property.
|
|
176
185
|
* All properties will contains the settings value or undefined if current settings is not founded.
|
|
177
186
|
* @decorator {PropertyDecorator} Setting - Assign Server Settings to Property as default value.
|
|
178
187
|
* @summary Automatically assign a settings declared on the Server Settings decorator to a class property.
|
|
179
|
-
* @param {string} settingName The Setting name tha
|
|
180
188
|
*/
|
|
181
|
-
function Setting(
|
|
189
|
+
function Setting() {
|
|
182
190
|
return (target, propertyName) => {
|
|
183
191
|
Object.defineProperty(target, propertyName, {
|
|
184
192
|
configurable: false,
|
|
@@ -186,7 +194,6 @@ function Setting(settingName) {
|
|
|
186
194
|
});
|
|
187
195
|
};
|
|
188
196
|
}
|
|
189
|
-
exports.Setting = Setting;
|
|
190
197
|
/**
|
|
191
198
|
* Register Modules Method Decorator this Method Decorator is used at bootstrap level and should decorate bootstrap class
|
|
192
199
|
* and register modules.
|
|
@@ -195,25 +202,23 @@ exports.Setting = Setting;
|
|
|
195
202
|
* @param Modules
|
|
196
203
|
*/
|
|
197
204
|
function Modules(Modules) {
|
|
198
|
-
return target => {
|
|
205
|
+
return (target) => {
|
|
199
206
|
for (const Module of Modules) {
|
|
200
|
-
const registeredModules =
|
|
207
|
+
const registeredModules = commons_1.Metadata.get(commons_2.REGISTERED_MODULE_KEY, target, 'start') || [];
|
|
201
208
|
registeredModules.unshift(Module);
|
|
202
|
-
|
|
209
|
+
commons_1.Metadata.set(commons_2.REGISTERED_MODULE_KEY, registeredModules, target, 'start');
|
|
203
210
|
}
|
|
204
211
|
};
|
|
205
212
|
}
|
|
206
|
-
exports.Modules = Modules;
|
|
207
213
|
function Proxies(proxyContainers) {
|
|
208
|
-
return target => {
|
|
214
|
+
return (target) => {
|
|
209
215
|
for (const proxyContainer of proxyContainers) {
|
|
210
|
-
const registeredProxyContainers =
|
|
216
|
+
const registeredProxyContainers = commons_1.Metadata.get(commons_2.ROUTER_PROXIES_KEY, target) || [];
|
|
211
217
|
registeredProxyContainers.unshift(proxyContainer);
|
|
212
|
-
|
|
218
|
+
commons_1.Metadata.set(commons_2.ROUTER_PROXIES_KEY, registeredProxyContainers, target);
|
|
213
219
|
}
|
|
214
220
|
};
|
|
215
221
|
}
|
|
216
|
-
exports.Proxies = Proxies;
|
|
217
222
|
/**
|
|
218
223
|
* Register Module Method Decorator this Method Decorator is used at bootstrap level and should decorate the start
|
|
219
224
|
* method with a Module Class.
|
|
@@ -222,28 +227,21 @@ exports.Proxies = Proxies;
|
|
|
222
227
|
* @param {Class} Module
|
|
223
228
|
* @deprecated Use the new decorator Modules that allow add modules into registered modules.
|
|
224
229
|
*/
|
|
230
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
225
231
|
function RegisterModule(Module) {
|
|
226
|
-
return (
|
|
227
|
-
|
|
228
|
-
throw new Error('Register Module needs to decorate ONLY start method');
|
|
229
|
-
}
|
|
230
|
-
const registeredModules = Metadata_1.default.get(constants_1.REGISTERED_MODULE_KEY, target, property) || [];
|
|
231
|
-
registeredModules.push(Module);
|
|
232
|
-
Metadata_1.default.set(constants_1.REGISTERED_MODULE_KEY, registeredModules, target, property);
|
|
232
|
+
return (_, __) => {
|
|
233
|
+
throw new Error('RegisterModule is deprecated, use the new decorator Modules that allow add modules into registered modules.');
|
|
233
234
|
};
|
|
234
235
|
}
|
|
235
|
-
exports.RegisterModule = RegisterModule;
|
|
236
236
|
function Teapot(teapotSettings) {
|
|
237
237
|
return (target) => {
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
commons_1.Metadata.set(commons_2.ASSIGN_TEAPOT_KEY, true, target, 'isTeapotActive');
|
|
239
|
+
commons_1.Metadata.set(commons_2.ASSIGN_TEAPOT_KEY, teapotSettings, target);
|
|
240
240
|
};
|
|
241
241
|
}
|
|
242
|
-
exports.Teapot = Teapot;
|
|
243
242
|
function Teacup(teacupSettings) {
|
|
244
243
|
return (target) => {
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
commons_1.Metadata.set(commons_2.ASSIGN_TEACUP_KEY, true, target, 'isTeacupActive');
|
|
245
|
+
commons_1.Metadata.set(commons_2.ASSIGN_TEACUP_KEY, teacupSettings, target);
|
|
247
246
|
};
|
|
248
247
|
}
|
|
249
|
-
exports.Teacup = Teacup;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import ExpressiveTeaEngine from '../../classes/Engine';
|
|
2
|
+
/**
|
|
3
|
+
* Health check status
|
|
4
|
+
* @since 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
export type HealthStatus = 'pass' | 'fail' | 'warn';
|
|
7
|
+
/**
|
|
8
|
+
* Individual health check result
|
|
9
|
+
* @since 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
export interface HealthCheckResult {
|
|
12
|
+
/** Check status */
|
|
13
|
+
status: HealthStatus;
|
|
14
|
+
/** Optional details about the check */
|
|
15
|
+
details?: Record<string, any>;
|
|
16
|
+
/** Optional error message if failed */
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Health check function
|
|
21
|
+
* @since 2.0.0
|
|
22
|
+
*/
|
|
23
|
+
export type HealthCheckFunction = () => Promise<HealthCheckResult> | HealthCheckResult;
|
|
24
|
+
/**
|
|
25
|
+
* Health check definition
|
|
26
|
+
* @since 2.0.0
|
|
27
|
+
*/
|
|
28
|
+
export interface HealthCheck {
|
|
29
|
+
/** Unique name for this health check */
|
|
30
|
+
name: string;
|
|
31
|
+
/** The check function to execute */
|
|
32
|
+
check: HealthCheckFunction;
|
|
33
|
+
/** Whether this check is critical for readiness */
|
|
34
|
+
critical?: boolean;
|
|
35
|
+
/** Optional timeout in milliseconds */
|
|
36
|
+
timeout?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Health Check Engine
|
|
40
|
+
*
|
|
41
|
+
* Provides standardized health check endpoints for monitoring and orchestration:
|
|
42
|
+
* - `/health` - Detailed health status with all checks
|
|
43
|
+
* - `/health/live` - Liveness probe (always 200 if server is running)
|
|
44
|
+
* - `/health/ready` - Readiness probe (200 only if all critical checks pass)
|
|
45
|
+
*
|
|
46
|
+
* Compatible with Kubernetes liveness and readiness probes.
|
|
47
|
+
*
|
|
48
|
+
* @class HealthCheckEngine
|
|
49
|
+
* @extends ExpressiveTeaEngine
|
|
50
|
+
* @since 2.0.0
|
|
51
|
+
* @summary Standardized health check endpoints for monitoring
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* // Enable health checks with custom checks
|
|
55
|
+
* @HealthCheck({
|
|
56
|
+
* checks: [
|
|
57
|
+
* {
|
|
58
|
+
* name: 'database',
|
|
59
|
+
* check: async () => {
|
|
60
|
+
* const isConnected = await db.ping();
|
|
61
|
+
* return { status: isConnected ? 'pass' : 'fail' };
|
|
62
|
+
* },
|
|
63
|
+
* critical: true,
|
|
64
|
+
* timeout: 5000
|
|
65
|
+
* },
|
|
66
|
+
* {
|
|
67
|
+
* name: 'cache',
|
|
68
|
+
* check: async () => {
|
|
69
|
+
* const isReady = await redis.ping();
|
|
70
|
+
* return {
|
|
71
|
+
* status: isReady ? 'pass' : 'warn',
|
|
72
|
+
* details: { connected: isReady }
|
|
73
|
+
* };
|
|
74
|
+
* }
|
|
75
|
+
* }
|
|
76
|
+
* ]
|
|
77
|
+
* })
|
|
78
|
+
* class MyApp extends Boot {}
|
|
79
|
+
*/
|
|
80
|
+
export default class HealthCheckEngine extends ExpressiveTeaEngine {
|
|
81
|
+
private checks;
|
|
82
|
+
/**
|
|
83
|
+
* Register a health check
|
|
84
|
+
* @param check - Health check configuration
|
|
85
|
+
* @since 2.0.0
|
|
86
|
+
*/
|
|
87
|
+
registerCheck(check: HealthCheck): void;
|
|
88
|
+
/**
|
|
89
|
+
* Execute a single health check with timeout
|
|
90
|
+
* @param check - Health check to execute
|
|
91
|
+
* @returns Check result
|
|
92
|
+
* @private
|
|
93
|
+
*/
|
|
94
|
+
private executeCheck;
|
|
95
|
+
/**
|
|
96
|
+
* Initialize health check routes
|
|
97
|
+
* @since 2.0.0
|
|
98
|
+
*/
|
|
99
|
+
init(): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Start the health check engine
|
|
102
|
+
* @since 2.0.0
|
|
103
|
+
*/
|
|
104
|
+
start(): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Stop the health check engine
|
|
107
|
+
* @since 2.0.0
|
|
108
|
+
*/
|
|
109
|
+
stop(): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Determine if this engine should be registered
|
|
112
|
+
*
|
|
113
|
+
* Health checks are enabled by default for all applications.
|
|
114
|
+
* To disable, set `enableHealthChecks: false` in ServerSettings.
|
|
115
|
+
*
|
|
116
|
+
* @returns true if health checks should be enabled
|
|
117
|
+
* @since 2.0.0
|
|
118
|
+
*/
|
|
119
|
+
static canRegister(): boolean;
|
|
120
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const inversify_1 = require("inversify");
|
|
5
|
+
const Engine_1 = require("../../classes/Engine");
|
|
6
|
+
/**
|
|
7
|
+
* Health Check Engine
|
|
8
|
+
*
|
|
9
|
+
* Provides standardized health check endpoints for monitoring and orchestration:
|
|
10
|
+
* - `/health` - Detailed health status with all checks
|
|
11
|
+
* - `/health/live` - Liveness probe (always 200 if server is running)
|
|
12
|
+
* - `/health/ready` - Readiness probe (200 only if all critical checks pass)
|
|
13
|
+
*
|
|
14
|
+
* Compatible with Kubernetes liveness and readiness probes.
|
|
15
|
+
*
|
|
16
|
+
* @class HealthCheckEngine
|
|
17
|
+
* @extends ExpressiveTeaEngine
|
|
18
|
+
* @since 2.0.0
|
|
19
|
+
* @summary Standardized health check endpoints for monitoring
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Enable health checks with custom checks
|
|
23
|
+
* @HealthCheck({
|
|
24
|
+
* checks: [
|
|
25
|
+
* {
|
|
26
|
+
* name: 'database',
|
|
27
|
+
* check: async () => {
|
|
28
|
+
* const isConnected = await db.ping();
|
|
29
|
+
* return { status: isConnected ? 'pass' : 'fail' };
|
|
30
|
+
* },
|
|
31
|
+
* critical: true,
|
|
32
|
+
* timeout: 5000
|
|
33
|
+
* },
|
|
34
|
+
* {
|
|
35
|
+
* name: 'cache',
|
|
36
|
+
* check: async () => {
|
|
37
|
+
* const isReady = await redis.ping();
|
|
38
|
+
* return {
|
|
39
|
+
* status: isReady ? 'pass' : 'warn',
|
|
40
|
+
* details: { connected: isReady }
|
|
41
|
+
* };
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* ]
|
|
45
|
+
* })
|
|
46
|
+
* class MyApp extends Boot {}
|
|
47
|
+
*/
|
|
48
|
+
let HealthCheckEngine = class HealthCheckEngine extends Engine_1.default {
|
|
49
|
+
constructor() {
|
|
50
|
+
super(...arguments);
|
|
51
|
+
this.checks = [];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register a health check
|
|
55
|
+
* @param check - Health check configuration
|
|
56
|
+
* @since 2.0.0
|
|
57
|
+
*/
|
|
58
|
+
registerCheck(check) {
|
|
59
|
+
this.checks.push(check);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Execute a single health check with timeout
|
|
63
|
+
* @param check - Health check to execute
|
|
64
|
+
* @returns Check result
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
async executeCheck(check) {
|
|
68
|
+
const timeout = check.timeout || 5000;
|
|
69
|
+
try {
|
|
70
|
+
const result = await Promise.race([
|
|
71
|
+
Promise.resolve(check.check()),
|
|
72
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Health check timeout')), timeout)),
|
|
73
|
+
]);
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
status: 'fail',
|
|
79
|
+
error: error instanceof Error ? error.message : String(error),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Initialize health check routes
|
|
85
|
+
* @since 2.0.0
|
|
86
|
+
*/
|
|
87
|
+
async init() {
|
|
88
|
+
const app = this.context.getApplication();
|
|
89
|
+
// Detailed health endpoint
|
|
90
|
+
app.get('/health', async (_req, res) => {
|
|
91
|
+
const results = {};
|
|
92
|
+
let overallStatus = 'pass';
|
|
93
|
+
// Execute all checks in parallel
|
|
94
|
+
await Promise.all(this.checks.map(async (check) => {
|
|
95
|
+
const result = await this.executeCheck(check);
|
|
96
|
+
results[check.name] = result;
|
|
97
|
+
// Update overall status
|
|
98
|
+
if (result.status === 'fail') {
|
|
99
|
+
overallStatus = 'fail';
|
|
100
|
+
}
|
|
101
|
+
else if (result.status === 'warn' && overallStatus === 'pass') {
|
|
102
|
+
overallStatus = 'warn';
|
|
103
|
+
}
|
|
104
|
+
}));
|
|
105
|
+
const statusCode = overallStatus === 'pass' ? 200 : overallStatus === 'warn' ? 200 : 503;
|
|
106
|
+
res.status(statusCode).json({
|
|
107
|
+
status: overallStatus,
|
|
108
|
+
timestamp: new Date().toISOString(),
|
|
109
|
+
checks: results,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
// Liveness probe - always returns 200 if server is running
|
|
113
|
+
app.get('/health/live', (_req, res) => {
|
|
114
|
+
res.status(200).json({
|
|
115
|
+
status: 'pass',
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
// Readiness probe - returns 200 only if all critical checks pass
|
|
120
|
+
app.get('/health/ready', async (_req, res) => {
|
|
121
|
+
const criticalChecks = this.checks.filter((c) => c.critical);
|
|
122
|
+
if (criticalChecks.length === 0) {
|
|
123
|
+
// No critical checks, always ready
|
|
124
|
+
res.status(200).json({
|
|
125
|
+
status: 'pass',
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const results = {};
|
|
131
|
+
let isReady = true;
|
|
132
|
+
// Execute only critical checks
|
|
133
|
+
await Promise.all(criticalChecks.map(async (check) => {
|
|
134
|
+
const result = await this.executeCheck(check);
|
|
135
|
+
results[check.name] = result;
|
|
136
|
+
if (result.status === 'fail') {
|
|
137
|
+
isReady = false;
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
res.status(isReady ? 200 : 503).json({
|
|
141
|
+
status: isReady ? 'pass' : 'fail',
|
|
142
|
+
timestamp: new Date().toISOString(),
|
|
143
|
+
checks: results,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Start the health check engine
|
|
149
|
+
* @since 2.0.0
|
|
150
|
+
*/
|
|
151
|
+
async start() {
|
|
152
|
+
// Nothing to start - routes are already registered
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Stop the health check engine
|
|
156
|
+
* @since 2.0.0
|
|
157
|
+
*/
|
|
158
|
+
async stop() {
|
|
159
|
+
// Clear checks on shutdown
|
|
160
|
+
this.checks = [];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Determine if this engine should be registered
|
|
164
|
+
*
|
|
165
|
+
* Health checks are enabled by default for all applications.
|
|
166
|
+
* To disable, set `enableHealthChecks: false` in ServerSettings.
|
|
167
|
+
*
|
|
168
|
+
* @returns true if health checks should be enabled
|
|
169
|
+
* @since 2.0.0
|
|
170
|
+
*/
|
|
171
|
+
static canRegister() {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
HealthCheckEngine = tslib_1.__decorate([
|
|
176
|
+
(0, inversify_1.injectable)(),
|
|
177
|
+
(0, inversify_1.injectFromBase)({ extendConstructorArguments: true })
|
|
178
|
+
], HealthCheckEngine);
|
|
179
|
+
exports.default = HealthCheckEngine;
|
package/engines/http/index.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import * as https from 'https';
|
|
5
|
-
import { BOOT_STAGES } from '@expressive-tea/commons/constants';
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import * as https from 'node:https';
|
|
3
|
+
import { BOOT_STAGES } from '@expressive-tea/commons';
|
|
6
4
|
import ExpressiveTeaEngine from '../../classes/Engine';
|
|
7
5
|
export default class HTTPEngine extends ExpressiveTeaEngine {
|
|
8
6
|
private listen;
|
|
9
7
|
start(): Promise<(http.Server | https.Server)[]>;
|
|
10
8
|
init(): Promise<void>;
|
|
11
|
-
resolveStages(stages: BOOT_STAGES[], ...extraArgs:
|
|
12
|
-
resolveProxyContainers():
|
|
9
|
+
resolveStages(stages: BOOT_STAGES[], ...extraArgs: unknown[]): Promise<unknown[]>;
|
|
10
|
+
resolveProxyContainers(): void;
|
|
11
|
+
static canRegister(): boolean;
|
|
13
12
|
}
|
package/engines/http/index.js
CHANGED
|
@@ -3,14 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const inversify_1 = require("inversify");
|
|
5
5
|
const boot_helper_1 = require("../../helpers/boot-helper");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
const commons_1 = require("@expressive-tea/commons");
|
|
7
|
+
const commons_2 = require("@expressive-tea/commons");
|
|
8
|
+
const commons_3 = require("@expressive-tea/commons");
|
|
9
9
|
const Engine_1 = require("../../classes/Engine");
|
|
10
10
|
let HTTPEngine = class HTTPEngine extends Engine_1.default {
|
|
11
|
-
listen(server, port) {
|
|
11
|
+
async listen(server, port) {
|
|
12
12
|
return new Promise((resolve, reject) => {
|
|
13
|
-
console.log('Listening on port', port, server.listen);
|
|
14
13
|
server.listen(port);
|
|
15
14
|
server.on('error', error => {
|
|
16
15
|
reject(error);
|
|
@@ -22,33 +21,39 @@ let HTTPEngine = class HTTPEngine extends Engine_1.default {
|
|
|
22
21
|
});
|
|
23
22
|
}
|
|
24
23
|
async start() {
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const servers = [
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
27
26
|
await this.listen(this.server, this.settings.get('port')),
|
|
28
27
|
(this.serverSecure) ? await this.listen(this.serverSecure, this.settings.get('securePort')) : null
|
|
29
28
|
];
|
|
30
|
-
|
|
29
|
+
const listenerServers = servers.filter((server) => server !== null);
|
|
30
|
+
await this.resolveStages([commons_1.BOOT_STAGES.START], ...listenerServers);
|
|
31
31
|
return listenerServers;
|
|
32
32
|
}
|
|
33
33
|
async init() {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
(0, boot_helper_1.resolveDirectives)(this.context, this.context.getApplication());
|
|
35
|
+
(0, boot_helper_1.resolveStatic)(this.context, this.context.getApplication());
|
|
36
36
|
// HTTP Engine Resolve Stages
|
|
37
|
-
|
|
38
|
-
await this.resolveStages(
|
|
39
|
-
await this.resolveStages([
|
|
37
|
+
this.resolveProxyContainers();
|
|
38
|
+
await this.resolveStages(commons_1.BOOT_ORDER);
|
|
39
|
+
await this.resolveStages([commons_1.BOOT_STAGES.AFTER_APPLICATION_MIDDLEWARES, commons_1.BOOT_STAGES.ON_HTTP_CREATION], this.server, this.serverSecure);
|
|
40
40
|
}
|
|
41
41
|
async resolveStages(stages, ...extraArgs) {
|
|
42
|
-
return Promise.all(stages.map(s => (0, boot_helper_1.resolveStage)(s, this.context, this.context.getApplication(), ...extraArgs)));
|
|
42
|
+
return Promise.all(stages.map(async (s) => (0, boot_helper_1.resolveStage)(s, this.context, this.context.getApplication(), ...extraArgs)));
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
resolveProxyContainers() {
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
46
|
+
const ProxyContainers = commons_3.Metadata.get(commons_1.ROUTER_PROXIES_KEY, (0, commons_2.getClass)(this.context)) || [];
|
|
46
47
|
for (const Container of ProxyContainers) {
|
|
47
48
|
(0, boot_helper_1.resolveProxy)(Container, this.context.getApplication());
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
static canRegister() {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
50
54
|
};
|
|
51
55
|
HTTPEngine = tslib_1.__decorate([
|
|
52
|
-
(0, inversify_1.injectable)()
|
|
56
|
+
(0, inversify_1.injectable)(),
|
|
57
|
+
(0, inversify_1.injectFromBase)({ extendConstructorArguments: true })
|
|
53
58
|
], HTTPEngine);
|
|
54
59
|
exports.default = HTTPEngine;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Engine Registration
|
|
3
|
+
*
|
|
4
|
+
* Automatically registers all core Expressive Tea engines with the EngineRegistry.
|
|
5
|
+
* Import this file to enable all core engines with proper dependency order.
|
|
6
|
+
*
|
|
7
|
+
* Engine Priority Order:
|
|
8
|
+
* - 0: HTTPEngine (base engine, no dependencies)
|
|
9
|
+
* - 5: HealthCheckEngine (health endpoints, depends on http)
|
|
10
|
+
* - 10: SocketIOEngine, WebsocketEngine (depend on http)
|
|
11
|
+
* - 20: TeapotEngine, TeacupEngine (depend on http, socketio)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import '@expressive-tea/core/engines'; // Registers all core engines
|
|
16
|
+
* import Boot from '@expressive-tea/core';
|
|
17
|
+
*
|
|
18
|
+
* class MyApp extends Boot {}
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module engines
|
|
22
|
+
* @since 2.0.0
|
|
23
|
+
*/
|
|
24
|
+
import EngineRegistry from '../classes/EngineRegistry';
|
|
25
|
+
import HTTPEngine from './http';
|
|
26
|
+
import HealthCheckEngine from './health';
|
|
27
|
+
import SocketIOEngine from './socketio';
|
|
28
|
+
import WebsocketEngine from './websocket';
|
|
29
|
+
import TeapotEngine from './teapot';
|
|
30
|
+
import TeacupEngine from './teacup';
|
|
31
|
+
export { HTTPEngine, HealthCheckEngine, SocketIOEngine, WebsocketEngine, TeapotEngine, TeacupEngine };
|
|
32
|
+
export default EngineRegistry;
|