@hkdigital/lib-core 0.3.11 → 0.3.13

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