@hkdigital/lib-core 0.4.38 → 0.4.40
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/network/loaders/audio/AudioScene.svelte.d.ts +4 -11
- package/dist/network/loaders/audio/AudioScene.svelte.js +21 -15
- package/dist/network/loaders/audio/typedef.d.ts +20 -0
- package/dist/network/loaders/audio/typedef.js +13 -0
- package/dist/network/loaders/image/ImageLoader.svelte.d.ts +1 -1
- package/dist/network/loaders/image/typedef.d.ts +1 -14
- package/dist/network/loaders/image/typedef.js +1 -6
- package/dist/services/service-base/ServiceBase.d.ts +12 -12
- package/dist/services/service-base/ServiceBase.js +36 -27
- package/dist/services/service-base/typedef.d.ts +13 -0
- package/dist/services/service-base/typedef.js +8 -0
- package/dist/services/service-manager/ServiceManager.d.ts +16 -12
- package/dist/services/service-manager/ServiceManager.js +60 -31
- package/dist/services/service-manager/typedef.d.ts +4 -3
- package/dist/services/service-manager/typedef.js +5 -3
- package/package.json +1 -1
|
@@ -29,16 +29,9 @@ export default class AudioScene extends SceneBase {
|
|
|
29
29
|
* Add in-memory audio source
|
|
30
30
|
* - Uses an AudioLoader instance to load audio data from network
|
|
31
31
|
*
|
|
32
|
-
* @param {
|
|
33
|
-
* @param {string} _.label
|
|
34
|
-
* @param {string} _.url
|
|
35
|
-
* @param {SourceConfig} [_.config]
|
|
32
|
+
* @param {import('./typedef.js').MemorySourceParams} params
|
|
36
33
|
*/
|
|
37
|
-
defineMemorySource({ label, url, config }:
|
|
38
|
-
label: string;
|
|
39
|
-
url: string;
|
|
40
|
-
config?: object | undefined;
|
|
41
|
-
}): void;
|
|
34
|
+
defineMemorySource({ label, url, config }: import("./typedef.js").MemorySourceParams): void;
|
|
42
35
|
/**
|
|
43
36
|
* Get a source that can be used to play the audio once
|
|
44
37
|
*
|
|
@@ -63,9 +56,9 @@ export default class AudioScene extends SceneBase {
|
|
|
63
56
|
*/
|
|
64
57
|
setTargetGain(value: number): void;
|
|
65
58
|
/**
|
|
66
|
-
* Get the current target gain
|
|
59
|
+
* Get the current target gain
|
|
67
60
|
*
|
|
68
|
-
* @returns {number}
|
|
61
|
+
* @returns {number}
|
|
69
62
|
*/
|
|
70
63
|
getTargetGain(): number;
|
|
71
64
|
/**
|
|
@@ -25,19 +25,16 @@ export default class AudioScene extends SceneBase {
|
|
|
25
25
|
|
|
26
26
|
targetGain = $derived( this.#targetGain );
|
|
27
27
|
|
|
28
|
-
/** @type {AudioContext|
|
|
29
|
-
#audioContext
|
|
28
|
+
/** @type {AudioContext|undefined} */
|
|
29
|
+
#audioContext;
|
|
30
30
|
|
|
31
|
-
/** {GainNode} */
|
|
32
|
-
#targetGainNode
|
|
31
|
+
/** @type {GainNode|undefined} */
|
|
32
|
+
#targetGainNode;
|
|
33
33
|
|
|
34
34
|
/** @type {MemorySource[]} */
|
|
35
35
|
#memorySources = $state([]);
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
/**
|
|
39
|
-
* Construct AudioScene
|
|
40
|
-
*/
|
|
41
38
|
constructor() {
|
|
42
39
|
super();
|
|
43
40
|
}
|
|
@@ -71,10 +68,7 @@ export default class AudioScene extends SceneBase {
|
|
|
71
68
|
* Add in-memory audio source
|
|
72
69
|
* - Uses an AudioLoader instance to load audio data from network
|
|
73
70
|
*
|
|
74
|
-
* @param {
|
|
75
|
-
* @param {string} _.label
|
|
76
|
-
* @param {string} _.url
|
|
77
|
-
* @param {SourceConfig} [_.config]
|
|
71
|
+
* @param {import('./typedef.js').MemorySourceParams} params
|
|
78
72
|
*/
|
|
79
73
|
defineMemorySource({ label, url, config }) {
|
|
80
74
|
expect.notEmptyString(label);
|
|
@@ -95,8 +89,10 @@ export default class AudioScene extends SceneBase {
|
|
|
95
89
|
* @returns {Promise<AudioBufferSourceNode>}
|
|
96
90
|
*/
|
|
97
91
|
async getSourceNode(label) {
|
|
92
|
+
|
|
98
93
|
// @note Gain setup
|
|
99
|
-
// https://stackoverflow.com/
|
|
94
|
+
// https://stackoverflow.com/
|
|
95
|
+
// questions/46203191/should-i-disconnect-nodes-that-cant-be-used-anymore
|
|
100
96
|
|
|
101
97
|
const { audioLoader /*, config */ } = this.#getMemorySource(label);
|
|
102
98
|
|
|
@@ -149,9 +145,9 @@ export default class AudioScene extends SceneBase {
|
|
|
149
145
|
}
|
|
150
146
|
|
|
151
147
|
/**
|
|
152
|
-
* Get the current target gain
|
|
148
|
+
* Get the current target gain
|
|
153
149
|
*
|
|
154
|
-
* @returns {number}
|
|
150
|
+
* @returns {number}
|
|
155
151
|
*/
|
|
156
152
|
getTargetGain()
|
|
157
153
|
{
|
|
@@ -191,6 +187,11 @@ export default class AudioScene extends SceneBase {
|
|
|
191
187
|
|
|
192
188
|
/* ==== Internals */
|
|
193
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Get or create the master gain node
|
|
192
|
+
*
|
|
193
|
+
* @returns {GainNode}
|
|
194
|
+
*/
|
|
194
195
|
#getGainNode()
|
|
195
196
|
{
|
|
196
197
|
if( !this.#targetGainNode )
|
|
@@ -205,6 +206,11 @@ export default class AudioScene extends SceneBase {
|
|
|
205
206
|
return this.#targetGainNode;
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Get or create the audio context
|
|
211
|
+
*
|
|
212
|
+
* @returns {AudioContext}
|
|
213
|
+
*/
|
|
208
214
|
#getAudioContext()
|
|
209
215
|
{
|
|
210
216
|
if( !this.#audioContext )
|
|
@@ -216,7 +222,7 @@ export default class AudioScene extends SceneBase {
|
|
|
216
222
|
}
|
|
217
223
|
|
|
218
224
|
/**
|
|
219
|
-
*
|
|
225
|
+
* Find memory source by label
|
|
220
226
|
*
|
|
221
227
|
* @param {string} label
|
|
222
228
|
*
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare const _default: {};
|
|
2
|
+
export default _default;
|
|
3
|
+
export type MemorySourceParams = {
|
|
4
|
+
/**
|
|
5
|
+
* - Source identifier
|
|
6
|
+
*/
|
|
7
|
+
label: string;
|
|
8
|
+
/**
|
|
9
|
+
* - Audio file URL
|
|
10
|
+
*/
|
|
11
|
+
url: string;
|
|
12
|
+
/**
|
|
13
|
+
* - Optional source configuration
|
|
14
|
+
*/
|
|
15
|
+
config?: object | undefined;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* // property ...
|
|
19
|
+
*/
|
|
20
|
+
export type SourceConfig = object;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} MemorySourceParams
|
|
3
|
+
* @property {string} label - Source identifier
|
|
4
|
+
* @property {string} url - Audio file URL
|
|
5
|
+
* @property {SourceConfig} [config] - Optional source configuration
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} SourceConfig
|
|
10
|
+
* // property ...
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export default {};
|
|
@@ -8,7 +8,7 @@ export default class ImageLoader extends NetworkLoader {
|
|
|
8
8
|
* @param {import('../../../config/typedef.js').ImageSource} imageSource
|
|
9
9
|
*/
|
|
10
10
|
constructor(imageSource: import("../../../config/typedef.js").ImageSource);
|
|
11
|
-
get imageMeta(): import("
|
|
11
|
+
get imageMeta(): import("../../../config/typedef.js").ImageMeta;
|
|
12
12
|
get url(): string | null;
|
|
13
13
|
#private;
|
|
14
14
|
}
|
|
@@ -1,16 +1,3 @@
|
|
|
1
1
|
declare const _default: {};
|
|
2
2
|
export default _default;
|
|
3
|
-
export type ImageMeta =
|
|
4
|
-
/**
|
|
5
|
-
* - URL of the image
|
|
6
|
-
*/
|
|
7
|
-
src: string;
|
|
8
|
-
/**
|
|
9
|
-
* - Width of the image
|
|
10
|
-
*/
|
|
11
|
-
width: number;
|
|
12
|
-
/**
|
|
13
|
-
* - Height of the image
|
|
14
|
-
*/
|
|
15
|
-
height: number;
|
|
16
|
-
};
|
|
3
|
+
export type ImageMeta = import("../../../config/typedef.js").ImageMeta;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {Object} ImageMeta
|
|
3
|
-
* @property {string} src - URL of the image
|
|
4
|
-
* @property {number} width - Width of the image
|
|
5
|
-
* @property {number} height - Height of the image
|
|
6
|
-
*/
|
|
1
|
+
/** @typedef {import('../../../config/typedef.js').ImageMeta} ImageMeta */
|
|
7
2
|
|
|
8
3
|
export default {};
|
|
@@ -36,11 +36,11 @@ export class ServiceBase extends EventEmitter {
|
|
|
36
36
|
*
|
|
37
37
|
* @param {Object<string,any>|null} [config={}]
|
|
38
38
|
*
|
|
39
|
-
* @returns {Promise<
|
|
39
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
40
40
|
*/
|
|
41
41
|
configure(config?: {
|
|
42
42
|
[x: string]: any;
|
|
43
|
-
} | null): Promise<
|
|
43
|
+
} | null): Promise<import("./typedef.js").OperationResult>;
|
|
44
44
|
/**
|
|
45
45
|
* Get the last applied config
|
|
46
46
|
*
|
|
@@ -52,29 +52,29 @@ export class ServiceBase extends EventEmitter {
|
|
|
52
52
|
/**
|
|
53
53
|
* Start the service
|
|
54
54
|
*
|
|
55
|
-
* @returns {Promise<
|
|
55
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
56
56
|
*/
|
|
57
|
-
start(): Promise<
|
|
57
|
+
start(): Promise<import("./typedef.js").OperationResult>;
|
|
58
58
|
/**
|
|
59
59
|
* Stop the service with optional timeout
|
|
60
60
|
*
|
|
61
61
|
* @param {import('./typedef.js').StopOptions} [options={}] - Stop options
|
|
62
62
|
*
|
|
63
|
-
* @returns {Promise<
|
|
63
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
64
64
|
*/
|
|
65
|
-
stop(options?: import("./typedef.js").StopOptions): Promise<
|
|
65
|
+
stop(options?: import("./typedef.js").StopOptions): Promise<import("./typedef.js").OperationResult>;
|
|
66
66
|
/**
|
|
67
67
|
* Recover the service from error state
|
|
68
68
|
*
|
|
69
|
-
* @returns {Promise<
|
|
69
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
70
70
|
*/
|
|
71
|
-
recover(): Promise<
|
|
71
|
+
recover(): Promise<import("./typedef.js").OperationResult>;
|
|
72
72
|
/**
|
|
73
73
|
* Destroy the service and cleanup resources
|
|
74
74
|
*
|
|
75
|
-
* @returns {Promise<
|
|
75
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
76
76
|
*/
|
|
77
|
-
destroy(): Promise<
|
|
77
|
+
destroy(): Promise<import("./typedef.js").OperationResult>;
|
|
78
78
|
/**
|
|
79
79
|
* Get the current health status of the service
|
|
80
80
|
*
|
|
@@ -85,11 +85,11 @@ export class ServiceBase extends EventEmitter {
|
|
|
85
85
|
/**
|
|
86
86
|
* Set the service log level
|
|
87
87
|
*
|
|
88
|
-
* @param {
|
|
88
|
+
* @param {import('../../logging/typedef.js').LogLevel} level - New log level
|
|
89
89
|
*
|
|
90
90
|
* @returns {boolean} True if the level was set successfully
|
|
91
91
|
*/
|
|
92
|
-
setLogLevel(level:
|
|
92
|
+
setLogLevel(level: import("../../logging/typedef.js").LogLevel): boolean;
|
|
93
93
|
/**
|
|
94
94
|
* Configure the service (handles both initial config and reconfiguration)
|
|
95
95
|
*
|
|
@@ -130,7 +130,7 @@ export class ServiceBase extends EventEmitter {
|
|
|
130
130
|
*
|
|
131
131
|
* @param {Object<string,any>|null} [config={}]
|
|
132
132
|
*
|
|
133
|
-
* @returns {Promise<
|
|
133
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
134
134
|
*/
|
|
135
135
|
async configure(config = {}) {
|
|
136
136
|
if (
|
|
@@ -140,8 +140,9 @@ export class ServiceBase extends EventEmitter {
|
|
|
140
140
|
this.state !== STATE_STOPPED &&
|
|
141
141
|
this.state !== STATE_DESTROYED
|
|
142
142
|
) {
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
const error = new Error(`Cannot configure from state: ${this.state}`);
|
|
144
|
+
this.logger.warn(error.message);
|
|
145
|
+
return { ok: false, error };
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
const wasRunning = this.state === STATE_RUNNING;
|
|
@@ -156,10 +157,10 @@ export class ServiceBase extends EventEmitter {
|
|
|
156
157
|
|
|
157
158
|
this._setState(wasRunning ? STATE_RUNNING : STATE_CONFIGURED);
|
|
158
159
|
this.logger.info('Service configured');
|
|
159
|
-
return true;
|
|
160
|
+
return { ok: true };
|
|
160
161
|
} catch (error) {
|
|
161
162
|
this._setError('configuration', /** @type {Error} */ (error));
|
|
162
|
-
return false;
|
|
163
|
+
return { ok: false, error: this.error };
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
166
|
|
|
@@ -175,12 +176,13 @@ export class ServiceBase extends EventEmitter {
|
|
|
175
176
|
/**
|
|
176
177
|
* Start the service
|
|
177
178
|
*
|
|
178
|
-
* @returns {Promise<
|
|
179
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
179
180
|
*/
|
|
180
181
|
async start() {
|
|
181
182
|
if (this.state !== STATE_CONFIGURED && this.state !== STATE_STOPPED) {
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
const error = new Error(`Cannot start from state: ${this.state}`);
|
|
184
|
+
this.logger.warn(error.message);
|
|
185
|
+
return { ok: false, error };
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
try {
|
|
@@ -193,10 +195,10 @@ export class ServiceBase extends EventEmitter {
|
|
|
193
195
|
this._setState(STATE_RUNNING);
|
|
194
196
|
this._setHealthy(true);
|
|
195
197
|
this.logger.info('Service started');
|
|
196
|
-
return true;
|
|
198
|
+
return { ok: true };
|
|
197
199
|
} catch (error) {
|
|
198
200
|
this._setError('startup', /** @type {Error} */ (error));
|
|
199
|
-
return false;
|
|
201
|
+
return { ok: false, error: this.error };
|
|
200
202
|
}
|
|
201
203
|
}
|
|
202
204
|
|
|
@@ -205,12 +207,12 @@ export class ServiceBase extends EventEmitter {
|
|
|
205
207
|
*
|
|
206
208
|
* @param {import('./typedef.js').StopOptions} [options={}] - Stop options
|
|
207
209
|
*
|
|
208
|
-
* @returns {Promise<
|
|
210
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
209
211
|
*/
|
|
210
212
|
async stop(options = {}) {
|
|
211
213
|
if (this.state !== STATE_RUNNING && this.state !== STATE_ERROR) {
|
|
212
214
|
this.logger.warn(`Cannot stop from state: ${this.state}`);
|
|
213
|
-
return true; // Already stopped
|
|
215
|
+
return { ok: true }; // Already stopped
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
const timeout = options.timeout ?? this._shutdownTimeout;
|
|
@@ -237,7 +239,7 @@ export class ServiceBase extends EventEmitter {
|
|
|
237
239
|
|
|
238
240
|
this._setState(STATE_STOPPED);
|
|
239
241
|
this.logger.info('Service stopped');
|
|
240
|
-
return true;
|
|
242
|
+
return { ok: true };
|
|
241
243
|
} catch (error) {
|
|
242
244
|
if (
|
|
243
245
|
/** @type {Error} */ (error).message === 'Shutdown timeout' &&
|
|
@@ -245,24 +247,25 @@ export class ServiceBase extends EventEmitter {
|
|
|
245
247
|
) {
|
|
246
248
|
this.logger.warn('Forced shutdown after timeout');
|
|
247
249
|
this._setState(STATE_STOPPED);
|
|
248
|
-
return true;
|
|
250
|
+
return { ok: true };
|
|
249
251
|
}
|
|
250
252
|
this._setError('shutdown', /** @type {Error} */ (error));
|
|
251
|
-
return false;
|
|
253
|
+
return { ok: false, error: this.error };
|
|
252
254
|
}
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
/**
|
|
256
258
|
* Recover the service from error state
|
|
257
259
|
*
|
|
258
|
-
* @returns {Promise<
|
|
260
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
259
261
|
*/
|
|
260
262
|
async recover() {
|
|
261
263
|
if (this.state !== STATE_ERROR) {
|
|
262
|
-
|
|
264
|
+
const error = new Error(
|
|
263
265
|
`Can only recover from ERROR state, current: ${this.state}`
|
|
264
266
|
);
|
|
265
|
-
|
|
267
|
+
this.logger.warn(error.message);
|
|
268
|
+
return { ok: false, error };
|
|
266
269
|
}
|
|
267
270
|
|
|
268
271
|
try {
|
|
@@ -278,31 +281,37 @@ export class ServiceBase extends EventEmitter {
|
|
|
278
281
|
} else {
|
|
279
282
|
// Default: restart
|
|
280
283
|
this._setState(STATE_STOPPED);
|
|
281
|
-
await this.start();
|
|
284
|
+
const startResult = await this.start();
|
|
285
|
+
if (!startResult.ok) {
|
|
286
|
+
return startResult; // Forward the start error
|
|
287
|
+
}
|
|
282
288
|
}
|
|
283
289
|
|
|
284
290
|
this.error = null;
|
|
285
291
|
this.logger.info('Recovery successful');
|
|
286
|
-
return true;
|
|
292
|
+
return { ok: true };
|
|
287
293
|
} catch (error) {
|
|
288
294
|
this._setError('recovery', /** @type {Error} */ (error));
|
|
289
|
-
return false;
|
|
295
|
+
return { ok: false, error: this.error };
|
|
290
296
|
}
|
|
291
297
|
}
|
|
292
298
|
|
|
293
299
|
/**
|
|
294
300
|
* Destroy the service and cleanup resources
|
|
295
301
|
*
|
|
296
|
-
* @returns {Promise<
|
|
302
|
+
* @returns {Promise<import('./typedef.js').OperationResult>} Operation result
|
|
297
303
|
*/
|
|
298
304
|
async destroy() {
|
|
299
305
|
if (this.state === STATE_DESTROYED) {
|
|
300
|
-
return true;
|
|
306
|
+
return { ok: true };
|
|
301
307
|
}
|
|
302
308
|
|
|
303
309
|
try {
|
|
304
310
|
if (this.state === STATE_RUNNING) {
|
|
305
|
-
await this.stop();
|
|
311
|
+
const stopResult = await this.stop();
|
|
312
|
+
if (!stopResult.ok) {
|
|
313
|
+
return stopResult; // Forward the stop error
|
|
314
|
+
}
|
|
306
315
|
}
|
|
307
316
|
|
|
308
317
|
this._setTargetState(STATE_DESTROYED);
|
|
@@ -321,10 +330,10 @@ export class ServiceBase extends EventEmitter {
|
|
|
321
330
|
this.removeAllListeners();
|
|
322
331
|
this.logger.removeAllListeners();
|
|
323
332
|
|
|
324
|
-
return true;
|
|
333
|
+
return { ok: true };
|
|
325
334
|
} catch (error) {
|
|
326
335
|
this._setError('destruction', /** @type {Error} */ (error));
|
|
327
|
-
return false;
|
|
336
|
+
return { ok: false, error: this.error };
|
|
328
337
|
}
|
|
329
338
|
}
|
|
330
339
|
|
|
@@ -361,7 +370,7 @@ export class ServiceBase extends EventEmitter {
|
|
|
361
370
|
/**
|
|
362
371
|
* Set the service log level
|
|
363
372
|
*
|
|
364
|
-
* @param {
|
|
373
|
+
* @param {import('../../logging/typedef.js').LogLevel} level - New log level
|
|
365
374
|
*
|
|
366
375
|
* @returns {boolean} True if the level was set successfully
|
|
367
376
|
*/
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result of a service operation
|
|
3
|
+
*/
|
|
4
|
+
export type OperationResult = {
|
|
5
|
+
/**
|
|
6
|
+
* - Whether the operation succeeded
|
|
7
|
+
*/
|
|
8
|
+
ok: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* - Error details if operation failed
|
|
11
|
+
*/
|
|
12
|
+
error?: Error | undefined;
|
|
13
|
+
};
|
|
1
14
|
/**
|
|
2
15
|
* All possible service states during lifecycle management
|
|
3
16
|
*/
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
// PUBLIC TYPES
|
|
24
24
|
// ============================================================================
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Result of a service operation
|
|
28
|
+
*
|
|
29
|
+
* @typedef {Object} OperationResult
|
|
30
|
+
* @property {boolean} ok - Whether the operation succeeded
|
|
31
|
+
* @property {Error} [error] - Error details if operation failed
|
|
32
|
+
*/
|
|
33
|
+
|
|
26
34
|
/**
|
|
27
35
|
* All possible service states during lifecycle management
|
|
28
36
|
*
|
|
@@ -72,42 +72,46 @@ export class ServiceManager extends EventEmitter {
|
|
|
72
72
|
*
|
|
73
73
|
* @param {string} name - Service name
|
|
74
74
|
*
|
|
75
|
-
* @returns {Promise<
|
|
75
|
+
* @returns {Promise<import('../service-base/typedef.js').OperationResult>}
|
|
76
|
+
* Operation result
|
|
76
77
|
*/
|
|
77
|
-
configureService(name: string): Promise<
|
|
78
|
+
configureService(name: string): Promise<import("../service-base/typedef.js").OperationResult>;
|
|
78
79
|
/**
|
|
79
80
|
* Start a service and its dependencies
|
|
80
81
|
*
|
|
81
82
|
* @param {string} name - Service name
|
|
82
83
|
*
|
|
83
|
-
* @returns {Promise<
|
|
84
|
+
* @returns {Promise<import('../service-base/typedef.js').OperationResult>}
|
|
85
|
+
* Operation result
|
|
84
86
|
*/
|
|
85
|
-
startService(name: string): Promise<
|
|
87
|
+
startService(name: string): Promise<import("../service-base/typedef.js").OperationResult>;
|
|
86
88
|
/**
|
|
87
89
|
* Stop a service
|
|
88
90
|
*
|
|
89
91
|
* @param {string} name - Service name
|
|
90
92
|
* @param {StopOptions} [options={}] - Stop options
|
|
91
93
|
*
|
|
92
|
-
* @returns {Promise<
|
|
94
|
+
* @returns {Promise<import('../service-base/typedef.js').OperationResult>}
|
|
95
|
+
* Operation result
|
|
93
96
|
*/
|
|
94
|
-
stopService(name: string, options?: StopOptions): Promise<
|
|
97
|
+
stopService(name: string, options?: StopOptions): Promise<import("../service-base/typedef.js").OperationResult>;
|
|
95
98
|
/**
|
|
96
99
|
* Recover a service from error state
|
|
97
100
|
*
|
|
98
101
|
* @param {string} name - Service name
|
|
99
102
|
*
|
|
100
|
-
* @returns {Promise<
|
|
103
|
+
* @returns {Promise<import('../service-base/typedef.js').OperationResult>}
|
|
104
|
+
* Operation result
|
|
101
105
|
*/
|
|
102
|
-
recoverService(name: string): Promise<
|
|
106
|
+
recoverService(name: string): Promise<import("../service-base/typedef.js").OperationResult>;
|
|
103
107
|
/**
|
|
104
108
|
* Start all registered services in dependency order
|
|
105
109
|
*
|
|
106
|
-
* @
|
|
110
|
+
* @throws {DetailedError} if one of the services did not start
|
|
111
|
+
*
|
|
112
|
+
* @returns {Promise<string[]>} list of started service names
|
|
107
113
|
*/
|
|
108
|
-
startAll(): Promise<
|
|
109
|
-
[x: string]: boolean;
|
|
110
|
-
}>;
|
|
114
|
+
startAll(): Promise<string[]>;
|
|
111
115
|
/**
|
|
112
116
|
* Stop all services in reverse dependency order
|
|
113
117
|
*
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
|
|
66
66
|
import { EventEmitter } from '../../generic/events.js';
|
|
67
67
|
import { Logger, DEBUG, INFO } from '../../logging/index.js';
|
|
68
|
+
import { DetailedError } from '../../generic/errors.js';
|
|
68
69
|
|
|
69
70
|
import {
|
|
70
71
|
SERVICE_LOG,
|
|
@@ -265,11 +266,15 @@ export class ServiceManager extends EventEmitter {
|
|
|
265
266
|
*
|
|
266
267
|
* @param {string} name - Service name
|
|
267
268
|
*
|
|
268
|
-
* @returns {Promise<
|
|
269
|
+
* @returns {Promise<import('../service-base/typedef.js').OperationResult>}
|
|
270
|
+
* Operation result
|
|
269
271
|
*/
|
|
270
272
|
async configureService(name) {
|
|
271
273
|
const instance = this.get(name);
|
|
272
|
-
if (!instance)
|
|
274
|
+
if (!instance) {
|
|
275
|
+
const error = new Error(`Service [${name}] not found`);
|
|
276
|
+
return { ok: false, error };
|
|
277
|
+
}
|
|
273
278
|
|
|
274
279
|
const entry = this.#getServiceEntry(name);
|
|
275
280
|
|
|
@@ -283,13 +288,15 @@ export class ServiceManager extends EventEmitter {
|
|
|
283
288
|
*
|
|
284
289
|
* @param {string} name - Service name
|
|
285
290
|
*
|
|
286
|
-
* @returns {Promise<
|
|
291
|
+
* @returns {Promise<import('../service-base/typedef.js').OperationResult>}
|
|
292
|
+
* Operation result
|
|
287
293
|
*/
|
|
288
294
|
async startService(name) {
|
|
289
295
|
const entry = this.#getServiceEntry(name);
|
|
290
296
|
if (!entry) {
|
|
291
|
-
|
|
292
|
-
|
|
297
|
+
const error = new Error(`Service [${name}] not registered`);
|
|
298
|
+
this.logger.warn(error.message);
|
|
299
|
+
return { ok: false, error };
|
|
293
300
|
}
|
|
294
301
|
|
|
295
302
|
// Start dependencies first
|
|
@@ -297,19 +304,25 @@ export class ServiceManager extends EventEmitter {
|
|
|
297
304
|
if (!(await this.isRunning(dep))) {
|
|
298
305
|
this.logger.debug(`Starting dependency '${dep}' for '${name}'`);
|
|
299
306
|
|
|
300
|
-
const
|
|
307
|
+
const dependencyResult = await this.startService(dep);
|
|
301
308
|
|
|
302
|
-
if (!
|
|
303
|
-
|
|
304
|
-
|
|
309
|
+
if (!dependencyResult.ok) {
|
|
310
|
+
const error = new DetailedError(
|
|
311
|
+
`Failed to start dependency [${dep}] for service [${name}]`,
|
|
312
|
+
null,
|
|
313
|
+
dependencyResult.error
|
|
305
314
|
);
|
|
306
|
-
|
|
315
|
+
this.logger.error(error);
|
|
316
|
+
return { ok: false, error };
|
|
307
317
|
}
|
|
308
318
|
}
|
|
309
319
|
}
|
|
310
320
|
|
|
311
321
|
const instance = this.get(name);
|
|
312
|
-
if (!instance)
|
|
322
|
+
if (!instance) {
|
|
323
|
+
const error = new Error(`Service [${name}] instance not found`);
|
|
324
|
+
return { ok: false, error };
|
|
325
|
+
}
|
|
313
326
|
|
|
314
327
|
if (
|
|
315
328
|
instance.state === STATE_CREATED ||
|
|
@@ -318,10 +331,10 @@ export class ServiceManager extends EventEmitter {
|
|
|
318
331
|
// Service is not created or has been destroyed
|
|
319
332
|
// => configure needed
|
|
320
333
|
|
|
321
|
-
const
|
|
334
|
+
const configResult = await this.configureService(name);
|
|
322
335
|
|
|
323
|
-
if (!
|
|
324
|
-
return
|
|
336
|
+
if (!configResult.ok) {
|
|
337
|
+
return configResult; // Forward the configuration error
|
|
325
338
|
}
|
|
326
339
|
}
|
|
327
340
|
|
|
@@ -334,14 +347,15 @@ export class ServiceManager extends EventEmitter {
|
|
|
334
347
|
* @param {string} name - Service name
|
|
335
348
|
* @param {StopOptions} [options={}] - Stop options
|
|
336
349
|
*
|
|
337
|
-
* @returns {Promise<
|
|
350
|
+
* @returns {Promise<import('../service-base/typedef.js').OperationResult>}
|
|
351
|
+
* Operation result
|
|
338
352
|
*/
|
|
339
353
|
async stopService(name, options = {}) {
|
|
340
354
|
const instance = this.get(name);
|
|
341
355
|
|
|
342
356
|
if (!instance) {
|
|
343
357
|
this.logger.warn(`Cannot stop unregistered service '${name}'`);
|
|
344
|
-
return true; // Already stopped
|
|
358
|
+
return { ok: true }; // Already stopped
|
|
345
359
|
}
|
|
346
360
|
|
|
347
361
|
// Check dependents
|
|
@@ -356,10 +370,11 @@ export class ServiceManager extends EventEmitter {
|
|
|
356
370
|
}
|
|
357
371
|
|
|
358
372
|
if (runningDependents.length > 0) {
|
|
359
|
-
|
|
360
|
-
`Cannot stop
|
|
373
|
+
const error = new Error(
|
|
374
|
+
`Cannot stop [${name}] - required by: ${runningDependents.join(', ')}`
|
|
361
375
|
);
|
|
362
|
-
|
|
376
|
+
this.logger.warn(error.message);
|
|
377
|
+
return { ok: false, error };
|
|
363
378
|
}
|
|
364
379
|
}
|
|
365
380
|
|
|
@@ -371,11 +386,15 @@ export class ServiceManager extends EventEmitter {
|
|
|
371
386
|
*
|
|
372
387
|
* @param {string} name - Service name
|
|
373
388
|
*
|
|
374
|
-
* @returns {Promise<
|
|
389
|
+
* @returns {Promise<import('../service-base/typedef.js').OperationResult>}
|
|
390
|
+
* Operation result
|
|
375
391
|
*/
|
|
376
392
|
async recoverService(name) {
|
|
377
393
|
const instance = this.get(name);
|
|
378
|
-
if (!instance)
|
|
394
|
+
if (!instance) {
|
|
395
|
+
const error = new Error(`Service [${name}] not found`);
|
|
396
|
+
return { ok: false, error };
|
|
397
|
+
}
|
|
379
398
|
|
|
380
399
|
return await instance.recover();
|
|
381
400
|
}
|
|
@@ -383,25 +402,35 @@ export class ServiceManager extends EventEmitter {
|
|
|
383
402
|
/**
|
|
384
403
|
* Start all registered services in dependency order
|
|
385
404
|
*
|
|
386
|
-
* @
|
|
405
|
+
* @throws {DetailedError} if one of the services did not start
|
|
406
|
+
*
|
|
407
|
+
* @returns {Promise<string[]>} list of started service names
|
|
387
408
|
*/
|
|
388
409
|
async startAll() {
|
|
389
410
|
this.logger.info('Starting all services');
|
|
390
411
|
|
|
391
412
|
// Sort by priority and dependencies
|
|
392
413
|
const sorted = this.#topologicalSort();
|
|
393
|
-
const results = new Map();
|
|
394
414
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
results.set(name, success);
|
|
415
|
+
/** @type {string[]} */
|
|
416
|
+
const startedServiceNames = [];
|
|
398
417
|
|
|
399
|
-
|
|
400
|
-
|
|
418
|
+
for (const name of sorted) {
|
|
419
|
+
const result = await this.startService(name);
|
|
420
|
+
startedServiceNames.push(name);
|
|
421
|
+
|
|
422
|
+
if (!result.ok) {
|
|
423
|
+
// Create detailed error with the actual service failure
|
|
424
|
+
const detailedError = new DetailedError(
|
|
425
|
+
`Failed to start service [${name}]`,
|
|
426
|
+
null,
|
|
427
|
+
result.error
|
|
428
|
+
);
|
|
429
|
+
throw detailedError;
|
|
401
430
|
}
|
|
402
431
|
}
|
|
403
432
|
|
|
404
|
-
return
|
|
433
|
+
return startedServiceNames;
|
|
405
434
|
}
|
|
406
435
|
|
|
407
436
|
/**
|
|
@@ -729,8 +758,8 @@ export class ServiceManager extends EventEmitter {
|
|
|
729
758
|
async #stopAllSequentially(serviceNames, results, options) {
|
|
730
759
|
for (const name of serviceNames) {
|
|
731
760
|
try {
|
|
732
|
-
const
|
|
733
|
-
results.set(name,
|
|
761
|
+
const result = await this.stopService(name, options);
|
|
762
|
+
results.set(name, result.ok);
|
|
734
763
|
} catch (error) {
|
|
735
764
|
this.logger.error(
|
|
736
765
|
`Error stopping '${name}'`,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type LogLevel = import("../../logging/typedef.js").LogLevel;
|
|
1
2
|
/**
|
|
2
3
|
* Service configuration - either a config object or a config label string
|
|
3
4
|
*/
|
|
@@ -40,18 +41,18 @@ export type ServiceManagerConfig = {
|
|
|
40
41
|
/**
|
|
41
42
|
* - Default log level for new services
|
|
42
43
|
*/
|
|
43
|
-
defaultLogLevel?:
|
|
44
|
+
defaultLogLevel?: import("../../logging/typedef.js").LogLevel | undefined;
|
|
44
45
|
/**
|
|
45
46
|
* - Initial log level for ServiceManager
|
|
46
47
|
*/
|
|
47
|
-
managerLogLevel?:
|
|
48
|
+
managerLogLevel?: import("../../logging/typedef.js").LogLevel | undefined;
|
|
48
49
|
/**
|
|
49
50
|
* Per-service log levels:
|
|
50
51
|
* - String: "auth:debug,database:info"
|
|
51
52
|
* - Object: { auth: "debug", database: "info" }
|
|
52
53
|
*/
|
|
53
54
|
serviceLogLevels?: string | {
|
|
54
|
-
[x: string]:
|
|
55
|
+
[x: string]: import("../../logging/typedef.js").LogLevel;
|
|
55
56
|
} | undefined;
|
|
56
57
|
};
|
|
57
58
|
/**
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
* manager.register('auth', AuthService, {}, options);
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
|
+
/** @typedef {import('../../logging/typedef.js').LogLevel} LogLevel */
|
|
30
|
+
|
|
29
31
|
// ============================================================================
|
|
30
32
|
// PUBLIC TYPES
|
|
31
33
|
// ============================================================================
|
|
@@ -52,9 +54,9 @@
|
|
|
52
54
|
* @property {boolean} [debug=false] - Debug mode switch
|
|
53
55
|
* @property {boolean} [autoStart=false] - Auto-start services on registration
|
|
54
56
|
* @property {number} [stopTimeout=10000] - Default timeout for stopping services
|
|
55
|
-
* @property {
|
|
56
|
-
* @property {
|
|
57
|
-
* @property {string|Object<string,
|
|
57
|
+
* @property {LogLevel} [defaultLogLevel] - Default log level for new services
|
|
58
|
+
* @property {LogLevel} [managerLogLevel] - Initial log level for ServiceManager
|
|
59
|
+
* @property {string|Object<string,LogLevel>} [serviceLogLevels]
|
|
58
60
|
* Per-service log levels:
|
|
59
61
|
* - String: "auth:debug,database:info"
|
|
60
62
|
* - Object: { auth: "debug", database: "info" }
|