@hkdigital/lib-sveltekit 0.2.21 → 0.2.22
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/README.md +149 -135
- package/dist/assets/autospuiten/car-paint-picker.js +41 -41
- package/dist/assets/autospuiten/labels.js +7 -7
- package/dist/classes/cache/IndexedDbCache.js +1407 -1407
- package/dist/classes/cache/MemoryResponseCache.js +138 -138
- package/dist/classes/cache/index.js +5 -5
- package/dist/classes/cache/typedef.js +41 -41
- package/dist/classes/data/IterableTree.js +243 -243
- package/dist/classes/data/Selector.js +190 -190
- package/dist/classes/data/index.js +2 -2
- package/dist/classes/events/EventEmitter.js +275 -275
- package/dist/classes/events/index.js +2 -2
- package/dist/classes/index.js +4 -4
- package/dist/classes/logging/Logger.js +210 -210
- package/dist/classes/logging/constants.js +16 -16
- package/dist/classes/logging/index.js +4 -4
- package/dist/classes/logging/typedef.js +17 -17
- package/dist/classes/promise/HkPromise.js +377 -377
- package/dist/classes/promise/index.js +1 -1
- package/dist/classes/services/ServiceBase.js +463 -463
- package/dist/classes/services/ServiceManager.js +614 -614
- package/dist/classes/services/index.js +5 -5
- package/dist/classes/services/service-states.js +205 -205
- package/dist/classes/services/typedef.js +179 -179
- package/dist/classes/stores/SubscribersCount.js +107 -107
- package/dist/classes/stores/index.js +1 -1
- package/dist/classes/streams/LogTransformStream.js +19 -19
- package/dist/classes/streams/ServerEventsStore.js +110 -110
- package/dist/classes/streams/TimeStampSource.js +26 -26
- package/dist/classes/streams/index.js +3 -3
- package/dist/classes/svelte/audio/AudioLoader.svelte.js +58 -58
- package/dist/classes/svelte/audio/AudioScene.svelte.js +324 -324
- package/dist/classes/svelte/audio/mocks.js +35 -35
- package/dist/classes/svelte/finite-state-machine/FiniteStateMachine.svelte.js +133 -133
- package/dist/classes/svelte/finite-state-machine/index.js +1 -1
- package/dist/classes/svelte/image/ImageLoader.svelte.js +45 -45
- package/dist/classes/svelte/image/ImageScene.svelte.js +249 -249
- package/dist/classes/svelte/image/ImageVariantsLoader.svelte.js +152 -152
- package/dist/classes/svelte/image/index.js +4 -4
- package/dist/classes/svelte/image/mocks.js +35 -35
- package/dist/classes/svelte/image/typedef.js +8 -8
- package/dist/classes/svelte/index.js +14 -14
- package/dist/classes/svelte/loading-state-machine/LoadingStateMachine.svelte.js +109 -109
- package/dist/classes/svelte/loading-state-machine/constants.js +16 -16
- package/dist/classes/svelte/loading-state-machine/index.js +3 -3
- package/dist/classes/svelte/network-loader/NetworkLoader.svelte.js +338 -338
- package/dist/classes/svelte/network-loader/constants.js +3 -3
- package/dist/classes/svelte/network-loader/index.js +3 -3
- package/dist/classes/svelte/network-loader/mocks.js +30 -30
- package/dist/classes/svelte/network-loader/typedef.js +8 -8
- package/dist/components/area/HkArea.svelte +49 -49
- package/dist/components/area/HkGridArea.svelte +77 -77
- package/dist/components/area/index.js +2 -2
- package/dist/components/buttons/button/Button.svelte +82 -82
- package/dist/components/buttons/button-icon-steeze/SteezeIconButton.svelte +30 -30
- package/dist/components/buttons/button-text/TextButton.svelte +21 -21
- package/dist/components/buttons/index.js +3 -3
- package/dist/components/debug/debug-panel-design-scaling/DebugPanelDesignScaling.svelte +146 -146
- package/dist/components/debug/index.js +1 -1
- package/dist/components/drag-drop/DragController.js +44 -44
- package/dist/components/drag-drop/DragDropContext.svelte +111 -111
- package/dist/components/drag-drop/Draggable.svelte +519 -519
- package/dist/components/drag-drop/{Dropzone.svelte → DropZone.svelte} +258 -258
- package/dist/components/drag-drop/DropZoneArea.svelte +119 -119
- package/dist/components/drag-drop/DropZoneList.svelte +125 -125
- package/dist/components/drag-drop/actions.js +26 -26
- package/dist/components/drag-drop/drag-state.svelte.js +322 -322
- package/dist/components/drag-drop/index.js +7 -7
- package/dist/components/drag-drop/util.js +85 -85
- package/dist/components/hkdev/blocks/TextBlock.svelte +46 -46
- package/dist/components/hkdev/buttons/CheckButton.svelte +62 -62
- package/dist/components/icons/HkIcon.svelte +86 -86
- package/dist/components/icons/HkTabIcon.svelte +116 -116
- package/dist/components/icons/SteezeIcon.svelte +97 -97
- package/dist/components/icons/index.js +6 -6
- package/dist/components/icons/typedef.js +16 -16
- package/dist/components/index.js +2 -2
- package/dist/components/inputs/index.js +1 -1
- package/dist/components/inputs/text-input/TestTextInput.svelte__ +102 -102
- package/dist/components/inputs/text-input/TextInput.svelte +223 -223
- package/dist/components/inputs/text-input/TextInput.svelte___ +83 -83
- package/dist/components/inputs/text-input/assets/IconInvalid.svelte +14 -14
- package/dist/components/inputs/text-input/assets/IconValid.svelte +12 -12
- package/dist/components/layout/grid-layers/GridLayers.svelte +63 -63
- package/dist/components/layout/grid-layers/GridLayers.svelte__heightFrom__ +372 -0
- package/dist/components/layout/grid-layers/util.js +74 -74
- package/dist/components/layout/index.js +1 -1
- package/dist/components/panels/index.js +1 -1
- package/dist/components/panels/panel/Panel.svelte +43 -43
- package/dist/components/rows/index.js +3 -3
- package/dist/components/rows/panel-grid-row/PanelGridRow.svelte +104 -104
- package/dist/components/rows/panel-row-2/PanelRow2.svelte +40 -40
- package/dist/components/tab-bar/HkTabBar.state.svelte.js +149 -149
- package/dist/components/tab-bar/HkTabBar.svelte +74 -74
- package/dist/components/tab-bar/HkTabBarSelector.state.svelte.js +93 -93
- package/dist/components/tab-bar/HkTabBarSelector.svelte +49 -49
- package/dist/components/tab-bar/index.js +17 -17
- package/dist/components/tab-bar/typedef.js +11 -11
- package/dist/config/imagetools-config.js +189 -189
- package/dist/config/imagetools.d.ts +72 -72
- package/dist/constants/bases.js +13 -13
- package/dist/constants/errors/api.js +9 -9
- package/dist/constants/errors/generic.js +5 -5
- package/dist/constants/errors/index.js +3 -3
- package/dist/constants/errors/jwt.js +5 -5
- package/dist/constants/http/headers.js +6 -6
- package/dist/constants/http/index.js +2 -2
- package/dist/constants/http/methods.js +14 -14
- package/dist/constants/index.js +3 -3
- package/dist/constants/mime/application.js +5 -5
- package/dist/constants/mime/audio.js +13 -13
- package/dist/constants/mime/image.js +3 -3
- package/dist/constants/mime/index.js +4 -4
- package/dist/constants/mime/text.js +2 -2
- package/dist/constants/regexp/index.js +31 -31
- package/dist/constants/regexp/inspiratie.js__ +95 -95
- package/dist/constants/regexp/text.js +49 -49
- package/dist/constants/regexp/user.js +32 -32
- package/dist/constants/regexp/web.js +3 -3
- package/dist/constants/state-labels/drag-states.js +6 -6
- package/dist/constants/state-labels/drop-states.js +6 -6
- package/dist/constants/state-labels/input-states.js +11 -11
- package/dist/constants/state-labels/submit-states.js +4 -4
- package/dist/constants/time.js +28 -28
- package/dist/css/utilities.css +43 -43
- package/dist/design/design-config.js +73 -73
- package/dist/design/tailwind-theme-extend.js +158 -158
- package/dist/features/button-group/ButtonGroup.svelte +82 -82
- package/dist/features/button-group/typedef.js +10 -10
- package/dist/features/compare-left-right/CompareLeftRight.svelte +179 -179
- package/dist/features/compare-left-right/index.js +1 -1
- package/dist/features/game-box/GameBox.svelte +577 -577
- package/dist/features/game-box/gamebox.util.js +83 -83
- package/dist/features/hk-app-layout/HkAppLayout.state.svelte.js +25 -25
- package/dist/features/hk-app-layout/HkAppLayout.svelte +251 -251
- package/dist/features/image-box/ImageBox.svelte +210 -210
- package/dist/features/image-box/index.js +5 -5
- package/dist/features/image-box/typedef.js +32 -32
- package/dist/features/index.js +23 -23
- package/dist/features/presenter/ImageSlide.svelte +64 -64
- package/dist/features/presenter/Presenter.state.svelte.js +638 -638
- package/dist/features/presenter/Presenter.svelte +142 -142
- package/dist/features/presenter/constants.js +7 -7
- package/dist/features/presenter/index.js +10 -10
- package/dist/features/presenter/typedef.js +106 -106
- package/dist/features/presenter/util.js +210 -210
- package/dist/features/virtual-viewport/VirtualViewport.svelte +196 -196
- package/dist/logging/adapters/console.js +114 -114
- package/dist/logging/adapters/pino.js +60 -60
- package/dist/logging/constants.js +1 -1
- package/dist/logging/factories/client.js +21 -21
- package/dist/logging/factories/server.js +22 -22
- package/dist/logging/factories/universal.js +23 -23
- package/dist/logging/index.js +8 -8
- package/dist/schemas/index.js +1 -1
- package/dist/schemas/validate-url.js +180 -180
- package/dist/server/index.js +1 -1
- package/dist/server/logger.js +94 -94
- package/dist/states/index.js +1 -1
- package/dist/states/navigation.svelte.js +55 -55
- package/dist/stores/index.js +1 -1
- package/dist/stores/theme.js +80 -80
- package/dist/themes/hkdev/components/blocks/text-block.css +34 -34
- package/dist/themes/hkdev/components/boxes/game-box.css +11 -11
- package/dist/themes/hkdev/components/buttons/button-icon-steeze.css +22 -22
- package/dist/themes/hkdev/components/buttons/button-text.css +32 -32
- package/dist/themes/hkdev/components/buttons/button.css +146 -146
- package/dist/themes/hkdev/components/buttons/skip-button.css +5 -5
- package/dist/themes/hkdev/components/drag-drop/draggable.css +73 -73
- package/dist/themes/hkdev/components/drag-drop/drop-zone.css +58 -58
- package/dist/themes/hkdev/components/icons/icon-steeze.css +15 -15
- package/dist/themes/hkdev/components/inputs/text-input.css +102 -102
- package/dist/themes/hkdev/components/panels/panel.css +25 -25
- package/dist/themes/hkdev/components/rows/panel-grid-row.css +4 -4
- package/dist/themes/hkdev/components/rows/panel-row-2.css +5 -5
- package/dist/themes/hkdev/components.css +29 -29
- package/dist/themes/hkdev/debug.css +1 -1
- package/dist/themes/hkdev/global/layout.css +32 -32
- package/dist/themes/hkdev/global/on-colors.css +32 -32
- package/dist/themes/hkdev/globals.css +3 -3
- package/dist/themes/hkdev/responsive.css +12 -12
- package/dist/themes/hkdev/theme-ext.js +12 -12
- package/dist/themes/hkdev/theme.css +218 -218
- package/dist/themes/index.js +1 -1
- package/dist/typedef/context.js +6 -6
- package/dist/typedef/drag.js +25 -25
- package/dist/typedef/drop.js +12 -12
- package/dist/typedef/image.js +38 -38
- package/dist/typedef/index.js +4 -4
- package/dist/util/array/index.js +436 -436
- package/dist/util/bases/base58.js +262 -262
- package/dist/util/bases/index.js +1 -1
- package/dist/util/compare/index.js +247 -247
- package/dist/util/css/css-vars.js +83 -83
- package/dist/util/css/index.js +1 -1
- package/dist/util/design-system/components/states.js +22 -22
- package/dist/util/design-system/css/clamp.js +66 -66
- package/dist/util/design-system/css/root-design-vars.js +102 -102
- package/dist/util/design-system/index.js +5 -5
- package/dist/util/design-system/layout/scaling.js +228 -228
- package/dist/util/design-system/skeleton.js +208 -208
- package/dist/util/design-system/tailwind.js +288 -288
- package/dist/util/env/index.js +9 -9
- package/dist/util/exceptions/index.d.ts +11 -0
- package/dist/util/exceptions/index.js +17 -0
- package/dist/util/expect/arrays.js +47 -47
- package/dist/util/expect/index.js +259 -259
- package/dist/util/expect/primitives.js +55 -55
- package/dist/util/expect/url.js +60 -60
- package/dist/util/function/index.js +218 -218
- package/dist/util/geo/index.js +26 -26
- package/dist/util/http/caching.js +263 -263
- package/dist/util/http/errors.js +97 -97
- package/dist/util/http/headers.js +75 -75
- package/dist/util/http/http-request.js +578 -578
- package/dist/util/http/index.js +22 -22
- package/dist/util/http/json-request.js +224 -224
- package/dist/util/http/mocks.js +65 -65
- package/dist/util/http/response.js +294 -294
- package/dist/util/http/test-data__/content-length-test-hkdigital-small.V4HfZyBQ.avif +0 -0
- package/dist/util/http/typedef.js +93 -93
- package/dist/util/http/url.js +52 -52
- package/dist/util/image/index.js +86 -86
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +3 -2
- package/dist/util/is/index.js +140 -140
- package/dist/util/iterate/index.js +234 -234
- package/dist/util/object/index.js +1361 -1361
- package/dist/util/singleton/index.js +97 -97
- package/dist/util/string/array-path.js +75 -75
- package/dist/util/string/convert.js +54 -54
- package/dist/util/string/fs.js +226 -226
- package/dist/util/string/index.js +5 -5
- package/dist/util/string/interpolate.js +61 -61
- package/dist/util/string/pad.js +10 -10
- package/dist/util/svelte/index.js +4 -4
- package/dist/util/svelte/loading/loading-tracker.svelte.js +108 -108
- package/dist/util/svelte/observe/index.js +49 -49
- package/dist/util/svelte/state-context/index.js +117 -117
- package/dist/util/svelte/wait/index.js +38 -38
- package/dist/util/sveltekit/index.js +1 -1
- package/dist/util/sveltekit/route-folders/index.js +101 -101
- package/dist/util/time/index.js +323 -323
- package/dist/util/unique/index.js +249 -249
- package/dist/valibot/date.js__ +10 -10
- package/dist/valibot/index.js +9 -9
- package/dist/valibot/url.js +95 -95
- package/dist/valibot/user.js +23 -23
- package/dist/zod/all.js +33 -33
- package/dist/zod/generic.js +11 -11
- package/dist/zod/javascript.js +32 -32
- package/dist/zod/user.js +16 -16
- package/dist/zod/web.js +52 -52
- package/package.json +133 -132
@@ -1,463 +1,463 @@
|
|
1
|
-
/**
|
2
|
-
* @fileoverview Base service class with lifecycle management, health checks,
|
3
|
-
* and integrated logging.
|
4
|
-
*
|
5
|
-
* ServiceBase provides a standardized lifecycle for all services with states,
|
6
|
-
* events, logging, and error handling. Services extend this class and override
|
7
|
-
* the protected methods to implement their specific functionality.
|
8
|
-
*
|
9
|
-
* @example
|
10
|
-
* // Basic service implementation
|
11
|
-
* import { ServiceBase } from './ServiceBase.js';
|
12
|
-
*
|
13
|
-
* class DatabaseService extends ServiceBase {
|
14
|
-
* async _init(config) {
|
15
|
-
* this.connectionString = config.connectionString;
|
16
|
-
* }
|
17
|
-
*
|
18
|
-
* async _start() {
|
19
|
-
* this.connection = await createConnection(this.connectionString);
|
20
|
-
* }
|
21
|
-
*
|
22
|
-
* async _stop() {
|
23
|
-
* await this.connection?.close();
|
24
|
-
* }
|
25
|
-
* }
|
26
|
-
*
|
27
|
-
* // Usage
|
28
|
-
* const db = new DatabaseService('database');
|
29
|
-
* await db.initialize({ connectionString: 'postgres://...' });
|
30
|
-
* await db.start();
|
31
|
-
*
|
32
|
-
* // Listen to events
|
33
|
-
* db.on('healthChanged', ({ healthy }) => {
|
34
|
-
* console.log(`Database is ${healthy ? 'healthy' : 'unhealthy'}`);
|
35
|
-
* });
|
36
|
-
*
|
37
|
-
* @example
|
38
|
-
* // Service with recovery and health checks
|
39
|
-
* class ApiService extends ServiceBase {
|
40
|
-
* async _recover() {
|
41
|
-
* // Custom recovery logic
|
42
|
-
* await this.reconnect();
|
43
|
-
* }
|
44
|
-
*
|
45
|
-
* async _healthCheck() {
|
46
|
-
* const start = Date.now();
|
47
|
-
* await this.ping();
|
48
|
-
* return { latency: Date.now() - start };
|
49
|
-
* }
|
50
|
-
* }
|
51
|
-
*/
|
52
|
-
|
53
|
-
import { EventEmitter } from '../events';
|
54
|
-
import { Logger, INFO } from '../logging';
|
55
|
-
|
56
|
-
import {
|
57
|
-
CREATED,
|
58
|
-
INITIALIZING,
|
59
|
-
INITIALIZED,
|
60
|
-
STARTING,
|
61
|
-
RUNNING,
|
62
|
-
STOPPING,
|
63
|
-
STOPPED,
|
64
|
-
DESTROYING,
|
65
|
-
DESTROYED,
|
66
|
-
ERROR as ERROR_STATE,
|
67
|
-
RECOVERING
|
68
|
-
} from './service-states.js';
|
69
|
-
|
70
|
-
/**
|
71
|
-
* @typedef {import('./typedef.js').ServiceConfig} ServiceConfig
|
72
|
-
* @typedef {import('./typedef.js').ServiceOptions} ServiceOptions
|
73
|
-
* @typedef {import('./typedef.js').StopOptions} StopOptions
|
74
|
-
* @typedef {import('./typedef.js').HealthStatus} HealthStatus
|
75
|
-
* @typedef {import('./typedef.js').StateChangeEvent} StateChangeEvent
|
76
|
-
* @typedef {import('./typedef.js').HealthChangeEvent} HealthChangeEvent
|
77
|
-
* @typedef {import('./typedef.js').ServiceErrorEvent} ServiceErrorEvent
|
78
|
-
*/
|
79
|
-
|
80
|
-
/**
|
81
|
-
* Base class for all services with lifecycle management
|
82
|
-
* @extends EventEmitter
|
83
|
-
*/
|
84
|
-
export class ServiceBase extends EventEmitter {
|
85
|
-
/**
|
86
|
-
* Create a new service instance
|
87
|
-
*
|
88
|
-
* @param {string} name - Service name
|
89
|
-
* @param {ServiceOptions} [options={}] - Service options
|
90
|
-
*/
|
91
|
-
constructor(name, options = {}) {
|
92
|
-
super();
|
93
|
-
|
94
|
-
/** @type {string} */
|
95
|
-
this.name = name;
|
96
|
-
|
97
|
-
/** @type {string} */
|
98
|
-
this.state = CREATED;
|
99
|
-
|
100
|
-
/** @type {boolean} */
|
101
|
-
this.healthy = false;
|
102
|
-
|
103
|
-
/** @type {Error|null} */
|
104
|
-
this.error = null;
|
105
|
-
|
106
|
-
/** @type {Logger} */
|
107
|
-
this.logger = new Logger(name, options.logLevel || INFO);
|
108
|
-
|
109
|
-
/** @private @type {number} */
|
110
|
-
this._shutdownTimeout = options.shutdownTimeout || 5000;
|
111
|
-
}
|
112
|
-
|
113
|
-
/**
|
114
|
-
* Initialize the service with configuration
|
115
|
-
*
|
116
|
-
* @param {ServiceConfig} [config={}] - Service-specific configuration
|
117
|
-
*
|
118
|
-
* @returns {Promise<boolean>} True if initialization succeeded
|
119
|
-
*/
|
120
|
-
async initialize(config = {}) {
|
121
|
-
if (this.state !== CREATED &&
|
122
|
-
this.state !== STOPPED &&
|
123
|
-
this.state !== DESTROYED) {
|
124
|
-
this.logger.warn(`Cannot initialize from state: ${this.state}`);
|
125
|
-
return false;
|
126
|
-
}
|
127
|
-
|
128
|
-
try {
|
129
|
-
this._setState(INITIALIZING);
|
130
|
-
this.logger.debug('Initializing service', { config });
|
131
|
-
|
132
|
-
await this._init(config);
|
133
|
-
|
134
|
-
this._setState(INITIALIZED);
|
135
|
-
this.logger.info('Service initialized');
|
136
|
-
return true;
|
137
|
-
} catch (error) {
|
138
|
-
this._setError('initialization', error);
|
139
|
-
return false;
|
140
|
-
}
|
141
|
-
}
|
142
|
-
|
143
|
-
/**
|
144
|
-
* Start the service
|
145
|
-
*
|
146
|
-
* @returns {Promise<boolean>} True if the service started successfully
|
147
|
-
*/
|
148
|
-
async start() {
|
149
|
-
if (this.state !== INITIALIZED && this.state !== STOPPED) {
|
150
|
-
this.logger.warn(`Cannot start from state: ${this.state}`);
|
151
|
-
return false;
|
152
|
-
}
|
153
|
-
|
154
|
-
try {
|
155
|
-
this._setState(STARTING);
|
156
|
-
this.logger.debug('Starting service');
|
157
|
-
|
158
|
-
await this._start();
|
159
|
-
|
160
|
-
this._setState(RUNNING);
|
161
|
-
this._setHealthy(true);
|
162
|
-
this.logger.info('Service started');
|
163
|
-
return true;
|
164
|
-
} catch (error) {
|
165
|
-
this._setError('startup', error);
|
166
|
-
return false;
|
167
|
-
}
|
168
|
-
}
|
169
|
-
|
170
|
-
/**
|
171
|
-
* Stop the service with optional timeout
|
172
|
-
*
|
173
|
-
* @param {StopOptions} [options={}] - Stop options
|
174
|
-
*
|
175
|
-
* @returns {Promise<boolean>} True if the service stopped successfully
|
176
|
-
*/
|
177
|
-
async stop(options = {}) {
|
178
|
-
if (this.state !== RUNNING && this.state !== ERROR_STATE) {
|
179
|
-
this.logger.warn(`Cannot stop from state: ${this.state}`);
|
180
|
-
return true; // Already stopped
|
181
|
-
}
|
182
|
-
|
183
|
-
const timeout = options.timeout ?? this._shutdownTimeout;
|
184
|
-
|
185
|
-
try {
|
186
|
-
this._setState(STOPPING);
|
187
|
-
this._setHealthy(false);
|
188
|
-
this.logger.debug('Stopping service');
|
189
|
-
|
190
|
-
// Wrap _stop in a timeout
|
191
|
-
const stopPromise = this._stop();
|
192
|
-
|
193
|
-
if (timeout > 0) {
|
194
|
-
await Promise.race([
|
195
|
-
stopPromise,
|
196
|
-
new Promise((_, reject) =>
|
197
|
-
setTimeout(() => reject(new Error('Shutdown timeout')), timeout)
|
198
|
-
)
|
199
|
-
]);
|
200
|
-
} else {
|
201
|
-
await stopPromise;
|
202
|
-
}
|
203
|
-
|
204
|
-
this._setState(STOPPED);
|
205
|
-
this.logger.info('Service stopped');
|
206
|
-
return true;
|
207
|
-
} catch (error) {
|
208
|
-
if (error.message === 'Shutdown timeout' && options.force) {
|
209
|
-
this.logger.warn('Forced shutdown after timeout');
|
210
|
-
this._setState(STOPPED);
|
211
|
-
return true;
|
212
|
-
}
|
213
|
-
this._setError('shutdown', error);
|
214
|
-
return false;
|
215
|
-
}
|
216
|
-
}
|
217
|
-
|
218
|
-
/**
|
219
|
-
* Recover the service from error state
|
220
|
-
*
|
221
|
-
* @returns {Promise<boolean>} True if recovery succeeded
|
222
|
-
*/
|
223
|
-
async recover() {
|
224
|
-
if (this.state !== ERROR_STATE) {
|
225
|
-
this.logger.warn(
|
226
|
-
`Can only recover from ERROR state, current: ${this.state}`
|
227
|
-
);
|
228
|
-
return false;
|
229
|
-
}
|
230
|
-
|
231
|
-
try {
|
232
|
-
this._setState(RECOVERING);
|
233
|
-
this.logger.info('Attempting recovery');
|
234
|
-
|
235
|
-
// Try custom recovery first
|
236
|
-
if (this._recover) {
|
237
|
-
await this._recover();
|
238
|
-
this._setState(RUNNING);
|
239
|
-
this._setHealthy(true);
|
240
|
-
} else {
|
241
|
-
// Default: restart
|
242
|
-
this._setState(STOPPED);
|
243
|
-
await this.start();
|
244
|
-
}
|
245
|
-
|
246
|
-
this.error = null;
|
247
|
-
this.logger.info('Recovery successful');
|
248
|
-
return true;
|
249
|
-
} catch (error) {
|
250
|
-
this._setError('recovery', error);
|
251
|
-
return false;
|
252
|
-
}
|
253
|
-
}
|
254
|
-
|
255
|
-
/**
|
256
|
-
* Destroy the service and cleanup resources
|
257
|
-
*
|
258
|
-
* @returns {Promise<boolean>} True if destruction succeeded
|
259
|
-
*/
|
260
|
-
async destroy() {
|
261
|
-
if (this.state === DESTROYED) {
|
262
|
-
return true;
|
263
|
-
}
|
264
|
-
|
265
|
-
try {
|
266
|
-
if (this.state === RUNNING) {
|
267
|
-
await this.stop();
|
268
|
-
}
|
269
|
-
|
270
|
-
this._setState(DESTROYING);
|
271
|
-
this.logger.debug('Destroying service');
|
272
|
-
|
273
|
-
if (this._destroy) {
|
274
|
-
await this._destroy();
|
275
|
-
}
|
276
|
-
|
277
|
-
this._setState(DESTROYED);
|
278
|
-
this._setHealthy(false);
|
279
|
-
this.logger.info('Service destroyed');
|
280
|
-
|
281
|
-
// Cleanup
|
282
|
-
this.removeAllListeners();
|
283
|
-
this.logger.removeAllListeners();
|
284
|
-
|
285
|
-
return true;
|
286
|
-
} catch (error) {
|
287
|
-
this._setError('destruction', error);
|
288
|
-
return false;
|
289
|
-
}
|
290
|
-
}
|
291
|
-
|
292
|
-
/**
|
293
|
-
* Get the current health status of the service
|
294
|
-
*
|
295
|
-
* @returns {Promise<HealthStatus>} Health status object
|
296
|
-
*/
|
297
|
-
async getHealth() {
|
298
|
-
const baseHealth = {
|
299
|
-
name: this.name,
|
300
|
-
state: this.state,
|
301
|
-
healthy: this.healthy,
|
302
|
-
error: this.error?.message
|
303
|
-
};
|
304
|
-
|
305
|
-
if (this._healthCheck) {
|
306
|
-
try {
|
307
|
-
const customHealth = await this._healthCheck();
|
308
|
-
return { ...baseHealth, ...customHealth };
|
309
|
-
} catch (error) {
|
310
|
-
return {
|
311
|
-
...baseHealth,
|
312
|
-
healthy: false,
|
313
|
-
checkError: error.message
|
314
|
-
};
|
315
|
-
}
|
316
|
-
}
|
317
|
-
|
318
|
-
return baseHealth;
|
319
|
-
}
|
320
|
-
|
321
|
-
/**
|
322
|
-
* Set the service log level
|
323
|
-
*
|
324
|
-
* @param {string} level - New log level
|
325
|
-
*
|
326
|
-
* @returns {boolean} True if the level was set successfully
|
327
|
-
*/
|
328
|
-
setLogLevel(level) {
|
329
|
-
return this.logger.setLevel(level);
|
330
|
-
}
|
331
|
-
|
332
|
-
// Protected methods to override in subclasses
|
333
|
-
|
334
|
-
/**
|
335
|
-
* Initialize the service (override in subclass)
|
336
|
-
*
|
337
|
-
* @protected
|
338
|
-
* @param {ServiceConfig} config - Service configuration
|
339
|
-
*
|
340
|
-
* @returns {Promise<void>}
|
341
|
-
*/
|
342
|
-
async _init(config) {
|
343
|
-
// Override in subclass
|
344
|
-
}
|
345
|
-
|
346
|
-
/**
|
347
|
-
* Start the service (override in subclass)
|
348
|
-
*
|
349
|
-
* @protected
|
350
|
-
*
|
351
|
-
* @returns {Promise<void>}
|
352
|
-
*/
|
353
|
-
async _start() {
|
354
|
-
// Override in subclass
|
355
|
-
}
|
356
|
-
|
357
|
-
/**
|
358
|
-
* Stop the service (override in subclass)
|
359
|
-
*
|
360
|
-
* @protected
|
361
|
-
*
|
362
|
-
* @returns {Promise<void>}
|
363
|
-
*/
|
364
|
-
async _stop() {
|
365
|
-
// Override in subclass
|
366
|
-
}
|
367
|
-
|
368
|
-
/**
|
369
|
-
* Destroy the service (optional override)
|
370
|
-
*
|
371
|
-
* @protected
|
372
|
-
*
|
373
|
-
* @returns {Promise<void>}
|
374
|
-
*/
|
375
|
-
async _destroy() {
|
376
|
-
// Override in subclass if needed
|
377
|
-
}
|
378
|
-
|
379
|
-
/**
|
380
|
-
* Recover from error state (optional override)
|
381
|
-
*
|
382
|
-
* @protected
|
383
|
-
*
|
384
|
-
* @returns {Promise<void>}
|
385
|
-
*/
|
386
|
-
async _recover() {
|
387
|
-
// Override in subclass if custom recovery needed
|
388
|
-
// Default behavior is stop + start
|
389
|
-
}
|
390
|
-
|
391
|
-
/**
|
392
|
-
* Perform health check (optional override)
|
393
|
-
*
|
394
|
-
* @protected
|
395
|
-
*
|
396
|
-
* @returns {Promise<Object>} Additional health information
|
397
|
-
*/
|
398
|
-
async _healthCheck() {
|
399
|
-
// Override in subclass if health checks needed
|
400
|
-
return {};
|
401
|
-
}
|
402
|
-
|
403
|
-
// Private methods
|
404
|
-
|
405
|
-
/**
|
406
|
-
* Set the service state and emit event
|
407
|
-
*
|
408
|
-
* @private
|
409
|
-
* @param {string} newState - New state value
|
410
|
-
*/
|
411
|
-
_setState(newState) {
|
412
|
-
const oldState = this.state;
|
413
|
-
this.state = newState;
|
414
|
-
|
415
|
-
this.emit('stateChanged', {
|
416
|
-
oldState,
|
417
|
-
newState
|
418
|
-
});
|
419
|
-
}
|
420
|
-
|
421
|
-
/**
|
422
|
-
* Set the health status and emit event if changed
|
423
|
-
*
|
424
|
-
* @private
|
425
|
-
* @param {boolean} healthy - New health status
|
426
|
-
*/
|
427
|
-
_setHealthy(healthy) {
|
428
|
-
const wasHealthy = this.healthy;
|
429
|
-
this.healthy = healthy;
|
430
|
-
|
431
|
-
if (wasHealthy !== healthy) {
|
432
|
-
this.emit('healthChanged', {
|
433
|
-
healthy,
|
434
|
-
wasHealthy
|
435
|
-
});
|
436
|
-
}
|
437
|
-
}
|
438
|
-
|
439
|
-
/**
|
440
|
-
* Set error state and emit error event
|
441
|
-
*
|
442
|
-
* @private
|
443
|
-
* @param {string} operation - Operation that failed
|
444
|
-
* @param {Error} error - Error that occurred
|
445
|
-
*/
|
446
|
-
_setError(operation, error) {
|
447
|
-
this.error = error;
|
448
|
-
this._setState(ERROR_STATE);
|
449
|
-
this._setHealthy(false);
|
450
|
-
|
451
|
-
this.logger.error(`${operation} failed`, {
|
452
|
-
error: error.message,
|
453
|
-
stack: error.stack
|
454
|
-
});
|
455
|
-
|
456
|
-
this.emit('error', {
|
457
|
-
operation,
|
458
|
-
error
|
459
|
-
});
|
460
|
-
}
|
461
|
-
}
|
462
|
-
|
463
|
-
export default ServiceBase;
|
1
|
+
/**
|
2
|
+
* @fileoverview Base service class with lifecycle management, health checks,
|
3
|
+
* and integrated logging.
|
4
|
+
*
|
5
|
+
* ServiceBase provides a standardized lifecycle for all services with states,
|
6
|
+
* events, logging, and error handling. Services extend this class and override
|
7
|
+
* the protected methods to implement their specific functionality.
|
8
|
+
*
|
9
|
+
* @example
|
10
|
+
* // Basic service implementation
|
11
|
+
* import { ServiceBase } from './ServiceBase.js';
|
12
|
+
*
|
13
|
+
* class DatabaseService extends ServiceBase {
|
14
|
+
* async _init(config) {
|
15
|
+
* this.connectionString = config.connectionString;
|
16
|
+
* }
|
17
|
+
*
|
18
|
+
* async _start() {
|
19
|
+
* this.connection = await createConnection(this.connectionString);
|
20
|
+
* }
|
21
|
+
*
|
22
|
+
* async _stop() {
|
23
|
+
* await this.connection?.close();
|
24
|
+
* }
|
25
|
+
* }
|
26
|
+
*
|
27
|
+
* // Usage
|
28
|
+
* const db = new DatabaseService('database');
|
29
|
+
* await db.initialize({ connectionString: 'postgres://...' });
|
30
|
+
* await db.start();
|
31
|
+
*
|
32
|
+
* // Listen to events
|
33
|
+
* db.on('healthChanged', ({ healthy }) => {
|
34
|
+
* console.log(`Database is ${healthy ? 'healthy' : 'unhealthy'}`);
|
35
|
+
* });
|
36
|
+
*
|
37
|
+
* @example
|
38
|
+
* // Service with recovery and health checks
|
39
|
+
* class ApiService extends ServiceBase {
|
40
|
+
* async _recover() {
|
41
|
+
* // Custom recovery logic
|
42
|
+
* await this.reconnect();
|
43
|
+
* }
|
44
|
+
*
|
45
|
+
* async _healthCheck() {
|
46
|
+
* const start = Date.now();
|
47
|
+
* await this.ping();
|
48
|
+
* return { latency: Date.now() - start };
|
49
|
+
* }
|
50
|
+
* }
|
51
|
+
*/
|
52
|
+
|
53
|
+
import { EventEmitter } from '../events';
|
54
|
+
import { Logger, INFO } from '../logging';
|
55
|
+
|
56
|
+
import {
|
57
|
+
CREATED,
|
58
|
+
INITIALIZING,
|
59
|
+
INITIALIZED,
|
60
|
+
STARTING,
|
61
|
+
RUNNING,
|
62
|
+
STOPPING,
|
63
|
+
STOPPED,
|
64
|
+
DESTROYING,
|
65
|
+
DESTROYED,
|
66
|
+
ERROR as ERROR_STATE,
|
67
|
+
RECOVERING
|
68
|
+
} from './service-states.js';
|
69
|
+
|
70
|
+
/**
|
71
|
+
* @typedef {import('./typedef.js').ServiceConfig} ServiceConfig
|
72
|
+
* @typedef {import('./typedef.js').ServiceOptions} ServiceOptions
|
73
|
+
* @typedef {import('./typedef.js').StopOptions} StopOptions
|
74
|
+
* @typedef {import('./typedef.js').HealthStatus} HealthStatus
|
75
|
+
* @typedef {import('./typedef.js').StateChangeEvent} StateChangeEvent
|
76
|
+
* @typedef {import('./typedef.js').HealthChangeEvent} HealthChangeEvent
|
77
|
+
* @typedef {import('./typedef.js').ServiceErrorEvent} ServiceErrorEvent
|
78
|
+
*/
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Base class for all services with lifecycle management
|
82
|
+
* @extends EventEmitter
|
83
|
+
*/
|
84
|
+
export class ServiceBase extends EventEmitter {
|
85
|
+
/**
|
86
|
+
* Create a new service instance
|
87
|
+
*
|
88
|
+
* @param {string} name - Service name
|
89
|
+
* @param {ServiceOptions} [options={}] - Service options
|
90
|
+
*/
|
91
|
+
constructor(name, options = {}) {
|
92
|
+
super();
|
93
|
+
|
94
|
+
/** @type {string} */
|
95
|
+
this.name = name;
|
96
|
+
|
97
|
+
/** @type {string} */
|
98
|
+
this.state = CREATED;
|
99
|
+
|
100
|
+
/** @type {boolean} */
|
101
|
+
this.healthy = false;
|
102
|
+
|
103
|
+
/** @type {Error|null} */
|
104
|
+
this.error = null;
|
105
|
+
|
106
|
+
/** @type {Logger} */
|
107
|
+
this.logger = new Logger(name, options.logLevel || INFO);
|
108
|
+
|
109
|
+
/** @private @type {number} */
|
110
|
+
this._shutdownTimeout = options.shutdownTimeout || 5000;
|
111
|
+
}
|
112
|
+
|
113
|
+
/**
|
114
|
+
* Initialize the service with configuration
|
115
|
+
*
|
116
|
+
* @param {ServiceConfig} [config={}] - Service-specific configuration
|
117
|
+
*
|
118
|
+
* @returns {Promise<boolean>} True if initialization succeeded
|
119
|
+
*/
|
120
|
+
async initialize(config = {}) {
|
121
|
+
if (this.state !== CREATED &&
|
122
|
+
this.state !== STOPPED &&
|
123
|
+
this.state !== DESTROYED) {
|
124
|
+
this.logger.warn(`Cannot initialize from state: ${this.state}`);
|
125
|
+
return false;
|
126
|
+
}
|
127
|
+
|
128
|
+
try {
|
129
|
+
this._setState(INITIALIZING);
|
130
|
+
this.logger.debug('Initializing service', { config });
|
131
|
+
|
132
|
+
await this._init(config);
|
133
|
+
|
134
|
+
this._setState(INITIALIZED);
|
135
|
+
this.logger.info('Service initialized');
|
136
|
+
return true;
|
137
|
+
} catch (error) {
|
138
|
+
this._setError('initialization', error);
|
139
|
+
return false;
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
/**
|
144
|
+
* Start the service
|
145
|
+
*
|
146
|
+
* @returns {Promise<boolean>} True if the service started successfully
|
147
|
+
*/
|
148
|
+
async start() {
|
149
|
+
if (this.state !== INITIALIZED && this.state !== STOPPED) {
|
150
|
+
this.logger.warn(`Cannot start from state: ${this.state}`);
|
151
|
+
return false;
|
152
|
+
}
|
153
|
+
|
154
|
+
try {
|
155
|
+
this._setState(STARTING);
|
156
|
+
this.logger.debug('Starting service');
|
157
|
+
|
158
|
+
await this._start();
|
159
|
+
|
160
|
+
this._setState(RUNNING);
|
161
|
+
this._setHealthy(true);
|
162
|
+
this.logger.info('Service started');
|
163
|
+
return true;
|
164
|
+
} catch (error) {
|
165
|
+
this._setError('startup', error);
|
166
|
+
return false;
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Stop the service with optional timeout
|
172
|
+
*
|
173
|
+
* @param {StopOptions} [options={}] - Stop options
|
174
|
+
*
|
175
|
+
* @returns {Promise<boolean>} True if the service stopped successfully
|
176
|
+
*/
|
177
|
+
async stop(options = {}) {
|
178
|
+
if (this.state !== RUNNING && this.state !== ERROR_STATE) {
|
179
|
+
this.logger.warn(`Cannot stop from state: ${this.state}`);
|
180
|
+
return true; // Already stopped
|
181
|
+
}
|
182
|
+
|
183
|
+
const timeout = options.timeout ?? this._shutdownTimeout;
|
184
|
+
|
185
|
+
try {
|
186
|
+
this._setState(STOPPING);
|
187
|
+
this._setHealthy(false);
|
188
|
+
this.logger.debug('Stopping service');
|
189
|
+
|
190
|
+
// Wrap _stop in a timeout
|
191
|
+
const stopPromise = this._stop();
|
192
|
+
|
193
|
+
if (timeout > 0) {
|
194
|
+
await Promise.race([
|
195
|
+
stopPromise,
|
196
|
+
new Promise((_, reject) =>
|
197
|
+
setTimeout(() => reject(new Error('Shutdown timeout')), timeout)
|
198
|
+
)
|
199
|
+
]);
|
200
|
+
} else {
|
201
|
+
await stopPromise;
|
202
|
+
}
|
203
|
+
|
204
|
+
this._setState(STOPPED);
|
205
|
+
this.logger.info('Service stopped');
|
206
|
+
return true;
|
207
|
+
} catch (error) {
|
208
|
+
if (error.message === 'Shutdown timeout' && options.force) {
|
209
|
+
this.logger.warn('Forced shutdown after timeout');
|
210
|
+
this._setState(STOPPED);
|
211
|
+
return true;
|
212
|
+
}
|
213
|
+
this._setError('shutdown', error);
|
214
|
+
return false;
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
/**
|
219
|
+
* Recover the service from error state
|
220
|
+
*
|
221
|
+
* @returns {Promise<boolean>} True if recovery succeeded
|
222
|
+
*/
|
223
|
+
async recover() {
|
224
|
+
if (this.state !== ERROR_STATE) {
|
225
|
+
this.logger.warn(
|
226
|
+
`Can only recover from ERROR state, current: ${this.state}`
|
227
|
+
);
|
228
|
+
return false;
|
229
|
+
}
|
230
|
+
|
231
|
+
try {
|
232
|
+
this._setState(RECOVERING);
|
233
|
+
this.logger.info('Attempting recovery');
|
234
|
+
|
235
|
+
// Try custom recovery first
|
236
|
+
if (this._recover) {
|
237
|
+
await this._recover();
|
238
|
+
this._setState(RUNNING);
|
239
|
+
this._setHealthy(true);
|
240
|
+
} else {
|
241
|
+
// Default: restart
|
242
|
+
this._setState(STOPPED);
|
243
|
+
await this.start();
|
244
|
+
}
|
245
|
+
|
246
|
+
this.error = null;
|
247
|
+
this.logger.info('Recovery successful');
|
248
|
+
return true;
|
249
|
+
} catch (error) {
|
250
|
+
this._setError('recovery', error);
|
251
|
+
return false;
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
/**
|
256
|
+
* Destroy the service and cleanup resources
|
257
|
+
*
|
258
|
+
* @returns {Promise<boolean>} True if destruction succeeded
|
259
|
+
*/
|
260
|
+
async destroy() {
|
261
|
+
if (this.state === DESTROYED) {
|
262
|
+
return true;
|
263
|
+
}
|
264
|
+
|
265
|
+
try {
|
266
|
+
if (this.state === RUNNING) {
|
267
|
+
await this.stop();
|
268
|
+
}
|
269
|
+
|
270
|
+
this._setState(DESTROYING);
|
271
|
+
this.logger.debug('Destroying service');
|
272
|
+
|
273
|
+
if (this._destroy) {
|
274
|
+
await this._destroy();
|
275
|
+
}
|
276
|
+
|
277
|
+
this._setState(DESTROYED);
|
278
|
+
this._setHealthy(false);
|
279
|
+
this.logger.info('Service destroyed');
|
280
|
+
|
281
|
+
// Cleanup
|
282
|
+
this.removeAllListeners();
|
283
|
+
this.logger.removeAllListeners();
|
284
|
+
|
285
|
+
return true;
|
286
|
+
} catch (error) {
|
287
|
+
this._setError('destruction', error);
|
288
|
+
return false;
|
289
|
+
}
|
290
|
+
}
|
291
|
+
|
292
|
+
/**
|
293
|
+
* Get the current health status of the service
|
294
|
+
*
|
295
|
+
* @returns {Promise<HealthStatus>} Health status object
|
296
|
+
*/
|
297
|
+
async getHealth() {
|
298
|
+
const baseHealth = {
|
299
|
+
name: this.name,
|
300
|
+
state: this.state,
|
301
|
+
healthy: this.healthy,
|
302
|
+
error: this.error?.message
|
303
|
+
};
|
304
|
+
|
305
|
+
if (this._healthCheck) {
|
306
|
+
try {
|
307
|
+
const customHealth = await this._healthCheck();
|
308
|
+
return { ...baseHealth, ...customHealth };
|
309
|
+
} catch (error) {
|
310
|
+
return {
|
311
|
+
...baseHealth,
|
312
|
+
healthy: false,
|
313
|
+
checkError: error.message
|
314
|
+
};
|
315
|
+
}
|
316
|
+
}
|
317
|
+
|
318
|
+
return baseHealth;
|
319
|
+
}
|
320
|
+
|
321
|
+
/**
|
322
|
+
* Set the service log level
|
323
|
+
*
|
324
|
+
* @param {string} level - New log level
|
325
|
+
*
|
326
|
+
* @returns {boolean} True if the level was set successfully
|
327
|
+
*/
|
328
|
+
setLogLevel(level) {
|
329
|
+
return this.logger.setLevel(level);
|
330
|
+
}
|
331
|
+
|
332
|
+
// Protected methods to override in subclasses
|
333
|
+
|
334
|
+
/**
|
335
|
+
* Initialize the service (override in subclass)
|
336
|
+
*
|
337
|
+
* @protected
|
338
|
+
* @param {ServiceConfig} config - Service configuration
|
339
|
+
*
|
340
|
+
* @returns {Promise<void>}
|
341
|
+
*/
|
342
|
+
async _init(config) {
|
343
|
+
// Override in subclass
|
344
|
+
}
|
345
|
+
|
346
|
+
/**
|
347
|
+
* Start the service (override in subclass)
|
348
|
+
*
|
349
|
+
* @protected
|
350
|
+
*
|
351
|
+
* @returns {Promise<void>}
|
352
|
+
*/
|
353
|
+
async _start() {
|
354
|
+
// Override in subclass
|
355
|
+
}
|
356
|
+
|
357
|
+
/**
|
358
|
+
* Stop the service (override in subclass)
|
359
|
+
*
|
360
|
+
* @protected
|
361
|
+
*
|
362
|
+
* @returns {Promise<void>}
|
363
|
+
*/
|
364
|
+
async _stop() {
|
365
|
+
// Override in subclass
|
366
|
+
}
|
367
|
+
|
368
|
+
/**
|
369
|
+
* Destroy the service (optional override)
|
370
|
+
*
|
371
|
+
* @protected
|
372
|
+
*
|
373
|
+
* @returns {Promise<void>}
|
374
|
+
*/
|
375
|
+
async _destroy() {
|
376
|
+
// Override in subclass if needed
|
377
|
+
}
|
378
|
+
|
379
|
+
/**
|
380
|
+
* Recover from error state (optional override)
|
381
|
+
*
|
382
|
+
* @protected
|
383
|
+
*
|
384
|
+
* @returns {Promise<void>}
|
385
|
+
*/
|
386
|
+
async _recover() {
|
387
|
+
// Override in subclass if custom recovery needed
|
388
|
+
// Default behavior is stop + start
|
389
|
+
}
|
390
|
+
|
391
|
+
/**
|
392
|
+
* Perform health check (optional override)
|
393
|
+
*
|
394
|
+
* @protected
|
395
|
+
*
|
396
|
+
* @returns {Promise<Object>} Additional health information
|
397
|
+
*/
|
398
|
+
async _healthCheck() {
|
399
|
+
// Override in subclass if health checks needed
|
400
|
+
return {};
|
401
|
+
}
|
402
|
+
|
403
|
+
// Private methods
|
404
|
+
|
405
|
+
/**
|
406
|
+
* Set the service state and emit event
|
407
|
+
*
|
408
|
+
* @private
|
409
|
+
* @param {string} newState - New state value
|
410
|
+
*/
|
411
|
+
_setState(newState) {
|
412
|
+
const oldState = this.state;
|
413
|
+
this.state = newState;
|
414
|
+
|
415
|
+
this.emit('stateChanged', {
|
416
|
+
oldState,
|
417
|
+
newState
|
418
|
+
});
|
419
|
+
}
|
420
|
+
|
421
|
+
/**
|
422
|
+
* Set the health status and emit event if changed
|
423
|
+
*
|
424
|
+
* @private
|
425
|
+
* @param {boolean} healthy - New health status
|
426
|
+
*/
|
427
|
+
_setHealthy(healthy) {
|
428
|
+
const wasHealthy = this.healthy;
|
429
|
+
this.healthy = healthy;
|
430
|
+
|
431
|
+
if (wasHealthy !== healthy) {
|
432
|
+
this.emit('healthChanged', {
|
433
|
+
healthy,
|
434
|
+
wasHealthy
|
435
|
+
});
|
436
|
+
}
|
437
|
+
}
|
438
|
+
|
439
|
+
/**
|
440
|
+
* Set error state and emit error event
|
441
|
+
*
|
442
|
+
* @private
|
443
|
+
* @param {string} operation - Operation that failed
|
444
|
+
* @param {Error} error - Error that occurred
|
445
|
+
*/
|
446
|
+
_setError(operation, error) {
|
447
|
+
this.error = error;
|
448
|
+
this._setState(ERROR_STATE);
|
449
|
+
this._setHealthy(false);
|
450
|
+
|
451
|
+
this.logger.error(`${operation} failed`, {
|
452
|
+
error: error.message,
|
453
|
+
stack: error.stack
|
454
|
+
});
|
455
|
+
|
456
|
+
this.emit('error', {
|
457
|
+
operation,
|
458
|
+
error
|
459
|
+
});
|
460
|
+
}
|
461
|
+
}
|
462
|
+
|
463
|
+
export default ServiceBase;
|