@vpmedia/simplify 1.74.0 → 1.75.0
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/CHANGELOG.md +60 -0
- package/dist/const/http_status.d.ts +66 -0
- package/dist/const/http_status.d.ts.map +1 -0
- package/dist/const/http_status.js +133 -0
- package/dist/const/http_status.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/{src → dist}/index.js +3 -20
- package/dist/index.js.map +1 -0
- package/dist/logging/AbstractLogHandler.d.ts +17 -0
- package/dist/logging/AbstractLogHandler.d.ts.map +1 -0
- package/dist/logging/AbstractLogHandler.js +16 -0
- package/dist/logging/AbstractLogHandler.js.map +1 -0
- package/dist/logging/ConsoleLogHandler.d.ts +13 -0
- package/dist/logging/ConsoleLogHandler.d.ts.map +1 -0
- package/dist/logging/ConsoleLogHandler.js +41 -0
- package/dist/logging/ConsoleLogHandler.js.map +1 -0
- package/dist/logging/Logger.d.ts +19 -0
- package/dist/logging/Logger.d.ts.map +1 -0
- package/dist/logging/Logger.js +51 -0
- package/dist/logging/Logger.js.map +1 -0
- package/dist/logging/OpenTelemetryLogHandler.d.ts +15 -0
- package/dist/logging/OpenTelemetryLogHandler.d.ts.map +1 -0
- package/dist/logging/OpenTelemetryLogHandler.js +21 -0
- package/dist/logging/OpenTelemetryLogHandler.js.map +1 -0
- package/dist/logging/SentryLogHandler.d.ts +13 -0
- package/dist/logging/SentryLogHandler.d.ts.map +1 -0
- package/dist/logging/SentryLogHandler.js +36 -0
- package/dist/logging/SentryLogHandler.js.map +1 -0
- package/dist/logging/const.d.ts +14 -0
- package/dist/logging/const.d.ts.map +1 -0
- package/dist/logging/const.js +21 -0
- package/dist/logging/const.js.map +1 -0
- package/dist/logging/util.d.ts +14 -0
- package/dist/logging/util.d.ts.map +1 -0
- package/dist/logging/util.js +34 -0
- package/dist/logging/util.js.map +1 -0
- package/dist/pagelifecycle/const.d.ts +15 -0
- package/dist/pagelifecycle/const.d.ts.map +1 -0
- package/dist/pagelifecycle/const.js +27 -0
- package/dist/pagelifecycle/const.js.map +1 -0
- package/dist/pagelifecycle/typedef.d.ts +4 -0
- package/dist/pagelifecycle/typedef.d.ts.map +1 -0
- package/dist/pagelifecycle/typedef.js +2 -0
- package/dist/pagelifecycle/typedef.js.map +1 -0
- package/dist/pagelifecycle/util.d.ts +31 -0
- package/dist/pagelifecycle/util.d.ts.map +1 -0
- package/dist/pagelifecycle/util.js +117 -0
- package/dist/pagelifecycle/util.js.map +1 -0
- package/dist/typecheck/TypeCheckError.d.ts +11 -0
- package/dist/typecheck/TypeCheckError.d.ts.map +1 -0
- package/dist/typecheck/TypeCheckError.js +12 -0
- package/dist/typecheck/TypeCheckError.js.map +1 -0
- package/dist/typecheck/TypeChecker.d.ts +27 -0
- package/dist/typecheck/TypeChecker.d.ts.map +1 -0
- package/dist/typecheck/TypeChecker.js +69 -0
- package/dist/typecheck/TypeChecker.js.map +1 -0
- package/dist/typecheck/util.d.ts +17 -0
- package/dist/typecheck/util.d.ts.map +1 -0
- package/dist/typecheck/util.js +42 -0
- package/dist/typecheck/util.js.map +1 -0
- package/dist/util/async.d.ts +13 -0
- package/dist/util/async.d.ts.map +1 -0
- package/dist/util/async.js +39 -0
- package/dist/util/async.js.map +1 -0
- package/dist/util/error.d.ts +16 -0
- package/dist/util/error.d.ts.map +1 -0
- package/dist/util/error.js +27 -0
- package/dist/util/error.js.map +1 -0
- package/dist/util/event_emitter.d.ts +42 -0
- package/dist/util/event_emitter.d.ts.map +1 -0
- package/dist/util/event_emitter.js +127 -0
- package/dist/util/event_emitter.js.map +1 -0
- package/dist/util/fetch.d.ts +22 -0
- package/dist/util/fetch.d.ts.map +1 -0
- package/dist/util/fetch.js +75 -0
- package/dist/util/fetch.js.map +1 -0
- package/dist/util/number.d.ts +23 -0
- package/dist/util/number.d.ts.map +1 -0
- package/dist/util/number.js +52 -0
- package/dist/util/number.js.map +1 -0
- package/dist/util/object.d.ts +24 -0
- package/dist/util/object.d.ts.map +1 -0
- package/dist/util/object.js +83 -0
- package/dist/util/object.js.map +1 -0
- package/dist/util/query.d.ts +11 -0
- package/dist/util/query.d.ts.map +1 -0
- package/dist/util/query.js +24 -0
- package/dist/util/query.js.map +1 -0
- package/dist/util/state.d.ts +5 -0
- package/dist/util/state.d.ts.map +1 -0
- package/dist/util/state.js +19 -0
- package/dist/util/state.js.map +1 -0
- package/dist/util/string.d.ts +25 -0
- package/dist/util/string.d.ts.map +1 -0
- package/dist/util/string.js +59 -0
- package/dist/util/string.js.map +1 -0
- package/dist/util/uuid.d.ts +13 -0
- package/dist/util/uuid.d.ts.map +1 -0
- package/dist/util/uuid.js +27 -0
- package/dist/util/uuid.js.map +1 -0
- package/dist/util/validate.d.ts +106 -0
- package/dist/util/validate.d.ts.map +1 -0
- package/dist/util/validate.js +139 -0
- package/dist/util/validate.js.map +1 -0
- package/package.json +31 -16
- package/src/const/http_status.test.ts +7 -0
- package/src/const/{http_status.js → http_status.ts} +1 -1
- package/src/index.ts +51 -0
- package/src/logging/AbstractLogHandler.ts +31 -0
- package/src/logging/{ConsoleLogHandler.js → ConsoleLogHandler.ts} +15 -13
- package/src/logging/Logger.test.ts +69 -0
- package/src/logging/Logger.ts +77 -0
- package/src/logging/OpenTelemetryLogHandler.ts +40 -0
- package/src/logging/SentryLogHandler.ts +44 -0
- package/src/logging/{const.js → const.ts} +1 -1
- package/src/logging/util.test.ts +33 -0
- package/src/logging/util.ts +36 -0
- package/src/pagelifecycle/{const.js → const.ts} +2 -2
- package/src/pagelifecycle/typedef.ts +5 -0
- package/src/pagelifecycle/util.test.ts +99 -0
- package/src/pagelifecycle/{util.js → util.ts} +14 -27
- package/src/typecheck/{TypeCheckError.js → TypeCheckError.ts} +7 -3
- package/src/typecheck/TypeChecker.test.ts +70 -0
- package/src/typecheck/{TypeChecker.js → TypeChecker.ts} +10 -27
- package/src/typecheck/util.test.ts +36 -0
- package/src/typecheck/util.ts +50 -0
- package/src/util/async.test.ts +74 -0
- package/src/util/{async.js → async.ts} +3 -12
- package/src/util/error.test.ts +32 -0
- package/src/util/error.ts +37 -0
- package/src/util/event_emitter.test.ts +228 -0
- package/src/util/event_emitter.ts +147 -0
- package/src/util/fetch.test.ts +62 -0
- package/src/util/{fetch.js → fetch.ts} +40 -31
- package/src/util/number.test.ts +124 -0
- package/src/util/number.ts +58 -0
- package/src/util/object.test.ts +203 -0
- package/src/util/{object.js → object.ts} +14 -21
- package/src/util/query.test.ts +71 -0
- package/src/util/query.ts +35 -0
- package/src/util/state.test.ts +47 -0
- package/src/util/{state.js → state.ts} +3 -6
- package/src/util/string.test.ts +64 -0
- package/src/util/string.ts +65 -0
- package/src/util/uuid.test.ts +53 -0
- package/src/util/uuid.ts +31 -0
- package/src/util/validate.test.ts +309 -0
- package/src/util/validate.ts +230 -0
- package/.vscode/extensions.json +0 -6
- package/.vscode/settings.json +0 -27
- package/src/logging/AbstractLogHandler.js +0 -23
- package/src/logging/Logger.js +0 -115
- package/src/logging/OpenTelemetryLogHandler.js +0 -30
- package/src/logging/SentryLogHandler.js +0 -46
- package/src/logging/util.js +0 -41
- package/src/pagelifecycle/typedef.js +0 -9
- package/src/typecheck/util.js +0 -60
- package/src/util/error.js +0 -33
- package/src/util/event_emitter.js +0 -196
- package/src/util/number.js +0 -118
- package/src/util/query.js +0 -32
- package/src/util/string.js +0 -76
- package/src/util/uuid.js +0 -35
- package/src/util/validate.js +0 -247
- package/types/const/http_status.d.ts +0 -131
- package/types/const/http_status.d.ts.map +0 -1
- package/types/index.d.ts +0 -26
- package/types/index.d.ts.map +0 -1
- package/types/logging/AbstractLogHandler.d.ts +0 -20
- package/types/logging/AbstractLogHandler.d.ts.map +0 -1
- package/types/logging/ConsoleLogHandler.d.ts +0 -9
- package/types/logging/ConsoleLogHandler.d.ts.map +0 -1
- package/types/logging/Logger.d.ts +0 -66
- package/types/logging/Logger.d.ts.map +0 -1
- package/types/logging/OpenTelemetryLogHandler.d.ts +0 -11
- package/types/logging/OpenTelemetryLogHandler.d.ts.map +0 -1
- package/types/logging/SentryLogHandler.d.ts +0 -9
- package/types/logging/SentryLogHandler.d.ts.map +0 -1
- package/types/logging/const.d.ts +0 -14
- package/types/logging/const.d.ts.map +0 -1
- package/types/logging/util.d.ts +0 -4
- package/types/logging/util.d.ts.map +0 -1
- package/types/pagelifecycle/const.d.ts +0 -15
- package/types/pagelifecycle/const.d.ts.map +0 -1
- package/types/pagelifecycle/typedef.d.ts +0 -4
- package/types/pagelifecycle/typedef.d.ts.map +0 -1
- package/types/pagelifecycle/util.d.ts +0 -8
- package/types/pagelifecycle/util.d.ts.map +0 -1
- package/types/typecheck/TypeCheckError.d.ts +0 -13
- package/types/typecheck/TypeCheckError.d.ts.map +0 -1
- package/types/typecheck/TypeChecker.d.ts +0 -40
- package/types/typecheck/TypeChecker.d.ts.map +0 -1
- package/types/typecheck/util.d.ts +0 -4
- package/types/typecheck/util.d.ts.map +0 -1
- package/types/util/async.d.ts +0 -4
- package/types/util/async.d.ts.map +0 -1
- package/types/util/error.d.ts +0 -3
- package/types/util/error.d.ts.map +0 -1
- package/types/util/event_emitter.d.ts +0 -69
- package/types/util/event_emitter.d.ts.map +0 -1
- package/types/util/fetch.d.ts +0 -22
- package/types/util/fetch.d.ts.map +0 -1
- package/types/util/number.d.ts +0 -11
- package/types/util/number.d.ts.map +0 -1
- package/types/util/object.d.ts +0 -6
- package/types/util/object.d.ts.map +0 -1
- package/types/util/query.d.ts +0 -3
- package/types/util/query.d.ts.map +0 -1
- package/types/util/state.d.ts +0 -2
- package/types/util/state.d.ts.map +0 -1
- package/types/util/string.d.ts +0 -7
- package/types/util/string.d.ts.map +0 -1
- package/types/util/uuid.d.ts +0 -4
- package/types/util/uuid.d.ts.map +0 -1
- package/types/util/validate.d.ts +0 -45
- package/types/util/validate.d.ts.map +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export type EventListener = (...args: any[]) => void;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Internal listener wrapper that stores metadata
|
|
5
|
+
* about a registered event listener.
|
|
6
|
+
*/
|
|
7
|
+
class Listener {
|
|
8
|
+
fn: EventListener;
|
|
9
|
+
context: unknown;
|
|
10
|
+
once: boolean;
|
|
11
|
+
|
|
12
|
+
constructor(fn: EventListener, context: unknown, once = false) {
|
|
13
|
+
this.fn = fn;
|
|
14
|
+
this.context = context;
|
|
15
|
+
this.once = once;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Event emitter implementation inspired by Node.js/EventEmitter3.
|
|
21
|
+
*/
|
|
22
|
+
export class EventEmitter {
|
|
23
|
+
#events: Map<string | symbol, Listener[]>;
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
this.#events = new Map();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get all registered event names.
|
|
31
|
+
*/
|
|
32
|
+
eventNames(): (string | symbol)[] {
|
|
33
|
+
return [...this.#events.keys()];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get all listener functions registered for an event.
|
|
38
|
+
*/
|
|
39
|
+
listeners(event: string | symbol): EventListener[] {
|
|
40
|
+
const listeners = this.#events.get(event);
|
|
41
|
+
return listeners ? listeners.map((l) => l.fn) : [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get the number of listeners registered for an event.
|
|
46
|
+
*/
|
|
47
|
+
listenerCount(event: string | symbol): number {
|
|
48
|
+
const listeners = this.#events.get(event);
|
|
49
|
+
return listeners ? listeners.length : 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Emit an event, invoking all registered listeners.
|
|
54
|
+
*/
|
|
55
|
+
emit(event: string | symbol, ...args: unknown[]): boolean {
|
|
56
|
+
const listeners = this.#events.get(event);
|
|
57
|
+
if (!listeners || listeners.length === 0) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const listener of [...listeners]) {
|
|
62
|
+
listener.fn.apply(listener.context, args);
|
|
63
|
+
if (listener.once) {
|
|
64
|
+
this.off(event, listener.fn, listener.context);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#addListener(event: string | symbol, fn: EventListener, context: unknown, once: boolean): this {
|
|
72
|
+
if (typeof fn !== 'function') {
|
|
73
|
+
throw new TypeError('Listener must be a function');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const listener = new Listener(fn, context ?? this, once);
|
|
77
|
+
const listeners = this.#events.get(event);
|
|
78
|
+
|
|
79
|
+
if (listeners) {
|
|
80
|
+
listeners.push(listener);
|
|
81
|
+
} else {
|
|
82
|
+
this.#events.set(event, [listener]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Register a persistent listener for an event.
|
|
90
|
+
*/
|
|
91
|
+
on(event: string | symbol, fn: EventListener, context?: unknown): this {
|
|
92
|
+
return this.#addListener(event, fn, context, false);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Register a one-time listener for an event.
|
|
97
|
+
*/
|
|
98
|
+
once(event: string | symbol, fn: EventListener, context?: unknown): this {
|
|
99
|
+
return this.#addListener(event, fn, context, true);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Remove a specific listener, or all listeners for an event.
|
|
104
|
+
*/
|
|
105
|
+
off(event: string | symbol, fn?: EventListener, context?: unknown): this {
|
|
106
|
+
if (!this.#events.has(event)) {
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!fn) {
|
|
111
|
+
this.#events.delete(event);
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const filtered = this.#events.get(event)!.filter((listener) => {
|
|
116
|
+
if (listener.fn !== fn) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (context !== undefined && listener.context !== context) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (filtered.length > 0) {
|
|
126
|
+
this.#events.set(event, filtered);
|
|
127
|
+
} else {
|
|
128
|
+
this.#events.delete(event);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Remove all listeners from the emitter,
|
|
136
|
+
* or all listeners for a specific event.
|
|
137
|
+
*/
|
|
138
|
+
removeAllListeners(event?: string | symbol): this {
|
|
139
|
+
if (event === undefined) {
|
|
140
|
+
this.#events.clear();
|
|
141
|
+
} else {
|
|
142
|
+
this.#events.delete(event);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { HTTP_404_NOT_FOUND } from '../const/http_status.js';
|
|
2
|
+
import { fetchRetry, FetchError } from './fetch.js';
|
|
3
|
+
|
|
4
|
+
describe('FetchError', () => {
|
|
5
|
+
test('constructor', () => {
|
|
6
|
+
const error = new FetchError('message', 'url', { method: 'GET' }, null);
|
|
7
|
+
expect(error.message).toEqual('message');
|
|
8
|
+
expect(error.resource).toEqual('url');
|
|
9
|
+
expect(error.fetchOptions).toMatchObject({ method: 'GET' });
|
|
10
|
+
expect(error.response).toBe(null);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('fetchRetry', () => {
|
|
15
|
+
test('fetch OK', async () => {
|
|
16
|
+
const response = await fetchRetry('/test.json', {
|
|
17
|
+
cache: 'no-cache',
|
|
18
|
+
keepalive: false,
|
|
19
|
+
method: 'GET',
|
|
20
|
+
redirect: 'error',
|
|
21
|
+
});
|
|
22
|
+
const json = await response.json();
|
|
23
|
+
const expectedJSON = {
|
|
24
|
+
success: true,
|
|
25
|
+
method: 'GET',
|
|
26
|
+
};
|
|
27
|
+
expect(json).toEqual(expectedJSON);
|
|
28
|
+
});
|
|
29
|
+
test('fetch unknown scheme', async () => {
|
|
30
|
+
try {
|
|
31
|
+
await fetchRetry('htps://', {});
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const typedError = error instanceof Error ? error : new Error(String(error));
|
|
34
|
+
expect(typedError.message).toEqual('fetch failed');
|
|
35
|
+
const typedErrorCause =
|
|
36
|
+
typedError.cause instanceof Error ? typedError.cause : new Error(String(typedError.cause));
|
|
37
|
+
expect(typedErrorCause.message).toEqual('unknown scheme');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
test('fetch 404 error with retry', async () => {
|
|
41
|
+
try {
|
|
42
|
+
await fetchRetry(
|
|
43
|
+
'/test_error.json',
|
|
44
|
+
{
|
|
45
|
+
cache: 'no-cache',
|
|
46
|
+
keepalive: false,
|
|
47
|
+
method: 'POST',
|
|
48
|
+
redirect: 'error',
|
|
49
|
+
},
|
|
50
|
+
{ numTries: 2, statusExcludes: [], delay: 1 }
|
|
51
|
+
);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
const typedError = error instanceof Error ? error : new Error(String(error));
|
|
54
|
+
expect(typedError).toBeInstanceOf(FetchError);
|
|
55
|
+
if (typedError instanceof FetchError) {
|
|
56
|
+
expect(typedError.message).toBe('Fetch error 404');
|
|
57
|
+
expect(typedError.response?.status).toBe(HTTP_404_NOT_FOUND);
|
|
58
|
+
expect(typedError.cause).toBe(HTTP_404_NOT_FOUND);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -12,15 +12,23 @@ const logger = new Logger('fetch');
|
|
|
12
12
|
|
|
13
13
|
export const HTTP_0_ANY = 0;
|
|
14
14
|
|
|
15
|
+
export interface FetchRetryOptions {
|
|
16
|
+
delay?: number;
|
|
17
|
+
numTries?: number;
|
|
18
|
+
statusExcludes?: number[];
|
|
19
|
+
timeout?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
export class FetchError extends Error {
|
|
23
|
+
resource: string | URL | Request;
|
|
24
|
+
fetchOptions: RequestInit;
|
|
25
|
+
response: Response | null;
|
|
26
|
+
override cause: number | null;
|
|
27
|
+
|
|
16
28
|
/**
|
|
17
29
|
* Creates a new FetchError instance.
|
|
18
|
-
* @param {string} message - Error message.
|
|
19
|
-
* @param {string | URL | Request} resource - Fetch URL.
|
|
20
|
-
* @param {RequestInit} fetchOptions - Fetch options.
|
|
21
|
-
* @param {Response} response - Fetch response.
|
|
22
30
|
*/
|
|
23
|
-
constructor(message, resource, fetchOptions, response) {
|
|
31
|
+
constructor(message: string, resource: string | URL | Request, fetchOptions: RequestInit, response: Response | null) {
|
|
24
32
|
super(message);
|
|
25
33
|
this.name = 'FetchError';
|
|
26
34
|
this.resource = resource;
|
|
@@ -32,31 +40,32 @@ export class FetchError extends Error {
|
|
|
32
40
|
|
|
33
41
|
/**
|
|
34
42
|
* Fetch with retry.
|
|
35
|
-
* @param {string | URL | Request} resource - Fetch URL.
|
|
36
|
-
* @param {RequestInit} fetchOptions - Fetch options.
|
|
37
|
-
* @param {{delay?: number, numTries?: number, statusExcludes?: number[], timeout?: number}} [retryOptions] - Retry options.
|
|
38
|
-
* @returns {Promise<Response>} Fetch result.
|
|
39
43
|
*/
|
|
40
|
-
export const fetchRetry = async (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
retryOptions
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
export const fetchRetry = async (
|
|
45
|
+
resource: string | URL | Request,
|
|
46
|
+
fetchOptions: RequestInit,
|
|
47
|
+
retryOptions?: FetchRetryOptions
|
|
48
|
+
): Promise<Response> => {
|
|
49
|
+
const opts: Required<FetchRetryOptions> = {
|
|
50
|
+
timeout: Math.max(retryOptions?.timeout ?? 30_000, 1),
|
|
51
|
+
delay: Math.max(retryOptions?.delay ?? 1000, 1),
|
|
52
|
+
numTries: Math.max(retryOptions?.numTries ?? 1, 1),
|
|
53
|
+
statusExcludes: retryOptions?.statusExcludes ?? [
|
|
54
|
+
HTTP_401_UNAUTHORIZED,
|
|
55
|
+
HTTP_403_FORBIDDEN,
|
|
56
|
+
HTTP_405_METHOD_NOT_ALLOWED,
|
|
57
|
+
HTTP_422_UNPROCESSABLE_CONTENT,
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
while (opts.numTries > 0) {
|
|
52
61
|
const isOnline = globalThis.navigator?.onLine;
|
|
53
|
-
logger.info('request', { resource, fetchOptions, retryOptions, isOnline });
|
|
62
|
+
logger.info('request', { resource: String(resource), fetchOptions, retryOptions: { ...opts }, isOnline });
|
|
54
63
|
const controller = new AbortController();
|
|
55
64
|
const timeoutId = setTimeout(
|
|
56
65
|
() => controller.abort(new DOMException('Fetch timed out', 'AbortError')),
|
|
57
|
-
|
|
66
|
+
opts.timeout
|
|
58
67
|
);
|
|
59
|
-
const options = {
|
|
68
|
+
const options: RequestInit = {
|
|
60
69
|
...fetchOptions,
|
|
61
70
|
signal: controller.signal,
|
|
62
71
|
};
|
|
@@ -65,22 +74,22 @@ export const fetchRetry = async (resource, fetchOptions, retryOptions) => {
|
|
|
65
74
|
if (!response.ok) {
|
|
66
75
|
throw new FetchError(`Fetch error ${response.status}`, resource, options, response);
|
|
67
76
|
}
|
|
68
|
-
logger.info('response', response);
|
|
77
|
+
logger.info('response', { status: response.status });
|
|
69
78
|
return response;
|
|
70
79
|
} catch (error) {
|
|
71
80
|
const typedError = error instanceof Error ? error : new Error(String(error));
|
|
72
81
|
logger.debug('error', getErrorDetails(typedError));
|
|
73
|
-
|
|
82
|
+
opts.numTries -= 1;
|
|
74
83
|
if (
|
|
75
|
-
|
|
84
|
+
opts.numTries === 0 ||
|
|
76
85
|
(typedError instanceof FetchError &&
|
|
77
|
-
(
|
|
78
|
-
|
|
86
|
+
(opts.statusExcludes.includes(typedError.response?.status ?? -1) ||
|
|
87
|
+
opts.statusExcludes.includes(HTTP_0_ANY)))
|
|
79
88
|
) {
|
|
80
89
|
throw error;
|
|
81
90
|
}
|
|
82
|
-
await delayPromise(
|
|
83
|
-
|
|
91
|
+
await delayPromise(opts.delay);
|
|
92
|
+
opts.delay *= 2;
|
|
84
93
|
} finally {
|
|
85
94
|
clearTimeout(timeoutId);
|
|
86
95
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { TypeCheckError } from '../typecheck/TypeCheckError.js';
|
|
2
|
+
import {
|
|
3
|
+
fixFloatPrecision,
|
|
4
|
+
getRandomInt,
|
|
5
|
+
isEqual,
|
|
6
|
+
isGreater,
|
|
7
|
+
isGreaterOrEqual,
|
|
8
|
+
isInRange,
|
|
9
|
+
isLess,
|
|
10
|
+
isLessOrEqual,
|
|
11
|
+
deg2rad,
|
|
12
|
+
rad2deg,
|
|
13
|
+
} from './number.js';
|
|
14
|
+
|
|
15
|
+
test('Converts angle in degrees to radians', () => {
|
|
16
|
+
expect(deg2rad(90)).toBe(1.5707963267948966);
|
|
17
|
+
// @ts-expect-error
|
|
18
|
+
expect(() => deg2rad('')).toThrowError(TypeCheckError);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('Converts angle in radians to degrees', () => {
|
|
22
|
+
expect(rad2deg(1.5707963267948966)).toBe(90);
|
|
23
|
+
// @ts-expect-error
|
|
24
|
+
expect(() => rad2deg('')).toThrowError(TypeCheckError);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('fixFloatPrecision', () => {
|
|
28
|
+
test('Fixes float precision issues', () => {
|
|
29
|
+
expect(fixFloatPrecision(0.20000000000000004)).toBe(0.2);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('Handles zero', () => {
|
|
33
|
+
expect(fixFloatPrecision(0)).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('Handles negative numbers', () => {
|
|
37
|
+
expect(fixFloatPrecision(-0.20000000000000004)).toBe(-0.2);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('Handles very small numbers', () => {
|
|
41
|
+
expect(fixFloatPrecision(0.0000000000001)).toBe(0);
|
|
42
|
+
expect(fixFloatPrecision(-0.0000000000001)).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('Handles integer numbers', () => {
|
|
46
|
+
expect(fixFloatPrecision(5)).toBe(5);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('Handles string number input', () => {
|
|
50
|
+
expect(fixFloatPrecision('5.123456789')).toBe(5.123456789);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('Throws error for invalid input', () => {
|
|
54
|
+
expect(fixFloatPrecision('abc')).toBe(Number.NaN);
|
|
55
|
+
expect(fixFloatPrecision(null)).toBe(Number.NaN);
|
|
56
|
+
expect(fixFloatPrecision(undefined)).toBe(Number.NaN);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('getRandomInt', () => {
|
|
61
|
+
test('Throws error if min or max is not finite number', () => {
|
|
62
|
+
// @ts-expect-error
|
|
63
|
+
expect(() => getRandomInt('', 1)).toThrowError(TypeCheckError);
|
|
64
|
+
// @ts-expect-error
|
|
65
|
+
expect(() => getRandomInt(1, '')).toThrowError(TypeCheckError);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('Returns random integer within range when min equals max', () => {
|
|
69
|
+
expect(getRandomInt(1, 1)).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('Returns random integer within range when min is less than max', () => {
|
|
73
|
+
const result = getRandomInt(1, 10);
|
|
74
|
+
expect(result).toBeGreaterThanOrEqual(1);
|
|
75
|
+
expect(result).toBeLessThanOrEqual(10);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('Works with negative numbers', () => {
|
|
79
|
+
const result = getRandomInt(-5, -1);
|
|
80
|
+
expect(result).toBeGreaterThanOrEqual(-5);
|
|
81
|
+
expect(result).toBeLessThanOrEqual(-1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('Works with zero range', () => {
|
|
85
|
+
const result = getRandomInt(0, 0);
|
|
86
|
+
expect(result).toBe(0);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('number', () => {
|
|
91
|
+
test('isEq', () => {
|
|
92
|
+
expect(isEqual(1, 0)).toBe(false);
|
|
93
|
+
expect(isEqual(1, 1)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('isGt', () => {
|
|
97
|
+
expect(isGreater(1, 0)).toBe(true);
|
|
98
|
+
expect(isGreater(1, 1)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('isGtOrEq', () => {
|
|
102
|
+
expect(isGreaterOrEqual(1, 0)).toBe(true);
|
|
103
|
+
expect(isGreaterOrEqual(1, 1)).toBe(true);
|
|
104
|
+
expect(isGreaterOrEqual(1, 2)).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('isGtOrEq', () => {
|
|
108
|
+
expect(isInRange(1, 0, 2)).toBe(true);
|
|
109
|
+
expect(isInRange(1, 0, 1)).toBe(true);
|
|
110
|
+
expect(isInRange(2, 0, 1)).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('isLe', () => {
|
|
114
|
+
expect(isLess(1, 0)).toBe(false);
|
|
115
|
+
expect(isLess(0, 0)).toBe(false);
|
|
116
|
+
expect(isLess(0, 1)).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('isLeOrEq', () => {
|
|
120
|
+
expect(isLessOrEqual(1, 0)).toBe(false);
|
|
121
|
+
expect(isLessOrEqual(0, 0)).toBe(true);
|
|
122
|
+
expect(isLessOrEqual(0, 1)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { TypeCheckError } from '../typecheck/TypeCheckError.js';
|
|
2
|
+
|
|
3
|
+
const DEG_TO_RAD = Math.PI / 180;
|
|
4
|
+
const RAD_TO_DEG = 180 / Math.PI;
|
|
5
|
+
|
|
6
|
+
const PRECISION = 12;
|
|
7
|
+
const EPSILON = 1e-11;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Converts degrees to radians.
|
|
11
|
+
*/
|
|
12
|
+
export const deg2rad = (degrees: number): number => {
|
|
13
|
+
if (!Number.isFinite(degrees)) {
|
|
14
|
+
throw new TypeCheckError('Argument degrees must be a finite number', { value: degrees });
|
|
15
|
+
}
|
|
16
|
+
return degrees * DEG_TO_RAD;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Converts radians to degrees.
|
|
21
|
+
*/
|
|
22
|
+
export const rad2deg = (radians: number): number => {
|
|
23
|
+
if (!Number.isFinite(radians)) {
|
|
24
|
+
throw new TypeCheckError('Argument radians must be a finite number', { value: radians });
|
|
25
|
+
}
|
|
26
|
+
return radians * RAD_TO_DEG;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns random integer in range.
|
|
31
|
+
*/
|
|
32
|
+
export const getRandomInt = (min: number, max: number): number => {
|
|
33
|
+
if (!Number.isFinite(min)) {
|
|
34
|
+
throw new TypeCheckError('Argument min must be finite number', { value: min });
|
|
35
|
+
}
|
|
36
|
+
if (!Number.isFinite(max)) {
|
|
37
|
+
throw new TypeCheckError('Argument max must be finite number', { value: max });
|
|
38
|
+
}
|
|
39
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Normalizes floating point precision (e.g. 0.20000000000000004 → 0.2).
|
|
44
|
+
*/
|
|
45
|
+
export const fixFloatPrecision = (value: number | string | null | undefined): number => {
|
|
46
|
+
const parsedValue = typeof value === 'string' ? Number(value) : value;
|
|
47
|
+
if (parsedValue === null || parsedValue === undefined || !Number.isFinite(parsedValue)) {
|
|
48
|
+
return Number.NaN;
|
|
49
|
+
}
|
|
50
|
+
return Math.abs(parsedValue) < EPSILON ? 0 : Number(parsedValue.toPrecision(PRECISION));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const isGreater = (value: number, min: number): boolean => value > min;
|
|
54
|
+
export const isGreaterOrEqual = (value: number, min: number): boolean => value >= min;
|
|
55
|
+
export const isLess = (value: number, min: number): boolean => value < min;
|
|
56
|
+
export const isLessOrEqual = (value: number, min: number): boolean => value <= min;
|
|
57
|
+
export const isInRange = (value: number, min: number, max: number): boolean => value >= min && value <= max;
|
|
58
|
+
export const isEqual = (value: number, expected: number): boolean => value === expected;
|