@hkdigital/lib-core 0.4.34 → 0.4.36

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 CHANGED
@@ -38,6 +38,12 @@ This is a modern SvelteKit library built with Svelte 5 and Skeleton.dev v3 compo
38
38
  - English for all documentation and comments
39
39
  - No dollar signs in variable names (reserved for Svelte)
40
40
 
41
+ ### Testing Commands
42
+ - Run all tests: `pnpm test`
43
+ - Run specific test file: `pnpm test:file path/to/test.js`
44
+ - Run tests in directory: `pnpm test:file src/lib/logging/`
45
+ - Use `pnpm test:file` for single test execution - it's cross-platform compatible
46
+
41
47
  ### ESLint Rule Suppression
42
48
  - Use specific rule suppression instead of blanket disables
43
49
  - For unused variables in method signatures (e.g., base class methods to be overridden):
@@ -103,40 +103,92 @@ export async function handle({ event, resolve }) {
103
103
  ### Client-side logging (src/hooks.client.js)
104
104
 
105
105
  ```javascript
106
- import { createClientLogger } from '@hkdigital/lib-core/logging/index.js';
106
+ import { initClientServices } from '$lib/services/client.js';
107
+ import { getClientLogger } from '$lib/logging/client.js';
107
108
 
108
- const logger = createClientLogger('client');
109
+ export async function init() {
110
+ // Init services
111
+ try {
112
+ await initClientServices();
113
+
114
+ getClientLogger().info('Client initialization complete');
115
+ } catch (error) {
116
+ getClientLogger().error('Client initialization failed',
117
+ /** @type {Error} */ (error));
118
+ // throw error;
119
+ }
120
+ finally {
121
+ getClientLogger().info('Client application initialized', {
122
+ userAgent: navigator.userAgent,
123
+ viewport: `${window.innerWidth}x${window.innerHeight}`
124
+ });
125
+ }
126
+ }
109
127
 
110
128
  /** @type {import('@sveltejs/kit').HandleClientError} */
111
129
  export function handleError({ error, event }) {
112
- logger.error(error, {
113
- url: event.url?.pathname,
114
- userAgent: navigator.userAgent
130
+ // Handle SvelteKit-specific errors:
131
+ // navigation errors, load function failures, component errors, ...
132
+ getClientLogger().error(/** @type {Error} */ (error), {
133
+ url: event.url?.pathname,
134
+ userAgent: navigator.userAgent
115
135
  });
116
136
  }
137
+ ```
117
138
 
118
- // Initialize client-side logging
119
- export function init() {
120
- logger.info('Client application initialized', {
121
- userAgent: navigator.userAgent,
122
- viewport: `${window.innerWidth}x${window.innerHeight}`
123
- });
124
-
125
- // Log unhandled errors
126
- window.addEventListener('error', (event) => {
127
- logger.error(event, { url: window.location.pathname });
128
- });
129
-
130
- // Log unhandled promise rejections
131
- window.addEventListener('unhandledrejection', (event) => {
132
- logger.error(event, { url: window.location.pathname });
133
- });
139
+ ### Client Service Integration
140
+
141
+ When integrating with a service management system, you can set up global
142
+ error handling and forward service logs to the main logger:
143
+
144
+ ```javascript
145
+ import { ServiceManager } from '$hklib-core/services/index.js';
146
+ import { initClientLogger } from '$lib/logging/client.js';
147
+
148
+ /** @type {ServiceManager} */
149
+ let manager;
150
+
151
+ export async function initClientServices() {
152
+ if (!manager) {
153
+ const logger = initClientLogger();
154
+
155
+ // Catch errors and unhandled promise rejections
156
+
157
+ // Log unhandled errors
158
+ window.addEventListener('error', (event) => {
159
+ logger.error(event, { url: window.location.pathname });
160
+ event.preventDefault();
161
+ });
162
+
163
+ // Log unhandled promise rejections
164
+ window.addEventListener('unhandledrejection', (event) => {
165
+ logger.error(event, { url: window.location.pathname });
166
+ // Ignored by Firefox
167
+ event.preventDefault();
168
+ });
169
+
170
+ manager = new ServiceManager({ debug: true });
171
+
172
+ // Listen to all log events and forward them to the logger
173
+ manager.onLogEvent((logEvent) => {
174
+ logger.logFromEvent(logEvent);
175
+ });
176
+
177
+ // Register services
178
+ manager.register(SERVICE_AUDIO, AudioService);
179
+ manager.register(SERVICE_EVENT_LOG, EventLogService);
180
+ manager.register(SERVICE_PLAYER_DATA, PlayerDataService);
181
+ }
182
+
183
+ await manager.startAll();
184
+ return manager;
134
185
  }
135
186
 
136
- // Cleanup when app is destroyed
137
- export function destroy() {
138
- logger.info('Client application destroyed');
139
- // Note: Console adapter doesn't require cleanup
187
+ export function getManager() {
188
+ if (!manager) {
189
+ throw new Error('Client services should be initialised first');
190
+ }
191
+ return manager;
140
192
  }
141
193
  ```
142
194
 
@@ -212,7 +212,9 @@ export class ConsoleAdapter {
212
212
  message: details.message,
213
213
  stack: cleanedStack,
214
214
  errorType: formatErrorDisplay(errorMeta),
215
- ...(relevantFrameIndex >= 0 && { relevantFrameIndex })
215
+ ...(relevantFrameIndex >= 0 && { relevantFrameIndex }),
216
+ ...('details' in details && { details: details.details }),
217
+ ...('status' in details && { status: details.status })
216
218
  };
217
219
  }
218
220
  } else if (details.error instanceof Error) {
@@ -233,7 +235,9 @@ export class ConsoleAdapter {
233
235
  message: details.error.message,
234
236
  stack: cleanedStack,
235
237
  errorType: formatErrorDisplay(errorMeta),
236
- ...(relevantFrameIndex >= 0 && { relevantFrameIndex })
238
+ ...(relevantFrameIndex >= 0 && { relevantFrameIndex }),
239
+ ...('details' in details.error && { details: details.error.details }),
240
+ ...('status' in details.error && { status: details.error.status })
237
241
  };
238
242
  }
239
243
  // Include other details except the error
@@ -377,8 +381,22 @@ export class ConsoleAdapter {
377
381
  'node_modules/vite-deps'
378
382
  );
379
383
 
384
+ // Clean up pnpm paths - convert complex pnpm paths to simple package names
385
+ // Before: node_modules/.pnpm/@hkdigital+lib-core@0.4.35_@eslint+js@9.35.0_.../node_modules/@hkdigital/lib-core/dist/...
386
+ // After: @hkdigital/lib-core/dist/...
387
+ cleaned = cleaned.replace(
388
+ /node_modules\/\.pnpm\/([^\/]+)@[^_]+[^\/]*\/node_modules\/([^\/]+(?:\/[^\/]+)?)\//g,
389
+ '$2/'
390
+ );
391
+
392
+ // Clean up regular node_modules paths for known HK packages
393
+ cleaned = cleaned.replace(
394
+ /node_modules\/@hkdigital\/([^\/]+)\//g,
395
+ '@hkdigital/$1/'
396
+ );
397
+
380
398
  // Clean up query parameters on source files
381
- cleaned = cleaned.replace(/\?t=\d+/g, '');
399
+ cleaned = cleaned.replace(/\?[tv]=[a-f0-9]+/g, '');
382
400
 
383
401
  // Skip vite-deps (Svelte framework internals) but keep other node_modules
384
402
  if (cleaned.includes('node_modules/vite-deps')) {
@@ -404,8 +404,6 @@ export function isMeaningfulFunctionName(functionName) {
404
404
  if (
405
405
  !functionName ||
406
406
  functionName === '' ||
407
- functionName.includes('<') ||
408
- functionName.includes('/') ||
409
407
  functionName.startsWith('async ') ||
410
408
  functionName === 'async' ||
411
409
  functionName === 'Promise' ||
@@ -416,6 +414,13 @@ export function isMeaningfulFunctionName(functionName) {
416
414
  return false;
417
415
  }
418
416
 
417
+ // Skip pure anonymous functions (just '<' without a parent function name)
418
+ if (functionName === '<' || functionName === '</') {
419
+ return false;
420
+ }
421
+
422
+ // Allow function names that contain '/<' (anonymous functions within named functions)
423
+ // These are meaningful as they show "parentFunction/<anonymous>"
419
424
  return true;
420
425
  }
421
426
 
@@ -429,17 +434,35 @@ export function parseFunctionName(frame) {
429
434
  // Handle both Firefox format: "functionName@file:line:col"
430
435
  // and Node.js format: "at functionName (file:line:col)" or "at Module.functionName (file:line:col)"
431
436
 
437
+ let functionName = null;
438
+
432
439
  // Firefox format
433
440
  const firefoxMatch = frame.match(/^([^@]+)@/);
434
441
  if (firefoxMatch) {
435
- return firefoxMatch[1];
442
+ functionName = firefoxMatch[1];
443
+ } else {
444
+ // Node.js format
445
+ const nodeMatch = frame.match(/^\s*at\s+(?:Module\.)?([^\s(]+)/);
446
+ if (nodeMatch) {
447
+ functionName = nodeMatch[1];
448
+ }
436
449
  }
437
450
 
438
- // Node.js format
439
- const nodeMatch = frame.match(/^\s*at\s+(?:Module\.)?([^\s(]+)/);
440
- if (nodeMatch) {
441
- return nodeMatch[1];
451
+ if (!functionName) {
452
+ return null;
442
453
  }
443
454
 
444
- return null;
455
+ // Clean up common patterns
456
+ functionName = functionName.trim();
457
+
458
+ // Handle anonymous functions within named functions
459
+ // Convert "functionName/<" to "functionName (anonymous)"
460
+ if (functionName.endsWith('/<')) {
461
+ const baseName = functionName.slice(0, -2);
462
+ if (baseName) {
463
+ functionName = `${baseName} (anonymous)`;
464
+ }
465
+ }
466
+
467
+ return functionName;
445
468
  }
@@ -200,6 +200,10 @@ export default class Logger extends EventEmitter {
200
200
  // Log without message
201
201
  return this.#log(ERROR, errorObject.message, errorObject);
202
202
  }
203
+ // else if( message ) {
204
+ // // Only log message, no Error object supplied
205
+ // return this.#log(ERROR, message);
206
+ // }
203
207
  else {
204
208
  // Missing error like object
205
209
  // => invalid parameters supplied to logger.error
@@ -66,8 +66,8 @@
66
66
  import { EventEmitter } from '../../generic/events.js';
67
67
  import { Logger, DEBUG, INFO } from '../../logging/index.js';
68
68
 
69
- import {
70
- SERVICE_LOG,
69
+ import {
70
+ SERVICE_LOG,
71
71
  STATE_CHANGED,
72
72
  HEALTH_CHANGED,
73
73
  ERROR,
@@ -298,9 +298,10 @@ export class ServiceManager extends EventEmitter {
298
298
  this.logger.debug(`Starting dependency '${dep}' for '${name}'`);
299
299
 
300
300
  const started = await this.startService(dep);
301
+
301
302
  if (!started) {
302
303
  this.logger.error(
303
- `Failed to start dependency '${dep}' for '${name}'`
304
+ new Error(`Failed to start dependency '${dep}' for '${name}'`)
304
305
  );
305
306
  return false;
306
307
  }
@@ -396,14 +397,7 @@ export class ServiceManager extends EventEmitter {
396
397
  results.set(name, success);
397
398
 
398
399
  if (!success) {
399
- this.logger.error(`Failed to start '${name}', stopping`);
400
- // Mark remaining services as not started
401
- for (const remaining of sorted) {
402
- if (!results.has(remaining)) {
403
- results.set(remaining, false);
404
- }
405
- }
406
- break;
400
+ throw new Error(`Failed to start service [${name}], stopping`);
407
401
  }
408
402
  }
409
403
 
@@ -430,7 +424,7 @@ export class ServiceManager extends EventEmitter {
430
424
  const results = new Map();
431
425
 
432
426
  // Handle global timeout if specified
433
- if (stopOptions.timeout > 0) {
427
+ if (stopOptions.timeout) {
434
428
  const timeoutPromise = new Promise((_, reject) =>
435
429
  setTimeout(
436
430
  () => reject(new Error('Global shutdown timeout')),
@@ -445,8 +439,10 @@ export class ServiceManager extends EventEmitter {
445
439
  timeoutPromise
446
440
  ]);
447
441
  } catch (error) {
448
- if (error.message === 'Global shutdown timeout') {
449
- this.logger.error('Global shutdown timeout reached');
442
+ if (
443
+ /** @type {Error} */ (error).message === 'Global shutdown timeout'
444
+ ) {
445
+ this.logger.error( new Error('Global shutdown timeout reached' ) );
450
446
  // Mark any remaining services as failed
451
447
  for (const name of sorted) {
452
448
  if (!results.has(name)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.4.34",
3
+ "version": "0.4.36",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -27,6 +27,7 @@
27
27
  "format": "prettier --write .",
28
28
  "lint": "run-s lint:*",
29
29
  "test": "run-s test:unit-run",
30
+ "test:file": "vitest run",
30
31
  "prepack": "run-s prepack:*",
31
32
  "publish:npm": "run-s publish:npm:version publish:npm:publish git:push",
32
33
  "upgrade:hk": "run-s upgrade:hk:update pnpm:install",