@hkdigital/lib-core 0.4.24 → 0.4.26

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 (30) hide show
  1. package/dist/logging/internal/adapters/pino.d.ts +7 -3
  2. package/dist/logging/internal/adapters/pino.js +200 -67
  3. package/dist/logging/internal/transports/pretty-transport.d.ts +17 -0
  4. package/dist/logging/internal/transports/pretty-transport.js +104 -0
  5. package/dist/logging/internal/transports/test-transport.d.ts +19 -0
  6. package/dist/logging/internal/transports/test-transport.js +79 -0
  7. package/dist/network/loaders/audio/AudioScene.svelte.d.ts +19 -10
  8. package/dist/network/loaders/audio/AudioScene.svelte.js +50 -75
  9. package/dist/network/loaders/image/ImageScene.svelte.d.ts +13 -13
  10. package/dist/network/loaders/image/ImageScene.svelte.js +56 -83
  11. package/dist/network/states/NetworkLoader.svelte.d.ts +6 -0
  12. package/dist/network/states/NetworkLoader.svelte.js +15 -6
  13. package/dist/services/service-base/ServiceBase.d.ts +12 -8
  14. package/dist/services/service-base/ServiceBase.js +8 -6
  15. package/dist/state/machines/finite-state-machine/FiniteStateMachine.svelte.d.ts +5 -9
  16. package/dist/state/machines/finite-state-machine/FiniteStateMachine.svelte.js +62 -32
  17. package/dist/state/machines/finite-state-machine/README.md +48 -46
  18. package/dist/state/machines/finite-state-machine/constants.d.ts +13 -0
  19. package/dist/state/machines/finite-state-machine/constants.js +15 -0
  20. package/dist/state/machines/finite-state-machine/index.d.ts +1 -0
  21. package/dist/state/machines/finite-state-machine/index.js +1 -0
  22. package/dist/state/machines/finite-state-machine/typedef.d.ts +3 -3
  23. package/dist/state/machines/finite-state-machine/typedef.js +21 -15
  24. package/dist/state/machines/loading-state-machine/LoadingStateMachine.svelte.d.ts +12 -0
  25. package/dist/state/machines/loading-state-machine/LoadingStateMachine.svelte.js +27 -2
  26. package/dist/state/machines/loading-state-machine/README.md +89 -41
  27. package/dist/state/machines/loading-state-machine/constants.d.ts +2 -0
  28. package/dist/state/machines/loading-state-machine/constants.js +2 -0
  29. package/package.json +1 -1
  30. package/dist/logging/internal/adapters/pino.js__ +0 -260
@@ -8,7 +8,7 @@ A specialized finite state machine designed for managing loading operations with
8
8
 
9
9
  ## States
10
10
 
11
- The machine defines six primary states:
11
+ The machine defines seven primary states:
12
12
 
13
13
  - **`INITIAL`** - Starting state, ready to begin loading
14
14
  - **`LOADING`** - Currently loading data or resources
@@ -16,6 +16,7 @@ The machine defines six primary states:
16
16
  - **`UNLOADING`** - Cleaning up or releasing resources
17
17
  - **`CANCELLED`** - Loading operation was cancelled
18
18
  - **`ERROR`** - An error occurred during loading
19
+ - **`TIMEOUT`** - Loading operation exceeded time limit
19
20
 
20
21
  ## Events
21
22
 
@@ -26,6 +27,7 @@ Available events to trigger state transitions:
26
27
  - **`UNLOAD`** - Begin cleanup/unloading
27
28
  - **`CANCEL`** - Cancel the loading operation
28
29
  - **`ERROR`** - Signal an error occurred
30
+ - **`TIMEOUT`** - Signal operation has timed out
29
31
  - **`INITIAL`** - Return to initial state
30
32
 
31
33
  ## Basic Usage
@@ -65,35 +67,36 @@ console.log(loader.current); // STATE_LOADED
65
67
 
66
68
  ```
67
69
  INITIAL → LOADING
68
- LOADING → LOADED | CANCELLED | ERROR
70
+ LOADING → LOADED | CANCELLED | ERROR | TIMEOUT
69
71
  LOADED → LOADING | UNLOADING
70
72
  UNLOADING → INITIAL | ERROR
71
73
  CANCELLED → LOADING | UNLOADING
72
74
  ERROR → LOADING | UNLOADING
75
+ TIMEOUT → LOADING | UNLOADING
73
76
  ```
74
77
 
75
78
  ### Transition Diagram
76
79
 
77
80
  ```
78
- ┌─────────┐
79
- │ INITIAL │
80
- └────┬────┘
81
- │ LOAD
82
-
81
+ ┌─────────┐
82
+ │ INITIAL │
83
+ └────┬────┘
84
+ │ LOAD
85
+
83
86
  ┌─────────┐ CANCEL ┌───────────┐
84
87
  │ LOADING │──────────────→│ CANCELLED │
85
88
  └────┬────┘ └─────┬─────┘
86
89
  │ │
87
- LOADED│ ERROR LOAD │ UNLOAD
88
- │ │ │ │
89
- ▼ ▼ ▼ ▼
90
- ┌────────┐ ┌───────┐ ┌──────────┐
91
- │ LOADED │ │ ERROR │ │UNLOADING │
92
- └────┬───┘ └───┬───┘ └────┬─────┘
93
- │ │
94
- UNLOAD│ LOAD │ UNLOAD INITIAL│ ERROR
95
- ▼ ▼
96
- ┌──────────┐◄────────────────────┘
90
+ LOADED│ ERROR TIMEOUT LOAD │ UNLOAD
91
+ │ │
92
+ ▼ ▼
93
+ ┌────────┐ ┌───────┐ ┌─────────┐ ┌──────────┐
94
+ │ LOADED │ │ ERROR │ TIMEOUT │ │UNLOADING │
95
+ └────┬───┘ └───┬───┘ └────┬────┘ └────┬─────┘
96
+ │ │
97
+ UNLOAD│ LOAD │ UNLOAD LOAD│ UNLOAD INITIAL│ ERROR
98
+ ▼ ▼
99
+ ┌──────────┐◄──────────────────────────┘
97
100
  │UNLOADING │
98
101
  └──────────┘
99
102
  ```
@@ -105,8 +108,8 @@ The `onenter` callback provides a reliable way to react to state changes, design
105
108
  ```javascript
106
109
  const loader = new LoadingStateMachine();
107
110
 
108
- loader.onenter = (state) => {
109
- switch (state) {
111
+ loader.onenter = (currentState) => {
112
+ switch (currentState) {
110
113
  case STATE_LOADING:
111
114
  console.log('Started loading...');
112
115
  showSpinner();
@@ -119,6 +122,10 @@ loader.onenter = (state) => {
119
122
  console.log('Loading failed');
120
123
  showError();
121
124
  break;
125
+ case STATE_TIMEOUT:
126
+ console.log('Loading timed out');
127
+ showRetryOption();
128
+ break;
122
129
  }
123
130
  };
124
131
  ```
@@ -134,8 +141,8 @@ $effect(() => {
134
141
  });
135
142
 
136
143
  // onenter catches every transition
137
- loader.onenter = (state) => {
138
- console.log(state); // Sees every state change
144
+ loader.onenter = (currentState) => {
145
+ console.log(currentState); // Sees every state change
139
146
  };
140
147
  ```
141
148
 
@@ -167,6 +174,42 @@ if (loader.current === 'error') {
167
174
  }
168
175
  ```
169
176
 
177
+ ## Timeout Handling
178
+
179
+ The machine supports timeout functionality for managing loading operations that exceed expected duration:
180
+
181
+ ```javascript
182
+ const loader = new LoadingStateMachine();
183
+
184
+ // External timeout management
185
+ const timeoutId = setTimeout(() => {
186
+ if (loader.current === STATE_LOADING) {
187
+ loader.doTimeout(); // Transitions to STATE_TIMEOUT
188
+ }
189
+ }, 10000); // 10 second timeout
190
+
191
+ loader.onenter = (currentState) => {
192
+ switch (currentState) {
193
+ case STATE_TIMEOUT:
194
+ console.log('Loading timed out');
195
+ showRetryButton();
196
+ break;
197
+ case STATE_LOADED:
198
+ clearTimeout(timeoutId); // Cancel timeout on success
199
+ break;
200
+ }
201
+ };
202
+ ```
203
+
204
+ ### doTimeout Method
205
+
206
+ Use `doTimeout()` to manually trigger a timeout transition:
207
+
208
+ ```javascript
209
+ // Only works when in STATE_LOADING
210
+ loader.doTimeout(); // Sends TIMEOUT signal
211
+ ```
212
+
170
213
  ## Integration with Svelte Reactivity
171
214
 
172
215
  LoadingStateMachine inherits the `onenter` callback and Svelte reactivity integration patterns from [FiniteStateMachine](../finite-state-machine/README.md#integration-with-svelte-reactivity).
@@ -177,8 +220,8 @@ LoadingStateMachine inherits the `onenter` callback and Svelte reactivity integr
177
220
  ```javascript
178
221
  const loader = new LoadingStateMachine();
179
222
 
180
- loader.onenter = (state) => {
181
- switch (state) {
223
+ loader.onenter = (currentState) => {
224
+ switch (currentState) {
182
225
  case STATE_LOADING:
183
226
  this.#startLoading(); // Start async process immediately
184
227
  break;
@@ -224,8 +267,8 @@ export default class MultiSourceLoader {
224
267
 
225
268
  constructor() {
226
269
  // Handle immediate loading state actions
227
- this.#loader.onenter = (state) => {
228
- switch (state) {
270
+ this.#loader.onenter = (currentState) => {
271
+ switch (currentState) {
229
272
  case STATE_LOADING:
230
273
  this.#startAllSources();
231
274
  break;
@@ -235,8 +278,11 @@ export default class MultiSourceLoader {
235
278
  case STATE_ERROR:
236
279
  this.#handleLoadError();
237
280
  break;
281
+ case STATE_TIMEOUT:
282
+ this.#handleTimeout();
283
+ break;
238
284
  }
239
- this.state = state;
285
+ this.state = currentState;
240
286
  };
241
287
 
242
288
  // Monitor reactive completion
@@ -273,8 +319,8 @@ export default class MultiSourceLoader {
273
319
  const loader = new LoadingStateMachine();
274
320
  let data = $state(null);
275
321
 
276
- loader.onenter = (state) => {
277
- if (state === STATE_LOADING) {
322
+ loader.onenter = (currentState) => {
323
+ if (currentState === STATE_LOADING) {
278
324
  loadData();
279
325
  }
280
326
  };
@@ -325,8 +371,8 @@ export default class MultiSourceLoader {
325
371
  const loader = new LoadingStateMachine();
326
372
  let abortController = null;
327
373
 
328
- loader.onenter = (state) => {
329
- switch (state) {
374
+ loader.onenter = (currentState) => {
375
+ switch (currentState) {
330
376
  case STATE_LOADING:
331
377
  startLoad();
332
378
  break;
@@ -380,8 +426,8 @@ export default class MultiSourceLoader {
380
426
 
381
427
  ```javascript
382
428
  // ✅ Good - use onenter for reliable side effects
383
- loader.onenter = (state) => {
384
- if (state === STATE_LOADING) {
429
+ loader.onenter = (currentState) => {
430
+ if (currentState === STATE_LOADING) {
385
431
  analytics.track('loading_started');
386
432
  }
387
433
  };
@@ -397,8 +443,8 @@ $effect(() => {
397
443
  ### 2. Handle All Error States
398
444
 
399
445
  ```javascript
400
- loader.onenter = (state) => {
401
- switch (state) {
446
+ loader.onenter = (currentState) => {
447
+ switch (currentState) {
402
448
  case STATE_ERROR:
403
449
  showErrorToast(loader.error.message);
404
450
  logError(loader.error);
@@ -413,8 +459,8 @@ loader.onenter = (state) => {
413
459
  ### 3. Implement Proper Cleanup
414
460
 
415
461
  ```javascript
416
- loader.onenter = (state) => {
417
- switch (state) {
462
+ loader.onenter = (currentState) => {
463
+ switch (currentState) {
418
464
  case STATE_LOADING:
419
465
  showProgressBar();
420
466
  break;
@@ -454,8 +500,8 @@ loader.send('load');
454
500
  const resourceLoader = new LoadingStateMachine();
455
501
  let resource = null;
456
502
 
457
- resourceLoader.onenter = async (state) => {
458
- switch (state) {
503
+ resourceLoader.onenter = async (currentState) => {
504
+ switch (currentState) {
459
505
  case STATE_LOADING:
460
506
  try {
461
507
  resource = await loadResource();
@@ -483,8 +529,8 @@ const retryLoader = new LoadingStateMachine();
483
529
  let retryCount = 0;
484
530
  const maxRetries = 3;
485
531
 
486
- retryLoader.onenter = async (state) => {
487
- switch (state) {
532
+ retryLoader.onenter = async (currentState) => {
533
+ switch (currentState) {
488
534
  case STATE_LOADING:
489
535
  try {
490
536
  await performLoad();
@@ -532,6 +578,7 @@ import {
532
578
  STATE_UNLOADING,
533
579
  STATE_CANCELLED,
534
580
  STATE_ERROR,
581
+ STATE_TIMEOUT,
535
582
 
536
583
  // Events
537
584
  LOAD,
@@ -539,6 +586,7 @@ import {
539
586
  UNLOAD,
540
587
  CANCEL,
541
588
  ERROR,
542
- INITIAL
589
+ INITIAL,
590
+ TIMEOUT
543
591
  } from './constants.js';
544
592
  ```
@@ -4,9 +4,11 @@ export const STATE_UNLOADING: "unloading";
4
4
  export const STATE_LOADED: "loaded";
5
5
  export const STATE_CANCELLED: "cancelled";
6
6
  export const STATE_ERROR: "error";
7
+ export const STATE_TIMEOUT: "timeout";
7
8
  export const INITIAL: "initial";
8
9
  export const LOAD: "load";
9
10
  export const CANCEL: "cancel";
10
11
  export const ERROR: "error";
11
12
  export const LOADED: "loaded";
12
13
  export const UNLOAD: "unload";
14
+ export const TIMEOUT: "timeout";
@@ -5,6 +5,7 @@ export const STATE_LOADED = 'loaded';
5
5
 
6
6
  export const STATE_CANCELLED = 'cancelled';
7
7
  export const STATE_ERROR = 'error';
8
+ export const STATE_TIMEOUT = 'timeout';
8
9
 
9
10
  // > Signals
10
11
 
@@ -14,3 +15,4 @@ export const CANCEL = 'cancel';
14
15
  export const ERROR = 'error';
15
16
  export const LOADED = 'loaded';
16
17
  export const UNLOAD = 'unload';
18
+ export const TIMEOUT = 'timeout';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.4.24",
3
+ "version": "0.4.26",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"
@@ -1,260 +0,0 @@
1
- /**
2
- * Pino adapter for server-side logging
3
- */
4
- import pino from 'pino';
5
- import { dev } from '$app/environment';
6
-
7
- import {
8
- detectErrorMeta,
9
- findRelevantFrameIndex,
10
- formatErrorDisplay
11
- } from './formatting.js';
12
-
13
- /**
14
- * Pino adapter that bridges Logger events to pino
15
- */
16
- export class PinoAdapter {
17
- #projectRoot = null;
18
-
19
- /**
20
- * Create a new PinoAdapter
21
- *
22
- * @param {Object} [options] - Pino configuration options
23
- */
24
- constructor(options = {}) {
25
- // Determine project root once for stack trace cleaning
26
- this.#projectRoot = import.meta.env.VITE_PROJECT_ROOT || process.cwd();
27
- const baseOptions = {
28
- serializers: {
29
- errors: (err) => {
30
-
31
- /** @type {import('./typedef').ErrorSummary[]} */
32
- const chain = [];
33
- let loggedAt = null;
34
-
35
- let current = err;
36
- let isFirst = true;
37
-
38
- while (current && current instanceof Error) {
39
- // Check if this is the first error and it's a LoggerError - extract logging context
40
- if (isFirst && current.name === 'LoggerError') {
41
- if (current.stack) {
42
- const cleanedStackString = this.#cleanStackTrace(current.stack);
43
- const cleanedStackArray = cleanedStackString
44
- .split('\n')
45
- .map((line) => line.trim())
46
- .filter(
47
- (line) =>
48
- line && line !== current.name + ': ' + current.message
49
- );
50
-
51
- // For LoggerError, we know it's a logger.error call, so find the relevant frame
52
- const loggerErrorIndex = cleanedStackArray.findIndex(frame =>
53
- (frame.includes('Logger.error') && frame.includes('logger/Logger.js')) ||
54
- (frame.includes('error@') && frame.includes('logger/Logger.js'))
55
- );
56
-
57
- if (loggerErrorIndex >= 0 && loggerErrorIndex + 1 < cleanedStackArray.length) {
58
- const relevantFrame = cleanedStackArray[loggerErrorIndex + 1];
59
-
60
- // Extract function name from the relevant frame
61
- // const functionName = parseFunctionName(relevantFrame);
62
-
63
- // const errorType = functionName ? `logger.error in ${functionName}` : 'logger.error';
64
- loggedAt = relevantFrame.slice(3); // remove "at "
65
- }
66
- }
67
-
68
- // Skip the LoggerError and move to the actual error
69
- current = current.cause;
70
- isFirst = false;
71
- continue;
72
- }
73
- /** @type {import('./typedef').ErrorSummary} */
74
- const serialized = {
75
- name: current.name,
76
- message: current.message
77
- };
78
-
79
- // Add error metadata for structured logging and terminal display
80
- if (current.stack) {
81
- // Convert cleaned stack string to array format expected by formatting functions
82
- const cleanedStackString = this.#cleanStackTrace(current.stack);
83
- const cleanedStackArray = cleanedStackString
84
- .split('\n')
85
- .map((line) => line.trim())
86
- .filter(
87
- (line) =>
88
- line && line !== current.name + ': ' + current.message
89
- );
90
-
91
- const errorMeta = detectErrorMeta(current, cleanedStackArray);
92
- const relevantFrameIndex = findRelevantFrameIndex(
93
- current,
94
- cleanedStackArray
95
- );
96
-
97
- serialized.meta = errorMeta;
98
- serialized.errorType = formatErrorDisplay(errorMeta);
99
-
100
- // Include stack frames for terminal display
101
- serialized.stackFrames = cleanedStackArray
102
- .slice(0, 9)
103
- .map((frame, index) => {
104
- const marker = index === relevantFrameIndex ? '→' : ' ';
105
-
106
- return `${marker} ${frame}`;
107
- });
108
- }
109
-
110
- // Include HttpError-specific properties
111
- const httpError = /** @type {import('$lib/network/errors.js').HttpError} */ (current);
112
- if (httpError.status !== undefined) {
113
- serialized.status = httpError.status;
114
- }
115
- if (httpError.details !== undefined) {
116
- serialized.details = httpError.details;
117
- }
118
-
119
- chain.push(serialized);
120
- current = current.cause;
121
- isFirst = false;
122
- }
123
-
124
- return loggedAt ? { chain, loggedAt } : chain;
125
- }
126
- }
127
- };
128
-
129
- // Add error handling for missing pino-pretty in dev
130
- if ( dev) {
131
- const devOptions = {
132
- level: 'debug',
133
- transport: {
134
- target: 'pino-pretty',
135
- options: {
136
- colorize: true,
137
- ignore: 'hostname,pid'
138
- }
139
- }
140
- };
141
-
142
- try {
143
- this.pino = pino({ ...baseOptions, ...devOptions, ...options });
144
- } catch (error) {
145
- if (
146
- error.message.includes('Cannot find module') &&
147
- error.message.includes('pino-pretty')
148
- ) {
149
- const errorMessage = `
150
- ╭─────────────────────────────────────────────────────────────╮
151
- │ Missing Dependency │
152
- ├─────────────────────────────────────────────────────────────┤
153
- │ 'pino-pretty' is required for development logging │
154
- │ Install it with: pnpm add -D pino-pretty │
155
- ╰─────────────────────────────────────────────────────────────╯`;
156
- console.error(errorMessage);
157
- throw new Error('pino-pretty is required for development mode');
158
- }
159
- throw error;
160
- }
161
- } else {
162
- this.pino = pino({ ...baseOptions, ...options });
163
- }
164
- }
165
-
166
- /**
167
- * Clean stack trace by removing project root path and simplifying node_modules
168
- *
169
- * @param {string} stack - Original stack trace
170
- * @returns {string} Cleaned stack trace
171
- */
172
- #cleanStackTrace(stack) {
173
- if (!stack || !this.#projectRoot) {
174
- return stack;
175
- }
176
-
177
- let cleaned = stack;
178
-
179
- // Escape special regex characters in the project root path
180
- const escapedRoot = this.#projectRoot.replace(
181
- /[.*+?^${}()|[\]\\]/g,
182
- '\\$&'
183
- );
184
-
185
- // Replace project root path with relative path, handling file:// protocol
186
- // Match both regular paths and file:// URLs
187
- const rootRegex = new RegExp(
188
- `(\\s+at\\s+.*\\()(file://)?${escapedRoot}[\\/\\\\]`,
189
- 'g'
190
- );
191
- cleaned = cleaned.replace(rootRegex, '$1');
192
-
193
- // Simplify pnpm paths: node_modules/.pnpm/package@version_deps/node_modules/package
194
- // becomes: node_modules/package
195
- const pnpmRegex =
196
- /node_modules\/\.pnpm\/([^@\/]+)@[^\/]+\/node_modules\/\1/g;
197
- cleaned = cleaned.replace(pnpmRegex, 'node_modules/$1');
198
-
199
- // Also handle cases where the package name might be different in the final path
200
- const pnpmRegex2 = /node_modules\/\.pnpm\/[^\/]+\/node_modules\/([^\/]+)/g;
201
- cleaned = cleaned.replace(pnpmRegex2, 'node_modules/$1');
202
-
203
- return cleaned;
204
- }
205
-
206
- /**
207
- * Handle log events from Logger
208
- *
209
- * @param {Object} logEvent - Log event from Logger
210
- */
211
- handleLog(logEvent) {
212
- const { level, message, details, source, timestamp } = logEvent;
213
-
214
- const logData = {
215
- source,
216
- timestamp
217
- };
218
-
219
- // Check if details contains an error and promote it to err property for pino serializer
220
- if (details) {
221
- if (details instanceof Error) {
222
- // details is directly an error
223
- logData.err = details;
224
- } else if (details.error instanceof Error) {
225
- // details has an error property
226
- logData.err = details.error;
227
- // Include other details except the error
228
- // eslint-disable-next-line no-unused-vars
229
- const { error, ...otherDetails } = details;
230
- if (Object.keys(otherDetails).length > 0) {
231
- logData.details = otherDetails;
232
- }
233
- } else {
234
- // No error found in details, include all details
235
- logData.details = details;
236
- }
237
- }
238
-
239
- // Check if we have loggedAt info from the serializer
240
- if (logData.err && typeof logData.err === 'object' && logData.err.loggedAt) {
241
- logData.loggedAt = logData.err.loggedAt;
242
- logData.err = logData.err.chain;
243
- }
244
-
245
- this.pino[level](logData, message);
246
- }
247
-
248
- /**
249
- * Create a child logger with additional context
250
- *
251
- * @param {Object} context - Additional context data
252
- * @returns {PinoAdapter} New adapter instance with context
253
- */
254
- child(context) {
255
- const childPino = this.pino.child(context);
256
- const adapter = new PinoAdapter();
257
- adapter.pino = childPino;
258
- return adapter;
259
- }
260
- }