@hkdigital/lib-core 0.4.22 → 0.4.24

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/auth/jwt/util.js +35 -41
  2. package/dist/network/loaders/audio/AudioScene.svelte.js +2 -2
  3. package/dist/network/loaders/image/ImageScene.svelte.js +18 -28
  4. package/dist/network/states/NetworkLoader.svelte.d.ts +1 -1
  5. package/dist/network/states/NetworkLoader.svelte.js +2 -2
  6. package/dist/services/README.md +23 -0
  7. package/dist/state/classes.d.ts +0 -2
  8. package/dist/state/classes.js +0 -2
  9. package/dist/state/{classes → machines}/finite-state-machine/FiniteStateMachine.svelte.d.ts +10 -0
  10. package/dist/state/{classes → machines}/finite-state-machine/FiniteStateMachine.svelte.js +19 -1
  11. package/dist/state/machines/finite-state-machine/README.md +545 -0
  12. package/dist/state/{classes → machines}/finite-state-machine/index.d.ts +1 -1
  13. package/dist/state/{classes → machines}/finite-state-machine/index.js +1 -1
  14. package/dist/state/machines/finite-state-machine/typedef.d.ts +29 -0
  15. package/dist/state/machines/finite-state-machine/typedef.js +28 -0
  16. package/dist/state/{classes → machines}/loading-state-machine/LoadingStateMachine.svelte.d.ts +0 -2
  17. package/dist/state/{classes → machines}/loading-state-machine/LoadingStateMachine.svelte.js +7 -27
  18. package/dist/state/machines/loading-state-machine/README.md +544 -0
  19. package/dist/state/machines/typedef.d.ts +1 -0
  20. package/dist/state/machines/typedef.js +1 -0
  21. package/dist/state/machines.d.ts +2 -0
  22. package/dist/state/machines.js +2 -0
  23. package/dist/state/typedef.d.ts +1 -0
  24. package/dist/state/typedef.js +1 -0
  25. package/dist/ui/components/game-box/README.md +245 -0
  26. package/package.json +1 -1
  27. /package/dist/state/{classes → machines}/loading-state-machine/constants.d.ts +0 -0
  28. /package/dist/state/{classes → machines}/loading-state-machine/constants.js +0 -0
  29. /package/dist/state/{classes → machines}/loading-state-machine/index.d.ts +0 -0
  30. /package/dist/state/{classes → machines}/loading-state-machine/index.js +0 -0
@@ -0,0 +1,544 @@
1
+ # LoadingStateMachine
2
+
3
+ A specialized finite state machine designed for managing loading operations with comprehensive state tracking and error handling.
4
+
5
+ ## Overview
6
+
7
+ `LoadingStateMachine` extends `FiniteStateMachine` to provide a standardized way to handle loading workflows. It includes predefined states for initial, loading, loaded, unloading, cancelled, and error conditions.
8
+
9
+ ## States
10
+
11
+ The machine defines six primary states:
12
+
13
+ - **`INITIAL`** - Starting state, ready to begin loading
14
+ - **`LOADING`** - Currently loading data or resources
15
+ - **`LOADED`** - Successfully loaded, data available
16
+ - **`UNLOADING`** - Cleaning up or releasing resources
17
+ - **`CANCELLED`** - Loading operation was cancelled
18
+ - **`ERROR`** - An error occurred during loading
19
+
20
+ ## Events
21
+
22
+ Available events to trigger state transitions:
23
+
24
+ - **`LOAD`** - Start loading operation
25
+ - **`LOADED`** - Mark loading as complete
26
+ - **`UNLOAD`** - Begin cleanup/unloading
27
+ - **`CANCEL`** - Cancel the loading operation
28
+ - **`ERROR`** - Signal an error occurred
29
+ - **`INITIAL`** - Return to initial state
30
+
31
+ ## Basic Usage
32
+
33
+ ```javascript
34
+ import { LoadingStateMachine } from '$lib/state/machines.js';
35
+ import {
36
+ // States
37
+ STATE_INITIAL,
38
+ STATE_LOADING,
39
+ STATE_LOADED,
40
+ STATE_ERROR,
41
+ // Events
42
+ LOAD,
43
+ LOADED,
44
+ ERROR,
45
+ CANCEL
46
+ } from '$lib/state/machines.js';
47
+
48
+ const loader = new LoadingStateMachine();
49
+
50
+ // Check initial state
51
+ console.log(loader.current); // STATE_INITIAL
52
+
53
+ // Start loading
54
+ loader.send(LOAD);
55
+ console.log(loader.current); // STATE_LOADING
56
+
57
+ // Complete successfully
58
+ loader.send(LOADED);
59
+ console.log(loader.current); // STATE_LOADED
60
+ ```
61
+
62
+ ## State Transitions
63
+
64
+ ### Valid Transitions
65
+
66
+ ```
67
+ INITIAL → LOADING
68
+ LOADING → LOADED | CANCELLED | ERROR
69
+ LOADED → LOADING | UNLOADING
70
+ UNLOADING → INITIAL | ERROR
71
+ CANCELLED → LOADING | UNLOADING
72
+ ERROR → LOADING | UNLOADING
73
+ ```
74
+
75
+ ### Transition Diagram
76
+
77
+ ```
78
+ ┌─────────┐
79
+ │ INITIAL │
80
+ └────┬────┘
81
+ │ LOAD
82
+
83
+ ┌─────────┐ CANCEL ┌───────────┐
84
+ │ LOADING │──────────────→│ CANCELLED │
85
+ └────┬────┘ └─────┬─────┘
86
+ │ │
87
+ LOADED│ ERROR LOAD │ UNLOAD
88
+ │ │ │ │
89
+ ▼ ▼ ▼ ▼
90
+ ┌────────┐ ┌───────┐ ┌──────────┐
91
+ │ LOADED │ │ ERROR │ │UNLOADING │
92
+ └────┬───┘ └───┬───┘ └────┬─────┘
93
+ │ │ │
94
+ UNLOAD│ LOAD │ UNLOAD INITIAL│ ERROR
95
+ ▼ ▼ ▼
96
+ ┌──────────┐◄────────────────────┘
97
+ │UNLOADING │
98
+ └──────────┘
99
+ ```
100
+
101
+ ## onenter Callback
102
+
103
+ The `onenter` callback provides a reliable way to react to state changes, designed to work around Svelte's reactive batching behavior.
104
+
105
+ ```javascript
106
+ const loader = new LoadingStateMachine();
107
+
108
+ loader.onenter = (state) => {
109
+ switch (state) {
110
+ case STATE_LOADING:
111
+ console.log('Started loading...');
112
+ showSpinner();
113
+ break;
114
+ case STATE_LOADED:
115
+ console.log('Loading complete!');
116
+ hideSpinner();
117
+ break;
118
+ case STATE_ERROR:
119
+ console.log('Loading failed');
120
+ showError();
121
+ break;
122
+ }
123
+ };
124
+ ```
125
+
126
+ ### Why onenter?
127
+
128
+ The `onenter` callback was added because there might be issues when the stats machine is combined with Svelte's `$effect`. This callback ensures you can reliably respond to every state change.
129
+
130
+ ```javascript
131
+ // Svelte $effect might miss rapid transitions
132
+ $effect(() => {
133
+ console.log(loader.current); // May only see final state
134
+ });
135
+
136
+ // onenter catches every transition
137
+ loader.onenter = (state) => {
138
+ console.log(state); // Sees every state change
139
+ };
140
+ ```
141
+
142
+ ## Error Handling
143
+
144
+ The machine includes sophisticated error handling with automatic error object creation:
145
+
146
+ ```javascript
147
+ const loader = new LoadingStateMachine();
148
+
149
+ // Send error with Error object
150
+ loader.send(ERROR, new Error('Network failed'));
151
+ console.log(loader.error.message); // 'Network failed'
152
+
153
+ // Send error with error details
154
+ loader.send(ERROR, { error: 'Timeout occurred' });
155
+ console.log(loader.error.message); // 'Timeout occurred'
156
+
157
+ // Send error with just a string
158
+ loader.send(ERROR, { error: new Error('Parse error') });
159
+ console.log(loader.error.message); // 'Parse error'
160
+ ```
161
+
162
+ The `error` getter provides access to the last error:
163
+
164
+ ```javascript
165
+ if (loader.current === 'error') {
166
+ console.error('Loading failed:', loader.error);
167
+ }
168
+ ```
169
+
170
+ ## Integration with Svelte Reactivity
171
+
172
+ LoadingStateMachine inherits the `onenter` callback and Svelte reactivity integration patterns from [FiniteStateMachine](../finite-state-machine/README.md#integration-with-svelte-reactivity).
173
+
174
+ ### Quick Reference: Loading State Pattern
175
+
176
+ **✅ Use `onenter` for immediate loading actions:**
177
+ ```javascript
178
+ const loader = new LoadingStateMachine();
179
+
180
+ loader.onenter = (state) => {
181
+ switch (state) {
182
+ case STATE_LOADING:
183
+ this.#startLoading(); // Start async process immediately
184
+ break;
185
+ case STATE_LOADED:
186
+ this.#cleanup(); // Cleanup when complete
187
+ break;
188
+ case STATE_ERROR:
189
+ this.#handleError(); // Handle error state
190
+ break;
191
+ }
192
+ this.state = state; // Update public state
193
+ };
194
+ ```
195
+
196
+ **✅ Use `$effect` for reactive completion monitoring:**
197
+ ```javascript
198
+ // Monitor derived/computed values and trigger transitions when conditions are met
199
+ $effect(() => {
200
+ if (loader.current === STATE_LOADING) {
201
+ // Check completion based on reactive derived values
202
+ if (this.#sourcesLoaded === this.#numberOfSources && this.#numberOfSources > 0) {
203
+ loader.send(LOADED); // Trigger transition when condition met
204
+ }
205
+ }
206
+ });
207
+ ```
208
+
209
+ ### Loading-Specific Example
210
+
211
+ ```javascript
212
+ export default class MultiSourceLoader {
213
+ #loader = new LoadingStateMachine();
214
+ #sources = $state([]);
215
+
216
+ // Derived progress calculation
217
+ #progress = $derived.by(() => {
218
+ let completed = 0;
219
+ for (const source of this.#sources) {
220
+ if (source.loaded) completed++;
221
+ }
222
+ return { completed, total: this.#sources.length };
223
+ });
224
+
225
+ constructor() {
226
+ // Handle immediate loading state actions
227
+ this.#loader.onenter = (state) => {
228
+ switch (state) {
229
+ case STATE_LOADING:
230
+ this.#startAllSources();
231
+ break;
232
+ case STATE_LOADED:
233
+ this.#notifyComplete();
234
+ break;
235
+ case STATE_ERROR:
236
+ this.#handleLoadError();
237
+ break;
238
+ }
239
+ this.state = state;
240
+ };
241
+
242
+ // Monitor reactive completion
243
+ $effect(() => {
244
+ if (this.#loader.current === STATE_LOADING) {
245
+ const { completed, total } = this.#progress;
246
+ if (completed === total && total > 0) {
247
+ this.#loader.send(LOADED);
248
+ }
249
+ }
250
+ });
251
+ }
252
+ }
253
+ ```
254
+
255
+ **📖 For complete documentation** of the `onenter` callback and Svelte reactivity patterns, see the [FiniteStateMachine README](../finite-state-machine/README.md#integration-with-svelte-reactivity).
256
+
257
+ ### Basic Component Integration
258
+
259
+ ```javascript
260
+ // LoadingComponent.svelte
261
+ <script>
262
+ import { LoadingStateMachine } from '$lib/state/machines.js';
263
+ import {
264
+ STATE_LOADING,
265
+ STATE_LOADED,
266
+ STATE_ERROR,
267
+ LOAD,
268
+ LOADED,
269
+ ERROR,
270
+ CANCEL
271
+ } from '$lib/state/machines.js';
272
+
273
+ const loader = new LoadingStateMachine();
274
+ let data = $state(null);
275
+
276
+ loader.onenter = (state) => {
277
+ if (state === STATE_LOADING) {
278
+ loadData();
279
+ }
280
+ };
281
+
282
+ async function loadData() {
283
+ try {
284
+ const response = await fetch('/api/data');
285
+ data = await response.json();
286
+ loader.send(LOADED);
287
+ } catch (error) {
288
+ loader.send(ERROR, error);
289
+ }
290
+ }
291
+ </script>
292
+
293
+ <button onclick={() => loader.send(LOAD)} disabled={loader.current === STATE_LOADING}>
294
+ {#if loader.current === STATE_LOADING}
295
+ Loading...
296
+ {:else}
297
+ Load Data
298
+ {/if}
299
+ </button>
300
+
301
+ {#if loader.current === STATE_LOADED}
302
+ <div>Data loaded successfully!</div>
303
+ {:else if loader.current === STATE_ERROR}
304
+ <div>Error: {loader.error.message}</div>
305
+ {/if}
306
+ ```
307
+
308
+ ### Advanced Component with Cancellation
309
+
310
+ ```javascript
311
+ <script>
312
+ import { LoadingStateMachine } from '$lib/state/machines.js';
313
+ import {
314
+ STATE_INITIAL,
315
+ STATE_LOADING,
316
+ STATE_LOADED,
317
+ STATE_CANCELLED,
318
+ STATE_ERROR,
319
+ LOAD,
320
+ LOADED,
321
+ ERROR,
322
+ CANCEL
323
+ } from '$lib/state/machines.js';
324
+
325
+ const loader = new LoadingStateMachine();
326
+ let abortController = null;
327
+
328
+ loader.onenter = (state) => {
329
+ switch (state) {
330
+ case STATE_LOADING:
331
+ startLoad();
332
+ break;
333
+ case STATE_CANCELLED:
334
+ abortController?.abort();
335
+ break;
336
+ }
337
+ };
338
+
339
+ async function startLoad() {
340
+ abortController = new AbortController();
341
+
342
+ try {
343
+ const response = await fetch('/api/slow-data', {
344
+ signal: abortController.signal
345
+ });
346
+ const data = await response.json();
347
+ loader.send(LOADED);
348
+ } catch (error) {
349
+ if (error.name === 'AbortError') {
350
+ // Request was cancelled, machine already in cancelled state
351
+ } else {
352
+ loader.send(ERROR, error);
353
+ }
354
+ }
355
+ }
356
+ </script>
357
+
358
+ <div>
359
+ {#if loader.current === STATE_INITIAL}
360
+ <button onclick={() => loader.send(LOAD)}>Start Loading</button>
361
+ {:else if loader.current === STATE_LOADING}
362
+ <button onclick={() => loader.send(CANCEL)}>Cancel Loading</button>
363
+ <div>Loading data...</div>
364
+ {:else if loader.current === STATE_LOADED}
365
+ <div>Data loaded successfully!</div>
366
+ <button onclick={() => loader.send(LOAD)}>Reload</button>
367
+ {:else if loader.current === STATE_CANCELLED}
368
+ <div>Loading cancelled</div>
369
+ <button onclick={() => loader.send(LOAD)}>Try Again</button>
370
+ {:else if loader.current === STATE_ERROR}
371
+ <div>Error: {loader.error.message}</div>
372
+ <button onclick={() => loader.send(LOAD)}>Retry</button>
373
+ {/if}
374
+ </div>
375
+ ```
376
+
377
+ ## Best Practices
378
+
379
+ ### 1. Use onenter for Side Effects
380
+
381
+ ```javascript
382
+ // ✅ Good - use onenter for reliable side effects
383
+ loader.onenter = (state) => {
384
+ if (state === STATE_LOADING) {
385
+ analytics.track('loading_started');
386
+ }
387
+ };
388
+
389
+ // ❌ Avoid - $effect may miss rapid transitions
390
+ $effect(() => {
391
+ if (loader.current === STATE_LOADING) {
392
+ analytics.track('loading_started'); // May not fire
393
+ }
394
+ });
395
+ ```
396
+
397
+ ### 2. Handle All Error States
398
+
399
+ ```javascript
400
+ loader.onenter = (state) => {
401
+ switch (state) {
402
+ case STATE_ERROR:
403
+ showErrorToast(loader.error.message);
404
+ logError(loader.error);
405
+ break;
406
+ case STATE_CANCELLED:
407
+ showMessage('Operation cancelled');
408
+ break;
409
+ }
410
+ };
411
+ ```
412
+
413
+ ### 3. Implement Proper Cleanup
414
+
415
+ ```javascript
416
+ loader.onenter = (state) => {
417
+ switch (state) {
418
+ case STATE_LOADING:
419
+ showProgressBar();
420
+ break;
421
+ case STATE_LOADED:
422
+ case STATE_ERROR:
423
+ case STATE_CANCELLED:
424
+ hideProgressBar();
425
+ break;
426
+ case STATE_UNLOADING:
427
+ cleanup();
428
+ break;
429
+ }
430
+ };
431
+ ```
432
+
433
+ ### 4. Use Constants for Events
434
+
435
+ ```javascript
436
+ import {
437
+ LOAD,
438
+ LOADED,
439
+ ERROR
440
+ } from '$lib/state/machines.js';
441
+
442
+ // ✅ Good - type safe and refactor friendly
443
+ loader.send(LOAD);
444
+
445
+ // ❌ Avoid - prone to typos
446
+ loader.send('load');
447
+ ```
448
+
449
+ ## Common Patterns
450
+
451
+ ### Resource Loading with Cleanup
452
+
453
+ ```javascript
454
+ const resourceLoader = new LoadingStateMachine();
455
+ let resource = null;
456
+
457
+ resourceLoader.onenter = async (state) => {
458
+ switch (state) {
459
+ case STATE_LOADING:
460
+ try {
461
+ resource = await loadResource();
462
+ resourceLoader.send(LOADED);
463
+ } catch (error) {
464
+ resourceLoader.send(ERROR, error);
465
+ }
466
+ break;
467
+
468
+ case STATE_UNLOADING:
469
+ if (resource) {
470
+ await resource.cleanup();
471
+ resource = null;
472
+ }
473
+ resourceLoader.send(INITIAL);
474
+ break;
475
+ }
476
+ };
477
+ ```
478
+
479
+ ### Retry Logic
480
+
481
+ ```javascript
482
+ const retryLoader = new LoadingStateMachine();
483
+ let retryCount = 0;
484
+ const maxRetries = 3;
485
+
486
+ retryLoader.onenter = async (state) => {
487
+ switch (state) {
488
+ case STATE_LOADING:
489
+ try {
490
+ await performLoad();
491
+ retryCount = 0; // Reset on success
492
+ retryLoader.send(LOADED);
493
+ } catch (error) {
494
+ retryLoader.send(ERROR, error);
495
+ }
496
+ break;
497
+
498
+ case STATE_ERROR:
499
+ if (retryCount < maxRetries) {
500
+ retryCount++;
501
+ setTimeout(() => {
502
+ retryLoader.send(LOAD);
503
+ }, 1000 * retryCount); // Exponential backoff
504
+ }
505
+ break;
506
+ }
507
+ };
508
+ ```
509
+
510
+ ## Testing
511
+
512
+ The LoadingStateMachine includes comprehensive tests covering:
513
+
514
+ - All state transitions
515
+ - onenter callback functionality
516
+ - Error handling with different error types
517
+ - Null safety when no callback is set
518
+ - Dynamic callback changes
519
+
520
+ See `LoadingStateMachine.test.js` for detailed examples.
521
+
522
+ ## Constants
523
+
524
+ Import state and event constants from the constants file:
525
+
526
+ ```javascript
527
+ import {
528
+ // States
529
+ STATE_INITIAL,
530
+ STATE_LOADING,
531
+ STATE_LOADED,
532
+ STATE_UNLOADING,
533
+ STATE_CANCELLED,
534
+ STATE_ERROR,
535
+
536
+ // Events
537
+ LOAD,
538
+ LOADED,
539
+ UNLOAD,
540
+ CANCEL,
541
+ ERROR,
542
+ INITIAL
543
+ } from './constants.js';
544
+ ```
@@ -0,0 +1 @@
1
+ export * from "./finite-state-machine/typedef.js";
@@ -0,0 +1 @@
1
+ export * from './finite-state-machine/typedef.js';
@@ -0,0 +1,2 @@
1
+ export * from "./machines/finite-state-machine/index.js";
2
+ export * from "./machines/loading-state-machine/index.js";
@@ -0,0 +1,2 @@
1
+ export * from './machines/finite-state-machine/index.js';
2
+ export * from './machines/loading-state-machine/index.js';
@@ -1,3 +1,4 @@
1
1
  export * from "./context/typedef.js";
2
+ export * from "./machines/typedef.js";
2
3
  declare const _default: {};
3
4
  export default _default;
@@ -1,3 +1,4 @@
1
1
  export * from './context/typedef.js';
2
+ export * from './machines/typedef.js';
2
3
 
3
4
  export default {};