@zerooneit/expressive-tea 1.3.0-beta.6 → 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.
Files changed (78) hide show
  1. package/.swcrc +61 -0
  2. package/README.md +564 -174
  3. package/classes/Boot.d.ts +89 -2
  4. package/classes/Boot.js +149 -31
  5. package/classes/Engine.d.ts +58 -10
  6. package/classes/Engine.js +69 -9
  7. package/classes/EngineRegistry.d.ts +154 -0
  8. package/classes/EngineRegistry.js +247 -0
  9. package/classes/LoadBalancer.js +2 -5
  10. package/classes/ProxyRoute.js +5 -5
  11. package/classes/Settings.d.ts +31 -2
  12. package/classes/Settings.js +64 -11
  13. package/decorators/annotations.d.ts +1 -1
  14. package/decorators/annotations.js +17 -17
  15. package/decorators/env.d.ts +145 -0
  16. package/decorators/env.js +177 -0
  17. package/decorators/health.d.ts +115 -0
  18. package/decorators/health.js +124 -0
  19. package/decorators/module.d.ts +15 -16
  20. package/decorators/module.js +14 -24
  21. package/decorators/proxy.d.ts +26 -11
  22. package/decorators/proxy.js +35 -49
  23. package/decorators/router.d.ts +17 -16
  24. package/decorators/router.js +31 -53
  25. package/decorators/server.d.ts +7 -7
  26. package/decorators/server.js +48 -50
  27. package/engines/health/index.d.ts +120 -0
  28. package/engines/health/index.js +179 -0
  29. package/engines/http/index.d.ts +6 -10
  30. package/engines/http/index.js +18 -17
  31. package/engines/index.d.ts +32 -0
  32. package/engines/index.js +112 -0
  33. package/engines/socketio/index.d.ts +2 -4
  34. package/engines/socketio/index.js +14 -7
  35. package/engines/teacup/index.d.ts +12 -2
  36. package/engines/teacup/index.js +56 -10
  37. package/engines/teapot/index.d.ts +12 -2
  38. package/engines/teapot/index.js +58 -17
  39. package/engines/websocket/index.d.ts +1 -1
  40. package/engines/websocket/index.js +8 -3
  41. package/eslint.config.mjs +138 -0
  42. package/exceptions/RequestExceptions.d.ts +3 -3
  43. package/helpers/boot-helper.d.ts +4 -4
  44. package/helpers/boot-helper.js +27 -22
  45. package/helpers/decorators.js +7 -6
  46. package/helpers/promise-helper.d.ts +1 -1
  47. package/helpers/promise-helper.js +1 -2
  48. package/helpers/server.d.ts +31 -5
  49. package/helpers/server.js +98 -60
  50. package/helpers/teapot-helper.d.ts +2 -3
  51. package/helpers/teapot-helper.js +34 -8
  52. package/helpers/websocket-helper.d.ts +1 -3
  53. package/helpers/websocket-helper.js +3 -3
  54. package/interfaces/index.d.ts +1 -1
  55. package/inversify.config.d.ts +4 -4
  56. package/inversify.config.js +1 -1
  57. package/libs/utilities.d.ts +21910 -0
  58. package/libs/utilities.js +420 -0
  59. package/mixins/module.d.ts +45 -0
  60. package/mixins/module.js +71 -0
  61. package/mixins/proxy.d.ts +46 -0
  62. package/mixins/proxy.js +86 -0
  63. package/mixins/route.d.ts +48 -0
  64. package/mixins/route.js +96 -0
  65. package/package.json +85 -73
  66. package/services/DependencyInjection.d.ts +94 -8
  67. package/services/DependencyInjection.js +121 -3
  68. package/services/WebsocketService.d.ts +2 -4
  69. package/services/WebsocketService.js +5 -3
  70. package/types/core.d.ts +14 -0
  71. package/types/core.js +2 -0
  72. package/types/injection-types.d.ts +6 -0
  73. package/types/injection-types.js +10 -0
  74. package/types/inversify.d.ts +5 -0
  75. package/types/inversify.js +3 -0
  76. package/.eslintrc.js +0 -44
  77. package/tsconfig.linter.json +0 -24
  78. package/tslint-to-eslint-config.log +0 -12
package/classes/Boot.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import 'reflect-metadata';
2
2
  import { type Express } from 'express';
3
- import { type ExpressiveTeaApplication } from '@expressive-tea/commons/interfaces';
4
- import Settings from '../classes/Settings';
3
+ import { type ExpressiveTeaApplication } from '@expressive-tea/commons';
4
+ import Settings from './Settings';
5
+ import { Container, type Newable, type ServiceIdentifier } from 'inversify';
5
6
  /**
6
7
  * Expressive Tea Application interface is the response from an started application, contains the express application
7
8
  * and a node http server instance.
@@ -37,12 +38,78 @@ declare abstract class Boot {
37
38
  */
38
39
  private readonly server;
39
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;
40
48
  constructor();
41
49
  /**
42
50
  * Get Express Application
43
51
  * @returns Express
44
52
  */
45
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;
46
113
  /**
47
114
  * Bootstrap and verify that all the required plugins are correctly configured and proceed to attach all the
48
115
  * registered modules. <b>Remember</b> this is the unique method that must be decorated for the Register Module
@@ -51,6 +118,26 @@ declare abstract class Boot {
51
118
  * @returns {Promise<ExpressiveTeaApplication>}
52
119
  */
53
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>;
54
141
  private initializeEngines;
55
142
  private initializeHttp;
56
143
  private initializeContainer;
package/classes/Boot.js CHANGED
@@ -3,16 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  require("reflect-metadata");
4
4
  const inversify_config_1 = require("../inversify.config");
5
5
  const express = require("express");
6
- const http_1 = require("../engines/http");
7
- const websocket_1 = require("../engines/websocket");
8
- const index_1 = require("../engines/teapot/index");
9
- const teacup_1 = require("../engines/teacup");
10
- const Engine_1 = require("../classes/Engine");
11
- const Settings_1 = require("../classes/Settings");
12
- const index_2 = require("../engines/socketio/index");
13
- const fs = require("fs");
14
- const http = require("http");
15
- const https = require("https");
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");
16
14
  /**
17
15
  * Expressive Tea Application interface is the response from an started application, contains the express application
18
16
  * and a node http server instance.
@@ -39,7 +37,14 @@ class Boot {
39
37
  * @summary Express Application instance internal property.
40
38
  */
41
39
  this.server = express();
42
- this.containerDI = inversify_config_1.default.createChild();
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 = [];
43
48
  this.settings = Settings_1.default.getInstance(this);
44
49
  }
45
50
  /**
@@ -49,6 +54,75 @@ class Boot {
49
54
  getApplication() {
50
55
  return this.server;
51
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
+ }
52
126
  /**
53
127
  * Bootstrap and verify that all the required plugins are correctly configured and proceed to attach all the
54
128
  * registered modules. <b>Remember</b> this is the unique method that must be decorated for the Register Module
@@ -61,22 +135,61 @@ class Boot {
61
135
  const [server, secureServer] = this.initializeHttp();
62
136
  // Injectables
63
137
  this.initializeContainer(server, secureServer);
64
- // Initialize Engines
65
- const availableEngines = [
66
- http_1.default,
67
- index_2.default,
68
- websocket_1.default,
69
- index_1.default,
70
- teacup_1.default
71
- ];
72
- const registeredEngines = availableEngines.filter(Engine => Engine.canRegister(this, this.settings));
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);
73
145
  this.initializeEngines(registeredEngines);
74
146
  // Resolve Engines
75
- const readyEngines = registeredEngines.map(Engine => this.containerDI.resolve(Engine));
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;
76
153
  // Initialize Engines
77
- await Engine_1.default.exec(readyEngines.reverse(), 'init');
78
- await Engine_1.default.exec(readyEngines, 'start');
79
- return ({ application: this.server, server, secureServer });
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 = [];
80
193
  }
81
194
  initializeEngines(registeredEngines) {
82
195
  for (const Engine of registeredEngines) {
@@ -84,22 +197,27 @@ class Boot {
84
197
  }
85
198
  }
86
199
  initializeHttp() {
200
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
87
201
  const privateKey = this.settings.get('privateKey');
202
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
88
203
  const certificate = this.settings.get('certificate');
89
204
  const server = http.createServer(this.server);
90
205
  const secureServer = privateKey &&
91
- certificate &&
92
- https.createServer({
206
+ certificate
207
+ ? https.createServer({
93
208
  cert: fs.readFileSync(certificate).toString('utf-8'),
94
209
  key: fs.readFileSync(privateKey).toString('utf-8')
95
- });
210
+ }, this.server)
211
+ : undefined;
96
212
  return [server, secureServer];
97
213
  }
98
214
  initializeContainer(server, secureServer) {
99
- this.containerDI.bind('server').toConstantValue(server);
100
- this.containerDI.bind('secureServer').toConstantValue(secureServer !== null && secureServer !== void 0 ? secureServer : undefined);
101
- this.containerDI.bind('context').toConstantValue(this);
102
- this.containerDI.bind('settings').toConstantValue(this.settings);
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);
103
221
  }
104
222
  }
105
223
  exports.default = Boot;
@@ -1,15 +1,63 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
1
  import Settings from './Settings';
4
- import Boot from './Boot';
5
- import http from 'http';
6
- import https from 'https';
2
+ import type Boot from './Boot';
3
+ import { Server as HttpServer } from 'node:http';
4
+ import { Server as HttpsServer } from 'node:https';
7
5
  export default class ExpressiveTeaEngine {
8
- protected readonly settings: Settings;
9
6
  protected readonly context: Boot;
10
- protected readonly server: http.Server;
11
- protected readonly serverSecure?: https.Server;
12
- constructor(ctx: any, server: any, serverSecure: any, settings: any);
13
- static exec(availableEngines: ExpressiveTeaEngine[], method: string): any;
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
+ */
14
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>;
15
63
  }
package/classes/Engine.js CHANGED
@@ -2,29 +2,89 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
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");
5
9
  let ExpressiveTeaEngine = class ExpressiveTeaEngine {
6
- constructor(ctx, server, serverSecure, settings) {
7
- this.settings = settings;
8
- this.context = ctx;
10
+ constructor(context, server, serverSecure, settings) {
11
+ this.context = context;
9
12
  this.server = server;
10
13
  this.serverSecure = serverSecure;
14
+ this.settings = settings;
11
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
+ */
12
30
  static exec(availableEngines, method) {
13
31
  return Promise.all(availableEngines
14
32
  .filter(engine => typeof engine[method] === 'function')
15
- .map(engine => engine[method]()));
33
+ .map(engine => (engine[method])()));
16
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
17
54
  static canRegister(ctx, settings) {
18
55
  return false;
19
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
+ }
20
78
  };
21
79
  ExpressiveTeaEngine = tslib_1.__decorate([
22
80
  (0, inversify_1.injectable)(),
23
- tslib_1.__param(0, (0, inversify_1.inject)('context')),
24
- tslib_1.__param(1, (0, inversify_1.inject)('server')),
25
- tslib_1.__param(2, (0, inversify_1.inject)('secureServer')),
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)),
26
84
  tslib_1.__param(2, (0, inversify_1.optional)()),
27
- tslib_1.__param(3, (0, inversify_1.inject)('settings')),
28
- tslib_1.__metadata("design:paramtypes", [Object, Object, Object, Object])
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])
29
89
  ], ExpressiveTeaEngine);
30
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
+ }