@hkdigital/lib-core 0.5.92 → 0.5.94

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 (43) hide show
  1. package/README.md +63 -9
  2. package/dist/browser/info/device.js +9 -7
  3. package/dist/config/generators/imagetools.d.ts +14 -0
  4. package/dist/config/generators/imagetools.js +55 -0
  5. package/dist/config/imagetools.d.ts +12 -0
  6. package/dist/logging/README.md +15 -53
  7. package/dist/meta/README.md +92 -0
  8. package/dist/meta/components/Favicons.svelte +30 -0
  9. package/dist/meta/components/Favicons.svelte.d.ts +103 -0
  10. package/dist/meta/components/PWA.svelte +51 -0
  11. package/dist/meta/components/PWA.svelte.d.ts +103 -0
  12. package/dist/meta/components/SEO.svelte +146 -0
  13. package/dist/meta/components/SEO.svelte.d.ts +108 -0
  14. package/dist/meta/components.d.ts +3 -0
  15. package/dist/meta/components.js +3 -0
  16. package/dist/meta/config.typedef.d.ts +98 -0
  17. package/dist/meta/config.typedef.js +44 -0
  18. package/dist/meta/typedef.d.ts +3 -0
  19. package/dist/meta/typedef.js +14 -0
  20. package/dist/meta/utils/lang.d.ts +29 -0
  21. package/dist/meta/utils/lang.js +84 -0
  22. package/dist/meta/utils/robots.d.ts +1 -0
  23. package/dist/meta/utils/robots.js +1 -0
  24. package/dist/meta/utils/sitemap.d.ts +1 -0
  25. package/dist/meta/utils/sitemap.js +1 -0
  26. package/dist/meta/utils.d.ts +3 -0
  27. package/dist/meta/utils.js +11 -0
  28. package/dist/services/PATTERNS.md +476 -0
  29. package/dist/services/PLUGINS.md +520 -0
  30. package/dist/services/README.md +156 -229
  31. package/package.json +1 -1
  32. package/dist/meta/robots.d.ts +0 -1
  33. package/dist/meta/robots.js +0 -5
  34. package/dist/meta/sitemap.d.ts +0 -1
  35. package/dist/meta/sitemap.js +0 -5
  36. /package/dist/meta/{robots/index.d.ts → utils/robots/robots.d.ts} +0 -0
  37. /package/dist/meta/{robots/index.js → utils/robots/robots.js} +0 -0
  38. /package/dist/meta/{robots → utils/robots}/typedef.d.ts +0 -0
  39. /package/dist/meta/{robots → utils/robots}/typedef.js +0 -0
  40. /package/dist/meta/{sitemap/index.d.ts → utils/sitemap/sitemap.d.ts} +0 -0
  41. /package/dist/meta/{sitemap/index.js → utils/sitemap/sitemap.js} +0 -0
  42. /package/dist/meta/{sitemap → utils/sitemap}/typedef.d.ts +0 -0
  43. /package/dist/meta/{sitemap → utils/sitemap}/typedef.js +0 -0
@@ -0,0 +1,476 @@
1
+ # Service Patterns and Best Practices
2
+
3
+ Design patterns and best practices for implementing services with the
4
+ ServiceBase and ServiceManager system.
5
+
6
+ **See also:**
7
+ - **API Reference**: [README.md](./README.md) - ServiceBase and
8
+ ServiceManager API
9
+ - **Plugins**: [PLUGINS.md](./PLUGINS.md) - Plugin system and
10
+ ConfigPlugin
11
+ - **Architecture**: [docs/setup/services-logging.md](../../docs/setup/services-logging.md)
12
+ - Integration patterns
13
+
14
+ ## Service Access Patterns
15
+
16
+ Services receive helpful utilities in their constructor options for
17
+ accessing other services and the manager.
18
+
19
+ ### Basic Pattern
20
+
21
+ ```javascript
22
+ /**
23
+ * Example service that depends on other services
24
+ */
25
+ class AuthService extends ServiceBase {
26
+ /** @type {(<T>(serviceName: string) => T)} */
27
+ #getService;
28
+
29
+ /** @type {() => import('@hkdigital/lib-core/services/index.js').ServiceManager} */
30
+ #getManager;
31
+
32
+ constructor(serviceName, options) {
33
+ super(serviceName, options);
34
+
35
+ // Store service access utilities as private methods
36
+ this.#getService = options.getService; // Bound getService function
37
+ this.#getManager = options.getManager; // Function to get manager (lazy)
38
+ }
39
+
40
+ async authenticateUser(credentials) {
41
+ // Access other services with full type safety and error checking
42
+ const database = this.#getService('database');
43
+ const user = await database.findUser(credentials.username);
44
+
45
+ // Access manager for advanced operations when needed
46
+ const manager = this.#getManager();
47
+ const health = await manager.checkHealth();
48
+
49
+ return user;
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### Recommended Pattern: Private Methods
55
+
56
+ The recommended approach is to store service access functions as
57
+ **private methods** using the hash prefix. This pattern provides:
58
+
59
+ **Benefits:**
60
+ - **Keeps the API clean** - No public getService/getManager methods
61
+ exposed
62
+ - **Prevents serialization issues** - Private fields don't serialize
63
+ to JSON
64
+ - **Enforces proper encapsulation** - Service dependencies stay
65
+ internal
66
+ - **Provides type safety** - Full generic support with
67
+ `this.#getService<DatabaseService>('database')`
68
+
69
+ **Example:**
70
+
71
+ ```javascript
72
+ /**
73
+ * Unified service for tracking complete player data including progress
74
+ * and profile matches
75
+ */
76
+ export default class PlayerService extends ServiceBase {
77
+
78
+ /** @type {(<T>(serviceName: string) => T)} */
79
+ #getService;
80
+
81
+ /**
82
+ * @param {string} serviceName
83
+ * @param {import('@hkdigital/lib-core/services/typedef.js').ServiceOptions} [options]
84
+ */
85
+ constructor(serviceName, options) {
86
+ super(serviceName, options);
87
+
88
+ this.#getService = options?.getService;
89
+ }
90
+
91
+ async getPlayerProfile(playerId) {
92
+ // Access dependent services cleanly
93
+ const database = this.#getService('database');
94
+ const analytics = this.#getService('analytics');
95
+
96
+ const profile = await database.getPlayer(playerId);
97
+ const stats = await analytics.getPlayerStats(playerId);
98
+
99
+ return { ...profile, stats };
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### Service Access Methods
105
+
106
+ ServiceManager provides two access patterns:
107
+
108
+ ```javascript
109
+ // 1. Permissive - returns undefined if not found/created
110
+ const service = manager.get('optional-service');
111
+ if (service) {
112
+ // Use service safely
113
+ }
114
+
115
+ // 2. Strict - throws error if not found/created
116
+ const service = manager.getService('required-service'); // Throws if missing
117
+ ```
118
+
119
+ **When to use each:**
120
+ - Use `get()` for optional services or when checking availability
121
+ - Use `getService()` for required dependencies (clearer error messages)
122
+
123
+ ### Constructor Utilities Benefits
124
+
125
+ **Lightweight:**
126
+ Functions don't serialize, keeping services serialization-safe.
127
+
128
+ **Lazy access:**
129
+ Manager is only accessed when needed, avoiding circular dependencies
130
+ during initialization.
131
+
132
+ **Type safety:**
133
+ Full generic support with JSDoc annotations enables IDE autocomplete
134
+ and type checking.
135
+
136
+ **Error handling:**
137
+ Clear, consistent errors when services are missing or not yet
138
+ initialized.
139
+
140
+ ## Configuration Patterns
141
+
142
+ ### Initial Configuration vs Reconfiguration
143
+
144
+ Services should handle both initial setup and runtime reconfiguration
145
+ intelligently.
146
+
147
+ **Pattern:**
148
+
149
+ ```javascript
150
+ class DatabaseService extends ServiceBase {
151
+ // eslint-disable-next-line no-unused-vars
152
+ async _configure(newConfig, oldConfig = null) {
153
+ if (!oldConfig) {
154
+ // Initial configuration - store all settings
155
+ this.connectionString = newConfig.connectionString;
156
+ this.maxConnections = newConfig.maxConnections || 10;
157
+ this.timeout = newConfig.timeout || 5000;
158
+ return;
159
+ }
160
+
161
+ // Reconfiguration - handle changes intelligently
162
+ if (oldConfig.connectionString !== newConfig.connectionString) {
163
+ // Connection changed - need to reconnect
164
+ await this.connection?.close();
165
+ this.connectionString = newConfig.connectionString;
166
+
167
+ if (this.state === 'running') {
168
+ this.connection = await createConnection(this.connectionString);
169
+ }
170
+ }
171
+
172
+ if (oldConfig.maxConnections !== newConfig.maxConnections) {
173
+ // Pool size changed - update without reconnect
174
+ this.maxConnections = newConfig.maxConnections;
175
+ await this.connection?.setMaxConnections(this.maxConnections);
176
+ }
177
+
178
+ if (oldConfig.timeout !== newConfig.timeout) {
179
+ // Timeout changed - just update the setting
180
+ this.timeout = newConfig.timeout;
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ **Key principles:**
187
+ - Check `!oldConfig` to detect initial configuration
188
+ - Compare old and new config to identify what changed
189
+ - Apply minimal changes (don't restart if not needed)
190
+ - Update running state when possible
191
+
192
+ ### Configuration Defaults
193
+
194
+ Handle missing configuration gracefully with sensible defaults.
195
+
196
+ **Pattern:**
197
+
198
+ ```javascript
199
+ async _configure(newConfig, oldConfig = null) {
200
+ // Use defaults for missing values
201
+ this.host = newConfig.host || 'localhost';
202
+ this.port = newConfig.port || 5432;
203
+ this.maxConnections = newConfig.maxConnections || 10;
204
+ this.timeout = newConfig.timeout || 5000;
205
+ this.retryAttempts = newConfig.retryAttempts ?? 3; // Use ?? for zero values
206
+ }
207
+ ```
208
+
209
+ ## Lifecycle Patterns
210
+
211
+ ### Proper Resource Management
212
+
213
+ Always clean up resources in the stop method.
214
+
215
+ **Pattern:**
216
+
217
+ ```javascript
218
+ class WebSocketService extends ServiceBase {
219
+ async _start() {
220
+ this.ws = new WebSocket(this.url);
221
+ this.intervalId = setInterval(() => this.#ping(), 30000);
222
+
223
+ await new Promise((resolve) => {
224
+ this.ws.onopen = resolve;
225
+ });
226
+ }
227
+
228
+ async _stop() {
229
+ // Clear timers
230
+ if (this.intervalId) {
231
+ clearInterval(this.intervalId);
232
+ this.intervalId = null;
233
+ }
234
+
235
+ // Close connections
236
+ if (this.ws) {
237
+ this.ws.close();
238
+ this.ws = null;
239
+ }
240
+ }
241
+
242
+ async _destroy() {
243
+ // Clean up any remaining resources
244
+ await this._stop();
245
+ }
246
+ }
247
+ ```
248
+
249
+ ### Async Initialization
250
+
251
+ Keep `_configure()` lightweight, do heavy work in `_start()`.
252
+
253
+ **Good:**
254
+
255
+ ```javascript
256
+ async _configure(newConfig, oldConfig = null) {
257
+ // Just store config
258
+ this.apiKey = newConfig.apiKey;
259
+ this.endpoint = newConfig.endpoint;
260
+ }
261
+
262
+ async _start() {
263
+ // Heavy work here
264
+ this.client = await createApiClient(this.apiKey, this.endpoint);
265
+ await this.client.authenticate();
266
+ }
267
+ ```
268
+
269
+ **Bad:**
270
+
271
+ ```javascript
272
+ async _configure(newConfig, oldConfig = null) {
273
+ // Don't do heavy work in configure
274
+ this.client = await createApiClient(newConfig.apiKey);
275
+ await this.client.authenticate(); // ❌ Heavy operation
276
+ }
277
+ ```
278
+
279
+ ## Health Check Patterns
280
+
281
+ ### Return Useful Metrics
282
+
283
+ Health checks should return actionable information.
284
+
285
+ **Pattern:**
286
+
287
+ ```javascript
288
+ async _healthCheck() {
289
+ const start = Date.now();
290
+
291
+ try {
292
+ await this.connection.ping();
293
+
294
+ return {
295
+ latency: Date.now() - start,
296
+ connections: this.connection.activeConnections,
297
+ queueSize: this.connection.queueSize
298
+ };
299
+ } catch (error) {
300
+ return {
301
+ error: error.message,
302
+ lastSuccessful: this.lastSuccessfulPing
303
+ };
304
+ }
305
+ }
306
+ ```
307
+
308
+ ### Implement Recovery Logic
309
+
310
+ Provide custom recovery for services that can auto-heal.
311
+
312
+ **Pattern:**
313
+
314
+ ```javascript
315
+ async _recover() {
316
+ // Close broken connection
317
+ await this.connection?.close();
318
+
319
+ // Wait before reconnecting
320
+ await new Promise(resolve => setTimeout(resolve, 1000));
321
+
322
+ // Recreate connection
323
+ this.connection = await createConnection(this.connectionString);
324
+
325
+ // Verify it works
326
+ await this.connection.ping();
327
+ }
328
+ ```
329
+
330
+ ## Error Handling Patterns
331
+
332
+ ### Graceful Degradation
333
+
334
+ Handle errors without crashing the entire system.
335
+
336
+ **Pattern:**
337
+
338
+ ```javascript
339
+ class CacheService extends ServiceBase {
340
+ async get(key) {
341
+ try {
342
+ return await this.redis.get(key);
343
+ } catch (error) {
344
+ this.logger.warn('Cache read failed, falling back', { key, error });
345
+ return null; // Graceful fallback
346
+ }
347
+ }
348
+
349
+ async set(key, value) {
350
+ try {
351
+ await this.redis.set(key, value);
352
+ } catch (error) {
353
+ this.logger.error('Cache write failed', { key, error });
354
+ // Don't throw - cache writes are not critical
355
+ }
356
+ }
357
+ }
358
+ ```
359
+
360
+ ### Timeout Handling
361
+
362
+ Set appropriate timeouts for long-running operations.
363
+
364
+ **Pattern:**
365
+
366
+ ```javascript
367
+ async _start() {
368
+ const timeout = this.config.startTimeout || 10000;
369
+
370
+ const controller = new AbortController();
371
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
372
+
373
+ try {
374
+ this.connection = await fetch(this.endpoint, {
375
+ signal: controller.signal
376
+ });
377
+ } finally {
378
+ clearTimeout(timeoutId);
379
+ }
380
+ }
381
+ ```
382
+
383
+ ## Dependency Management Patterns
384
+
385
+ ### Declare Dependencies Explicitly
386
+
387
+ Always declare service dependencies for proper startup ordering.
388
+
389
+ **Pattern:**
390
+
391
+ ```javascript
392
+ // Register services with explicit dependencies
393
+ manager.register('database', DatabaseService, dbConfig);
394
+
395
+ manager.register(
396
+ 'cache',
397
+ CacheService,
398
+ cacheConfig,
399
+ {
400
+ dependencies: ['database'] // Cache needs database
401
+ }
402
+ );
403
+
404
+ manager.register(
405
+ 'auth',
406
+ AuthService,
407
+ authConfig,
408
+ {
409
+ dependencies: ['database', 'cache'] // Auth needs both
410
+ }
411
+ );
412
+ ```
413
+
414
+ ### Startup Priority
415
+
416
+ Use priority for services that should start early.
417
+
418
+ **Pattern:**
419
+
420
+ ```javascript
421
+ // Start logger first (highest priority)
422
+ manager.register(
423
+ 'logger',
424
+ LoggerService,
425
+ logConfig,
426
+ { startupPriority: 100 }
427
+ );
428
+
429
+ // Then database (high priority)
430
+ manager.register(
431
+ 'database',
432
+ DatabaseService,
433
+ dbConfig,
434
+ { startupPriority: 50 }
435
+ );
436
+
437
+ // Then other services (default priority: 0)
438
+ manager.register('auth', AuthService, authConfig);
439
+ ```
440
+
441
+ ## Best Practices
442
+
443
+ ### Service Design
444
+
445
+ 1. **Always extend ServiceBase** for consistent lifecycle management
446
+ 2. **Keep configuration lightweight** - heavy work should be in
447
+ `_start()`
448
+ 3. **Implement proper cleanup** in `_stop()` to prevent resource leaks
449
+ 4. **Use health checks** for monitoring critical service functionality
450
+ 5. **Handle errors gracefully** and implement recovery where
451
+ appropriate
452
+
453
+ ### Service Registration
454
+
455
+ 6. **Declare dependencies explicitly** when registering with
456
+ ServiceManager
457
+ 7. **Use descriptive service names** for better logging and debugging
458
+ 8. **Set appropriate priorities** for services with ordering
459
+ requirements
460
+
461
+ ### Service Implementation
462
+
463
+ 9. **Store service access as private methods** using hash prefix
464
+ 10. **Return useful metrics** from health checks
465
+ 11. **Implement intelligent reconfiguration** that applies minimal
466
+ changes
467
+ 12. **Handle missing config gracefully** with sensible defaults
468
+
469
+ ### Resource Management
470
+
471
+ 13. **Clean up all resources** in `_stop()` (timers, connections,
472
+ listeners)
473
+ 14. **Set appropriate timeouts** for long-running operations
474
+ 15. **Use graceful degradation** instead of throwing errors when
475
+ possible
476
+ 16. **Test service lifecycle** (start, stop, restart, reconfigure)