@hkdigital/lib-core 0.4.28 → 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.
package/CLAUDE.md ADDED
@@ -0,0 +1,350 @@
1
+ # SvelteKit Library
2
+
3
+ ## Project Overview
4
+ This is a modern SvelteKit library built with Svelte 5 and Skeleton.dev v3 components. The project emphasizes accessibility, clean code standards, and modern JavaScript patterns.
5
+
6
+ ## Architecture & Structure
7
+ - **Framework**: SvelteKit with Svelte 5 runes
8
+ - **UI Components**: Skeleton.dev version 3
9
+ - **Package Manager**: PNPM
10
+ - **Language**: Modern JavaScript (ES6+), no TypeScript
11
+ - **Module System**: ES Modules only (import/export)
12
+
13
+ ## Key Technical Decisions
14
+ - Uses Svelte 5 runes (`$state()`, `$derived()`, `$effect()`, `$props()`) for state management
15
+ - Snippet syntax with `{@render children()}` instead of deprecated `<slot />`
16
+ - WCAG 2.1 Level AA accessibility compliance
17
+ - 80-character line limit with rare exceptions
18
+ - Two-space indentation
19
+ - Descriptive camelCase naming conventions
20
+
21
+ ## Component Patterns
22
+ - All components use `let { ... } = $props()` with JSDoc type annotations
23
+ - Props always include `classes` and `...attrs` for flexibility
24
+ - Event handlers use Svelte 5 syntax (`onclick`, `onchange`)
25
+ - Snippet props typed as `import('svelte').Snippet`
26
+ - Private methods use hash prefix (`#methodName`)
27
+
28
+ ## JavaScript Class Patterns
29
+ - Use modern ES private methods with hash prefix: `#methodName()` instead of `_methodName()`
30
+ - Private fields also use hash prefix: `#privateField`
31
+ - Apply this to all JavaScript classes, not just Svelte components
32
+ - Never use `@private` in JSDoc for methods that start with `#` - the hash already indicates privacy
33
+
34
+ ## Development Standards
35
+ - Readable code over concise code
36
+ - Explicit error handling with try/catch for async operations
37
+ - JSDoc comments for all functions and variables
38
+ - English for all documentation and comments
39
+ - No dollar signs in variable names (reserved for Svelte)
40
+
41
+ ### ESLint Rule Suppression
42
+ - Use specific rule suppression instead of blanket disables
43
+ - For unused variables in method signatures (e.g., base class methods to be overridden):
44
+ ```javascript
45
+ // eslint-disable-next-line no-unused-vars
46
+ async _configure(newConfig, oldConfig = null) {
47
+ // Override in subclass
48
+ }
49
+ ```
50
+
51
+ ## Documentation & Comment Style
52
+ - **80-character line limit** - strictly enforce for ALL code and documentation
53
+ - Applies to code lines, JSDoc comments, parameter descriptions, and all text
54
+ - Wrap long JSDoc descriptions across multiple lines
55
+ - Break long parameter lists and descriptions at 80 characters
56
+ - **JSDoc formatting conventions:**
57
+ - Blank line between description and first `@param`
58
+ - Blank line between last `@param` and `@returns`
59
+ - No blank lines between consecutive `@param` tags with short descriptions
60
+ - Add extra newlines between `@param` entries ONLY when they have multi-line descriptions for better readability
61
+ - For long parameter types or descriptions, place description on next line:
62
+ ```javascript
63
+ // Multi-line @param descriptions (use extra newlines between)
64
+ /**
65
+ * Convert a path string to an array path
66
+ *
67
+ * @param {string|string[]} path
68
+ * String or array path (e.g. "some.path.to")
69
+ *
70
+ * @param {string} [pathSeparator=PATH_SEPARATOR]
71
+ * A custom path separator to use instead of the default "."
72
+ *
73
+ * @returns {string[]} array path (e.g. ["some", "path", "to"])
74
+ */
75
+
76
+ // Short descriptions (no extra newlines needed)
77
+ /**
78
+ * Log an info message
79
+ *
80
+ * @param {string} message - Log message
81
+ * @param {*} [details] - Additional details
82
+ *
83
+ * @returns {boolean} True if the log was emitted
84
+ */
85
+ ```
86
+ - **Concise descriptions** - avoid obvious/redundant explanations
87
+ - Keep only essential information that adds value for developers
88
+ - Remove descriptions that simply restate parameter names or types
89
+ - Examples:
90
+ - ✅ `@property {number} [exp] - Expiration time (seconds since epoch)`
91
+ - ❌ `@property {number} [exp] - Expiration time - timestamp when the JWT expires`
92
+ - ✅ `@property {boolean} [ignoreExpiration] - Skip expiration validation`
93
+ - ❌ `@property {boolean} [ignoreExpiration] - If true, do not validate the expiration of the token`
94
+
95
+ ## Writing Style
96
+ - Use normal English capitalization rules - avoid unnecessary capitals
97
+ - Don't capitalize common nouns like "server", "client", "button", "error", "validation", etc.
98
+ - Only capitalize proper nouns, beginnings of sentences, and official names
99
+ - Examples:
100
+ - ✅ "server-side validation" not "Server-side Validation"
101
+ - ✅ "expect function" not "Expect Function"
102
+ - ✅ "client error" not "Client Error"
103
+ - ✅ "JavaScript" (proper noun) but "validation error" (common noun)
104
+
105
+ ## Import Path Conventions
106
+ - Use `$lib/domain/...` imports for cross-domain references (e.g., `$lib/media/image.js`, `$lib/network/http.js`)
107
+ - Use relative imports (`./` or `../`) when staying within the same main folder under `$lib`
108
+ - **Always include file extensions** (`.js`, `.svelte`) in import statements
109
+ - **For cross-domain imports, use specific export files** (e.g., `parsers.js`, `valibot.js`) rather than directory-only paths - this ensures compatibility outside the library
110
+ - **For local imports within the same domain**, import specific files directly (e.g., `./ClassName.js`) rather than using local index files
111
+ - Examples:
112
+ - ✅ `import { ImageLoader } from '$lib/media/image.js'` (cross-domain import)
113
+ - ✅ `import ImageLoader from './ImageLoader.svelte.js'` (local import within same domain)
114
+ - ✅ `import IterableTree from './IterableTree.js'` (local import within same domain)
115
+ - ✅ `import { v } from '$lib/valibot/valibot.js'` (cross-domain with specific export file)
116
+ - ✅ `import { HumanUrl, Email } from '$lib/valibot/parsers.js'` (cross-domain with specific export file)
117
+ - ❌ `import { v, HumanUrl } from '$lib/valibot'` (missing specific export file)
118
+ - ❌ `import { IterableTree } from './index.js'` (local index when specific file should be used)
119
+ - ❌ `import something from '../../media/image.js'` (cross-domain relative import)
120
+
121
+ ## Class Export Conventions
122
+ - **All classes should be default exports**: `export default class ClassName`
123
+ - **Import classes without destructuring**: `import ClassName from './ClassName.js'`
124
+ - Examples:
125
+ - ✅ `export default class HkPromise extends Promise {}`
126
+ - ✅ `import HkPromise from './HkPromise.js'`
127
+ - ❌ `export class HkPromise extends Promise {}` (named export)
128
+ - ❌ `import { HkPromise } from './HkPromise.js'` (destructuring import)
129
+
130
+ ## Accessibility Requirements
131
+ - Proper ARIA roles, states, and properties
132
+ - Descriptive aria-labels for interactive elements
133
+ - Keyboard navigation support
134
+ - Logical tab order and focus management
135
+ - Proper heading hierarchy
136
+ - Screen reader compatibility (VoiceOver, NVDA)
137
+
138
+ ## Common Patterns to Follow
139
+ - Server/client code separation in SvelteKit
140
+ - Clear component hierarchy
141
+ - Minimal, targeted changes to working code
142
+ - Ask before making assumptions about existing structure
143
+ - Focus on specific requests rather than general improvements
144
+
145
+ ## Design System Usage in Examples
146
+
147
+ When creating examples in `routes/examples/`, always use the HKdigital Design System:
148
+
149
+ ### Typography
150
+ - Use complete typography classes: `type-heading-h1`, `type-base-md`, `type-ui-sm`
151
+ - Include dark mode variants when relevant: `type-heading-h1-dark`
152
+ - Never use raw Tailwind text classes like `text-lg` - use `text-base-lg` instead
153
+
154
+ ### Spacing
155
+ - Use UI points for element spacing: `p-20up`, `m-10up`, `gap-16up`
156
+ - Use text-based spacing for typography-related spacing: `mb-16bt`, `mt-12ut`
157
+ - Never use raw Tailwind spacing like `p-4` - use `p-4up` instead
158
+
159
+ ### Colors
160
+ - Use themed colors: `bg-primary-500`, `bg-surface-100`, `text-error-500`
161
+ - Always use contrast colors for accessibility: `text-primary-contrast-500`
162
+ - Include both light and dark mode considerations
163
+
164
+ ### Layout & Components
165
+ - Use design system utilities: `rounded-md`, `border-width-normal`
166
+ - Follow the responsive scaling system built around 1024×768 design reference
167
+ - Use component data attributes: `data-component="button"` `data-role="primary"`
168
+
169
+ ### Custom Styling for Examples
170
+ When examples require custom CSS beyond the design system:
171
+
172
+ - Always add a `data-page` attribute to the outer page element
173
+ - Create a `style.css` file alongside the `+page.svelte` file
174
+ - Use `<style src="./style.css"></style>` to include the styles
175
+ - Scope all CSS rules under `[data-page]` to prevent global conflicts
176
+ - Use CSS nesting syntax for better organization
177
+
178
+ **Example structure:**
179
+ ```svelte
180
+ <!-- +page.svelte -->
181
+ <div data-page>
182
+ <div data-section="content">
183
+ <!-- example content -->
184
+ </div>
185
+ </div>
186
+
187
+ <style src="./style.css"></style>
188
+ ```
189
+
190
+ ```css
191
+ /* style.css */
192
+ [data-page] {
193
+ & [data-section="content"] {
194
+ /* scoped styles here */
195
+ position: relative;
196
+ padding: 20px;
197
+ }
198
+
199
+ & .custom-element {
200
+ /* more scoped styles */
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Structure
206
+ - Always include proper heading hierarchy (`type-heading-h1`, `type-heading-h2`, etc.)
207
+ - Use semantic HTML with appropriate ARIA attributes
208
+ - Follow WCAG 2.1 Level AA accessibility guidelines
209
+
210
+ ### UI Components
211
+ - Always use components from `$lib/ui/primitives/` when available
212
+ - Prefer the `Button` component over raw `<button>` elements
213
+ - Import from the index: `import { Button } from '$lib/ui/primitives.js'`
214
+ - Use snippet syntax: `<Button>{content}</Button>` instead of slot syntax
215
+
216
+ ### Example Template
217
+ ```svelte
218
+ <script>
219
+ import { Button } from '$lib/ui/primitives.js';
220
+ </script>
221
+
222
+ <div class="container mx-auto p-20up" data-page>
223
+ <h1 class="type-heading-h1 mb-20up">Example Title</h1>
224
+
225
+ <div class="card p-20up mb-20up">
226
+ <p class="type-base-md mb-12bt">Description text</p>
227
+ <Button>
228
+ Action
229
+ </Button>
230
+ </div>
231
+ </div>
232
+
233
+ <!-- Only include if custom CSS is needed -->
234
+ <style src="./style.css"></style>
235
+ ```
236
+
237
+ ## Styling System & Tailwind CSS
238
+
239
+ ### Critical: Design System Spacing Values
240
+
241
+ **IMPORTANT**: Only use spacing values that exist in the design system configuration. Invalid spacing utilities will cause build failures.
242
+
243
+ #### Available Viewport-Based Spacing (`up` suffix)
244
+ Valid values: `1up`, `2up`, `4up`, `5up`, `6up`, `10up`, `20up`, `30up`, `40up`, `50up`, `60up`, `70up`, `80up`, `90up`, `100up`, `120up`, `140up`, `160up`, `180up`, `200up`
245
+
246
+ #### Available Text-Based Spacing (`ut`, `bt`, `ht` suffixes)
247
+ Valid values: `1ut/bt/ht`, `2ut/bt/ht`, `4ut/bt/ht`, `6ut/bt/ht`, `8ut/bt/ht`, `10ut/bt/ht`, `11ut/bt/ht`, `12ut/bt/ht`, `16ut/bt/ht`, `20ut/bt/ht`, `24ut/bt/ht`, `28ut/bt/ht`, `32ut/bt/ht`, `36ut/bt/ht`, `50ut/bt/ht`
248
+
249
+ #### ❌ Common Invalid Values (Will Cause Build Failures)
250
+ - `p-16up` (use `p-20up` instead)
251
+ - `px-16up py-12up` (use `px-20up py-10up` instead)
252
+ - `mb-16up` (use `mb-20up` instead)
253
+ - Any spacing value not listed above
254
+
255
+ ### External CSS Files (`<style src="./style.css">`)
256
+
257
+ When using external CSS files with `@apply` directives, you **MUST** include the `@reference` directive:
258
+
259
+ #### ✅ Correct External CSS
260
+ ```css
261
+ /* style.css */
262
+ @reference '../../app.css';
263
+
264
+ [data-page] {
265
+ & .my-component {
266
+ @apply p-20up bg-surface-300 border border-primary-500;
267
+ }
268
+ }
269
+ ```
270
+
271
+ #### ❌ Broken External CSS
272
+ ```css
273
+ /* style.css - MISSING @reference */
274
+ [data-page] {
275
+ & .my-component {
276
+ @apply p-20up bg-surface-300; /* ERROR: Cannot apply unknown utility class */
277
+ }
278
+ }
279
+ ```
280
+
281
+ ### Path Resolution for @reference
282
+
283
+ The `@reference` path must be relative to your CSS file's location:
284
+ - From `/src/routes/examples/style.css` → `@reference '../../app.css'`
285
+ - From `/src/routes/examples/ui/style.css` → `@reference '../../../app.css'`
286
+ - From `/src/lib/components/style.css` → `@reference '../../app.css'`
287
+
288
+ ### Inline vs External CSS
289
+
290
+ #### Inline Styles (No @reference needed)
291
+ ```svelte
292
+ <style>
293
+ [data-page] {
294
+ & .my-component {
295
+ @apply p-20up bg-surface-300 border border-primary-500;
296
+ }
297
+ }
298
+ </style>
299
+ ```
300
+
301
+ #### External CSS (Requires @reference)
302
+ ```svelte
303
+ <!-- Component.svelte -->
304
+ <div class="my-component">Content</div>
305
+ <style src="./style.css"></style>
306
+ ```
307
+
308
+ ### Troubleshooting Build Errors
309
+
310
+ #### "Cannot apply unknown utility class 'p-XYZup'"
311
+ 1. Check if the spacing value exists in `VIEWPORT_POINT_SIZES` or `TEXT_POINT_SIZES`
312
+ 2. Replace with the nearest valid value (e.g., `16up` → `20up`)
313
+ 3. If using external CSS, verify `@reference` directive is present and path is correct
314
+
315
+ #### "Are you using CSS modules or similar and missing @reference?"
316
+ 1. Add `@reference '../../app.css'` at the top of your external CSS file
317
+ 2. Verify the relative path from CSS file to `src/app.css` is correct
318
+ 3. Consider switching to inline styles if path resolution is problematic
319
+
320
+ ### Design System Integration
321
+
322
+ #### Always Use Design System Classes
323
+ - ✅ `type-heading-h1`, `type-base-md`, `type-ui-sm`
324
+ - ✅ `p-20up`, `m-10up`, `gap-16up` (viewport-based)
325
+ - ✅ `mb-16bt`, `mt-12ut` (text-based)
326
+ - ✅ `bg-surface-300`, `text-primary-500`, `border-error-500`
327
+ - ❌ Raw Tailwind: `text-lg`, `p-4`, `bg-gray-300`
328
+
329
+ #### Color System
330
+ All design system colors are available with contrast variants:
331
+ - Base colors: `bg-primary-500`, `bg-surface-100`, `bg-error-500`
332
+ - Contrast colors: `text-primary-contrast-500`, `text-surface-contrast-100`
333
+ - Shades: `50`, `100`, `200`, `300`, `400`, `500`, `600`, `700`, `800`, `900`, `950`
334
+
335
+ ### CSS Architecture
336
+ - Use `[data-page]` scoping for page-specific styles
337
+ - Use `[data-component]` attributes for component identification
338
+ - Prefer CSS nesting with `&` selector
339
+ - Layer styles: `@layer theme, base, utilities, components`
340
+
341
+ ## What Not to Do
342
+ - Don't use deprecated Svelte 4 syntax
343
+ - Don't mix slot and snippet syntax
344
+ - Don't use TypeScript syntax
345
+ - Don't use CommonJS (require/module.exports)
346
+ - Don't use underscore prefixes for private methods
347
+ - Don't use invalid spacing values (e.g., `p-16up`, `mb-14up`) - check design system configuration
348
+ - Don't use external CSS with `@apply` without the `@reference` directive
349
+ - Don't modify unrelated code unless necessary
350
+ - **NEVER run `npm run dev` or `pnpm run dev`** - it interferes with the user's running development server
@@ -1,5 +1,5 @@
1
1
  export { createServerLogger } from "./internal/factories/server.js";
2
2
  export { createClientLogger } from "./internal/factories/client.js";
3
3
  export { Logger } from "./internal/logger/index.js";
4
- export * from "./constants.js";
4
+ export * from "./levels.js";
5
5
  export * from "./typedef.js";
@@ -5,6 +5,6 @@ export { createClientLogger } from './internal/factories/client.js';
5
5
  // Logger (for advanced usage)
6
6
  export { Logger } from './internal/logger/index.js';
7
7
 
8
- // Constants and typedefs
9
- export * from './constants.js';
8
+ export * from './levels.js';
9
+
10
10
  export * from './typedef.js';
@@ -1,5 +1,5 @@
1
1
  import { dev } from '$app/environment';
2
- import { LEVELS } from '../../constants.js';
2
+ import { LEVELS } from '../../levels.js';
3
3
  import {
4
4
  findRelevantFrameIndex,
5
5
  detectErrorMeta,
@@ -1,6 +1,6 @@
1
1
  import { Logger } from '../logger/index.js';
2
2
  import { ConsoleAdapter } from '../adapters/console.js';
3
- import { INFO } from '../../constants.js';
3
+ import { INFO } from '../../levels.js';
4
4
 
5
5
  /**
6
6
  * Create a client-side logger with console adapter
@@ -1,6 +1,6 @@
1
1
  import { Logger } from '../logger/index.js';
2
2
  import { PinoAdapter } from '../adapters/pino.js';
3
- import { INFO } from '../../constants.js';
3
+ import { INFO } from '../../levels.js';
4
4
  // import { expectNoSSRContext } from '../../../util/ssr/index.js';
5
5
 
6
6
  /**
@@ -43,7 +43,7 @@ import {
43
43
  ERROR,
44
44
  LEVELS,
45
45
  LOG
46
- } from '../../constants.js';
46
+ } from '../../levels.js';
47
47
 
48
48
  import { DetailedError } from '../../../generic/errors.js';
49
49
  // import { LoggerError } from '../../errors.js';
@@ -129,12 +129,32 @@ export class ServiceManager extends EventEmitter {
129
129
  */
130
130
  isRunning(name: string): Promise<boolean>;
131
131
  /**
132
- * Set log level for a service or globally
132
+ * Listen to log messages emitted by individual services
133
133
  *
134
- * @param {string} name - Service name or '*' for global
135
- * @param {string} level - Log level to set
134
+ * @param {Function} listener - Log event handler
135
+ *
136
+ * @returns {Function} Unsubscribe function
137
+ */
138
+ onServiceLogEvent(listener: Function): Function;
139
+ /**
140
+ * Set log level for the ServiceManager itself
141
+ *
142
+ * @param {string} level - Log level to set for the ServiceManager
143
+ */
144
+ setManagerLogLevel(level: string): void;
145
+ /**
146
+ * Set log level for individual services
147
+ *
148
+ * @param {string|Object<string,string>} nameOrConfig
149
+ * Service configuration:
150
+ * - String with service name: 'auth' (requires level parameter)
151
+ * - String with config: 'auth:debug,database:info'
152
+ * - Object: { auth: 'debug', database: 'info' }
153
+ * @param {string} [level] - Log level (required when nameOrConfig is service name)
136
154
  */
137
- setLogLevel(name: string, level: string): void;
155
+ setServiceLogLevel(nameOrConfig: string | {
156
+ [x: string]: string;
157
+ }, level?: string): void;
138
158
  /**
139
159
  * Get all services with a specific tag
140
160
  *
@@ -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.28",
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