@expressive-tea/core 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/LICENSE +201 -0
- package/README.md +627 -0
- package/banner.png +0 -0
- package/classes/Boot.d.ts +145 -0
- package/classes/Boot.js +223 -0
- package/classes/Engine.d.ts +63 -0
- package/classes/Engine.js +90 -0
- package/classes/EngineRegistry.d.ts +154 -0
- package/classes/EngineRegistry.js +247 -0
- package/classes/LoadBalancer.d.ts +8 -0
- package/classes/LoadBalancer.js +28 -0
- package/classes/ProxyRoute.d.ts +14 -0
- package/classes/ProxyRoute.js +40 -0
- package/classes/Settings.d.ts +128 -0
- package/classes/Settings.js +172 -0
- package/decorators/annotations.d.ts +91 -0
- package/decorators/annotations.js +132 -0
- 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 +34 -0
- package/decorators/module.js +39 -0
- package/decorators/proxy.d.ts +28 -0
- package/decorators/proxy.js +60 -0
- package/decorators/router.d.ts +199 -0
- package/decorators/router.js +252 -0
- package/decorators/server.d.ts +92 -0
- package/decorators/server.js +247 -0
- package/engines/constants/constants.d.ts +2 -0
- package/engines/constants/constants.js +5 -0
- package/engines/health/index.d.ts +120 -0
- package/engines/health/index.js +179 -0
- package/engines/http/index.d.ts +12 -0
- package/engines/http/index.js +59 -0
- package/engines/index.d.ts +32 -0
- package/engines/index.js +112 -0
- package/engines/socketio/index.d.ts +7 -0
- package/engines/socketio/index.js +30 -0
- package/engines/teacup/index.d.ts +27 -0
- package/engines/teacup/index.js +136 -0
- package/engines/teapot/index.d.ts +32 -0
- package/engines/teapot/index.js +167 -0
- package/engines/websocket/index.d.ts +9 -0
- package/engines/websocket/index.js +39 -0
- package/eslint.config.mjs +138 -0
- package/exceptions/BootLoaderExceptions.d.ts +26 -0
- package/exceptions/BootLoaderExceptions.js +31 -0
- package/exceptions/RequestExceptions.d.ts +75 -0
- package/exceptions/RequestExceptions.js +89 -0
- package/helpers/boot-helper.d.ts +7 -0
- package/helpers/boot-helper.js +84 -0
- package/helpers/decorators.d.ts +1 -0
- package/helpers/decorators.js +15 -0
- package/helpers/promise-helper.d.ts +1 -0
- package/helpers/promise-helper.js +6 -0
- package/helpers/server.d.ts +35 -0
- package/helpers/server.js +141 -0
- package/helpers/teapot-helper.d.ts +18 -0
- package/helpers/teapot-helper.js +88 -0
- package/helpers/websocket-helper.d.ts +3 -0
- package/helpers/websocket-helper.js +20 -0
- package/images/announcement-01.png +0 -0
- package/images/logo-sticky-01.png +0 -0
- package/images/logo-wp-01.png +0 -0
- package/images/logo.png +0 -0
- package/images/zero-oneit.png +0 -0
- package/interfaces/index.d.ts +4 -0
- package/interfaces/index.js +2 -0
- package/inversify.config.d.ts +9 -0
- package/inversify.config.js +8 -0
- package/libs/classNames.d.ts +1 -0
- package/libs/classNames.js +4 -0
- 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 +137 -0
- package/services/DependencyInjection.d.ts +159 -0
- package/services/DependencyInjection.js +201 -0
- package/services/WebsocketService.d.ts +18 -0
- package/services/WebsocketService.js +47 -0
- 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
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { type Express } from 'express';
|
|
3
|
+
import { type ExpressiveTeaApplication } from '@expressive-tea/commons';
|
|
4
|
+
import Settings from './Settings';
|
|
5
|
+
import { Container, type Newable, type ServiceIdentifier } from 'inversify';
|
|
6
|
+
/**
|
|
7
|
+
* Expressive Tea Application interface is the response from an started application, contains the express application
|
|
8
|
+
* and a node http server instance.
|
|
9
|
+
* @typedef {Object} ExpressiveTeaApplication
|
|
10
|
+
* @property {Express} application - Express Application Instance
|
|
11
|
+
* @property { HTTPServer } server - HTTP Server Object
|
|
12
|
+
* @summary Application Interface
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* <b>Bootstrap Server Engine Class</b> is an abstract class to provide the Expressive Tea engine and bootstraps tools.
|
|
16
|
+
* This is containing the logic and full functionality of Expressive Tea and only can be extended.
|
|
17
|
+
*
|
|
18
|
+
* @abstract
|
|
19
|
+
* @class Boot
|
|
20
|
+
* @summary Bootstrap Engine Class
|
|
21
|
+
*/
|
|
22
|
+
declare abstract class Boot {
|
|
23
|
+
/**
|
|
24
|
+
* Maintain a reference to Singleton instance of Settings, if settings still does not initialized it will created
|
|
25
|
+
* automatically when extended class create a new instance.
|
|
26
|
+
*
|
|
27
|
+
* @type {Settings}
|
|
28
|
+
* @public
|
|
29
|
+
* @summary Server Settings instance reference
|
|
30
|
+
*/
|
|
31
|
+
settings: Settings;
|
|
32
|
+
/**
|
|
33
|
+
* Automatically create an Express application instance which will be user to configure over all the boot stages.
|
|
34
|
+
* @type {Express}
|
|
35
|
+
* @private
|
|
36
|
+
* @readonly
|
|
37
|
+
* @summary Express Application instance internal property.
|
|
38
|
+
*/
|
|
39
|
+
private readonly server;
|
|
40
|
+
private readonly containerDI;
|
|
41
|
+
/**
|
|
42
|
+
* Engine instances for cleanup during shutdown
|
|
43
|
+
* @type {ExpressiveTeaEngine[]}
|
|
44
|
+
* @private
|
|
45
|
+
* @since 2.0.0
|
|
46
|
+
*/
|
|
47
|
+
private engines;
|
|
48
|
+
constructor();
|
|
49
|
+
/**
|
|
50
|
+
* Get Express Application
|
|
51
|
+
* @returns Express
|
|
52
|
+
*/
|
|
53
|
+
getApplication(): Express;
|
|
54
|
+
/**
|
|
55
|
+
* Get the Dependency Injection container for this application instance.
|
|
56
|
+
* This container is scoped to the application and inherits from the global container.
|
|
57
|
+
*
|
|
58
|
+
* @returns {Container} The application's DI container
|
|
59
|
+
* @since 2.0.0
|
|
60
|
+
* @summary Get application DI container
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* class MyApp extends Boot {
|
|
64
|
+
* async start() {
|
|
65
|
+
* const container = this.getContainer();
|
|
66
|
+
* container.bind(MyService).toSelf();
|
|
67
|
+
* return super.start();
|
|
68
|
+
* }
|
|
69
|
+
* }
|
|
70
|
+
*/
|
|
71
|
+
getContainer(): Container;
|
|
72
|
+
/**
|
|
73
|
+
* Register a provider in the application's DI container.
|
|
74
|
+
* Providers registered here are available to all modules and engines in this application.
|
|
75
|
+
*
|
|
76
|
+
* @template T
|
|
77
|
+
* @param {ServiceIdentifier<T>} identifier - The service identifier (class or token)
|
|
78
|
+
* @param {Newable<T>} provider - The provider class to bind
|
|
79
|
+
* @returns {void}
|
|
80
|
+
* @since 2.0.0
|
|
81
|
+
* @summary Register a DI provider
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* class MyApp extends Boot {
|
|
85
|
+
* constructor() {
|
|
86
|
+
* super();
|
|
87
|
+
* this.registerProvider(DatabaseService, DatabaseService);
|
|
88
|
+
* this.registerProvider('API_KEY', ApiKeyProvider);
|
|
89
|
+
* }
|
|
90
|
+
* }
|
|
91
|
+
*/
|
|
92
|
+
registerProvider<T>(identifier: ServiceIdentifier<T>, provider: Newable<T>): void;
|
|
93
|
+
/**
|
|
94
|
+
* Register a constant value in the application's DI container.
|
|
95
|
+
*
|
|
96
|
+
* @template T
|
|
97
|
+
* @param {ServiceIdentifier<T>} identifier - The service identifier (usually a string or symbol)
|
|
98
|
+
* @param {T} value - The constant value to bind
|
|
99
|
+
* @returns {void}
|
|
100
|
+
* @since 2.0.0
|
|
101
|
+
* @summary Register a constant value
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* class MyApp extends Boot {
|
|
105
|
+
* constructor() {
|
|
106
|
+
* super();
|
|
107
|
+
* this.registerConstant('DATABASE_URL', process.env.DATABASE_URL);
|
|
108
|
+
* this.registerConstant('MAX_CONNECTIONS', 100);
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
*/
|
|
112
|
+
registerConstant<T>(identifier: ServiceIdentifier<T>, value: T): void;
|
|
113
|
+
/**
|
|
114
|
+
* Bootstrap and verify that all the required plugins are correctly configured and proceed to attach all the
|
|
115
|
+
* registered modules. <b>Remember</b> this is the unique method that must be decorated for the Register Module
|
|
116
|
+
* decorator.
|
|
117
|
+
* @summary Initialize and Bootstrap Server.
|
|
118
|
+
* @returns {Promise<ExpressiveTeaApplication>}
|
|
119
|
+
*/
|
|
120
|
+
start(): Promise<ExpressiveTeaApplication>;
|
|
121
|
+
/**
|
|
122
|
+
* Gracefully stop the application and all engines
|
|
123
|
+
*
|
|
124
|
+
* Calls the stop() lifecycle method on all registered engines in reverse order
|
|
125
|
+
* (opposite of initialization order) to ensure proper cleanup.
|
|
126
|
+
*
|
|
127
|
+
* @returns {Promise<void>} Promise that resolves when all engines have stopped
|
|
128
|
+
* @since 2.0.0
|
|
129
|
+
* @summary Graceful shutdown
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* const app = new MyApp();
|
|
134
|
+
* await app.start();
|
|
135
|
+
*
|
|
136
|
+
* // Later, during shutdown
|
|
137
|
+
* await app.stop();
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
stop(): Promise<void>;
|
|
141
|
+
private initializeEngines;
|
|
142
|
+
private initializeHttp;
|
|
143
|
+
private initializeContainer;
|
|
144
|
+
}
|
|
145
|
+
export default Boot;
|
package/classes/Boot.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
require("reflect-metadata");
|
|
4
|
+
const inversify_config_1 = require("../inversify.config");
|
|
5
|
+
const express = require("express");
|
|
6
|
+
const Engine_1 = require("./Engine");
|
|
7
|
+
const Settings_1 = require("./Settings");
|
|
8
|
+
const fs = require("node:fs");
|
|
9
|
+
const http = require("node:http");
|
|
10
|
+
const https = require("node:https");
|
|
11
|
+
const inversify_1 = require("inversify");
|
|
12
|
+
const injection_types_1 = require("../types/injection-types");
|
|
13
|
+
const EngineRegistry_1 = require("./EngineRegistry");
|
|
14
|
+
/**
|
|
15
|
+
* Expressive Tea Application interface is the response from an started application, contains the express application
|
|
16
|
+
* and a node http server instance.
|
|
17
|
+
* @typedef {Object} ExpressiveTeaApplication
|
|
18
|
+
* @property {Express} application - Express Application Instance
|
|
19
|
+
* @property { HTTPServer } server - HTTP Server Object
|
|
20
|
+
* @summary Application Interface
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* <b>Bootstrap Server Engine Class</b> is an abstract class to provide the Expressive Tea engine and bootstraps tools.
|
|
24
|
+
* This is containing the logic and full functionality of Expressive Tea and only can be extended.
|
|
25
|
+
*
|
|
26
|
+
* @abstract
|
|
27
|
+
* @class Boot
|
|
28
|
+
* @summary Bootstrap Engine Class
|
|
29
|
+
*/
|
|
30
|
+
class Boot {
|
|
31
|
+
constructor() {
|
|
32
|
+
/**
|
|
33
|
+
* Automatically create an Express application instance which will be user to configure over all the boot stages.
|
|
34
|
+
* @type {Express}
|
|
35
|
+
* @private
|
|
36
|
+
* @readonly
|
|
37
|
+
* @summary Express Application instance internal property.
|
|
38
|
+
*/
|
|
39
|
+
this.server = express();
|
|
40
|
+
this.containerDI = new inversify_1.Container({ parent: inversify_config_1.default });
|
|
41
|
+
/**
|
|
42
|
+
* Engine instances for cleanup during shutdown
|
|
43
|
+
* @type {ExpressiveTeaEngine[]}
|
|
44
|
+
* @private
|
|
45
|
+
* @since 2.0.0
|
|
46
|
+
*/
|
|
47
|
+
this.engines = [];
|
|
48
|
+
this.settings = Settings_1.default.getInstance(this);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get Express Application
|
|
52
|
+
* @returns Express
|
|
53
|
+
*/
|
|
54
|
+
getApplication() {
|
|
55
|
+
return this.server;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the Dependency Injection container for this application instance.
|
|
59
|
+
* This container is scoped to the application and inherits from the global container.
|
|
60
|
+
*
|
|
61
|
+
* @returns {Container} The application's DI container
|
|
62
|
+
* @since 2.0.0
|
|
63
|
+
* @summary Get application DI container
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* class MyApp extends Boot {
|
|
67
|
+
* async start() {
|
|
68
|
+
* const container = this.getContainer();
|
|
69
|
+
* container.bind(MyService).toSelf();
|
|
70
|
+
* return super.start();
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
*/
|
|
74
|
+
getContainer() {
|
|
75
|
+
return this.containerDI;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register a provider in the application's DI container.
|
|
79
|
+
* Providers registered here are available to all modules and engines in this application.
|
|
80
|
+
*
|
|
81
|
+
* @template T
|
|
82
|
+
* @param {ServiceIdentifier<T>} identifier - The service identifier (class or token)
|
|
83
|
+
* @param {Newable<T>} provider - The provider class to bind
|
|
84
|
+
* @returns {void}
|
|
85
|
+
* @since 2.0.0
|
|
86
|
+
* @summary Register a DI provider
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* class MyApp extends Boot {
|
|
90
|
+
* constructor() {
|
|
91
|
+
* super();
|
|
92
|
+
* this.registerProvider(DatabaseService, DatabaseService);
|
|
93
|
+
* this.registerProvider('API_KEY', ApiKeyProvider);
|
|
94
|
+
* }
|
|
95
|
+
* }
|
|
96
|
+
*/
|
|
97
|
+
registerProvider(identifier, provider) {
|
|
98
|
+
if (!this.containerDI.isBound(identifier)) {
|
|
99
|
+
this.containerDI.bind(identifier).to(provider);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Register a constant value in the application's DI container.
|
|
104
|
+
*
|
|
105
|
+
* @template T
|
|
106
|
+
* @param {ServiceIdentifier<T>} identifier - The service identifier (usually a string or symbol)
|
|
107
|
+
* @param {T} value - The constant value to bind
|
|
108
|
+
* @returns {void}
|
|
109
|
+
* @since 2.0.0
|
|
110
|
+
* @summary Register a constant value
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* class MyApp extends Boot {
|
|
114
|
+
* constructor() {
|
|
115
|
+
* super();
|
|
116
|
+
* this.registerConstant('DATABASE_URL', process.env.DATABASE_URL);
|
|
117
|
+
* this.registerConstant('MAX_CONNECTIONS', 100);
|
|
118
|
+
* }
|
|
119
|
+
* }
|
|
120
|
+
*/
|
|
121
|
+
registerConstant(identifier, value) {
|
|
122
|
+
if (!this.containerDI.isBound(identifier)) {
|
|
123
|
+
this.containerDI.bind(identifier).toConstantValue(value);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Bootstrap and verify that all the required plugins are correctly configured and proceed to attach all the
|
|
128
|
+
* registered modules. <b>Remember</b> this is the unique method that must be decorated for the Register Module
|
|
129
|
+
* decorator.
|
|
130
|
+
* @summary Initialize and Bootstrap Server.
|
|
131
|
+
* @returns {Promise<ExpressiveTeaApplication>}
|
|
132
|
+
*/
|
|
133
|
+
async start() {
|
|
134
|
+
// Initialize Server
|
|
135
|
+
const [server, secureServer] = this.initializeHttp();
|
|
136
|
+
// Injectables
|
|
137
|
+
this.initializeContainer(server, secureServer);
|
|
138
|
+
// Lazy load engines to avoid circular dependency
|
|
139
|
+
// This ensures engines are only loaded when needed
|
|
140
|
+
if (EngineRegistry_1.default.getAllEngines().length === 0) {
|
|
141
|
+
await Promise.resolve().then(() => require('../engines'));
|
|
142
|
+
}
|
|
143
|
+
// Get registered engines from EngineRegistry (automatically filtered and sorted by dependencies)
|
|
144
|
+
const registeredEngines = EngineRegistry_1.default.getRegisteredEngines(this, this.settings);
|
|
145
|
+
this.initializeEngines(registeredEngines);
|
|
146
|
+
// Resolve Engines
|
|
147
|
+
const readyEngines = registeredEngines.map(Engine => {
|
|
148
|
+
const instance = this.containerDI.get(Engine);
|
|
149
|
+
return instance;
|
|
150
|
+
});
|
|
151
|
+
// Store engine instances for cleanup
|
|
152
|
+
this.engines = readyEngines;
|
|
153
|
+
// Initialize Engines
|
|
154
|
+
try {
|
|
155
|
+
await Engine_1.default.exec(readyEngines.reverse(), 'init');
|
|
156
|
+
await Engine_1.default.exec(readyEngines, 'start');
|
|
157
|
+
return ({ application: this.server, server, secureServer });
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
// If anything failed during engine initialization or start, ensure servers are closed to avoid leaking
|
|
161
|
+
server === null || server === void 0 ? void 0 : server.close();
|
|
162
|
+
secureServer === null || secureServer === void 0 ? void 0 : secureServer.close();
|
|
163
|
+
throw e;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Gracefully stop the application and all engines
|
|
168
|
+
*
|
|
169
|
+
* Calls the stop() lifecycle method on all registered engines in reverse order
|
|
170
|
+
* (opposite of initialization order) to ensure proper cleanup.
|
|
171
|
+
*
|
|
172
|
+
* @returns {Promise<void>} Promise that resolves when all engines have stopped
|
|
173
|
+
* @since 2.0.0
|
|
174
|
+
* @summary Graceful shutdown
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```typescript
|
|
178
|
+
* const app = new MyApp();
|
|
179
|
+
* await app.start();
|
|
180
|
+
*
|
|
181
|
+
* // Later, during shutdown
|
|
182
|
+
* await app.stop();
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
async stop() {
|
|
186
|
+
if (this.engines.length === 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// Stop engines in reverse order (opposite of initialization)
|
|
190
|
+
await Engine_1.default.exec([...this.engines].reverse(), 'stop');
|
|
191
|
+
// Clear engine references
|
|
192
|
+
this.engines = [];
|
|
193
|
+
}
|
|
194
|
+
initializeEngines(registeredEngines) {
|
|
195
|
+
for (const Engine of registeredEngines) {
|
|
196
|
+
this.containerDI.bind(Engine).to(Engine);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
initializeHttp() {
|
|
200
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
201
|
+
const privateKey = this.settings.get('privateKey');
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
203
|
+
const certificate = this.settings.get('certificate');
|
|
204
|
+
const server = http.createServer(this.server);
|
|
205
|
+
const secureServer = privateKey &&
|
|
206
|
+
certificate
|
|
207
|
+
? https.createServer({
|
|
208
|
+
cert: fs.readFileSync(certificate).toString('utf-8'),
|
|
209
|
+
key: fs.readFileSync(privateKey).toString('utf-8')
|
|
210
|
+
}, this.server)
|
|
211
|
+
: undefined;
|
|
212
|
+
return [server, secureServer];
|
|
213
|
+
}
|
|
214
|
+
initializeContainer(server, secureServer) {
|
|
215
|
+
this.containerDI.bind(injection_types_1.TYPES.Server).toConstantValue(server);
|
|
216
|
+
if (secureServer) {
|
|
217
|
+
this.containerDI.bind(injection_types_1.TYPES.SecureServer).toConstantValue(secureServer);
|
|
218
|
+
}
|
|
219
|
+
this.containerDI.bind(injection_types_1.TYPES.Context).toConstantValue(this);
|
|
220
|
+
this.containerDI.bind(injection_types_1.TYPES.Settings).toConstantValue(this.settings);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
exports.default = Boot;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import Settings from './Settings';
|
|
2
|
+
import type Boot from './Boot';
|
|
3
|
+
import { Server as HttpServer } from 'node:http';
|
|
4
|
+
import { Server as HttpsServer } from 'node:https';
|
|
5
|
+
export default class ExpressiveTeaEngine {
|
|
6
|
+
protected readonly context: Boot;
|
|
7
|
+
protected readonly server: HttpServer;
|
|
8
|
+
protected readonly serverSecure: HttpsServer;
|
|
9
|
+
protected readonly settings: Settings;
|
|
10
|
+
constructor(context: Boot, server: HttpServer, serverSecure: HttpsServer, settings: Settings);
|
|
11
|
+
/**
|
|
12
|
+
* Execute a lifecycle method across all registered engines
|
|
13
|
+
*
|
|
14
|
+
* @param {ExpressiveTeaEngine[]} availableEngines - Array of engine instances
|
|
15
|
+
* @param {string} method - Method name to execute (e.g., 'init', 'start', 'stop')
|
|
16
|
+
* @returns {Promise<unknown[]>} Array of results from all engines
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* await ExpressiveTeaEngine.exec(engines, 'init');
|
|
21
|
+
* await ExpressiveTeaEngine.exec(engines, 'start');
|
|
22
|
+
* ```
|
|
23
|
+
* @since 1.0.0
|
|
24
|
+
*/
|
|
25
|
+
static exec(availableEngines: ExpressiveTeaEngine[], method: string): Promise<unknown[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Determine if this engine can be registered in the current context
|
|
28
|
+
*
|
|
29
|
+
* Override this method to conditionally enable/disable the engine based on
|
|
30
|
+
* settings or environment. Default returns false to prevent accidental registration.
|
|
31
|
+
*
|
|
32
|
+
* @param {Boot} [ctx] - Boot context
|
|
33
|
+
* @param {Settings} [settings] - Application settings
|
|
34
|
+
* @returns {boolean} True if engine can be registered
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* static canRegister(ctx?: Boot, settings?: Settings): boolean {
|
|
39
|
+
* return settings?.get('enableMyEngine') === true;
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
* @since 1.0.0
|
|
43
|
+
*/
|
|
44
|
+
static canRegister(ctx?: Boot, settings?: Settings): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Graceful shutdown lifecycle method
|
|
47
|
+
*
|
|
48
|
+
* Override this method to implement cleanup logic when the application stops.
|
|
49
|
+
* This is called during graceful shutdown to close connections, clean up resources, etc.
|
|
50
|
+
*
|
|
51
|
+
* @returns {Promise<void>} Promise that resolves when cleanup is complete
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* async stop(): Promise<void> {
|
|
56
|
+
* await this.closeConnections();
|
|
57
|
+
* await this.cleanup();
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
* @since 2.0.0
|
|
61
|
+
*/
|
|
62
|
+
stop(): Promise<void>;
|
|
63
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const inversify_1 = require("inversify");
|
|
5
|
+
const Settings_1 = require("./Settings");
|
|
6
|
+
const node_http_1 = require("node:http");
|
|
7
|
+
const node_https_1 = require("node:https");
|
|
8
|
+
const injection_types_1 = require("../types/injection-types");
|
|
9
|
+
let ExpressiveTeaEngine = class ExpressiveTeaEngine {
|
|
10
|
+
constructor(context, server, serverSecure, settings) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
this.server = server;
|
|
13
|
+
this.serverSecure = serverSecure;
|
|
14
|
+
this.settings = settings;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Execute a lifecycle method across all registered engines
|
|
18
|
+
*
|
|
19
|
+
* @param {ExpressiveTeaEngine[]} availableEngines - Array of engine instances
|
|
20
|
+
* @param {string} method - Method name to execute (e.g., 'init', 'start', 'stop')
|
|
21
|
+
* @returns {Promise<unknown[]>} Array of results from all engines
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* await ExpressiveTeaEngine.exec(engines, 'init');
|
|
26
|
+
* await ExpressiveTeaEngine.exec(engines, 'start');
|
|
27
|
+
* ```
|
|
28
|
+
* @since 1.0.0
|
|
29
|
+
*/
|
|
30
|
+
static exec(availableEngines, method) {
|
|
31
|
+
return Promise.all(availableEngines
|
|
32
|
+
.filter(engine => typeof engine[method] === 'function')
|
|
33
|
+
.map(engine => (engine[method])()));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Determine if this engine can be registered in the current context
|
|
37
|
+
*
|
|
38
|
+
* Override this method to conditionally enable/disable the engine based on
|
|
39
|
+
* settings or environment. Default returns false to prevent accidental registration.
|
|
40
|
+
*
|
|
41
|
+
* @param {Boot} [ctx] - Boot context
|
|
42
|
+
* @param {Settings} [settings] - Application settings
|
|
43
|
+
* @returns {boolean} True if engine can be registered
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* static canRegister(ctx?: Boot, settings?: Settings): boolean {
|
|
48
|
+
* return settings?.get('enableMyEngine') === true;
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
* @since 1.0.0
|
|
52
|
+
*/
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
54
|
+
static canRegister(ctx, settings) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Graceful shutdown lifecycle method
|
|
59
|
+
*
|
|
60
|
+
* Override this method to implement cleanup logic when the application stops.
|
|
61
|
+
* This is called during graceful shutdown to close connections, clean up resources, etc.
|
|
62
|
+
*
|
|
63
|
+
* @returns {Promise<void>} Promise that resolves when cleanup is complete
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* async stop(): Promise<void> {
|
|
68
|
+
* await this.closeConnections();
|
|
69
|
+
* await this.cleanup();
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
* @since 2.0.0
|
|
73
|
+
*/
|
|
74
|
+
async stop() {
|
|
75
|
+
// Default implementation does nothing
|
|
76
|
+
// Engines can override to implement cleanup
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
ExpressiveTeaEngine = tslib_1.__decorate([
|
|
80
|
+
(0, inversify_1.injectable)(),
|
|
81
|
+
tslib_1.__param(0, (0, inversify_1.inject)(injection_types_1.TYPES.Context)),
|
|
82
|
+
tslib_1.__param(1, (0, inversify_1.inject)(injection_types_1.TYPES.Server)),
|
|
83
|
+
tslib_1.__param(2, (0, inversify_1.inject)(injection_types_1.TYPES.SecureServer)),
|
|
84
|
+
tslib_1.__param(2, (0, inversify_1.optional)()),
|
|
85
|
+
tslib_1.__param(3, (0, inversify_1.inject)(injection_types_1.TYPES.Settings)),
|
|
86
|
+
tslib_1.__metadata("design:paramtypes", [Function, node_http_1.Server,
|
|
87
|
+
node_https_1.Server,
|
|
88
|
+
Settings_1.default])
|
|
89
|
+
], ExpressiveTeaEngine);
|
|
90
|
+
exports.default = ExpressiveTeaEngine;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import ExpressiveTeaEngine from './Engine';
|
|
2
|
+
/**
|
|
3
|
+
* Engine constructor type with static canRegister method
|
|
4
|
+
*/
|
|
5
|
+
export type EngineConstructor = typeof ExpressiveTeaEngine;
|
|
6
|
+
/**
|
|
7
|
+
* Metadata for registered engines
|
|
8
|
+
* @interface EngineMetadata
|
|
9
|
+
* @since 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
export interface EngineMetadata {
|
|
12
|
+
/** Engine class constructor */
|
|
13
|
+
engine: EngineConstructor;
|
|
14
|
+
/** Unique engine name */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Engine version (semver) */
|
|
17
|
+
version: string;
|
|
18
|
+
/** Initialization priority (lower runs first) */
|
|
19
|
+
priority: number;
|
|
20
|
+
/** Array of engine names this engine depends on */
|
|
21
|
+
dependencies: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Engine Registry - Manages engine registration, dependency resolution, and initialization order
|
|
25
|
+
*
|
|
26
|
+
* Replaces hardcoded engine arrays with an extensible plugin system that:
|
|
27
|
+
* - Registers engines with metadata (name, version, priority, dependencies)
|
|
28
|
+
* - Resolves engine dependencies using topological sort
|
|
29
|
+
* - Detects circular dependencies
|
|
30
|
+
* - Filters engines based on their `canRegister()` method
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* // Register a custom engine
|
|
35
|
+
* EngineRegistry.register({
|
|
36
|
+
* engine: MyCustomEngine,
|
|
37
|
+
* name: 'custom',
|
|
38
|
+
* version: '1.0.0',
|
|
39
|
+
* priority: 15,
|
|
40
|
+
* dependencies: ['http']
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // Get engines in dependency order
|
|
44
|
+
* const engines = EngineRegistry.getRegisteredEngines(context, settings);
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @class EngineRegistry
|
|
48
|
+
* @since 2.0.0
|
|
49
|
+
*/
|
|
50
|
+
export default class EngineRegistry {
|
|
51
|
+
private static engines;
|
|
52
|
+
/**
|
|
53
|
+
* Register an engine with metadata
|
|
54
|
+
*
|
|
55
|
+
* @param {EngineMetadata} metadata - Engine metadata including name, version, priority, and dependencies
|
|
56
|
+
* @throws {Error} If engine with same name is already registered
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* EngineRegistry.register({
|
|
61
|
+
* engine: HTTPEngine,
|
|
62
|
+
* name: 'http',
|
|
63
|
+
* version: '2.0.0',
|
|
64
|
+
* priority: 0,
|
|
65
|
+
* dependencies: []
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
static register(metadata: EngineMetadata): void;
|
|
70
|
+
/**
|
|
71
|
+
* Unregister an engine by name
|
|
72
|
+
*
|
|
73
|
+
* @param {string} name - Engine name to unregister
|
|
74
|
+
* @returns {boolean} True if engine was unregistered, false if not found
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* EngineRegistry.unregister('custom');
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
static unregister(name: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Clear all registered engines (useful for testing)
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* EngineRegistry.clear();
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
static clear(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Get all registered engine metadata
|
|
93
|
+
*
|
|
94
|
+
* @returns {EngineMetadata[]} Array of all registered engine metadata
|
|
95
|
+
*/
|
|
96
|
+
static getAllEngines(): EngineMetadata[];
|
|
97
|
+
/**
|
|
98
|
+
* Get engine metadata by name
|
|
99
|
+
*
|
|
100
|
+
* @param {string} name - Engine name
|
|
101
|
+
* @returns {EngineMetadata | undefined} Engine metadata or undefined if not found
|
|
102
|
+
*/
|
|
103
|
+
static getEngine(name: string): EngineMetadata | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Get registered engines filtered by canRegister() and sorted by dependency order
|
|
106
|
+
*
|
|
107
|
+
* This method:
|
|
108
|
+
* 1. Filters engines using their static `canRegister()` method
|
|
109
|
+
* 2. Validates all dependencies are registered
|
|
110
|
+
* 3. Detects circular dependencies
|
|
111
|
+
* 4. Sorts engines using topological sort (dependencies first)
|
|
112
|
+
* 5. Applies priority sorting within same dependency level
|
|
113
|
+
*
|
|
114
|
+
* @param {unknown} context - Boot context to pass to canRegister()
|
|
115
|
+
* @param {unknown} settings - Settings to pass to canRegister()
|
|
116
|
+
* @returns {EngineConstructor[]} Array of engine constructors in initialization order
|
|
117
|
+
* @throws {Error} If circular dependency is detected
|
|
118
|
+
* @throws {Error} If required dependency is not registered
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const engines = EngineRegistry.getRegisteredEngines(boot, settings);
|
|
123
|
+
* // Returns: [HTTPEngine, SocketIOEngine, TeapotEngine] in dependency order
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
static getRegisteredEngines(context?: unknown, settings?: unknown): EngineConstructor[];
|
|
127
|
+
/**
|
|
128
|
+
* Validate that all dependencies are registered
|
|
129
|
+
*
|
|
130
|
+
* @private
|
|
131
|
+
* @param {EngineMetadata[]} engines - Engines to validate
|
|
132
|
+
* @throws {Error} If a dependency is not registered
|
|
133
|
+
*/
|
|
134
|
+
private static validateDependencies;
|
|
135
|
+
/**
|
|
136
|
+
* Detect circular dependencies using depth-first search
|
|
137
|
+
*
|
|
138
|
+
* @private
|
|
139
|
+
* @param {EngineMetadata[]} engines - Engines to check
|
|
140
|
+
* @throws {Error} If circular dependency is detected
|
|
141
|
+
*/
|
|
142
|
+
private static detectCircularDependencies;
|
|
143
|
+
/**
|
|
144
|
+
* Topological sort with priority ordering
|
|
145
|
+
*
|
|
146
|
+
* Uses Kahn's algorithm for topological sorting, with priority-based ordering
|
|
147
|
+
* for engines at the same dependency level.
|
|
148
|
+
*
|
|
149
|
+
* @private
|
|
150
|
+
* @param {EngineMetadata[]} engines - Engines to sort
|
|
151
|
+
* @returns {EngineMetadata[]} Sorted engines (dependencies first, then by priority)
|
|
152
|
+
*/
|
|
153
|
+
private static topologicalSort;
|
|
154
|
+
}
|