@hkdigital/lib-core 0.4.19 → 0.4.21
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/dist/services/README.md +109 -127
- package/dist/services/{service-manager-plugins → manager-plugins}/ConfigPlugin.d.ts +19 -29
- package/dist/services/{service-manager-plugins → manager-plugins}/ConfigPlugin.js +131 -58
- package/dist/services/service-base/constants.js +0 -1
- package/dist/services/service-base/typedef.js +4 -4
- package/dist/services/service-manager/ServiceManager.d.ts +5 -2
- package/dist/services/service-manager/ServiceManager.js +27 -21
- package/dist/services/service-manager/constants.js +0 -1
- package/dist/services/service-manager/typedef.d.ts +12 -15
- package/dist/services/service-manager/typedef.js +20 -14
- package/package.json +1 -1
package/dist/services/README.md
CHANGED
|
@@ -11,21 +11,32 @@ The services module provides two main components:
|
|
|
11
11
|
|
|
12
12
|
All services follow a standardized state machine with proper error handling, logging, and health monitoring.
|
|
13
13
|
|
|
14
|
-
## Service
|
|
15
|
-
|
|
16
|
-
Services transition through these states during their lifecycle
|
|
17
|
-
|
|
18
|
-
- `
|
|
19
|
-
- `
|
|
20
|
-
- `
|
|
21
|
-
- `
|
|
22
|
-
- `
|
|
23
|
-
- `
|
|
24
|
-
- `
|
|
25
|
-
- `
|
|
26
|
-
- `
|
|
27
|
-
- `
|
|
28
|
-
- `
|
|
14
|
+
## Service states
|
|
15
|
+
|
|
16
|
+
Services transition through these states during their lifecycle. Use these constants from `$lib/services/service-base/constants.js`:
|
|
17
|
+
|
|
18
|
+
- `STATE_CREATED` - Service instantiated but not configured
|
|
19
|
+
- `STATE_CONFIGURING` - Currently running configuration
|
|
20
|
+
- `STATE_CONFIGURED` - Ready to start
|
|
21
|
+
- `STATE_STARTING` - Currently starting up
|
|
22
|
+
- `STATE_RUNNING` - Operational and healthy
|
|
23
|
+
- `STATE_STOPPING` - Currently shutting down
|
|
24
|
+
- `STATE_STOPPED` - Cleanly stopped
|
|
25
|
+
- `STATE_DESTROYING` - Being destroyed and cleaned up
|
|
26
|
+
- `STATE_DESTROYED` - Completely destroyed
|
|
27
|
+
- `STATE_ERROR` - Failed and non-operational
|
|
28
|
+
- `STATE_RECOVERING` - Attempting recovery from error
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
import {
|
|
32
|
+
STATE_RUNNING,
|
|
33
|
+
STATE_ERROR
|
|
34
|
+
} from '$lib/services/service-base/constants.js';
|
|
35
|
+
|
|
36
|
+
if (service.state === STATE_RUNNING) {
|
|
37
|
+
// Service is operational
|
|
38
|
+
}
|
|
39
|
+
```
|
|
29
40
|
|
|
30
41
|
## ServiceBase
|
|
31
42
|
|
|
@@ -46,7 +57,6 @@ import { ServiceBase } from '$lib/services/index.js';
|
|
|
46
57
|
class DatabaseService extends ServiceBase {
|
|
47
58
|
// eslint-disable-next-line no-unused-vars
|
|
48
59
|
async _configure(newConfig, oldConfig = null) {
|
|
49
|
-
|
|
50
60
|
if (!oldConfig) {
|
|
51
61
|
// Initial configuration
|
|
52
62
|
|
|
@@ -106,7 +116,7 @@ db.on('healthChanged', ({ healthy }) => {
|
|
|
106
116
|
// Reconfigure at runtime
|
|
107
117
|
await db.configure({
|
|
108
118
|
connectionString: 'postgres://localhost/myapp',
|
|
109
|
-
maxConnections: 50
|
|
119
|
+
maxConnections: 50 // Hot-reloaded without restart
|
|
110
120
|
});
|
|
111
121
|
```
|
|
112
122
|
|
|
@@ -119,11 +129,22 @@ await db.configure({
|
|
|
119
129
|
- `_recover()` - Custom recovery logic (optional)
|
|
120
130
|
- `_healthCheck()` - Return health status (optional)
|
|
121
131
|
|
|
122
|
-
###
|
|
132
|
+
### Service events
|
|
133
|
+
|
|
134
|
+
ServiceBase emits these events (constants from `$lib/services/service-base/constants.js`):
|
|
123
135
|
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
126
|
-
- `
|
|
136
|
+
- `EVENT_STATE_CHANGED` - Service state transitions
|
|
137
|
+
- `EVENT_TARGET_STATE_CHANGED` - Target state changes
|
|
138
|
+
- `EVENT_HEALTH_CHANGED` - Health status changes
|
|
139
|
+
- `EVENT_ERROR` - Service errors
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
import { EVENT_STATE_CHANGED } from '$lib/services/service-base/constants.js';
|
|
143
|
+
|
|
144
|
+
service.on(EVENT_STATE_CHANGED, ({ state, previousState }) => {
|
|
145
|
+
console.log(`Service transitioned from ${previousState} to ${state}`);
|
|
146
|
+
});
|
|
147
|
+
```
|
|
127
148
|
|
|
128
149
|
## ServiceManager
|
|
129
150
|
|
|
@@ -143,6 +164,7 @@ Manages multiple services with dependency resolution and coordinated lifecycle o
|
|
|
143
164
|
|
|
144
165
|
```javascript
|
|
145
166
|
import { ServiceManager } from '$hklib-core/services/index.js';
|
|
167
|
+
|
|
146
168
|
import DatabaseService from './services/DatabaseService.js';
|
|
147
169
|
import AuthService from './services/AuthService.js';
|
|
148
170
|
|
|
@@ -156,11 +178,16 @@ manager.register('database', DatabaseService, {
|
|
|
156
178
|
connectionString: 'postgres://localhost/myapp'
|
|
157
179
|
});
|
|
158
180
|
|
|
159
|
-
manager.register(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
181
|
+
manager.register(
|
|
182
|
+
'auth',
|
|
183
|
+
AuthService,
|
|
184
|
+
{
|
|
185
|
+
secret: process.env.JWT_SECRET
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
dependencies: ['database'] // auth depends on database
|
|
189
|
+
}
|
|
190
|
+
);
|
|
164
191
|
|
|
165
192
|
// Start all services in dependency order
|
|
166
193
|
await manager.startAll();
|
|
@@ -175,19 +202,27 @@ await manager.stopAll();
|
|
|
175
202
|
### Service Registration
|
|
176
203
|
|
|
177
204
|
```javascript
|
|
178
|
-
manager.register(name, ServiceClass,
|
|
205
|
+
manager.register(name, ServiceClass, serviceConfigOrLabel, options);
|
|
179
206
|
```
|
|
180
207
|
|
|
181
208
|
- `name` - Unique service identifier
|
|
182
209
|
- `ServiceClass` - Class extending ServiceBase
|
|
183
|
-
- `
|
|
210
|
+
- `serviceConfigOrLabel` - Service configuration object (`Object<string, *>`) or config label string
|
|
184
211
|
- `options.dependencies` - Array of service names this service depends on
|
|
212
|
+
- `options.startupPriority` - Higher priority services start first (default: 0)
|
|
185
213
|
|
|
186
214
|
### Health Monitoring
|
|
187
215
|
|
|
188
216
|
```javascript
|
|
217
|
+
import {
|
|
218
|
+
SERVICE_HEALTH_CHANGED,
|
|
219
|
+
SERVICE_ERROR,
|
|
220
|
+
SERVICE_STATE_CHANGED,
|
|
221
|
+
SERVICE_LOG
|
|
222
|
+
} from '$lib/services/service-manager/constants.js';
|
|
223
|
+
|
|
189
224
|
// Listen for health changes
|
|
190
|
-
manager.on(
|
|
225
|
+
manager.on(SERVICE_HEALTH_CHANGED, ({ service, healthy }) => {
|
|
191
226
|
if (!healthy) {
|
|
192
227
|
console.error(`Service ${service} became unhealthy`);
|
|
193
228
|
}
|
|
@@ -202,11 +237,20 @@ const systemHealth = await manager.checkHealth();
|
|
|
202
237
|
|
|
203
238
|
### Error Handling and Recovery
|
|
204
239
|
|
|
240
|
+
### ServiceManager events
|
|
241
|
+
|
|
242
|
+
ServiceManager emits these events (constants from `$lib/services/service-manager/constants.js`):
|
|
243
|
+
|
|
244
|
+
- `SERVICE_STATE_CHANGED` - Service state changes
|
|
245
|
+
- `SERVICE_HEALTH_CHANGED` - Service health changes
|
|
246
|
+
- `SERVICE_ERROR` - Service errors
|
|
247
|
+
- `SERVICE_LOG` - Service log messages
|
|
248
|
+
|
|
205
249
|
```javascript
|
|
206
250
|
// Listen for service errors
|
|
207
|
-
manager.on(
|
|
251
|
+
manager.on(SERVICE_ERROR, async ({ service, error }) => {
|
|
208
252
|
console.log(`Service ${service} failed:`, error.message);
|
|
209
|
-
|
|
253
|
+
|
|
210
254
|
// Attempt automatic recovery
|
|
211
255
|
await manager.recoverService(service);
|
|
212
256
|
});
|
|
@@ -215,9 +259,9 @@ manager.on('service:error', async ({ service, error }) => {
|
|
|
215
259
|
await manager.recoverService('database');
|
|
216
260
|
```
|
|
217
261
|
|
|
218
|
-
##
|
|
262
|
+
## Plugins
|
|
219
263
|
|
|
220
|
-
ServiceManager supports plugins
|
|
264
|
+
ServiceManager supports plugins e.g. to resolve service configurations dynamically.
|
|
221
265
|
|
|
222
266
|
### ConfigPlugin
|
|
223
267
|
|
|
@@ -227,17 +271,29 @@ The most common plugin for resolving service configuration from a pre-parsed con
|
|
|
227
271
|
|
|
228
272
|
```javascript
|
|
229
273
|
import { ServiceManager } from '$lib/services/index.js';
|
|
230
|
-
|
|
274
|
+
|
|
275
|
+
import ConfigPlugin from '$lib/services/manager-plugins/ConfigPlugin.js';
|
|
276
|
+
|
|
231
277
|
import { getPrivateEnv } from '$lib/util/sveltekit/env-private.js';
|
|
232
278
|
|
|
233
279
|
// Load and auto-group environment variables
|
|
234
280
|
const envConfig = getPrivateEnv();
|
|
235
|
-
//
|
|
281
|
+
//
|
|
282
|
+
// Example:
|
|
283
|
+
//
|
|
284
|
+
// DATABASE_HOST=localhost
|
|
285
|
+
// DATABASE_PORT=5432
|
|
286
|
+
// DATABASE_NAME=myapp
|
|
287
|
+
// REDIS_HOST=cache-server
|
|
288
|
+
// REDIS_PORT=6379
|
|
289
|
+
// JWT_SECRET=mysecret
|
|
290
|
+
// =>
|
|
236
291
|
// {
|
|
237
292
|
// database: { host: 'localhost', port: 5432, name: 'myapp' },
|
|
238
293
|
// redis: { host: 'cache-server', port: 6379 },
|
|
239
294
|
// jwtSecret: 'mysecret'
|
|
240
295
|
// }
|
|
296
|
+
//
|
|
241
297
|
|
|
242
298
|
// Create plugin with grouped config
|
|
243
299
|
const configPlugin = new ConfigPlugin(envConfig);
|
|
@@ -247,46 +303,23 @@ const manager = new ServiceManager();
|
|
|
247
303
|
manager.attachPlugin(configPlugin);
|
|
248
304
|
|
|
249
305
|
// Register services with config labels (not config objects)
|
|
250
|
-
manager.register('database', DatabaseService, 'database');
|
|
251
|
-
manager.register('cache', RedisService, 'redis');
|
|
306
|
+
manager.register('database', DatabaseService, 'database'); // Uses envConfig.database
|
|
307
|
+
manager.register('cache', RedisService, 'redis'); // Uses envConfig.redis
|
|
252
308
|
|
|
253
309
|
await manager.startAll();
|
|
254
310
|
```
|
|
255
311
|
|
|
256
|
-
####
|
|
257
|
-
|
|
258
|
-
The environment utilities automatically group related variables by prefix:
|
|
259
|
-
|
|
260
|
-
```bash
|
|
261
|
-
# Environment variables:
|
|
262
|
-
DATABASE_HOST=localhost
|
|
263
|
-
DATABASE_PORT=5432
|
|
264
|
-
DATABASE_NAME=myapp
|
|
265
|
-
REDIS_HOST=cache-server
|
|
266
|
-
REDIS_PORT=6379
|
|
267
|
-
JWT_SECRET=mysecret
|
|
268
|
-
SINGLE_FLAG=true
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
```javascript
|
|
272
|
-
const envConfig = getPrivateEnv();
|
|
273
|
-
// Auto-groups into:
|
|
274
|
-
// {
|
|
275
|
-
// database: { host: 'localhost', port: 5432, name: 'myapp' },
|
|
276
|
-
// redis: { host: 'cache-server', port: 6379 },
|
|
277
|
-
// jwtSecret: 'mysecret',
|
|
278
|
-
// singleFlag: true
|
|
279
|
-
// }
|
|
280
|
-
```
|
|
312
|
+
#### Configuration
|
|
281
313
|
|
|
282
|
-
|
|
314
|
+
The plugin constructor accepts an object with configuration data, which can come from any source. E.g. the environment or a configuration file.
|
|
283
315
|
|
|
284
316
|
```javascript
|
|
285
317
|
// Combine multiple config sources
|
|
286
318
|
const config = {
|
|
287
|
-
...getPrivateEnv(),
|
|
288
|
-
...await loadConfigFile(),
|
|
289
|
-
database: {
|
|
319
|
+
...getPrivateEnv(), // Environment variables
|
|
320
|
+
...(await loadConfigFile()), // Config file
|
|
321
|
+
database: {
|
|
322
|
+
// Override specific settings
|
|
290
323
|
...envConfig.database,
|
|
291
324
|
connectionTimeout: 5000
|
|
292
325
|
}
|
|
@@ -295,41 +328,17 @@ const config = {
|
|
|
295
328
|
const plugin = new ConfigPlugin(config);
|
|
296
329
|
```
|
|
297
330
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
1. **Simple Service Registration** - Use string labels instead of complex config objects
|
|
301
|
-
2. **Environment Integration** - Seamless SvelteKit environment variable integration
|
|
302
|
-
3. **Dynamic Configuration** - Update config object for live updates
|
|
303
|
-
4. **Clear Separation** - Configuration logic separate from service logic
|
|
304
|
-
5. **Extensible** - Easy to add custom configuration sources
|
|
305
|
-
|
|
306
|
-
#### Available Environment Utilities
|
|
331
|
+
### Methods
|
|
307
332
|
|
|
308
333
|
```javascript
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
const config = getAllEnv();
|
|
334
|
+
// Replace all configurations and clean up unused ones
|
|
335
|
+
await configPlugin.replaceAllConfigs(newConfig);
|
|
312
336
|
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
const config = getPrivateEnv();
|
|
337
|
+
// Replace configuration for a specific label
|
|
338
|
+
await configPlugin.replaceConfig('database', newDatabaseConfig);
|
|
316
339
|
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
const config = getPublicEnv();
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### Plugin Methods
|
|
323
|
-
|
|
324
|
-
```javascript
|
|
325
|
-
// Update configuration at runtime
|
|
326
|
-
configPlugin.updateConfigObject(newConfig);
|
|
327
|
-
|
|
328
|
-
// Merge additional configuration
|
|
329
|
-
configPlugin.mergeConfig({ additionalSettings: true });
|
|
330
|
-
|
|
331
|
-
// Get current configuration
|
|
332
|
-
const currentConfig = configPlugin.getConfigObject();
|
|
340
|
+
// Clean up configurations not used by any service
|
|
341
|
+
await configPlugin.cleanupConfigs();
|
|
333
342
|
```
|
|
334
343
|
|
|
335
344
|
### Live Configuration Updates
|
|
@@ -337,8 +346,8 @@ const currentConfig = configPlugin.getConfigObject();
|
|
|
337
346
|
The ConfigPlugin supports pushing configuration updates to running services:
|
|
338
347
|
|
|
339
348
|
```javascript
|
|
340
|
-
//
|
|
341
|
-
const updatedServices = await configPlugin.
|
|
349
|
+
// Replace config for a specific label and notify all affected services
|
|
350
|
+
const updatedServices = await configPlugin.replaceConfig('database', {
|
|
342
351
|
host: 'new-host.example.com',
|
|
343
352
|
port: 5433,
|
|
344
353
|
maxConnections: 50
|
|
@@ -348,15 +357,6 @@ const updatedServices = await configPlugin.updateConfigLabel('database', {
|
|
|
348
357
|
console.log(`Updated ${updatedServices.length} services`);
|
|
349
358
|
```
|
|
350
359
|
|
|
351
|
-
#### How Live Updates Work
|
|
352
|
-
|
|
353
|
-
1. **Updates the plugin's config object** for the specified label
|
|
354
|
-
2. **Finds all running services** that use that config label
|
|
355
|
-
3. **Calls `_configure(newConfig, oldConfig)`** on each service instance
|
|
356
|
-
4. **Updates the resolved config** stored in the ServiceManager
|
|
357
|
-
5. **Returns the list** of successfully updated service names
|
|
358
|
-
6. **Handles errors gracefully** with detailed logging
|
|
359
|
-
|
|
360
360
|
#### Service Requirements for Live Updates
|
|
361
361
|
|
|
362
362
|
For services to support live configuration updates, they must:
|
|
@@ -381,7 +381,7 @@ class DatabaseService extends ServiceBase {
|
|
|
381
381
|
// Connection changed - need to reconnect
|
|
382
382
|
await this.connection?.close();
|
|
383
383
|
this.connectionString = newConfig.connectionString;
|
|
384
|
-
|
|
384
|
+
|
|
385
385
|
if (this.state === 'running') {
|
|
386
386
|
this.connection = await createConnection(this.connectionString);
|
|
387
387
|
}
|
|
@@ -396,12 +396,6 @@ class DatabaseService extends ServiceBase {
|
|
|
396
396
|
}
|
|
397
397
|
```
|
|
398
398
|
|
|
399
|
-
This enables powerful scenarios like:
|
|
400
|
-
- **Runtime environment updates** without service restarts
|
|
401
|
-
- **A/B testing configuration changes** with instant rollback
|
|
402
|
-
- **Dynamic scaling** based on load conditions
|
|
403
|
-
- **Configuration management systems** that push updates to running applications
|
|
404
|
-
|
|
405
399
|
## Best Practices
|
|
406
400
|
|
|
407
401
|
1. **Always extend ServiceBase** for consistent lifecycle management
|
|
@@ -411,15 +405,3 @@ This enables powerful scenarios like:
|
|
|
411
405
|
5. **Declare dependencies explicitly** when registering with ServiceManager
|
|
412
406
|
6. **Handle errors gracefully** and implement recovery where appropriate
|
|
413
407
|
7. **Use descriptive service names** for better logging and debugging
|
|
414
|
-
|
|
415
|
-
## Testing
|
|
416
|
-
|
|
417
|
-
Services include comprehensive test suites demonstrating:
|
|
418
|
-
|
|
419
|
-
- Lifecycle state transitions
|
|
420
|
-
- Error handling and recovery
|
|
421
|
-
- Dependency resolution
|
|
422
|
-
- Health monitoring
|
|
423
|
-
- Event emission
|
|
424
|
-
|
|
425
|
-
Run tests with your project's test command to ensure service reliability.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @typedef {import('../service-manager/typedef.js').ServiceEntry} ServiceEntry */
|
|
1
2
|
/**
|
|
2
3
|
* Plugin that resolves service configuration from a configuration object
|
|
3
4
|
*/
|
|
@@ -5,9 +6,9 @@ export default class ConfigPlugin {
|
|
|
5
6
|
/**
|
|
6
7
|
* Create a new object configuration plugin
|
|
7
8
|
*
|
|
8
|
-
* @param {Object<string, *>}
|
|
9
|
+
* @param {Object<string, *>} allConfigs - Pre-parsed configuration object
|
|
9
10
|
*/
|
|
10
|
-
constructor(
|
|
11
|
+
constructor(allConfigs: {
|
|
11
12
|
[x: string]: any;
|
|
12
13
|
});
|
|
13
14
|
/** @type {string} */
|
|
@@ -15,54 +16,42 @@ export default class ConfigPlugin {
|
|
|
15
16
|
/** @type {import('../service-manager/ServiceManager.js').ServiceManager|null} */
|
|
16
17
|
manager: import("../service-manager/ServiceManager.js").ServiceManager | null;
|
|
17
18
|
/** @type {Object<string, *>} */
|
|
18
|
-
|
|
19
|
+
allConfigs: {
|
|
19
20
|
[x: string]: any;
|
|
20
21
|
};
|
|
21
22
|
/**
|
|
22
23
|
* Resolve service configuration from the configuration object
|
|
23
24
|
*
|
|
24
|
-
* @param {string} serviceName
|
|
25
|
-
* @param {
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* @param {string} serviceName
|
|
26
|
+
* @param {ServiceEntry} serviceEntry - Service registration entry
|
|
27
|
+
* @param {*} currentConfig
|
|
28
|
+
* Current config (could be object from previous plugins)
|
|
28
29
|
*
|
|
29
30
|
* @returns {Promise<Object|undefined>}
|
|
30
31
|
* Resolved config object, or undefined to use currentConfig as-is
|
|
31
32
|
*/
|
|
32
|
-
|
|
33
|
+
resolveServiceConfig(serviceName: string, serviceEntry: ServiceEntry, currentConfig: any): Promise<any | undefined>;
|
|
33
34
|
/**
|
|
34
|
-
*
|
|
35
|
+
* Replace the entire configuration object and clean up unused configs
|
|
35
36
|
*
|
|
36
37
|
* @param {Object<string, *>} newConfigObject - New configuration object
|
|
37
38
|
*/
|
|
38
|
-
|
|
39
|
+
replaceAllConfigs(newConfigObject: {
|
|
39
40
|
[x: string]: any;
|
|
40
|
-
}): void
|
|
41
|
+
}): Promise<void>;
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* @param {Object<string, *>} additionalConfig - Additional configuration
|
|
45
|
-
*/
|
|
46
|
-
mergeConfig(additionalConfig: {
|
|
47
|
-
[x: string]: any;
|
|
48
|
-
}): void;
|
|
49
|
-
/**
|
|
50
|
-
* Get the current configuration object
|
|
51
|
-
*
|
|
52
|
-
* @returns {Object<string, *>} Current configuration object
|
|
53
|
-
*/
|
|
54
|
-
getConfigObject(): {
|
|
55
|
-
[x: string]: any;
|
|
56
|
-
};
|
|
57
|
-
/**
|
|
58
|
-
* Update config for a specific label and push to affected services
|
|
43
|
+
* Replace a config for a specific label and push to affected services
|
|
59
44
|
*
|
|
60
45
|
* @param {string} configLabel - Config label to update
|
|
61
46
|
* @param {*} newConfig - New configuration value
|
|
62
47
|
*
|
|
63
48
|
* @returns {Promise<string[]>} Array of service names that were updated
|
|
64
49
|
*/
|
|
65
|
-
|
|
50
|
+
replaceConfig(configLabel: string, newConfig: any): Promise<string[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove all unused configurations (configs not referenced by any service)
|
|
53
|
+
*/
|
|
54
|
+
cleanupConfigs(): Promise<void>;
|
|
66
55
|
/**
|
|
67
56
|
* Attach plugin to ServiceManager
|
|
68
57
|
*
|
|
@@ -76,3 +65,4 @@ export default class ConfigPlugin {
|
|
|
76
65
|
detach(): void;
|
|
77
66
|
#private;
|
|
78
67
|
}
|
|
68
|
+
export type ServiceEntry = import("../service-manager/typedef.js").ServiceEntry;
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
* // Basic usage with config object
|
|
9
9
|
* import ConfigPlugin from './ConfigPlugin.js';
|
|
10
10
|
*
|
|
11
|
-
* const
|
|
11
|
+
* const allConfigs = {
|
|
12
12
|
* 'database': { host: 'localhost', port: 5432 },
|
|
13
13
|
* 'auth': { secret: 'my-secret', algorithm: 'HS256' }
|
|
14
14
|
* };
|
|
15
15
|
*
|
|
16
|
-
* const objectPlugin = new ConfigPlugin(
|
|
16
|
+
* const objectPlugin = new ConfigPlugin(allConfigs);
|
|
17
17
|
* manager.attachPlugin(objectPlugin);
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
@@ -41,20 +41,21 @@
|
|
|
41
41
|
|
|
42
42
|
import { SERVICE_STATE_CHANGED } from '../service-manager/constants.js';
|
|
43
43
|
|
|
44
|
+
/** @typedef {import('../service-manager/typedef.js').ServiceEntry} ServiceEntry */
|
|
45
|
+
|
|
44
46
|
/**
|
|
45
47
|
* Plugin that resolves service configuration from a configuration object
|
|
46
48
|
*/
|
|
47
49
|
export default class ConfigPlugin {
|
|
48
|
-
|
|
49
50
|
/** @type {Map<string, *>} */
|
|
50
51
|
#pendingConfigUpdates;
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
54
|
* Create a new object configuration plugin
|
|
54
55
|
*
|
|
55
|
-
* @param {Object<string, *>}
|
|
56
|
+
* @param {Object<string, *>} allConfigs - Pre-parsed configuration object
|
|
56
57
|
*/
|
|
57
|
-
constructor(
|
|
58
|
+
constructor(allConfigs) {
|
|
58
59
|
/** @type {string} */
|
|
59
60
|
this.name = 'object-config';
|
|
60
61
|
|
|
@@ -62,7 +63,7 @@ export default class ConfigPlugin {
|
|
|
62
63
|
this.manager = null;
|
|
63
64
|
|
|
64
65
|
/** @type {Object<string, *>} */
|
|
65
|
-
this.
|
|
66
|
+
this.allConfigs = allConfigs || {};
|
|
66
67
|
|
|
67
68
|
this.#pendingConfigUpdates = new Map();
|
|
68
69
|
}
|
|
@@ -70,25 +71,32 @@ export default class ConfigPlugin {
|
|
|
70
71
|
/**
|
|
71
72
|
* Resolve service configuration from the configuration object
|
|
72
73
|
*
|
|
73
|
-
* @param {string} serviceName
|
|
74
|
-
* @param {
|
|
75
|
-
*
|
|
76
|
-
*
|
|
74
|
+
* @param {string} serviceName
|
|
75
|
+
* @param {ServiceEntry} serviceEntry - Service registration entry
|
|
76
|
+
* @param {*} currentConfig
|
|
77
|
+
* Current config (could be object from previous plugins)
|
|
77
78
|
*
|
|
78
79
|
* @returns {Promise<Object|undefined>}
|
|
79
80
|
* Resolved config object, or undefined to use currentConfig as-is
|
|
80
81
|
*/
|
|
81
82
|
// eslint-disable-next-line no-unused-vars
|
|
82
|
-
async
|
|
83
|
-
//
|
|
84
|
-
|
|
83
|
+
async resolveServiceConfig(serviceName, serviceEntry, currentConfig) {
|
|
84
|
+
// console.log(`ConfigPlugin.resolveServiceConfig called for '${serviceName}'`);
|
|
85
|
+
// console.log('ServiceEntry:', serviceEntry);
|
|
86
|
+
// console.log('AllConfigs:', this.allConfigs);
|
|
87
|
+
|
|
88
|
+
const configLabel = serviceEntry.serviceConfigOrLabel;
|
|
89
|
+
// console.log('Config label:', configLabel, 'Type:', typeof configLabel);
|
|
90
|
+
|
|
91
|
+
if (typeof configLabel !== 'string') {
|
|
92
|
+
// console.log('Config label is not string, returning undefined');
|
|
93
|
+
// Expected config label
|
|
85
94
|
return undefined;
|
|
86
95
|
}
|
|
87
96
|
|
|
88
|
-
const configLabel = serviceEntry.config;
|
|
89
|
-
|
|
90
97
|
// Simple object lookup
|
|
91
|
-
const config = this.
|
|
98
|
+
const config = this.allConfigs[configLabel];
|
|
99
|
+
// console.log(`Looking up config for label '${configLabel}':`, config);
|
|
92
100
|
|
|
93
101
|
if (config !== undefined) {
|
|
94
102
|
this.manager?.logger?.debug(
|
|
@@ -98,65 +106,69 @@ export default class ConfigPlugin {
|
|
|
98
106
|
typeof config === 'object' ? Object.keys(config) : 'primitive'
|
|
99
107
|
}
|
|
100
108
|
);
|
|
109
|
+
// console.log(`Resolved config for '${serviceName}':`, config);
|
|
110
|
+
} else {
|
|
111
|
+
// console.log(`No config found for label '${configLabel}'`);
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
return config;
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
/**
|
|
107
|
-
*
|
|
118
|
+
* Replace the entire configuration object and clean up unused configs
|
|
108
119
|
*
|
|
109
120
|
* @param {Object<string, *>} newConfigObject - New configuration object
|
|
110
121
|
*/
|
|
111
|
-
|
|
112
|
-
this.
|
|
113
|
-
this.manager?.logger?.debug('Updated configuration object');
|
|
114
|
-
}
|
|
122
|
+
async replaceAllConfigs(newConfigObject) {
|
|
123
|
+
await this.cleanupConfigs();
|
|
115
124
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
+
// Apply new configs to all services that have config labels
|
|
126
|
+
const updatedServices = [];
|
|
127
|
+
for (const [configLabel, newConfig] of Object.entries(
|
|
128
|
+
newConfigObject || {}
|
|
129
|
+
)) {
|
|
130
|
+
const services = await this.replaceConfig(configLabel, newConfig);
|
|
131
|
+
updatedServices.push(...services);
|
|
132
|
+
}
|
|
125
133
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* @returns {Object<string, *>} Current configuration object
|
|
130
|
-
*/
|
|
131
|
-
getConfigObject() {
|
|
132
|
-
return { ...this.configObject };
|
|
134
|
+
this.manager?.logger?.debug(
|
|
135
|
+
`Replaced all configurations, updated ${updatedServices.length} services`
|
|
136
|
+
);
|
|
133
137
|
}
|
|
134
138
|
|
|
135
139
|
/**
|
|
136
|
-
*
|
|
140
|
+
* Replace a config for a specific label and push to affected services
|
|
137
141
|
*
|
|
138
142
|
* @param {string} configLabel - Config label to update
|
|
139
143
|
* @param {*} newConfig - New configuration value
|
|
140
144
|
*
|
|
141
145
|
* @returns {Promise<string[]>} Array of service names that were updated
|
|
142
146
|
*/
|
|
143
|
-
async
|
|
147
|
+
async replaceConfig(configLabel, newConfig) {
|
|
144
148
|
// Update the config object
|
|
145
|
-
this.
|
|
149
|
+
this.allConfigs[configLabel] = newConfig;
|
|
146
150
|
|
|
147
151
|
// Store as pending update
|
|
148
152
|
this.#pendingConfigUpdates.set(configLabel, newConfig);
|
|
149
153
|
|
|
150
154
|
const updatedServices = [];
|
|
151
155
|
|
|
152
|
-
// Find all services using this config label
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
// Find all services using this config label using helper function
|
|
157
|
+
const servicesByLabel = this.#servicesByConfigLabel();
|
|
158
|
+
const serviceNames = servicesByLabel.get(configLabel) || [];
|
|
159
|
+
|
|
160
|
+
for (const serviceName of serviceNames) {
|
|
161
|
+
const serviceEntry = this.manager.services.get(serviceName);
|
|
162
|
+
if (serviceEntry && serviceEntry.instance) {
|
|
155
163
|
try {
|
|
156
164
|
// Try to apply config - ServiceBase.configure() will handle state validation
|
|
157
|
-
await this.#applyConfigToService(
|
|
165
|
+
await this.#applyConfigToService(
|
|
166
|
+
serviceName,
|
|
167
|
+
serviceEntry,
|
|
168
|
+
newConfig
|
|
169
|
+
);
|
|
158
170
|
updatedServices.push(serviceName);
|
|
159
|
-
|
|
171
|
+
|
|
160
172
|
// Remove from pending since it was applied
|
|
161
173
|
this.#pendingConfigUpdates.delete(configLabel);
|
|
162
174
|
} catch (error) {
|
|
@@ -175,6 +187,60 @@ export default class ConfigPlugin {
|
|
|
175
187
|
return updatedServices;
|
|
176
188
|
}
|
|
177
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Get services organized by their config labels
|
|
192
|
+
*
|
|
193
|
+
* @returns {Map<string, string[]>}
|
|
194
|
+
* Map where keys are config labels and values are arrays of service names
|
|
195
|
+
*/
|
|
196
|
+
#servicesByConfigLabel() {
|
|
197
|
+
const servicesByLabel = new Map();
|
|
198
|
+
|
|
199
|
+
for (const [serviceName, serviceEntry] of this.manager.services) {
|
|
200
|
+
const configLabel = serviceEntry.serviceConfigOrLabel;
|
|
201
|
+
|
|
202
|
+
if (typeof configLabel === 'string') {
|
|
203
|
+
if (!servicesByLabel.has(configLabel)) {
|
|
204
|
+
servicesByLabel.set(configLabel, []);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
servicesByLabel.get(configLabel).push(serviceName);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return servicesByLabel;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Remove all unused configurations (configs not referenced by any service)
|
|
216
|
+
*/
|
|
217
|
+
async cleanupConfigs() {
|
|
218
|
+
const usedConfigLabels = new Set();
|
|
219
|
+
|
|
220
|
+
// Collect all config labels used by registered services
|
|
221
|
+
for (const [, serviceEntry] of this.manager.services) {
|
|
222
|
+
const configLabel = serviceEntry.serviceConfigOrLabel;
|
|
223
|
+
|
|
224
|
+
if (typeof configLabel === 'string') {
|
|
225
|
+
usedConfigLabels.add(configLabel);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Remove configs that aren't used by any service
|
|
230
|
+
const configKeys = Object.keys(this.allConfigs);
|
|
231
|
+
let removedCount = 0;
|
|
232
|
+
|
|
233
|
+
for (const key of configKeys) {
|
|
234
|
+
if (!usedConfigLabels.has(key)) {
|
|
235
|
+
delete this.allConfigs[key];
|
|
236
|
+
removedCount++;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.manager?.logger?.debug(
|
|
241
|
+
`Cleaned up ${removedCount} unused configurations`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
178
244
|
|
|
179
245
|
/**
|
|
180
246
|
* Apply configuration to a specific service
|
|
@@ -187,9 +253,7 @@ export default class ConfigPlugin {
|
|
|
187
253
|
async #applyConfigToService(serviceName, serviceEntry, newConfig) {
|
|
188
254
|
await serviceEntry.instance.configure(newConfig);
|
|
189
255
|
|
|
190
|
-
this.manager.logger.info(
|
|
191
|
-
`Updated config for service '${serviceName}'`
|
|
192
|
-
);
|
|
256
|
+
this.manager.logger.info(`Updated config for service '${serviceName}'`);
|
|
193
257
|
}
|
|
194
258
|
|
|
195
259
|
/**
|
|
@@ -201,18 +265,24 @@ export default class ConfigPlugin {
|
|
|
201
265
|
*/
|
|
202
266
|
async #processPendingUpdates(serviceName, serviceInstance) {
|
|
203
267
|
const serviceEntry = this.manager.services.get(serviceName);
|
|
204
|
-
|
|
268
|
+
|
|
269
|
+
const configLabel = serviceEntry?.serviceConfigOrLabel;
|
|
270
|
+
|
|
271
|
+
if (typeof configLabel !== 'string') {
|
|
205
272
|
return;
|
|
206
273
|
}
|
|
207
274
|
|
|
208
|
-
const configLabel = serviceEntry.config;
|
|
209
275
|
if (this.#pendingConfigUpdates.has(configLabel)) {
|
|
210
276
|
const pendingConfig = this.#pendingConfigUpdates.get(configLabel);
|
|
211
|
-
|
|
277
|
+
|
|
212
278
|
try {
|
|
213
|
-
await this.#applyConfigToService(
|
|
279
|
+
await this.#applyConfigToService(
|
|
280
|
+
serviceName,
|
|
281
|
+
serviceEntry,
|
|
282
|
+
pendingConfig
|
|
283
|
+
);
|
|
214
284
|
this.#pendingConfigUpdates.delete(configLabel);
|
|
215
|
-
|
|
285
|
+
|
|
216
286
|
this.manager.logger.info(
|
|
217
287
|
`Applied pending config update for service '${serviceName}' (label: ${configLabel})`
|
|
218
288
|
);
|
|
@@ -239,16 +309,19 @@ export default class ConfigPlugin {
|
|
|
239
309
|
|
|
240
310
|
this.manager = manager;
|
|
241
311
|
|
|
242
|
-
const configKeys = Object.keys(this.
|
|
312
|
+
const configKeys = Object.keys(this.allConfigs).length;
|
|
243
313
|
|
|
244
314
|
this.manager.logger.info(
|
|
245
315
|
`ConfigPlugin attached with ${configKeys} config keys`
|
|
246
316
|
);
|
|
247
317
|
|
|
248
318
|
// Listen for service state changes to process pending updates
|
|
249
|
-
this.manager.on(
|
|
250
|
-
|
|
251
|
-
|
|
319
|
+
this.manager.on(
|
|
320
|
+
SERVICE_STATE_CHANGED,
|
|
321
|
+
async ({ service, state, instance }) => {
|
|
322
|
+
await this.#processPendingUpdates(service, instance);
|
|
323
|
+
}
|
|
324
|
+
);
|
|
252
325
|
}
|
|
253
326
|
|
|
254
327
|
/**
|
|
@@ -258,7 +331,7 @@ export default class ConfigPlugin {
|
|
|
258
331
|
if (this.manager) {
|
|
259
332
|
// Clear pending updates
|
|
260
333
|
this.#pendingConfigUpdates.clear();
|
|
261
|
-
|
|
334
|
+
|
|
262
335
|
this.manager.logger.info('ConfigPlugin detached');
|
|
263
336
|
this.manager = null;
|
|
264
337
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Type definitions for ServiceBase class.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This file contains all TypeScript/JSDoc type definitions used by
|
|
5
5
|
* the ServiceBase class and service implementations.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @example
|
|
8
8
|
* // In your service implementation
|
|
9
9
|
* import { ServiceBase } from './ServiceBase.js';
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* class MyService extends ServiceBase {
|
|
12
12
|
* async _configure(newConfig, oldConfig) {
|
|
13
13
|
* }
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* async _healthCheck() {
|
|
16
16
|
* // Return type is HealthStatus
|
|
17
17
|
* return { latency: 10 };
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @typedef {import('./typedef.js').ServiceRegistrationOptions} ServiceRegistrationOptions
|
|
4
4
|
* @typedef {import('./typedef.js').ServiceManagerConfig} ServiceManagerConfig
|
|
5
5
|
* @typedef {import('./typedef.js').ServiceEntry} ServiceEntry
|
|
6
|
+
* @typedef {import('./typedef.js').ServiceConfigOrLabel} ServiceConfigOrLabel
|
|
6
7
|
* @typedef {import('./typedef.js').HealthCheckResult} HealthCheckResult
|
|
7
8
|
*
|
|
8
9
|
* @typedef {import('../service-base/typedef.js').StopOptions} StopOptions
|
|
@@ -46,12 +47,13 @@ export class ServiceManager extends EventEmitter {
|
|
|
46
47
|
*
|
|
47
48
|
* @param {string} name - Unique service identifier
|
|
48
49
|
* @param {ServiceConstructor} ServiceClass - Service class constructor
|
|
49
|
-
* @param {
|
|
50
|
+
* @param {ServiceConfigOrLabel} [serviceConfigOrLabel={}]
|
|
51
|
+
* Service configuration object or config label string
|
|
50
52
|
* @param {ServiceRegistrationOptions} [options={}] - Registration options
|
|
51
53
|
*
|
|
52
54
|
* @throws {Error} If service name is already registered
|
|
53
55
|
*/
|
|
54
|
-
register(name: string, ServiceClass: ServiceConstructor,
|
|
56
|
+
register(name: string, ServiceClass: ServiceConstructor, serviceConfigOrLabel?: ServiceConfigOrLabel, options?: ServiceRegistrationOptions): void;
|
|
55
57
|
/**
|
|
56
58
|
* Get or create a service instance
|
|
57
59
|
*
|
|
@@ -156,6 +158,7 @@ export type ServiceConstructor = import("./typedef.js").ServiceConstructor;
|
|
|
156
158
|
export type ServiceRegistrationOptions = import("./typedef.js").ServiceRegistrationOptions;
|
|
157
159
|
export type ServiceManagerConfig = import("./typedef.js").ServiceManagerConfig;
|
|
158
160
|
export type ServiceEntry = import("./typedef.js").ServiceEntry;
|
|
161
|
+
export type ServiceConfigOrLabel = import("./typedef.js").ServiceConfigOrLabel;
|
|
159
162
|
export type HealthCheckResult = import("./typedef.js").HealthCheckResult;
|
|
160
163
|
export type StopOptions = import("../service-base/typedef.js").StopOptions;
|
|
161
164
|
import { EventEmitter } from '../../generic/events.js';
|
|
@@ -78,6 +78,7 @@ import {
|
|
|
78
78
|
* @typedef {import('./typedef.js').ServiceRegistrationOptions} ServiceRegistrationOptions
|
|
79
79
|
* @typedef {import('./typedef.js').ServiceManagerConfig} ServiceManagerConfig
|
|
80
80
|
* @typedef {import('./typedef.js').ServiceEntry} ServiceEntry
|
|
81
|
+
* @typedef {import('./typedef.js').ServiceConfigOrLabel} ServiceConfigOrLabel
|
|
81
82
|
* @typedef {import('./typedef.js').HealthCheckResult} HealthCheckResult
|
|
82
83
|
*
|
|
83
84
|
* @typedef {import('../service-base/typedef.js').StopOptions} StopOptions
|
|
@@ -158,12 +159,13 @@ export class ServiceManager extends EventEmitter {
|
|
|
158
159
|
*
|
|
159
160
|
* @param {string} name - Unique service identifier
|
|
160
161
|
* @param {ServiceConstructor} ServiceClass - Service class constructor
|
|
161
|
-
* @param {
|
|
162
|
+
* @param {ServiceConfigOrLabel} [serviceConfigOrLabel={}]
|
|
163
|
+
* Service configuration object or config label string
|
|
162
164
|
* @param {ServiceRegistrationOptions} [options={}] - Registration options
|
|
163
165
|
*
|
|
164
166
|
* @throws {Error} If service name is already registered
|
|
165
167
|
*/
|
|
166
|
-
register(name, ServiceClass,
|
|
168
|
+
register(name, ServiceClass, serviceConfigOrLabel = {}, options = {}) {
|
|
167
169
|
if (this.services.has(name)) {
|
|
168
170
|
throw new Error(`Service '${name}' already registered`);
|
|
169
171
|
}
|
|
@@ -172,11 +174,11 @@ export class ServiceManager extends EventEmitter {
|
|
|
172
174
|
const entry = {
|
|
173
175
|
ServiceClass,
|
|
174
176
|
instance: null,
|
|
175
|
-
|
|
177
|
+
serviceConfigOrLabel: serviceConfigOrLabel,
|
|
176
178
|
dependencies: options.dependencies || [],
|
|
177
179
|
dependents: new Set(),
|
|
178
180
|
tags: options.tags || [],
|
|
179
|
-
|
|
181
|
+
startupPriority: options.startupPriority || 0
|
|
180
182
|
};
|
|
181
183
|
|
|
182
184
|
// Track dependents
|
|
@@ -245,7 +247,7 @@ export class ServiceManager extends EventEmitter {
|
|
|
245
247
|
if (!instance) return false;
|
|
246
248
|
|
|
247
249
|
const entry = this.services.get(name);
|
|
248
|
-
const config = await this.#
|
|
250
|
+
const config = await this.#resolveServiceConfig(name, entry);
|
|
249
251
|
|
|
250
252
|
return await instance.configure(config);
|
|
251
253
|
}
|
|
@@ -559,25 +561,29 @@ export class ServiceManager extends EventEmitter {
|
|
|
559
561
|
*
|
|
560
562
|
* @returns {Promise<*>} Resolved configuration object
|
|
561
563
|
*/
|
|
562
|
-
async #
|
|
563
|
-
let
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
config
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
564
|
+
async #resolveServiceConfig(serviceName, serviceEntry) {
|
|
565
|
+
let serviceConfigOrLabel = serviceEntry.serviceConfigOrLabel;
|
|
566
|
+
|
|
567
|
+
if (typeof serviceConfigOrLabel === 'string') {
|
|
568
|
+
const configLabel = serviceConfigOrLabel;
|
|
569
|
+
|
|
570
|
+
// Let plugins resolve the config
|
|
571
|
+
for (const plugin of this.#plugins.values()) {
|
|
572
|
+
if (plugin.resolveServiceConfig) {
|
|
573
|
+
const config = await plugin.resolveServiceConfig(
|
|
574
|
+
serviceName,
|
|
575
|
+
serviceEntry,
|
|
576
|
+
configLabel
|
|
577
|
+
);
|
|
578
|
+
if (config !== undefined) {
|
|
579
|
+
return config; // First plugin that resolves wins
|
|
580
|
+
}
|
|
576
581
|
}
|
|
577
582
|
}
|
|
583
|
+
} else {
|
|
584
|
+
const config = serviceConfigOrLabel;
|
|
585
|
+
return config;
|
|
578
586
|
}
|
|
579
|
-
|
|
580
|
-
return config || {};
|
|
581
587
|
}
|
|
582
588
|
|
|
583
589
|
/**
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service configuration - either a config object or a config label string
|
|
3
|
+
*/
|
|
4
|
+
export type ServiceConfigOrLabel = {
|
|
5
|
+
[x: string]: any;
|
|
6
|
+
} | string;
|
|
1
7
|
/**
|
|
2
8
|
* Options for registering a service
|
|
3
9
|
*/
|
|
@@ -11,9 +17,9 @@ export type ServiceRegistrationOptions = {
|
|
|
11
17
|
*/
|
|
12
18
|
tags?: string[];
|
|
13
19
|
/**
|
|
14
|
-
* -
|
|
20
|
+
* - Higher starts first
|
|
15
21
|
*/
|
|
16
|
-
|
|
22
|
+
startupPriority?: number;
|
|
17
23
|
};
|
|
18
24
|
/**
|
|
19
25
|
* Configuration for ServiceManager
|
|
@@ -92,7 +98,7 @@ export type ServiceManagerPlugin = {
|
|
|
92
98
|
/**
|
|
93
99
|
* - Optional config resolution method
|
|
94
100
|
*/
|
|
95
|
-
|
|
101
|
+
resolveServiceConfig?: (arg0: string, arg1: ServiceEntry, arg2: any) => Promise<any | undefined>;
|
|
96
102
|
};
|
|
97
103
|
/**
|
|
98
104
|
* Internal service registry entry
|
|
@@ -106,24 +112,15 @@ export type ServiceEntry = {
|
|
|
106
112
|
* - Service instance (lazy-created)
|
|
107
113
|
*/
|
|
108
114
|
instance: import("../service-base/typedef.js").ServiceInstance | null;
|
|
109
|
-
|
|
110
|
-
* - Service configuration
|
|
111
|
-
*/
|
|
112
|
-
config: any;
|
|
113
|
-
/**
|
|
114
|
-
* - Service dependencies
|
|
115
|
-
*/
|
|
115
|
+
serviceConfigOrLabel: ServiceConfigOrLabel;
|
|
116
116
|
dependencies: string[];
|
|
117
117
|
/**
|
|
118
|
-
* - Services that depend on this
|
|
118
|
+
* - Services that depend on this service
|
|
119
119
|
*/
|
|
120
120
|
dependents: Set<string>;
|
|
121
|
-
/**
|
|
122
|
-
* - Service tags
|
|
123
|
-
*/
|
|
124
121
|
tags: string[];
|
|
125
122
|
/**
|
|
126
123
|
* - Startup priority
|
|
127
124
|
*/
|
|
128
|
-
|
|
125
|
+
startupPriority: number;
|
|
129
126
|
};
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Type definitions for ServiceManager class.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* This file contains all TypeScript/JSDoc type definitions used by
|
|
5
5
|
* the ServiceManager class and service registration.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @example
|
|
8
8
|
* // When using ServiceManager
|
|
9
9
|
* import { ServiceManager } from './ServiceManager.js';
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* // @ typedef {import('./typedef-service-manager.js').ServiceManagerConfig} ServiceManagerConfig
|
|
12
12
|
* // @ typedef {import('./typedef-service-manager.js').ServiceRegistrationOptions} ServiceRegistrationOptions
|
|
13
|
-
*
|
|
13
|
+
*
|
|
14
14
|
* const config = {
|
|
15
15
|
* environment: 'development',
|
|
16
16
|
* stopTimeout: 5000
|
|
17
17
|
* };
|
|
18
|
-
*
|
|
18
|
+
*
|
|
19
19
|
* const manager = new ServiceManager(config);
|
|
20
|
-
*
|
|
20
|
+
*
|
|
21
21
|
* const options = {
|
|
22
22
|
* dependencies: ['database'],
|
|
23
23
|
* tags: ['critical']
|
|
24
24
|
* };
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* manager.register('auth', AuthService, {}, options);
|
|
27
27
|
*/
|
|
28
28
|
|
|
@@ -30,13 +30,19 @@
|
|
|
30
30
|
// PUBLIC TYPES
|
|
31
31
|
// ============================================================================
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Service configuration - either a config object or a config label string
|
|
35
|
+
*
|
|
36
|
+
* @typedef {Object<string, *>|string} ServiceConfigOrLabel
|
|
37
|
+
*/
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* Options for registering a service
|
|
35
41
|
*
|
|
36
42
|
* @typedef {Object} ServiceRegistrationOptions
|
|
37
43
|
* @property {string[]} [dependencies=[]] - Services this service depends on
|
|
38
44
|
* @property {string[]} [tags=[]] - Tags for grouping services
|
|
39
|
-
* @property {number} [
|
|
45
|
+
* @property {number} [startupPriority=0] - Higher starts first
|
|
40
46
|
*/
|
|
41
47
|
|
|
42
48
|
/**
|
|
@@ -78,7 +84,7 @@
|
|
|
78
84
|
* @property {import('./ServiceManager.js').ServiceManager|null} manager - ServiceManager reference
|
|
79
85
|
* @property {function(import('./ServiceManager.js').ServiceManager): void} attach - Attach to ServiceManager
|
|
80
86
|
* @property {function(): void} detach - Detach from ServiceManager
|
|
81
|
-
* @property {function(string, ServiceEntry, *): Promise<*|undefined>} [
|
|
87
|
+
* @property {function(string, ServiceEntry, *): Promise<*|undefined>} [resolveServiceConfig] - Optional config resolution method
|
|
82
88
|
*/
|
|
83
89
|
|
|
84
90
|
// ============================================================================
|
|
@@ -91,11 +97,11 @@
|
|
|
91
97
|
* @typedef {Object} ServiceEntry
|
|
92
98
|
* @property {ServiceConstructor} ServiceClass - Service class constructor
|
|
93
99
|
* @property {import('../service-base/typedef.js').ServiceInstance|null} instance - Service instance (lazy-created)
|
|
94
|
-
* @property {
|
|
95
|
-
* @property {string[]} dependencies
|
|
96
|
-
* @property {Set<string>} dependents - Services that depend on this
|
|
97
|
-
* @property {string[]} tags
|
|
98
|
-
* @property {number}
|
|
100
|
+
* @property {ServiceConfigOrLabel} serviceConfigOrLabel
|
|
101
|
+
* @property {string[]} dependencies
|
|
102
|
+
* @property {Set<string>} dependents - Services that depend on this service
|
|
103
|
+
* @property {string[]} tags
|
|
104
|
+
* @property {number} startupPriority - Startup priority
|
|
99
105
|
*/
|
|
100
106
|
|
|
101
107
|
export {};
|