@hkdigital/lib-core 0.4.62 → 0.4.64
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/generic/errors/generic.d.ts +8 -6
- package/dist/generic/errors/generic.js +8 -6
- package/dist/logging/internal/adapters/formatting.js +3 -0
- package/dist/network/errors/api.d.ts +5 -6
- package/dist/network/errors/api.js +7 -5
- package/dist/network/http/http-request.js +2 -1
- package/dist/network/http/json-request.js +10 -15
- package/dist/network/http/response.js +4 -4
- package/dist/network/loaders/base/SceneBase.svelte.js +123 -42
- package/dist/util/svelte/wait/index.js +3 -1
- package/package.json +1 -1
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
export class TypeOrValueError extends Error {
|
|
2
|
-
}
|
|
3
|
-
export class InternalError extends Error {
|
|
4
|
-
}
|
|
5
|
-
export class InternalEventOrLogError extends Error {
|
|
6
|
-
}
|
|
7
1
|
export class DetailedError extends Error {
|
|
8
2
|
/**
|
|
9
3
|
* @param {string} [message]
|
|
@@ -16,3 +10,11 @@ export class DetailedError extends Error {
|
|
|
16
10
|
} | null;
|
|
17
11
|
cause: unknown;
|
|
18
12
|
}
|
|
13
|
+
export class TypeOrValueError extends Error {
|
|
14
|
+
}
|
|
15
|
+
export class InternalError extends Error {
|
|
16
|
+
}
|
|
17
|
+
export class InternalEventOrLogError extends Error {
|
|
18
|
+
}
|
|
19
|
+
export class TimeoutError extends DetailedError {
|
|
20
|
+
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
export class TypeOrValueError extends Error {}
|
|
2
|
-
|
|
3
|
-
export class InternalError extends Error {}
|
|
4
|
-
|
|
5
|
-
export class InternalEventOrLogError extends Error {}
|
|
6
|
-
|
|
7
1
|
export class DetailedError extends Error
|
|
8
2
|
{
|
|
9
3
|
/**
|
|
@@ -28,3 +22,11 @@ export class DetailedError extends Error
|
|
|
28
22
|
}
|
|
29
23
|
}
|
|
30
24
|
}
|
|
25
|
+
|
|
26
|
+
export class TypeOrValueError extends Error {}
|
|
27
|
+
|
|
28
|
+
export class InternalError extends Error {}
|
|
29
|
+
|
|
30
|
+
export class InternalEventOrLogError extends Error {}
|
|
31
|
+
|
|
32
|
+
export class TimeoutError extends DetailedError {}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
export class ResponseError extends
|
|
1
|
+
export class ResponseError extends DetailedError {
|
|
2
2
|
}
|
|
3
|
-
export class AuthenticationError extends
|
|
3
|
+
export class AuthenticationError extends DetailedError {
|
|
4
4
|
}
|
|
5
|
-
export class BadRequestError extends
|
|
5
|
+
export class BadRequestError extends DetailedError {
|
|
6
6
|
}
|
|
7
|
-
export class AbortError extends
|
|
8
|
-
}
|
|
9
|
-
export class TimeoutError extends Error {
|
|
7
|
+
export class AbortError extends DetailedError {
|
|
10
8
|
}
|
|
9
|
+
import { DetailedError } from '../../generic/errors.js';
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { DetailedError } from '../../generic/errors.js';
|
|
2
2
|
|
|
3
|
-
export class
|
|
3
|
+
export class ResponseError extends DetailedError {}
|
|
4
4
|
|
|
5
|
-
export class
|
|
5
|
+
export class AuthenticationError extends DetailedError {}
|
|
6
6
|
|
|
7
|
-
export class
|
|
7
|
+
export class BadRequestError extends DetailedError {}
|
|
8
8
|
|
|
9
|
-
export class
|
|
9
|
+
export class AbortError extends DetailedError {}
|
|
10
|
+
|
|
11
|
+
// @note import TimeoutError from '../../generic/errors.js';
|
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
import { APPLICATION_JSON } from '../../constants/mime/application.js';
|
|
12
12
|
import { CONTENT_TYPE } from '../../constants/http/headers.js';
|
|
13
13
|
|
|
14
|
-
import { AbortError
|
|
14
|
+
import { AbortError } from '../errors/api.js';
|
|
15
|
+
import { TimeoutError } from '../../generic/errors.js';
|
|
15
16
|
|
|
16
17
|
import * as expect from '../../util/expect.js';
|
|
17
18
|
|
|
@@ -93,9 +93,8 @@ export async function jsonGet(options) {
|
|
|
93
93
|
} catch (e) {
|
|
94
94
|
throw new ResponseError(
|
|
95
95
|
`Failed to JSON decode server response from [${decodeURI(url.href)}]`,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
96
|
+
null,
|
|
97
|
+
e
|
|
99
98
|
);
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -206,9 +205,8 @@ export async function jsonPost(options) {
|
|
|
206
205
|
} catch (e) {
|
|
207
206
|
throw new ResponseError(
|
|
208
207
|
`Failed to JSON decode server response from [${decodeURI(url.href)}]`,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
208
|
+
null,
|
|
209
|
+
e
|
|
212
210
|
);
|
|
213
211
|
}
|
|
214
212
|
|
|
@@ -322,9 +320,8 @@ export async function jsonPut(options) {
|
|
|
322
320
|
} catch (e) {
|
|
323
321
|
throw new ResponseError(
|
|
324
322
|
`Failed to JSON decode server response from [${decodeURI(url.href)}]`,
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
323
|
+
null,
|
|
324
|
+
e
|
|
328
325
|
);
|
|
329
326
|
}
|
|
330
327
|
|
|
@@ -437,9 +434,8 @@ export async function jsonPatch(options) {
|
|
|
437
434
|
} catch (e) {
|
|
438
435
|
throw new ResponseError(
|
|
439
436
|
`Failed to JSON decode server response from [${decodeURI(url.href)}]`,
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}
|
|
437
|
+
null,
|
|
438
|
+
e
|
|
443
439
|
);
|
|
444
440
|
}
|
|
445
441
|
|
|
@@ -529,9 +525,8 @@ export async function jsonDelete(options) {
|
|
|
529
525
|
} catch (e) {
|
|
530
526
|
throw new ResponseError(
|
|
531
527
|
`Failed to JSON decode server response from [${decodeURI(url.href)}]`,
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
528
|
+
null,
|
|
529
|
+
e
|
|
535
530
|
);
|
|
536
531
|
}
|
|
537
532
|
|
|
@@ -91,7 +91,8 @@ export async function expectResponseOk(response, url) {
|
|
|
91
91
|
throw new ResponseError(
|
|
92
92
|
`Server returned - ${response.status} ${response.statusText} ` +
|
|
93
93
|
`[url=${href(url)}]`,
|
|
94
|
-
|
|
94
|
+
null,
|
|
95
|
+
error
|
|
95
96
|
);
|
|
96
97
|
}
|
|
97
98
|
|
|
@@ -186,9 +187,8 @@ export async function waitForAndCheckResponse(responsePromise, url) {
|
|
|
186
187
|
} else if (e instanceof TypeError || response?.ok === false) {
|
|
187
188
|
throw new ResponseError(
|
|
188
189
|
`A network error occurred for request [${href(url)}]`,
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
190
|
+
null,
|
|
191
|
+
e
|
|
192
192
|
);
|
|
193
193
|
} else {
|
|
194
194
|
throw e;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DetailedError } from '../../../generic/errors.js';
|
|
2
|
+
|
|
1
3
|
import { LoadingStateMachine } from '../../../state/machines.js';
|
|
2
4
|
|
|
3
5
|
import {
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
} from '../../../state/machines.js';
|
|
16
18
|
|
|
17
19
|
import { waitForState } from '../../../util/svelte.js';
|
|
20
|
+
import { TimeoutError } from '../../../generic/errors.js';
|
|
18
21
|
|
|
19
22
|
/** @typedef {import('./typedef.js').SceneLoadingProgress} SceneLoadingProgress */
|
|
20
23
|
|
|
@@ -39,7 +42,11 @@ export default class SceneBase {
|
|
|
39
42
|
// return this.state === STATE_ABORTED;
|
|
40
43
|
// });
|
|
41
44
|
|
|
45
|
+
/** @type {((progress:SceneLoadingProgress)=>void)[]} */
|
|
46
|
+
#preloadListeners = [];
|
|
42
47
|
|
|
48
|
+
/** @type {SceneLoadingProgress|null} */
|
|
49
|
+
#lastReportedProgress = null;
|
|
43
50
|
|
|
44
51
|
/** @type {SceneLoadingProgress} */
|
|
45
52
|
progress = $derived.by(() => {
|
|
@@ -85,7 +92,6 @@ export default class SceneBase {
|
|
|
85
92
|
};
|
|
86
93
|
});
|
|
87
94
|
|
|
88
|
-
|
|
89
95
|
/**
|
|
90
96
|
* Construct SceneBase
|
|
91
97
|
*/
|
|
@@ -110,25 +116,61 @@ export default class SceneBase {
|
|
|
110
116
|
}
|
|
111
117
|
});
|
|
112
118
|
|
|
113
|
-
|
|
114
119
|
$effect(() => {
|
|
115
120
|
if (this.state === STATE_LOADING) {
|
|
116
|
-
|
|
117
121
|
// Check if any source failed during loading
|
|
118
122
|
const sources = this.sources;
|
|
119
123
|
|
|
120
124
|
for (const source of sources) {
|
|
121
125
|
const loader = this.getLoaderFromSource(source);
|
|
122
126
|
if (loader.state === STATE_ERROR) {
|
|
123
|
-
this.#state.send(
|
|
127
|
+
this.#state.send(
|
|
128
|
+
ERROR,
|
|
129
|
+
loader.error || new Error('Source loading failed')
|
|
130
|
+
);
|
|
124
131
|
break;
|
|
125
132
|
}
|
|
126
133
|
}
|
|
127
134
|
}
|
|
135
|
+
});
|
|
128
136
|
|
|
137
|
+
$effect(() => {
|
|
138
|
+
this.#updatePreloadProgressListeners(this.progress);
|
|
129
139
|
});
|
|
130
140
|
} // end constructor
|
|
131
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Call preload progress listeners (with deduplication)
|
|
144
|
+
*
|
|
145
|
+
* @param {SceneLoadingProgress} progress
|
|
146
|
+
*/
|
|
147
|
+
#updatePreloadProgressListeners(progress) {
|
|
148
|
+
// Skip if progress hasn't actually changed
|
|
149
|
+
if (this.#lastReportedProgress &&
|
|
150
|
+
this.#lastReportedProgress.totalBytesLoaded === progress.totalBytesLoaded &&
|
|
151
|
+
this.#lastReportedProgress.totalSize === progress.totalSize &&
|
|
152
|
+
this.#lastReportedProgress.sourcesLoaded === progress.sourcesLoaded &&
|
|
153
|
+
this.#lastReportedProgress.numberOfSources === progress.numberOfSources &&
|
|
154
|
+
this.#lastReportedProgress.percentageLoaded === progress.percentageLoaded) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Update last reported progress
|
|
159
|
+
this.#lastReportedProgress = { ...progress };
|
|
160
|
+
|
|
161
|
+
for (const fn of this.#preloadListeners) {
|
|
162
|
+
try {
|
|
163
|
+
fn(progress);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
throw new DetailedError(
|
|
166
|
+
'Error in progress listener',
|
|
167
|
+
null,
|
|
168
|
+
/** @type {Error} */ (e)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
132
174
|
/* ==== Abstract methods - must be implemented by subclasses */
|
|
133
175
|
|
|
134
176
|
/**
|
|
@@ -181,7 +223,6 @@ export default class SceneBase {
|
|
|
181
223
|
* Object with promise that resolves when loaded and abort function
|
|
182
224
|
*/
|
|
183
225
|
preload({ timeoutMs = 10000, onProgress } = {}) {
|
|
184
|
-
|
|
185
226
|
/** @type {number|NodeJS.Timeout|null} */
|
|
186
227
|
let timeoutId = null;
|
|
187
228
|
|
|
@@ -189,17 +230,17 @@ export default class SceneBase {
|
|
|
189
230
|
let progressIntervalId = null;
|
|
190
231
|
|
|
191
232
|
let isAborted = false;
|
|
192
|
-
|
|
193
|
-
/** @type {SceneLoadingProgress|null} */
|
|
194
|
-
let lastSentProgress = null;
|
|
195
233
|
|
|
196
234
|
const abort = () => {
|
|
197
235
|
if (isAborted) return;
|
|
198
236
|
isAborted = true;
|
|
199
237
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
238
|
+
// Remove progress listener
|
|
239
|
+
if (onProgress) {
|
|
240
|
+
const index = this.#preloadListeners.indexOf(onProgress);
|
|
241
|
+
if (index >= 0) {
|
|
242
|
+
this.#preloadListeners.splice(index, 1);
|
|
243
|
+
}
|
|
203
244
|
}
|
|
204
245
|
|
|
205
246
|
if (progressIntervalId) {
|
|
@@ -211,51 +252,64 @@ export default class SceneBase {
|
|
|
211
252
|
};
|
|
212
253
|
|
|
213
254
|
const promise = new Promise((resolve, reject) => {
|
|
214
|
-
// Set up progress tracking with
|
|
255
|
+
// Set up progress tracking with reactive listener
|
|
215
256
|
if (onProgress) {
|
|
216
|
-
|
|
217
|
-
if (!isAborted && this.state === STATE_LOADING) {
|
|
218
|
-
const currentProgress = this.progress;
|
|
219
|
-
lastSentProgress = currentProgress;
|
|
220
|
-
onProgress(currentProgress);
|
|
221
|
-
}
|
|
222
|
-
}, 50); // Poll every 50ms
|
|
257
|
+
this.#preloadListeners.push(onProgress);
|
|
223
258
|
}
|
|
224
259
|
|
|
225
|
-
// Set up
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
260
|
+
// // Set up progress tracking with polling (fallback if reactive doesn't work)
|
|
261
|
+
// if (onProgress) {
|
|
262
|
+
// progressIntervalId = setInterval(() => {
|
|
263
|
+
// if (!isAborted && this.state === STATE_LOADING) {
|
|
264
|
+
// const currentProgress = this.progress;
|
|
265
|
+
// onProgress(currentProgress);
|
|
266
|
+
// }
|
|
267
|
+
// }, 50); // Poll every 50ms
|
|
268
|
+
// }
|
|
269
|
+
|
|
270
|
+
// // Set up progress tracking with polling (fallback if reactive doesn't work)
|
|
271
|
+
// if (onProgress) {
|
|
272
|
+
// progressIntervalId = setInterval(() => {
|
|
273
|
+
// if (!isAborted && this.state === STATE_LOADING) {
|
|
274
|
+
// const currentProgress = this.progress;
|
|
275
|
+
// onProgress(currentProgress);
|
|
276
|
+
// }
|
|
277
|
+
// }, 50); // Poll every 50ms
|
|
278
|
+
// }
|
|
232
279
|
|
|
233
280
|
// Start loading
|
|
234
281
|
this.load();
|
|
235
282
|
|
|
236
|
-
// Wait for completion with
|
|
237
|
-
|
|
283
|
+
// Wait for completion with timeout
|
|
284
|
+
// 0 means no timeout, but we still need a reasonable value for waitForState
|
|
285
|
+
const waitTimeout = timeoutMs > 0 ? timeoutMs : 120000;
|
|
286
|
+
|
|
238
287
|
waitForState(() => {
|
|
239
|
-
return
|
|
240
|
-
|
|
241
|
-
|
|
288
|
+
return (
|
|
289
|
+
this.loaded ||
|
|
290
|
+
this.state === STATE_ABORTED ||
|
|
291
|
+
this.state === STATE_ERROR
|
|
292
|
+
);
|
|
242
293
|
}, waitTimeout)
|
|
243
294
|
.then(() => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
295
|
+
// Remove progress listener
|
|
296
|
+
if (onProgress) {
|
|
297
|
+
const index = this.#preloadListeners.indexOf(onProgress);
|
|
298
|
+
if (index >= 0) {
|
|
299
|
+
this.#preloadListeners.splice(index, 1);
|
|
300
|
+
}
|
|
301
|
+
|
|
247
302
|
}
|
|
248
303
|
|
|
304
|
+
// Cleanup polling (fallback if reactive doesn't work)
|
|
249
305
|
if (progressIntervalId) {
|
|
250
306
|
clearInterval(progressIntervalId);
|
|
251
307
|
progressIntervalId = null;
|
|
252
|
-
|
|
253
|
-
|
|
308
|
+
|
|
309
|
+
// Send final progress when loading completes (for polling fallback)
|
|
310
|
+
if (onProgress && this.loaded) {
|
|
254
311
|
const finalProgress = this.progress;
|
|
255
|
-
|
|
256
|
-
if (this.loaded) {
|
|
257
|
-
onProgress(finalProgress);
|
|
258
|
-
}
|
|
312
|
+
onProgress(finalProgress);
|
|
259
313
|
}
|
|
260
314
|
}
|
|
261
315
|
|
|
@@ -269,7 +323,34 @@ export default class SceneBase {
|
|
|
269
323
|
reject(new Error(`Preload failed: unexpected state ${this.state}`));
|
|
270
324
|
}
|
|
271
325
|
})
|
|
272
|
-
.catch(
|
|
326
|
+
.catch((error) => {
|
|
327
|
+
// Handle timeout errors from waitForState
|
|
328
|
+
if (error instanceof TimeoutError) {
|
|
329
|
+
abort();
|
|
330
|
+
reject(new Error(`Preload timed out after ${timeoutMs}ms`));
|
|
331
|
+
} else {
|
|
332
|
+
reject(error);
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
.finally(() => {
|
|
336
|
+
// Send final progress update regardless of success/failure
|
|
337
|
+
if (onProgress) {
|
|
338
|
+
const finalProgress = this.progress;
|
|
339
|
+
onProgress(finalProgress);
|
|
340
|
+
|
|
341
|
+
// Remove progress listener
|
|
342
|
+
const index = this.#preloadListeners.indexOf(onProgress);
|
|
343
|
+
if (index >= 0) {
|
|
344
|
+
this.#preloadListeners.splice(index, 1);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// // Cleanup polling (fallback if reactive doesn't work)
|
|
349
|
+
// if (progressIntervalId) {
|
|
350
|
+
// clearInterval(progressIntervalId);
|
|
351
|
+
// progressIntervalId = null;
|
|
352
|
+
// }
|
|
353
|
+
});
|
|
273
354
|
});
|
|
274
355
|
|
|
275
356
|
return { promise, abort };
|
|
@@ -296,7 +377,7 @@ export default class SceneBase {
|
|
|
296
377
|
const loader = this.getLoaderFromSource(source);
|
|
297
378
|
loader.abort();
|
|
298
379
|
}
|
|
299
|
-
|
|
380
|
+
|
|
300
381
|
// Defer ABORTED transition to avoid re-entrant state machine calls
|
|
301
382
|
setTimeout(() => {
|
|
302
383
|
// Only transition to ABORTED if still in ABORTING state
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { tick } from 'svelte';
|
|
2
2
|
|
|
3
|
+
import { TimeoutError } from '../../../generic/errors.js';
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Waits for a state condition to be met by running the checkFn
|
|
5
7
|
* function after each Svelte tick
|
|
@@ -25,7 +27,7 @@ export function waitForState(checkFn, maxWaitMs = 1000) {
|
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
if (Date.now() - startedAt >= maxWaitMs) {
|
|
28
|
-
reject(new
|
|
30
|
+
reject(new TimeoutError(`State change timeout`));
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
|