@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.
- package/dist/auth/jwt/util.js +35 -41
- package/dist/network/loaders/audio/AudioScene.svelte.js +2 -2
- package/dist/network/loaders/image/ImageScene.svelte.js +18 -28
- package/dist/network/states/NetworkLoader.svelte.d.ts +1 -1
- package/dist/network/states/NetworkLoader.svelte.js +2 -2
- package/dist/services/README.md +23 -0
- package/dist/state/classes.d.ts +0 -2
- package/dist/state/classes.js +0 -2
- package/dist/state/{classes → machines}/finite-state-machine/FiniteStateMachine.svelte.d.ts +10 -0
- package/dist/state/{classes → machines}/finite-state-machine/FiniteStateMachine.svelte.js +19 -1
- package/dist/state/machines/finite-state-machine/README.md +545 -0
- package/dist/state/{classes → machines}/finite-state-machine/index.d.ts +1 -1
- package/dist/state/{classes → machines}/finite-state-machine/index.js +1 -1
- package/dist/state/machines/finite-state-machine/typedef.d.ts +29 -0
- package/dist/state/machines/finite-state-machine/typedef.js +28 -0
- package/dist/state/{classes → machines}/loading-state-machine/LoadingStateMachine.svelte.d.ts +0 -2
- package/dist/state/{classes → machines}/loading-state-machine/LoadingStateMachine.svelte.js +7 -27
- package/dist/state/machines/loading-state-machine/README.md +544 -0
- package/dist/state/machines/typedef.d.ts +1 -0
- package/dist/state/machines/typedef.js +1 -0
- package/dist/state/machines.d.ts +2 -0
- package/dist/state/machines.js +2 -0
- package/dist/state/typedef.d.ts +1 -0
- package/dist/state/typedef.js +1 -0
- package/dist/ui/components/game-box/README.md +245 -0
- package/package.json +1 -1
- /package/dist/state/{classes → machines}/loading-state-machine/constants.d.ts +0 -0
- /package/dist/state/{classes → machines}/loading-state-machine/constants.js +0 -0
- /package/dist/state/{classes → machines}/loading-state-machine/index.d.ts +0 -0
- /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';
|
package/dist/state/typedef.d.ts
CHANGED
package/dist/state/typedef.js
CHANGED