@hkdigital/lib-core 0.4.27 → 0.4.29

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.
@@ -51,11 +51,11 @@
51
51
  *
52
52
  * @example
53
53
  * // Logging control
54
- * // Set global log level
55
- * manager.setLogLevel('*', 'DEBUG');
54
+ * // Set manager log level (affects all services)
55
+ * manager.setManagerLogLevel('DEBUG');
56
56
  *
57
57
  * // Set specific service log level
58
- * manager.setLogLevel('database', 'ERROR');
58
+ * manager.setServiceLogLevel('database', 'ERROR');
59
59
  *
60
60
  * // Listen to all service logs
61
61
  * manager.on('service:log', (logEvent) => {
@@ -64,7 +64,10 @@
64
64
  */
65
65
 
66
66
  import { EventEmitter } from '../../generic/events.js';
67
- import { Logger, DEBUG, INFO, WARN } from '../../logging/index.js';
67
+ import { Logger, DEBUG, INFO } from '../../logging/index.js';
68
+
69
+ import { SERVICE_LOG } from './constants.js';
70
+ import { parseServiceLogLevels } from './util.js';
68
71
 
69
72
  import {
70
73
  STATE_NOT_CREATED,
@@ -103,17 +106,27 @@ export class ServiceManager extends EventEmitter {
103
106
  /** @type {Map<string, ServiceEntry>} */
104
107
  this.services = new Map();
105
108
 
109
+ const defaultLogLevel = config.defaultLogLevel || (config.debug ? DEBUG : INFO);
110
+ const managerLogLevel = config.managerLogLevel || defaultLogLevel;
111
+ const serviceLogLevels = config.serviceLogLevels;
112
+
106
113
  /** @type {Logger} */
107
- this.logger = new Logger('ServiceManager', config.logLevel || INFO);
114
+ this.logger = new Logger('ServiceManager', managerLogLevel);
108
115
 
109
116
  /** @type {ServiceManagerConfig} */
110
117
  this.config = {
111
118
  debug: config.debug ?? false,
112
119
  autoStart: config.autoStart ?? false,
113
120
  stopTimeout: config.stopTimeout || 10000,
114
- logConfig: config.logConfig || {}
121
+ defaultLogLevel,
122
+ managerLogLevel
123
+ // serviceLogLevels will be set by setServiceLogLevel()
115
124
  };
116
125
 
126
+ if (serviceLogLevels) {
127
+ this.setServiceLogLevel(serviceLogLevels);
128
+ }
129
+
117
130
  this.#setupLogging();
118
131
  }
119
132
 
@@ -474,34 +487,68 @@ export class ServiceManager extends EventEmitter {
474
487
  }
475
488
 
476
489
  /**
477
- * Set log level for a service or globally
490
+ * Listen to log messages emitted by individual services
491
+ *
492
+ * @param {Function} listener - Log event handler
478
493
  *
479
- * @param {string} name - Service name or '*' for global
480
- * @param {string} level - Log level to set
494
+ * @returns {Function} Unsubscribe function
481
495
  */
482
- setLogLevel(name, level) {
483
- if (name === '*') {
484
- // Global level
485
- this.config.logConfig.globalLevel = level;
486
-
487
- // Apply to all existing services
488
- // eslint-disable-next-line no-unused-vars
489
- for (const [_, entry] of this.services) {
490
- if (entry.instance) {
491
- entry.instance.setLogLevel(level);
496
+ onServiceLogEvent(listener) {
497
+ return this.on(SERVICE_LOG, listener);
498
+ }
499
+
500
+ /**
501
+ * Set log level for the ServiceManager itself
502
+ *
503
+ * @param {string} level - Log level to set for the ServiceManager
504
+ */
505
+ setManagerLogLevel(level) {
506
+ this.config.managerLogLevel = level;
507
+ this.logger.setLevel(level);
508
+ }
509
+
510
+ /**
511
+ * Set log level for individual services
512
+ *
513
+ * @param {string|Object<string,string>} nameOrConfig
514
+ * Service configuration:
515
+ * - String with service name: 'auth' (requires level parameter)
516
+ * - String with config: 'auth:debug,database:info'
517
+ * - Object: { auth: 'debug', database: 'info' }
518
+ * @param {string} [level] - Log level (required when nameOrConfig is service name)
519
+ */
520
+ setServiceLogLevel(nameOrConfig, level) {
521
+ /** @type {{[name:string]: string}} */
522
+ let serviceLevels = {};
523
+
524
+ if (typeof nameOrConfig === 'string') {
525
+ if (nameOrConfig.includes(':')) {
526
+ // Parse string config: 'auth:debug,database:info'
527
+ serviceLevels = parseServiceLogLevels(nameOrConfig);
528
+ } else {
529
+ // Single service name
530
+ if (!level) {
531
+ throw new Error(`Level parameter required for service '${nameOrConfig}'`);
492
532
  }
533
+ serviceLevels[nameOrConfig] = level;
493
534
  }
494
535
  } else {
495
- // Service-specific level
496
- if (!this.config.logConfig.serviceLevels) {
497
- this.config.logConfig.serviceLevels = {};
498
- }
499
- this.config.logConfig.serviceLevels[name] = level;
536
+ // Object config: { auth: 'debug', database: 'info' }
537
+ serviceLevels = nameOrConfig;
538
+ }
539
+
540
+ if (!this.config.serviceLogLevels) {
541
+ this.config.serviceLogLevels = {};
542
+ }
543
+
544
+ // Apply service-specific log levels
545
+ for (const [name, logLevel] of Object.entries(serviceLevels)) {
546
+ this.config.serviceLogLevels[name] = logLevel;
500
547
 
501
548
  // Apply to existing instance
502
549
  const instance = this.get(name);
503
550
  if (instance) {
504
- instance.setLogLevel(level);
551
+ instance.setLogLevel(logLevel);
505
552
  }
506
553
  }
507
554
  }
@@ -587,19 +634,19 @@ export class ServiceManager extends EventEmitter {
587
634
  }
588
635
 
589
636
  /**
590
- * Setup logging configuration based on config.dev
637
+ * Setup logging configuration based on config.debug
591
638
  */
592
639
  #setupLogging() {
593
- // Set default log levels based on config.debug flag
594
- if (this.config.debug) {
595
- this.config.logConfig.defaultLevel = DEBUG;
596
- } else {
597
- this.config.logConfig.defaultLevel = WARN;
640
+ // Set default level for services based on debug flag if not explicitly set
641
+ if (!this.config.defaultLogLevel) {
642
+ this.config.defaultLogLevel = this.config.debug ? DEBUG : INFO;
598
643
  }
599
644
 
600
- // Apply config
601
- if (this.config.logConfig.globalLevel) {
602
- this.logger.setLevel(this.config.logConfig.globalLevel);
645
+ // Set manager log level (use defaultLogLevel as fallback)
646
+ const managerLevel = this.config.managerLogLevel ||
647
+ this.config.defaultLogLevel;
648
+ if (managerLevel) {
649
+ this.logger.setLevel(managerLevel);
603
650
  }
604
651
  }
605
652
 
@@ -611,21 +658,20 @@ export class ServiceManager extends EventEmitter {
611
658
  * @returns {string|undefined} Log level or undefined
612
659
  */
613
660
  #getServiceLogLevel(name) {
614
- const config = this.config.logConfig;
661
+ const config = this.config;
615
662
 
616
663
  // Check in order of precedence:
617
- // 1. Global level (overrides everything)
618
- if (config.globalLevel) {
619
- return config.globalLevel;
664
+ // 1. Service-specific level
665
+ if (config.serviceLogLevels?.[name]) {
666
+ return config.serviceLogLevels[name];
620
667
  }
621
668
 
622
- // 2. Service-specific level
623
- if (config.serviceLevels?.[name]) {
624
- return config.serviceLevels[name];
669
+ // 2. Default level fallback
670
+ if (config.defaultLogLevel) {
671
+ return config.defaultLogLevel;
625
672
  }
626
673
 
627
- // 3. Don't use defaultLevel as it might be too restrictive
628
- // Return undefined to let the service use its own default
674
+ // 3. No fallback - let service use its own default
629
675
  return undefined;
630
676
  }
631
677
 
@@ -2,3 +2,5 @@ export const SERVICE_STATE_CHANGED: "service:state-changed";
2
2
  export const SERVICE_HEALTH_CHANGED: "service:health-changed";
3
3
  export const SERVICE_ERROR: "service:error";
4
4
  export const SERVICE_LOG: "service:log";
5
+ export const ANY_LOG_LEVEL: "*";
6
+ export const ANY_SERVICE_NAME: "*";
@@ -3,3 +3,6 @@ export const SERVICE_STATE_CHANGED = 'service:state-changed';
3
3
  export const SERVICE_HEALTH_CHANGED = 'service:health-changed';
4
4
  export const SERVICE_ERROR = 'service:error';
5
5
  export const SERVICE_LOG = 'service:log';
6
+
7
+ export const ANY_LOG_LEVEL = '*';
8
+ export const ANY_SERVICE_NAME = '*';
@@ -38,30 +38,19 @@ export type ServiceManagerConfig = {
38
38
  */
39
39
  stopTimeout?: number;
40
40
  /**
41
- * - Initial log level for ServiceManager
41
+ * - Default log level for new services
42
42
  */
43
- logLevel?: string;
43
+ defaultLogLevel?: string;
44
44
  /**
45
- * - Logging configuration
46
- */
47
- logConfig?: LogConfig;
48
- };
49
- /**
50
- * Logging configuration
51
- */
52
- export type LogConfig = {
53
- /**
54
- * - Default log level for services
55
- */
56
- defaultLevel?: string;
57
- /**
58
- * - Override level for all services
45
+ * - Initial log level for ServiceManager
59
46
  */
60
- globalLevel?: string;
47
+ managerLogLevel?: string;
61
48
  /**
62
- * - Per-service log levels
49
+ * Per-service log levels:
50
+ * - String: "auth:debug,database:info"
51
+ * - Object: { auth: "debug", database: "info" }
63
52
  */
64
- serviceLevels?: {
53
+ serviceLogLevels?: string | {
65
54
  [x: string]: string;
66
55
  };
67
56
  };
@@ -52,17 +52,12 @@
52
52
  * @property {boolean} [debug=false] - Debug mode switch
53
53
  * @property {boolean} [autoStart=false] - Auto-start services on registration
54
54
  * @property {number} [stopTimeout=10000] - Default timeout for stopping services
55
- * @property {string} [logLevel] - Initial log level for ServiceManager
56
- * @property {LogConfig} [logConfig={}] - Logging configuration
57
- */
58
-
59
- /**
60
- * Logging configuration
61
- *
62
- * @typedef {Object} LogConfig
63
- * @property {string} [defaultLevel] - Default log level for services
64
- * @property {string} [globalLevel] - Override level for all services
65
- * @property {Object<string, string>} [serviceLevels] - Per-service log levels
55
+ * @property {string} [defaultLogLevel] - Default log level for new services
56
+ * @property {string} [managerLogLevel] - Initial log level for ServiceManager
57
+ * @property {string|Object<string,string>} [serviceLogLevels]
58
+ * Per-service log levels:
59
+ * - String: "auth:debug,database:info"
60
+ * - Object: { auth: "debug", database: "info" }
66
61
  */
67
62
 
68
63
  /**
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Parse comma-separated service:level configuration string
3
+ *
4
+ * @param {string} configString
5
+ * Comma-separated string like "auth:debug,database:info,cache:warn"
6
+ *
7
+ * @returns {Object<string, string>} Service name to log level mapping
8
+ *
9
+ * @example
10
+ * const config = parseServiceLogLevels("auth:debug,database:info");
11
+ * // Returns: { auth: "debug", database: "info" }
12
+ */
13
+ export function parseServiceLogLevels(configString: string): {
14
+ [x: string]: string;
15
+ };
16
+ /**
17
+ * Expand log levels to include higher severity levels
18
+ *
19
+ * @param {{[name:string]: string}} serviceLevels
20
+ * Service name to log level mapping
21
+ *
22
+ * @returns {Object<string, string[]>} Service name to array of log levels
23
+ *
24
+ * @example
25
+ * const levels = expandLogLevels({ auth: "debug", cache: "warn" });
26
+ * // Returns: {
27
+ * // auth: ["debug", "info", "warn", "error"],
28
+ * // cache: ["warn", "error"]
29
+ * // }
30
+ */
31
+ export function expandLogLevels(serviceLevels: {
32
+ [name: string]: string;
33
+ }): {
34
+ [x: string]: string[];
35
+ };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @fileoverview Service Manager utility functions
3
+ *
4
+ * Provides utility functions for parsing service configurations, handling
5
+ * log level hierarchies, and managing service-specific operations.
6
+ */
7
+
8
+ import { DEBUG, INFO, WARN, ERROR } from '../../logging/index.js';
9
+
10
+ /**
11
+ * Parse comma-separated service:level configuration string
12
+ *
13
+ * @param {string} configString
14
+ * Comma-separated string like "auth:debug,database:info,cache:warn"
15
+ *
16
+ * @returns {Object<string, string>} Service name to log level mapping
17
+ *
18
+ * @example
19
+ * const config = parseServiceLogLevels("auth:debug,database:info");
20
+ * // Returns: { auth: "debug", database: "info" }
21
+ */
22
+ export function parseServiceLogLevels(configString) {
23
+ if (!configString || typeof configString !== 'string') {
24
+ /** @type {Object<string, string>} */
25
+ return {};
26
+ }
27
+
28
+ /** @type {Object<string, string>} */
29
+ const result = {};
30
+
31
+ const services = configString.split(',');
32
+
33
+ for (const serviceExpression of services) {
34
+ const trimmed = serviceExpression.trim();
35
+ if (!trimmed) continue;
36
+
37
+ const parts = trimmed.split(':');
38
+ if (parts.length === 2) {
39
+ const [serviceName, logLevel] = parts;
40
+ result[serviceName.trim()] = logLevel.trim();
41
+ }
42
+ }
43
+
44
+ return result;
45
+ }
46
+
47
+ /**
48
+ * Expand log levels to include higher severity levels
49
+ *
50
+ * @param {{[name:string]: string}} serviceLevels
51
+ * Service name to log level mapping
52
+ *
53
+ * @returns {Object<string, string[]>} Service name to array of log levels
54
+ *
55
+ * @example
56
+ * const levels = expandLogLevels({ auth: "debug", cache: "warn" });
57
+ * // Returns: {
58
+ * // auth: ["debug", "info", "warn", "error"],
59
+ * // cache: ["warn", "error"]
60
+ * // }
61
+ */
62
+ export function expandLogLevels(serviceLevels) {
63
+ /** @type {Object<string, string[]>} */
64
+ const result = {};
65
+
66
+ for (const [serviceName, level] of Object.entries(serviceLevels)) {
67
+ const levels = [];
68
+
69
+ switch (level.toLowerCase()) {
70
+ case DEBUG:
71
+ levels.push(DEBUG, INFO, WARN, ERROR);
72
+ break;
73
+ case INFO:
74
+ levels.push(INFO, WARN, ERROR);
75
+ break;
76
+ case WARN:
77
+ levels.push(WARN, ERROR);
78
+ break;
79
+ case ERROR:
80
+ levels.push(ERROR);
81
+ break;
82
+ default:
83
+ levels.push(level);
84
+ }
85
+
86
+ result[serviceName] = levels;
87
+ }
88
+
89
+ return result;
90
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.4.27",
3
+ "version": "0.4.29",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -52,6 +52,7 @@
52
52
  },
53
53
  "files": [
54
54
  "dist",
55
+ "CLAUDE.md",
55
56
  "!dist/**/*.test.*",
56
57
  "!dist/**/*.spec.*",
57
58
  "!dist/**/testdata.*",
@@ -76,13 +77,17 @@
76
77
  "@skeletonlabs/skeleton": "^3.1.7",
77
78
  "@steeze-ui/heroicons": "^2.4.2",
78
79
  "@sveltejs/kit": "^2.28.0",
80
+ "@tailwindcss/postcss": "^4.1.11",
81
+ "autoprefixer": "^10.4.21",
79
82
  "eslint-plugin-import": "^2.32.0",
80
83
  "jsonwebtoken": "^9.0.0",
81
84
  "pino": "^9.8.0",
82
85
  "pino-pretty": "^13.1.1",
86
+ "postcss": "^8.5.6",
83
87
  "runed": "^0.31.1",
84
88
  "svelte": "^5.38.1",
85
89
  "svelte-preprocess": "^6.0.3",
90
+ "tailwindcss": "^4.1.11",
86
91
  "valibot": "^1.1.0",
87
92
  "vite-imagetools": "^8.0.0"
88
93
  },
@@ -98,6 +103,7 @@
98
103
  "@tailwindcss/typography": "^0.5.16",
99
104
  "@testing-library/svelte": "^5.2.8",
100
105
  "@testing-library/user-event": "^14.6.1",
106
+ "@tailwindcss/postcss": "^4.1.11",
101
107
  "@types/eslint": "^9.6.1",
102
108
  "@types/node": "^24.2.1",
103
109
  "autoprefixer": "^10.4.21",
@@ -128,8 +134,5 @@
128
134
  "vite": "^7.1.2",
129
135
  "vite-imagetools": "^8.0.0",
130
136
  "vitest": "^3.2.4"
131
- },
132
- "dependencies": {
133
- "@tailwindcss/postcss": "^4.1.11"
134
137
  }
135
138
  }
File without changes
File without changes