@hkdigital/lib-core 0.4.18 → 0.4.19
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 +234 -9
- package/dist/services/service-base/ServiceBase.d.ts +59 -22
- package/dist/services/service-base/ServiceBase.js +139 -61
- package/dist/services/service-base/constants.d.ts +30 -14
- package/dist/services/service-base/constants.js +43 -14
- package/dist/services/service-base/typedef.d.ts +15 -9
- package/dist/services/service-base/typedef.js +29 -7
- package/dist/services/service-manager/ServiceManager.d.ts +20 -3
- package/dist/services/service-manager/ServiceManager.js +93 -16
- package/dist/services/service-manager/typedef.d.ts +25 -0
- package/dist/services/service-manager/typedef.js +11 -0
- package/dist/services/service-manager-plugins/ConfigPlugin.d.ts +78 -0
- package/dist/services/service-manager-plugins/ConfigPlugin.js +266 -0
- package/dist/util/sveltekit/env/README.md +424 -0
- package/dist/util/sveltekit/env/all.d.ts +54 -0
- package/dist/util/sveltekit/env/all.js +97 -0
- package/dist/util/sveltekit/env/parsers.d.ts +135 -0
- package/dist/util/sveltekit/env/parsers.js +257 -0
- package/dist/util/sveltekit/env/private.d.ts +56 -0
- package/dist/util/sveltekit/env/private.js +87 -0
- package/dist/util/sveltekit/env/public.d.ts +52 -0
- package/dist/util/sveltekit/env/public.js +82 -0
- package/dist/util/sveltekit/env-all.d.ts +1 -0
- package/dist/util/sveltekit/env-all.js +19 -0
- package/dist/util/sveltekit/env-private.d.ts +1 -0
- package/dist/util/sveltekit/env-private.js +18 -0
- package/dist/util/sveltekit/env-public.d.ts +1 -0
- package/dist/util/sveltekit/env-public.js +18 -0
- package/package.json +1 -1
- package/dist/util/index.js__ +0 -20
- package/dist/util/sveltekit/index.d.ts +0 -0
- package/dist/util/sveltekit/index.js +0 -0
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* import { ServiceBase } from './ServiceBase.js';
|
|
10
10
|
*
|
|
11
11
|
* class MyService extends ServiceBase {
|
|
12
|
-
* async
|
|
12
|
+
* async _configure(newConfig, oldConfig) {
|
|
13
13
|
* }
|
|
14
14
|
*
|
|
15
15
|
* async _healthCheck() {
|
|
@@ -23,6 +23,24 @@
|
|
|
23
23
|
// PUBLIC TYPES
|
|
24
24
|
// ============================================================================
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* All possible service states during lifecycle management
|
|
28
|
+
*
|
|
29
|
+
* @typedef {import('./constants.js').STATE_NOT_CREATED |
|
|
30
|
+
* import('./constants.js').STATE_CREATED |
|
|
31
|
+
* import('./constants.js').STATE_CONFIGURING |
|
|
32
|
+
* import('./constants.js').STATE_CONFIGURED |
|
|
33
|
+
* import('./constants.js').STATE_STARTING |
|
|
34
|
+
* import('./constants.js').STATE_RUNNING |
|
|
35
|
+
* import('./constants.js').STATE_STOPPING |
|
|
36
|
+
* import('./constants.js').STATE_STOPPED |
|
|
37
|
+
* import('./constants.js').STATE_DESTROYING |
|
|
38
|
+
* import('./constants.js').STATE_DESTROYED |
|
|
39
|
+
* import('./constants.js').STATE_ERROR |
|
|
40
|
+
* import('./constants.js').STATE_RECOVERING
|
|
41
|
+
* } ServiceState
|
|
42
|
+
*/
|
|
43
|
+
|
|
26
44
|
/**
|
|
27
45
|
* Options for creating a service instance
|
|
28
46
|
*
|
|
@@ -57,11 +75,11 @@
|
|
|
57
75
|
*
|
|
58
76
|
* @typedef {Object & Record<string, any>} ServiceInstance
|
|
59
77
|
* @property {string} name - Service name
|
|
60
|
-
* @property {
|
|
78
|
+
* @property {ServiceState} state - Current state
|
|
61
79
|
* @property {boolean} healthy - Health status
|
|
62
80
|
* @property {Error|null} error - Last error
|
|
63
81
|
* @property {import('../../logging/index.js').Logger} logger - Service logger
|
|
64
|
-
* @property {(config?: *) => Promise<boolean>}
|
|
82
|
+
* @property {(config?: *) => Promise<boolean>} configure
|
|
65
83
|
* @property {() => Promise<boolean>} start
|
|
66
84
|
* @property {(options?: StopOptions) => Promise<boolean>} stop
|
|
67
85
|
* @property {() => Promise<boolean>} recover
|
|
@@ -79,13 +97,18 @@
|
|
|
79
97
|
/**
|
|
80
98
|
* @typedef {Object} StateChangeEvent
|
|
81
99
|
* @property {string} service - Service name (added by ServiceManager)
|
|
82
|
-
* @property {
|
|
83
|
-
* @property {
|
|
100
|
+
* @property {ServiceState} oldState - Previous state
|
|
101
|
+
* @property {ServiceState} newState - New state
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @typedef {Object} TargetStateChangeEvent
|
|
106
|
+
* @property {ServiceState} oldTargetState - Previous target state
|
|
107
|
+
* @property {ServiceState} newTargetState - New target state
|
|
84
108
|
*/
|
|
85
109
|
|
|
86
110
|
/**
|
|
87
111
|
* @typedef {Object} HealthChangeEvent
|
|
88
|
-
* @property {string} service - Service name (added by ServiceManager)
|
|
89
112
|
* @property {boolean} healthy - Current health status
|
|
90
113
|
* @property {boolean} [wasHealthy] - Previous health status
|
|
91
114
|
*/
|
|
@@ -94,7 +117,6 @@
|
|
|
94
117
|
* Event emitted when service encounters an error
|
|
95
118
|
*
|
|
96
119
|
* @typedef {Object} ServiceErrorEvent
|
|
97
|
-
* @property {string} service - Service name
|
|
98
120
|
* @property {string} operation - Operation that failed
|
|
99
121
|
* @property {Error} error - Error that occurred
|
|
100
122
|
*/
|
|
@@ -24,6 +24,23 @@ export class ServiceManager extends EventEmitter {
|
|
|
24
24
|
logger: Logger;
|
|
25
25
|
/** @type {ServiceManagerConfig} */
|
|
26
26
|
config: ServiceManagerConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Attach a plugin to the ServiceManager
|
|
29
|
+
*
|
|
30
|
+
* @param {import('./typedef.js').ServiceManagerPlugin} plugin
|
|
31
|
+
* Plugin instance
|
|
32
|
+
*
|
|
33
|
+
* @throws {Error} If plugin name is already registered
|
|
34
|
+
*/
|
|
35
|
+
attachPlugin(plugin: import("./typedef.js").ServiceManagerPlugin): void;
|
|
36
|
+
/**
|
|
37
|
+
* Detach a plugin from the ServiceManager
|
|
38
|
+
*
|
|
39
|
+
* @param {string} pluginName - Name of the plugin to detach
|
|
40
|
+
*
|
|
41
|
+
* @returns {boolean} True if plugin was detached
|
|
42
|
+
*/
|
|
43
|
+
detachPlugin(pluginName: string): boolean;
|
|
27
44
|
/**
|
|
28
45
|
* Register a service class with the manager
|
|
29
46
|
*
|
|
@@ -45,13 +62,13 @@ export class ServiceManager extends EventEmitter {
|
|
|
45
62
|
*/
|
|
46
63
|
get(name: string): import("../service-base/typedef.js").ServiceInstance | null;
|
|
47
64
|
/**
|
|
48
|
-
*
|
|
65
|
+
* Configure a service
|
|
49
66
|
*
|
|
50
67
|
* @param {string} name - Service name
|
|
51
68
|
*
|
|
52
|
-
* @returns {Promise<boolean>} True if
|
|
69
|
+
* @returns {Promise<boolean>} True if configuration succeeded
|
|
53
70
|
*/
|
|
54
|
-
|
|
71
|
+
configureService(name: string): Promise<boolean>;
|
|
55
72
|
/**
|
|
56
73
|
* Start a service and its dependencies
|
|
57
74
|
*
|
|
@@ -67,10 +67,10 @@ import { EventEmitter } from '../../generic/events.js';
|
|
|
67
67
|
import { Logger, DEBUG, INFO, WARN } from '../../logging/index.js';
|
|
68
68
|
|
|
69
69
|
import {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
STATE_NOT_CREATED,
|
|
71
|
+
STATE_CREATED,
|
|
72
|
+
STATE_RUNNING,
|
|
73
|
+
STATE_DESTROYED
|
|
74
74
|
} from '../service-base/constants.js';
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -88,6 +88,9 @@ import {
|
|
|
88
88
|
* @extends EventEmitter
|
|
89
89
|
*/
|
|
90
90
|
export class ServiceManager extends EventEmitter {
|
|
91
|
+
/** @type {Map<string, import('./typedef.js').ServiceManagerPlugin>} */
|
|
92
|
+
#plugins = new Map();
|
|
93
|
+
|
|
91
94
|
/**
|
|
92
95
|
* Create a new ServiceManager instance
|
|
93
96
|
*
|
|
@@ -113,6 +116,43 @@ export class ServiceManager extends EventEmitter {
|
|
|
113
116
|
this.#setupLogging();
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Attach a plugin to the ServiceManager
|
|
121
|
+
*
|
|
122
|
+
* @param {import('./typedef.js').ServiceManagerPlugin} plugin
|
|
123
|
+
* Plugin instance
|
|
124
|
+
*
|
|
125
|
+
* @throws {Error} If plugin name is already registered
|
|
126
|
+
*/
|
|
127
|
+
attachPlugin(plugin) {
|
|
128
|
+
if (this.#plugins.has(plugin.name)) {
|
|
129
|
+
throw new Error(`Plugin '${plugin.name}' is already attached`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.#plugins.set(plugin.name, plugin);
|
|
133
|
+
plugin.attach(this);
|
|
134
|
+
|
|
135
|
+
this.logger.debug(`Attached plugin '${plugin.name}'`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Detach a plugin from the ServiceManager
|
|
140
|
+
*
|
|
141
|
+
* @param {string} pluginName - Name of the plugin to detach
|
|
142
|
+
*
|
|
143
|
+
* @returns {boolean} True if plugin was detached
|
|
144
|
+
*/
|
|
145
|
+
detachPlugin(pluginName) {
|
|
146
|
+
const plugin = this.#plugins.get(pluginName);
|
|
147
|
+
if (!plugin) return false;
|
|
148
|
+
|
|
149
|
+
plugin.detach();
|
|
150
|
+
this.#plugins.delete(pluginName);
|
|
151
|
+
|
|
152
|
+
this.logger.debug(`Detached plugin '${pluginName}'`);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
116
156
|
/**
|
|
117
157
|
* Register a service class with the manager
|
|
118
158
|
*
|
|
@@ -194,18 +234,20 @@ export class ServiceManager extends EventEmitter {
|
|
|
194
234
|
}
|
|
195
235
|
|
|
196
236
|
/**
|
|
197
|
-
*
|
|
237
|
+
* Configure a service
|
|
198
238
|
*
|
|
199
239
|
* @param {string} name - Service name
|
|
200
240
|
*
|
|
201
|
-
* @returns {Promise<boolean>} True if
|
|
241
|
+
* @returns {Promise<boolean>} True if configuration succeeded
|
|
202
242
|
*/
|
|
203
|
-
async
|
|
243
|
+
async configureService(name) {
|
|
204
244
|
const instance = this.get(name);
|
|
205
245
|
if (!instance) return false;
|
|
206
246
|
|
|
207
247
|
const entry = this.services.get(name);
|
|
208
|
-
|
|
248
|
+
const config = await this.#resolveConfig(name, entry);
|
|
249
|
+
|
|
250
|
+
return await instance.configure(config);
|
|
209
251
|
}
|
|
210
252
|
|
|
211
253
|
/**
|
|
@@ -240,10 +282,18 @@ export class ServiceManager extends EventEmitter {
|
|
|
240
282
|
const instance = this.get(name);
|
|
241
283
|
if (!instance) return false;
|
|
242
284
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
285
|
+
if (
|
|
286
|
+
instance.state === STATE_CREATED ||
|
|
287
|
+
instance.state === STATE_DESTROYED
|
|
288
|
+
) {
|
|
289
|
+
// Service is not created or has been destroyed
|
|
290
|
+
// => configure needed
|
|
291
|
+
|
|
292
|
+
const configured = await this.configureService(name);
|
|
293
|
+
|
|
294
|
+
if (!configured) {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
247
297
|
}
|
|
248
298
|
|
|
249
299
|
return await instance.start();
|
|
@@ -385,7 +435,6 @@ export class ServiceManager extends EventEmitter {
|
|
|
385
435
|
return Object.fromEntries(results);
|
|
386
436
|
}
|
|
387
437
|
|
|
388
|
-
|
|
389
438
|
/**
|
|
390
439
|
* Get health status for all services
|
|
391
440
|
*
|
|
@@ -401,7 +450,7 @@ export class ServiceManager extends EventEmitter {
|
|
|
401
450
|
} else {
|
|
402
451
|
health[name] = {
|
|
403
452
|
name,
|
|
404
|
-
state:
|
|
453
|
+
state: STATE_NOT_CREATED,
|
|
405
454
|
healthy: false
|
|
406
455
|
};
|
|
407
456
|
}
|
|
@@ -419,7 +468,7 @@ export class ServiceManager extends EventEmitter {
|
|
|
419
468
|
*/
|
|
420
469
|
async isRunning(name) {
|
|
421
470
|
const instance = this.get(name);
|
|
422
|
-
return instance ? instance.state ===
|
|
471
|
+
return instance ? instance.state === STATE_RUNNING : false;
|
|
423
472
|
}
|
|
424
473
|
|
|
425
474
|
/**
|
|
@@ -472,7 +521,6 @@ export class ServiceManager extends EventEmitter {
|
|
|
472
521
|
return services;
|
|
473
522
|
}
|
|
474
523
|
|
|
475
|
-
|
|
476
524
|
/**
|
|
477
525
|
* Attach event listeners to forward service events
|
|
478
526
|
*
|
|
@@ -503,6 +551,35 @@ export class ServiceManager extends EventEmitter {
|
|
|
503
551
|
|
|
504
552
|
// Internal methods
|
|
505
553
|
|
|
554
|
+
/**
|
|
555
|
+
* Resolve service configuration using plugins
|
|
556
|
+
*
|
|
557
|
+
* @param {string} serviceName - Name of the service being configured
|
|
558
|
+
* @param {ServiceEntry} serviceEntry - Service registration entry
|
|
559
|
+
*
|
|
560
|
+
* @returns {Promise<*>} Resolved configuration object
|
|
561
|
+
*/
|
|
562
|
+
async #resolveConfig(serviceName, serviceEntry) {
|
|
563
|
+
let config = serviceEntry.config;
|
|
564
|
+
|
|
565
|
+
// Let plugins resolve the config
|
|
566
|
+
for (const plugin of this.#plugins.values()) {
|
|
567
|
+
if (plugin._getServiceConfig) {
|
|
568
|
+
const resolved = await plugin._getServiceConfig(
|
|
569
|
+
serviceName,
|
|
570
|
+
serviceEntry,
|
|
571
|
+
config
|
|
572
|
+
);
|
|
573
|
+
if (resolved !== undefined) {
|
|
574
|
+
config = resolved;
|
|
575
|
+
break; // First plugin that resolves wins
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return config || {};
|
|
581
|
+
}
|
|
582
|
+
|
|
506
583
|
/**
|
|
507
584
|
* Setup logging configuration based on config.dev
|
|
508
585
|
*/
|
|
@@ -69,6 +69,31 @@ export type HealthCheckResult = {
|
|
|
69
69
|
* Service class constructor type
|
|
70
70
|
*/
|
|
71
71
|
export type ServiceConstructor = new (name: string, options?: import("../service-base/typedef.js").ServiceOptions) => import("../service-base/typedef.js").ServiceInstance;
|
|
72
|
+
/**
|
|
73
|
+
* ServiceManager plugin interface
|
|
74
|
+
*/
|
|
75
|
+
export type ServiceManagerPlugin = {
|
|
76
|
+
/**
|
|
77
|
+
* - Unique plugin identifier
|
|
78
|
+
*/
|
|
79
|
+
name: string;
|
|
80
|
+
/**
|
|
81
|
+
* - ServiceManager reference
|
|
82
|
+
*/
|
|
83
|
+
manager: import("./ServiceManager.js").ServiceManager | null;
|
|
84
|
+
/**
|
|
85
|
+
* - Attach to ServiceManager
|
|
86
|
+
*/
|
|
87
|
+
attach: (arg0: import("./ServiceManager.js").ServiceManager) => void;
|
|
88
|
+
/**
|
|
89
|
+
* - Detach from ServiceManager
|
|
90
|
+
*/
|
|
91
|
+
detach: () => void;
|
|
92
|
+
/**
|
|
93
|
+
* - Optional config resolution method
|
|
94
|
+
*/
|
|
95
|
+
_getServiceConfig?: (arg0: string, arg1: ServiceEntry, arg2: any) => Promise<any | undefined>;
|
|
96
|
+
};
|
|
72
97
|
/**
|
|
73
98
|
* Internal service registry entry
|
|
74
99
|
*/
|
|
@@ -70,6 +70,17 @@
|
|
|
70
70
|
* @typedef {new (name: string, options?: import('../service-base/typedef.js').ServiceOptions) => import('../service-base/typedef.js').ServiceInstance} ServiceConstructor
|
|
71
71
|
*/
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* ServiceManager plugin interface
|
|
75
|
+
*
|
|
76
|
+
* @typedef {Object} ServiceManagerPlugin
|
|
77
|
+
* @property {string} name - Unique plugin identifier
|
|
78
|
+
* @property {import('./ServiceManager.js').ServiceManager|null} manager - ServiceManager reference
|
|
79
|
+
* @property {function(import('./ServiceManager.js').ServiceManager): void} attach - Attach to ServiceManager
|
|
80
|
+
* @property {function(): void} detach - Detach from ServiceManager
|
|
81
|
+
* @property {function(string, ServiceEntry, *): Promise<*|undefined>} [_getServiceConfig] - Optional config resolution method
|
|
82
|
+
*/
|
|
83
|
+
|
|
73
84
|
// ============================================================================
|
|
74
85
|
// INTERNAL TYPES
|
|
75
86
|
// ============================================================================
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin that resolves service configuration from a configuration object
|
|
3
|
+
*/
|
|
4
|
+
export default class ConfigPlugin {
|
|
5
|
+
/**
|
|
6
|
+
* Create a new object configuration plugin
|
|
7
|
+
*
|
|
8
|
+
* @param {Object<string, *>} configObject - Pre-parsed configuration object
|
|
9
|
+
*/
|
|
10
|
+
constructor(configObject: {
|
|
11
|
+
[x: string]: any;
|
|
12
|
+
});
|
|
13
|
+
/** @type {string} */
|
|
14
|
+
name: string;
|
|
15
|
+
/** @type {import('../service-manager/ServiceManager.js').ServiceManager|null} */
|
|
16
|
+
manager: import("../service-manager/ServiceManager.js").ServiceManager | null;
|
|
17
|
+
/** @type {Object<string, *>} */
|
|
18
|
+
configObject: {
|
|
19
|
+
[x: string]: any;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Resolve service configuration from the configuration object
|
|
23
|
+
*
|
|
24
|
+
* @param {string} serviceName - Name of the service being configured
|
|
25
|
+
* @param {import('../service-manager/typedef.js').ServiceEntry} serviceEntry
|
|
26
|
+
* Service registration entry
|
|
27
|
+
* @param {*} currentConfig - Current config (could be object from previous plugins)
|
|
28
|
+
*
|
|
29
|
+
* @returns {Promise<Object|undefined>}
|
|
30
|
+
* Resolved config object, or undefined to use currentConfig as-is
|
|
31
|
+
*/
|
|
32
|
+
_getServiceConfig(serviceName: string, serviceEntry: import("../service-manager/typedef.js").ServiceEntry, currentConfig: any): Promise<any | undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* Update the configuration object
|
|
35
|
+
*
|
|
36
|
+
* @param {Object<string, *>} newConfigObject - New configuration object
|
|
37
|
+
*/
|
|
38
|
+
updateConfigObject(newConfigObject: {
|
|
39
|
+
[x: string]: any;
|
|
40
|
+
}): void;
|
|
41
|
+
/**
|
|
42
|
+
* Merge additional configuration into the existing object
|
|
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
|
|
59
|
+
*
|
|
60
|
+
* @param {string} configLabel - Config label to update
|
|
61
|
+
* @param {*} newConfig - New configuration value
|
|
62
|
+
*
|
|
63
|
+
* @returns {Promise<string[]>} Array of service names that were updated
|
|
64
|
+
*/
|
|
65
|
+
updateConfigLabel(configLabel: string, newConfig: any): Promise<string[]>;
|
|
66
|
+
/**
|
|
67
|
+
* Attach plugin to ServiceManager
|
|
68
|
+
*
|
|
69
|
+
* @param {import('../service-manager/ServiceManager.js').ServiceManager} manager
|
|
70
|
+
* ServiceManager instance
|
|
71
|
+
*/
|
|
72
|
+
attach(manager: import("../service-manager/ServiceManager.js").ServiceManager): void;
|
|
73
|
+
/**
|
|
74
|
+
* Detach plugin from ServiceManager
|
|
75
|
+
*/
|
|
76
|
+
detach(): void;
|
|
77
|
+
#private;
|
|
78
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Object-based configuration plugin for ServiceManager
|
|
3
|
+
*
|
|
4
|
+
* Resolves service configuration from a pre-provided configuration object
|
|
5
|
+
* using config labels that map to object properties or prefixed keys.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Basic usage with config object
|
|
9
|
+
* import ConfigPlugin from './ConfigPlugin.js';
|
|
10
|
+
*
|
|
11
|
+
* const configObject = {
|
|
12
|
+
* 'database': { host: 'localhost', port: 5432 },
|
|
13
|
+
* 'auth': { secret: 'my-secret', algorithm: 'HS256' }
|
|
14
|
+
* };
|
|
15
|
+
*
|
|
16
|
+
* const objectPlugin = new ConfigPlugin(configObject);
|
|
17
|
+
* manager.attachPlugin(objectPlugin);
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // With environment variables using utility
|
|
21
|
+
* import { allEnv } from '../../util/sveltekit/env.js';
|
|
22
|
+
* import ConfigPlugin from './ConfigPlugin.js';
|
|
23
|
+
*
|
|
24
|
+
* const envConfig = await allEnv();
|
|
25
|
+
* const envPlugin = new ConfigPlugin(envConfig, {
|
|
26
|
+
* prefixMap: {
|
|
27
|
+
* 'database': 'DATABASE',
|
|
28
|
+
* 'auth': 'JWT'
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Mixed configuration sources
|
|
34
|
+
* const mixedConfig = {
|
|
35
|
+
* ...await allEnv(),
|
|
36
|
+
* 'custom-service': { specialOption: true },
|
|
37
|
+
* 'override-service': { host: 'custom-host' }
|
|
38
|
+
* };
|
|
39
|
+
* const mixedPlugin = new ConfigPlugin(mixedConfig);
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import { SERVICE_STATE_CHANGED } from '../service-manager/constants.js';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Plugin that resolves service configuration from a configuration object
|
|
46
|
+
*/
|
|
47
|
+
export default class ConfigPlugin {
|
|
48
|
+
|
|
49
|
+
/** @type {Map<string, *>} */
|
|
50
|
+
#pendingConfigUpdates;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a new object configuration plugin
|
|
54
|
+
*
|
|
55
|
+
* @param {Object<string, *>} configObject - Pre-parsed configuration object
|
|
56
|
+
*/
|
|
57
|
+
constructor(configObject) {
|
|
58
|
+
/** @type {string} */
|
|
59
|
+
this.name = 'object-config';
|
|
60
|
+
|
|
61
|
+
/** @type {import('../service-manager/ServiceManager.js').ServiceManager|null} */
|
|
62
|
+
this.manager = null;
|
|
63
|
+
|
|
64
|
+
/** @type {Object<string, *>} */
|
|
65
|
+
this.configObject = configObject || {};
|
|
66
|
+
|
|
67
|
+
this.#pendingConfigUpdates = new Map();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve service configuration from the configuration object
|
|
72
|
+
*
|
|
73
|
+
* @param {string} serviceName - Name of the service being configured
|
|
74
|
+
* @param {import('../service-manager/typedef.js').ServiceEntry} serviceEntry
|
|
75
|
+
* Service registration entry
|
|
76
|
+
* @param {*} currentConfig - Current config (could be object from previous plugins)
|
|
77
|
+
*
|
|
78
|
+
* @returns {Promise<Object|undefined>}
|
|
79
|
+
* Resolved config object, or undefined to use currentConfig as-is
|
|
80
|
+
*/
|
|
81
|
+
// eslint-disable-next-line no-unused-vars
|
|
82
|
+
async _getServiceConfig(serviceName, serviceEntry, currentConfig) {
|
|
83
|
+
// Only handle string config labels from original registration
|
|
84
|
+
if (typeof serviceEntry.config !== 'string') {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const configLabel = serviceEntry.config;
|
|
89
|
+
|
|
90
|
+
// Simple object lookup
|
|
91
|
+
const config = this.configObject[configLabel];
|
|
92
|
+
|
|
93
|
+
if (config !== undefined) {
|
|
94
|
+
this.manager?.logger?.debug(
|
|
95
|
+
`Resolved object config for '${serviceName}' (label: ${configLabel})`,
|
|
96
|
+
{
|
|
97
|
+
configKeys:
|
|
98
|
+
typeof config === 'object' ? Object.keys(config) : 'primitive'
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return config;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Update the configuration object
|
|
108
|
+
*
|
|
109
|
+
* @param {Object<string, *>} newConfigObject - New configuration object
|
|
110
|
+
*/
|
|
111
|
+
updateConfigObject(newConfigObject) {
|
|
112
|
+
this.configObject = newConfigObject || {};
|
|
113
|
+
this.manager?.logger?.debug('Updated configuration object');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Merge additional configuration into the existing object
|
|
118
|
+
*
|
|
119
|
+
* @param {Object<string, *>} additionalConfig - Additional configuration
|
|
120
|
+
*/
|
|
121
|
+
mergeConfig(additionalConfig) {
|
|
122
|
+
this.configObject = { ...this.configObject, ...additionalConfig };
|
|
123
|
+
this.manager?.logger?.debug('Merged additional configuration');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get the current configuration object
|
|
128
|
+
*
|
|
129
|
+
* @returns {Object<string, *>} Current configuration object
|
|
130
|
+
*/
|
|
131
|
+
getConfigObject() {
|
|
132
|
+
return { ...this.configObject };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Update config for a specific label and push to affected services
|
|
137
|
+
*
|
|
138
|
+
* @param {string} configLabel - Config label to update
|
|
139
|
+
* @param {*} newConfig - New configuration value
|
|
140
|
+
*
|
|
141
|
+
* @returns {Promise<string[]>} Array of service names that were updated
|
|
142
|
+
*/
|
|
143
|
+
async updateConfigLabel(configLabel, newConfig) {
|
|
144
|
+
// Update the config object
|
|
145
|
+
this.configObject[configLabel] = newConfig;
|
|
146
|
+
|
|
147
|
+
// Store as pending update
|
|
148
|
+
this.#pendingConfigUpdates.set(configLabel, newConfig);
|
|
149
|
+
|
|
150
|
+
const updatedServices = [];
|
|
151
|
+
|
|
152
|
+
// Find all services using this config label
|
|
153
|
+
for (const [serviceName, serviceEntry] of this.manager.services) {
|
|
154
|
+
if (serviceEntry.config === configLabel && serviceEntry.instance) {
|
|
155
|
+
try {
|
|
156
|
+
// Try to apply config - ServiceBase.configure() will handle state validation
|
|
157
|
+
await this.#applyConfigToService(serviceName, serviceEntry, newConfig);
|
|
158
|
+
updatedServices.push(serviceName);
|
|
159
|
+
|
|
160
|
+
// Remove from pending since it was applied
|
|
161
|
+
this.#pendingConfigUpdates.delete(configLabel);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
// If configure() fails due to invalid state, it will be retried later
|
|
164
|
+
this.manager.logger.debug(
|
|
165
|
+
`Could not update config for service '${serviceName}' (${error.message}), will retry when service state allows`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.manager.logger.debug(
|
|
172
|
+
`Config label '${configLabel}' updated, applied to ${updatedServices.length} services immediately`
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return updatedServices;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Apply configuration to a specific service
|
|
181
|
+
*
|
|
182
|
+
* @param {string} serviceName - Name of the service
|
|
183
|
+
* @param {import('../service-manager/typedef.js').ServiceEntry} serviceEntry
|
|
184
|
+
* Service entry from manager
|
|
185
|
+
* @param {*} newConfig - New configuration to apply
|
|
186
|
+
*/
|
|
187
|
+
async #applyConfigToService(serviceName, serviceEntry, newConfig) {
|
|
188
|
+
await serviceEntry.instance.configure(newConfig);
|
|
189
|
+
|
|
190
|
+
this.manager.logger.info(
|
|
191
|
+
`Updated config for service '${serviceName}'`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Process pending config updates for services that can now be configured
|
|
197
|
+
*
|
|
198
|
+
* @param {string} serviceName - Name of the service that changed state
|
|
199
|
+
* @param {import('../service-base/ServiceBase.js').default} serviceInstance
|
|
200
|
+
* Service instance
|
|
201
|
+
*/
|
|
202
|
+
async #processPendingUpdates(serviceName, serviceInstance) {
|
|
203
|
+
const serviceEntry = this.manager.services.get(serviceName);
|
|
204
|
+
if (!serviceEntry || typeof serviceEntry.config !== 'string') {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const configLabel = serviceEntry.config;
|
|
209
|
+
if (this.#pendingConfigUpdates.has(configLabel)) {
|
|
210
|
+
const pendingConfig = this.#pendingConfigUpdates.get(configLabel);
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
await this.#applyConfigToService(serviceName, serviceEntry, pendingConfig);
|
|
214
|
+
this.#pendingConfigUpdates.delete(configLabel);
|
|
215
|
+
|
|
216
|
+
this.manager.logger.info(
|
|
217
|
+
`Applied pending config update for service '${serviceName}' (label: ${configLabel})`
|
|
218
|
+
);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
this.manager.logger.debug(
|
|
221
|
+
`Could not apply pending config for service '${serviceName}': ${error.message}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Attach plugin to ServiceManager
|
|
229
|
+
*
|
|
230
|
+
* @param {import('../service-manager/ServiceManager.js').ServiceManager} manager
|
|
231
|
+
* ServiceManager instance
|
|
232
|
+
*/
|
|
233
|
+
attach(manager) {
|
|
234
|
+
if (this.manager) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Plugin '${this.name}' is already attached to a ServiceManager`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.manager = manager;
|
|
241
|
+
|
|
242
|
+
const configKeys = Object.keys(this.configObject).length;
|
|
243
|
+
|
|
244
|
+
this.manager.logger.info(
|
|
245
|
+
`ConfigPlugin attached with ${configKeys} config keys`
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Listen for service state changes to process pending updates
|
|
249
|
+
this.manager.on(SERVICE_STATE_CHANGED, async ({ service, state, instance }) => {
|
|
250
|
+
await this.#processPendingUpdates(service, instance);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Detach plugin from ServiceManager
|
|
256
|
+
*/
|
|
257
|
+
detach() {
|
|
258
|
+
if (this.manager) {
|
|
259
|
+
// Clear pending updates
|
|
260
|
+
this.#pendingConfigUpdates.clear();
|
|
261
|
+
|
|
262
|
+
this.manager.logger.info('ConfigPlugin detached');
|
|
263
|
+
this.manager = null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|