@hkdigital/lib-core 0.4.18 → 0.4.20

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 (33) hide show
  1. package/dist/services/README.md +254 -47
  2. package/dist/services/manager-plugins/ConfigPlugin.d.ts +68 -0
  3. package/dist/services/manager-plugins/ConfigPlugin.js +329 -0
  4. package/dist/services/service-base/ServiceBase.d.ts +59 -22
  5. package/dist/services/service-base/ServiceBase.js +139 -61
  6. package/dist/services/service-base/constants.d.ts +30 -14
  7. package/dist/services/service-base/constants.js +42 -14
  8. package/dist/services/service-base/typedef.d.ts +15 -9
  9. package/dist/services/service-base/typedef.js +33 -11
  10. package/dist/services/service-manager/ServiceManager.d.ts +25 -5
  11. package/dist/services/service-manager/ServiceManager.js +103 -20
  12. package/dist/services/service-manager/constants.js +0 -1
  13. package/dist/services/service-manager/typedef.d.ts +36 -14
  14. package/dist/services/service-manager/typedef.js +30 -13
  15. package/dist/util/sveltekit/env/README.md +424 -0
  16. package/dist/util/sveltekit/env/all.d.ts +54 -0
  17. package/dist/util/sveltekit/env/all.js +97 -0
  18. package/dist/util/sveltekit/env/parsers.d.ts +135 -0
  19. package/dist/util/sveltekit/env/parsers.js +257 -0
  20. package/dist/util/sveltekit/env/private.d.ts +56 -0
  21. package/dist/util/sveltekit/env/private.js +87 -0
  22. package/dist/util/sveltekit/env/public.d.ts +52 -0
  23. package/dist/util/sveltekit/env/public.js +82 -0
  24. package/dist/util/sveltekit/env-all.d.ts +1 -0
  25. package/dist/util/sveltekit/env-all.js +19 -0
  26. package/dist/util/sveltekit/env-private.d.ts +1 -0
  27. package/dist/util/sveltekit/env-private.js +18 -0
  28. package/dist/util/sveltekit/env-public.d.ts +1 -0
  29. package/dist/util/sveltekit/env-public.js +18 -0
  30. package/package.json +1 -1
  31. package/dist/util/index.js__ +0 -20
  32. package/dist/util/sveltekit/index.d.ts +0 -0
  33. package/dist/util/sveltekit/index.js +0 -0
@@ -11,27 +11,39 @@ The services module provides two main components:
11
11
 
12
12
  All services follow a standardized state machine with proper error handling, logging, and health monitoring.
13
13
 
14
- ## Service States
15
-
16
- Services transition through these states during their lifecycle:
17
-
18
- - `created` - Service instantiated but not initialized
19
- - `initializing` - Currently running initialization
20
- - `initialized` - Ready to start
21
- - `starting` - Currently starting up
22
- - `running` - Operational and healthy
23
- - `stopping` - Currently shutting down
24
- - `stopped` - Cleanly stopped
25
- - `destroying` - Being destroyed and cleaned up
26
- - `destroyed` - Completely destroyed
27
- - `error` - Failed and non-operational
28
- - `recovering` - Attempting recovery from error
14
+ ## Service states
15
+
16
+ Services transition through these states during their lifecycle. Use these constants from `$lib/services/service-base/constants.js`:
17
+
18
+ - `STATE_CREATED` - Service instantiated but not configured
19
+ - `STATE_CONFIGURING` - Currently running configuration
20
+ - `STATE_CONFIGURED` - Ready to start
21
+ - `STATE_STARTING` - Currently starting up
22
+ - `STATE_RUNNING` - Operational and healthy
23
+ - `STATE_STOPPING` - Currently shutting down
24
+ - `STATE_STOPPED` - Cleanly stopped
25
+ - `STATE_DESTROYING` - Being destroyed and cleaned up
26
+ - `STATE_DESTROYED` - Completely destroyed
27
+ - `STATE_ERROR` - Failed and non-operational
28
+ - `STATE_RECOVERING` - Attempting recovery from error
29
+
30
+ ```javascript
31
+ import {
32
+ STATE_RUNNING,
33
+ STATE_ERROR
34
+ } from '$lib/services/service-base/constants.js';
35
+
36
+ if (service.state === STATE_RUNNING) {
37
+ // Service is operational
38
+ }
39
+ ```
29
40
 
30
41
  ## ServiceBase
31
42
 
32
43
  Base class that all services should extend. Provides:
33
44
 
34
- - Standardized lifecycle methods (`initialize`, `start`, `stop`, `destroy`)
45
+ - Standardized lifecycle methods (`configure`, `start`, `stop`, `destroy`)
46
+ - Flexible configuration system with reconfiguration support
35
47
  - Health monitoring and recovery
36
48
  - Event emission for state changes
37
49
  - Integrated logging
@@ -43,8 +55,34 @@ Base class that all services should extend. Provides:
43
55
  import { ServiceBase } from '$lib/services/index.js';
44
56
 
45
57
  class DatabaseService extends ServiceBase {
46
- async _init(config) {
47
- this.connectionString = config.connectionString;
58
+ // eslint-disable-next-line no-unused-vars
59
+ async _configure(newConfig, oldConfig = null) {
60
+ if (!oldConfig) {
61
+ // Initial configuration
62
+
63
+ this.connectionString = newConfig.connectionString;
64
+ this.maxConnections = newConfig.maxConnections || 10;
65
+ return;
66
+ }
67
+
68
+ if (oldConfig.connectionString !== newConfig.connectionString) {
69
+ // Reconfiguration - handle changes intelligently
70
+
71
+ // Connection changed - need to reconnect
72
+ await this.connection?.close();
73
+
74
+ this.connectionString = newConfig.connectionString;
75
+ if (this.state === 'running') {
76
+ this.connection = await createConnection(this.connectionString);
77
+ }
78
+ }
79
+
80
+ if (oldConfig.maxConnections !== newConfig.maxConnections) {
81
+ // Pool size changed - update without reconnect
82
+ //
83
+ this.maxConnections = newConfig.maxConnections;
84
+ await this.connection?.setMaxConnections(this.maxConnections);
85
+ }
48
86
  }
49
87
 
50
88
  async _start() {
@@ -64,29 +102,49 @@ class DatabaseService extends ServiceBase {
64
102
 
65
103
  // Usage
66
104
  const db = new DatabaseService('database');
67
- await db.initialize({ connectionString: 'postgres://...' });
105
+ await db.configure({
106
+ connectionString: 'postgres://localhost/myapp',
107
+ maxConnections: 20
108
+ });
68
109
  await db.start();
69
110
 
70
111
  // Listen to events
71
112
  db.on('healthChanged', ({ healthy }) => {
72
113
  console.log(`Database is ${healthy ? 'healthy' : 'unhealthy'}`);
73
114
  });
115
+
116
+ // Reconfigure at runtime
117
+ await db.configure({
118
+ connectionString: 'postgres://localhost/myapp',
119
+ maxConnections: 50 // Hot-reloaded without restart
120
+ });
74
121
  ```
75
122
 
76
123
  ### Protected Methods to Override
77
124
 
78
- - `_init(config)` - Initialize service with configuration
125
+ - `_configure(newConfig, oldConfig = null)` - Configure service (handles both initial setup and reconfiguration)
79
126
  - `_start()` - Start the service
80
127
  - `_stop()` - Stop the service
81
128
  - `_destroy()` - Clean up resources (optional)
82
129
  - `_recover()` - Custom recovery logic (optional)
83
130
  - `_healthCheck()` - Return health status (optional)
84
131
 
85
- ### Events
132
+ ### Service events
133
+
134
+ ServiceBase emits these events (constants from `$lib/services/service-base/constants.js`):
86
135
 
87
- - `stateChanged` - Service state transitions
88
- - `healthChanged` - Health status changes
89
- - `error` - Service errors
136
+ - `EVENT_STATE_CHANGED` - Service state transitions
137
+ - `EVENT_TARGET_STATE_CHANGED` - Target state changes
138
+ - `EVENT_HEALTH_CHANGED` - Health status changes
139
+ - `EVENT_ERROR` - Service errors
140
+
141
+ ```javascript
142
+ import { EVENT_STATE_CHANGED } from '$lib/services/service-base/constants.js';
143
+
144
+ service.on(EVENT_STATE_CHANGED, ({ state, previousState }) => {
145
+ console.log(`Service transitioned from ${previousState} to ${state}`);
146
+ });
147
+ ```
90
148
 
91
149
  ## ServiceManager
92
150
 
@@ -100,11 +158,13 @@ Manages multiple services with dependency resolution and coordinated lifecycle o
100
158
  - Health monitoring for all services
101
159
  - Centralized logging control
102
160
  - Service recovery management
161
+ - Plugin system for extending configuration resolution
103
162
 
104
163
  ### Usage
105
164
 
106
165
  ```javascript
107
166
  import { ServiceManager } from '$hklib-core/services/index.js';
167
+
108
168
  import DatabaseService from './services/DatabaseService.js';
109
169
  import AuthService from './services/AuthService.js';
110
170
 
@@ -118,11 +178,16 @@ manager.register('database', DatabaseService, {
118
178
  connectionString: 'postgres://localhost/myapp'
119
179
  });
120
180
 
121
- manager.register('auth', AuthService, {
122
- secret: process.env.JWT_SECRET
123
- }, {
124
- dependencies: ['database'] // auth depends on database
125
- });
181
+ manager.register(
182
+ 'auth',
183
+ AuthService,
184
+ {
185
+ secret: process.env.JWT_SECRET
186
+ },
187
+ {
188
+ dependencies: ['database'] // auth depends on database
189
+ }
190
+ );
126
191
 
127
192
  // Start all services in dependency order
128
193
  await manager.startAll();
@@ -137,19 +202,27 @@ await manager.stopAll();
137
202
  ### Service Registration
138
203
 
139
204
  ```javascript
140
- manager.register(name, ServiceClass, config, options);
205
+ manager.register(name, ServiceClass, serviceConfigOrLabel, options);
141
206
  ```
142
207
 
143
208
  - `name` - Unique service identifier
144
209
  - `ServiceClass` - Class extending ServiceBase
145
- - `config` - Service-specific configuration
210
+ - `serviceConfigOrLabel` - Service configuration object (`Object<string, *>`) or config label string
146
211
  - `options.dependencies` - Array of service names this service depends on
212
+ - `options.startupPriority` - Higher priority services start first (default: 0)
147
213
 
148
214
  ### Health Monitoring
149
215
 
150
216
  ```javascript
217
+ import {
218
+ SERVICE_HEALTH_CHANGED,
219
+ SERVICE_ERROR,
220
+ SERVICE_STATE_CHANGED,
221
+ SERVICE_LOG
222
+ } from '$lib/services/service-manager/constants.js';
223
+
151
224
  // Listen for health changes
152
- manager.on('service:healthChanged', ({ service, healthy }) => {
225
+ manager.on(SERVICE_HEALTH_CHANGED, ({ service, healthy }) => {
153
226
  if (!healthy) {
154
227
  console.error(`Service ${service} became unhealthy`);
155
228
  }
@@ -164,11 +237,20 @@ const systemHealth = await manager.checkHealth();
164
237
 
165
238
  ### Error Handling and Recovery
166
239
 
240
+ ### ServiceManager events
241
+
242
+ ServiceManager emits these events (constants from `$lib/services/service-manager/constants.js`):
243
+
244
+ - `SERVICE_STATE_CHANGED` - Service state changes
245
+ - `SERVICE_HEALTH_CHANGED` - Service health changes
246
+ - `SERVICE_ERROR` - Service errors
247
+ - `SERVICE_LOG` - Service log messages
248
+
167
249
  ```javascript
168
250
  // Listen for service errors
169
- manager.on('service:error', async ({ service, error }) => {
251
+ manager.on(SERVICE_ERROR, async ({ service, error }) => {
170
252
  console.log(`Service ${service} failed:`, error.message);
171
-
253
+
172
254
  // Attempt automatic recovery
173
255
  await manager.recoverService(service);
174
256
  });
@@ -177,24 +259,149 @@ manager.on('service:error', async ({ service, error }) => {
177
259
  await manager.recoverService('database');
178
260
  ```
179
261
 
262
+ ## Plugins
263
+
264
+ ServiceManager supports plugins e.g. to resolve service configurations dynamically.
265
+
266
+ ### ConfigPlugin
267
+
268
+ The most common plugin for resolving service configuration from a pre-parsed configuration object. Perfect for environment variables, config files, or any structured configuration source.
269
+
270
+ #### Basic Usage with Environment Variables
271
+
272
+ ```javascript
273
+ import { ServiceManager } from '$lib/services/index.js';
274
+
275
+ import ConfigPlugin from '$lib/services/manager-plugins/ConfigPlugin.js';
276
+
277
+ import { getPrivateEnv } from '$lib/util/sveltekit/env-private.js';
278
+
279
+ // Load and auto-group environment variables
280
+ const envConfig = getPrivateEnv();
281
+ //
282
+ // Example:
283
+ //
284
+ // DATABASE_HOST=localhost
285
+ // DATABASE_PORT=5432
286
+ // DATABASE_NAME=myapp
287
+ // REDIS_HOST=cache-server
288
+ // REDIS_PORT=6379
289
+ // JWT_SECRET=mysecret
290
+ // =>
291
+ // {
292
+ // database: { host: 'localhost', port: 5432, name: 'myapp' },
293
+ // redis: { host: 'cache-server', port: 6379 },
294
+ // jwtSecret: 'mysecret'
295
+ // }
296
+ //
297
+
298
+ // Create plugin with grouped config
299
+ const configPlugin = new ConfigPlugin(envConfig);
300
+
301
+ // Attach to ServiceManager
302
+ const manager = new ServiceManager();
303
+ manager.attachPlugin(configPlugin);
304
+
305
+ // Register services with config labels (not config objects)
306
+ manager.register('database', DatabaseService, 'database'); // Uses envConfig.database
307
+ manager.register('cache', RedisService, 'redis'); // Uses envConfig.redis
308
+
309
+ await manager.startAll();
310
+ ```
311
+
312
+ #### Configuration
313
+
314
+ The plugin constructor accepts an object with configuration data, which can come from any source. E.g. the environment or a configuration file.
315
+
316
+ ```javascript
317
+ // Combine multiple config sources
318
+ const config = {
319
+ ...getPrivateEnv(), // Environment variables
320
+ ...(await loadConfigFile()), // Config file
321
+ database: {
322
+ // Override specific settings
323
+ ...envConfig.database,
324
+ connectionTimeout: 5000
325
+ }
326
+ };
327
+
328
+ const plugin = new ConfigPlugin(config);
329
+ ```
330
+
331
+ ### Methods
332
+
333
+ ```javascript
334
+ // Replace all configurations and clean up unused ones
335
+ await configPlugin.replaceAllConfigs(newConfig);
336
+
337
+ // Replace configuration for a specific label
338
+ await configPlugin.replaceConfig('database', newDatabaseConfig);
339
+
340
+ // Clean up configurations not used by any service
341
+ await configPlugin.cleanupConfigs();
342
+ ```
343
+
344
+ ### Live Configuration Updates
345
+
346
+ The ConfigPlugin supports pushing configuration updates to running services:
347
+
348
+ ```javascript
349
+ // Replace config for a specific label and notify all affected services
350
+ const updatedServices = await configPlugin.replaceConfig('database', {
351
+ host: 'new-host.example.com',
352
+ port: 5433,
353
+ maxConnections: 50
354
+ });
355
+
356
+ // Returns array of service names that were updated: ['user-service', 'order-service']
357
+ console.log(`Updated ${updatedServices.length} services`);
358
+ ```
359
+
360
+ #### Service Requirements for Live Updates
361
+
362
+ For services to support live configuration updates, they must:
363
+
364
+ 1. **Implement intelligent `_configure()` logic** that can handle both initial setup and reconfiguration
365
+ 2. **Check for meaningful changes** between old and new config
366
+ 3. **Apply changes without full restart** when possible
367
+
368
+ ```javascript
369
+ class DatabaseService extends ServiceBase {
370
+ // eslint-disable-next-line no-unused-vars
371
+ async _configure(newConfig, oldConfig = null) {
372
+ if (!oldConfig) {
373
+ // Initial configuration
374
+ this.connectionString = newConfig.connectionString;
375
+ this.maxConnections = newConfig.maxConnections || 10;
376
+ return;
377
+ }
378
+
379
+ // Live reconfiguration - handle changes intelligently
380
+ if (oldConfig.connectionString !== newConfig.connectionString) {
381
+ // Connection changed - need to reconnect
382
+ await this.connection?.close();
383
+ this.connectionString = newConfig.connectionString;
384
+
385
+ if (this.state === 'running') {
386
+ this.connection = await createConnection(this.connectionString);
387
+ }
388
+ }
389
+
390
+ if (oldConfig.maxConnections !== newConfig.maxConnections) {
391
+ // Pool size changed - update without reconnect
392
+ this.maxConnections = newConfig.maxConnections;
393
+ await this.connection?.setMaxConnections(this.maxConnections);
394
+ }
395
+ }
396
+ }
397
+ ```
398
+
180
399
  ## Best Practices
181
400
 
182
401
  1. **Always extend ServiceBase** for consistent lifecycle management
183
- 2. **Keep initialization lightweight** - heavy work should be in `_start()`
402
+ 2. **Keep configuration lightweight** - heavy work should be in `_start()`
184
403
  3. **Implement proper cleanup** in `_stop()` to prevent resource leaks
185
404
  4. **Use health checks** for monitoring critical service functionality
186
405
  5. **Declare dependencies explicitly** when registering with ServiceManager
187
406
  6. **Handle errors gracefully** and implement recovery where appropriate
188
407
  7. **Use descriptive service names** for better logging and debugging
189
-
190
- ## Testing
191
-
192
- Services include comprehensive test suites demonstrating:
193
-
194
- - Lifecycle state transitions
195
- - Error handling and recovery
196
- - Dependency resolution
197
- - Health monitoring
198
- - Event emission
199
-
200
- Run tests with your project's test command to ensure service reliability.
@@ -0,0 +1,68 @@
1
+ /** @typedef {import('../service-manager/typedef.js').ServiceEntry} ServiceEntry */
2
+ /**
3
+ * Plugin that resolves service configuration from a configuration object
4
+ */
5
+ export default class ConfigPlugin {
6
+ /**
7
+ * Create a new object configuration plugin
8
+ *
9
+ * @param {Object<string, *>} allConfigs - Pre-parsed configuration object
10
+ */
11
+ constructor(allConfigs: {
12
+ [x: string]: any;
13
+ });
14
+ /** @type {string} */
15
+ name: string;
16
+ /** @type {import('../service-manager/ServiceManager.js').ServiceManager|null} */
17
+ manager: import("../service-manager/ServiceManager.js").ServiceManager | null;
18
+ /** @type {Object<string, *>} */
19
+ allConfigs: {
20
+ [x: string]: any;
21
+ };
22
+ /**
23
+ * Resolve service configuration from the configuration object
24
+ *
25
+ * @param {string} serviceName
26
+ * @param {ServiceEntry} serviceEntry - Service registration entry
27
+ * @param {*} currentConfig
28
+ * Current config (could be object from previous plugins)
29
+ *
30
+ * @returns {Promise<Object|undefined>}
31
+ * Resolved config object, or undefined to use currentConfig as-is
32
+ */
33
+ resolveServiceConfig(serviceName: string, serviceEntry: ServiceEntry, currentConfig: any): Promise<any | undefined>;
34
+ /**
35
+ * Replace the entire configuration object and clean up unused configs
36
+ *
37
+ * @param {Object<string, *>} newConfigObject - New configuration object
38
+ */
39
+ replaceAllConfigs(newConfigObject: {
40
+ [x: string]: any;
41
+ }): Promise<void>;
42
+ /**
43
+ * Replace a config for a specific label and push to affected services
44
+ *
45
+ * @param {string} configLabel - Config label to update
46
+ * @param {*} newConfig - New configuration value
47
+ *
48
+ * @returns {Promise<string[]>} Array of service names that were updated
49
+ */
50
+ replaceConfig(configLabel: string, newConfig: any): Promise<string[]>;
51
+ /**
52
+ * Remove all unused configurations (configs not referenced by any service)
53
+ */
54
+ cleanupConfigs(): Promise<void>;
55
+ /**
56
+ * Attach plugin to ServiceManager
57
+ *
58
+ * @param {import('../service-manager/ServiceManager.js').ServiceManager} manager
59
+ * ServiceManager instance
60
+ */
61
+ attach(manager: import("../service-manager/ServiceManager.js").ServiceManager): void;
62
+ /**
63
+ * Detach plugin from ServiceManager
64
+ */
65
+ detach(): void;
66
+ #private;
67
+ }
68
+ export type ServiceEntry = import("../service-manager/typedef.js").ServiceEntry;