@vpmedia/simplify 1.74.0 → 1.76.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 +75 -0
- package/dist/const/http_status.d.ts +66 -0
- package/dist/const/http_status.d.ts.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1119 -0
- 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/ConsoleLogHandler.d.ts +13 -0
- package/dist/logging/ConsoleLogHandler.d.ts.map +1 -0
- package/dist/logging/Logger.d.ts +19 -0
- package/dist/logging/Logger.d.ts.map +1 -0
- package/dist/logging/OpenTelemetryLogHandler.d.ts +15 -0
- package/dist/logging/OpenTelemetryLogHandler.d.ts.map +1 -0
- package/dist/logging/SentryLogHandler.d.ts +13 -0
- package/dist/logging/SentryLogHandler.d.ts.map +1 -0
- package/dist/logging/const.d.ts +14 -0
- package/dist/logging/const.d.ts.map +1 -0
- package/dist/logging/util.d.ts +14 -0
- package/dist/logging/util.d.ts.map +1 -0
- package/dist/pagelifecycle/const.d.ts +15 -0
- package/dist/pagelifecycle/const.d.ts.map +1 -0
- package/dist/pagelifecycle/typedef.d.ts +4 -0
- package/dist/pagelifecycle/typedef.d.ts.map +1 -0
- package/dist/pagelifecycle/util.d.ts +31 -0
- package/dist/pagelifecycle/util.d.ts.map +1 -0
- package/dist/typecheck/TypeCheckError.d.ts +11 -0
- package/dist/typecheck/TypeCheckError.d.ts.map +1 -0
- package/dist/typecheck/TypeChecker.d.ts +27 -0
- package/dist/typecheck/TypeChecker.d.ts.map +1 -0
- package/dist/typecheck/util.d.ts +17 -0
- package/dist/typecheck/util.d.ts.map +1 -0
- package/dist/util/async.d.ts +13 -0
- package/dist/util/async.d.ts.map +1 -0
- package/dist/util/error.d.ts +16 -0
- package/dist/util/error.d.ts.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/fetch.d.ts +22 -0
- package/dist/util/fetch.d.ts.map +1 -0
- package/dist/util/number.d.ts +23 -0
- package/dist/util/number.d.ts.map +1 -0
- package/dist/util/object.d.ts +24 -0
- package/dist/util/object.d.ts.map +1 -0
- package/dist/util/query.d.ts +11 -0
- package/dist/util/query.d.ts.map +1 -0
- package/dist/util/state.d.ts +5 -0
- package/dist/util/state.d.ts.map +1 -0
- package/dist/util/string.d.ts +25 -0
- package/dist/util/string.d.ts.map +1 -0
- package/dist/util/uuid.d.ts +13 -0
- package/dist/util/uuid.d.ts.map +1 -0
- package/dist/util/validate.d.ts +106 -0
- package/dist/util/validate.d.ts.map +1 -0
- package/package.json +32 -16
- package/src/const/http_status.test.ts +7 -0
- package/src/const/{http_status.js → http_status.ts} +1 -1
- package/src/{index.js → index.ts} +8 -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
|
@@ -12,7 +12,7 @@ export const LOG_LEVEL_NAME_WARNING = 'warning';
|
|
|
12
12
|
export const LOG_LEVEL_NAME_INFO = 'info';
|
|
13
13
|
export const LOG_LEVEL_NAME_DEBUG = 'debug';
|
|
14
14
|
|
|
15
|
-
export const LOG_LEVEL_NAMES = [
|
|
15
|
+
export const LOG_LEVEL_NAMES: readonly string[] = [
|
|
16
16
|
LOG_LEVEL_NAME_SILENT,
|
|
17
17
|
LOG_LEVEL_NAME_FATAL,
|
|
18
18
|
LOG_LEVEL_NAME_ERROR,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOG_LEVEL_DEBUG,
|
|
3
|
+
LOG_LEVEL_ERROR,
|
|
4
|
+
LOG_LEVEL_FATAL,
|
|
5
|
+
LOG_LEVEL_INFO,
|
|
6
|
+
LOG_LEVEL_NAME_DEBUG,
|
|
7
|
+
LOG_LEVEL_NAME_ERROR,
|
|
8
|
+
LOG_LEVEL_NAME_FATAL,
|
|
9
|
+
LOG_LEVEL_NAME_INFO,
|
|
10
|
+
LOG_LEVEL_NAME_SILENT,
|
|
11
|
+
LOG_LEVEL_NAME_WARNING,
|
|
12
|
+
LOG_LEVEL_SILENT,
|
|
13
|
+
LOG_LEVEL_WARNING,
|
|
14
|
+
} from './const.js';
|
|
15
|
+
import { Logger } from './Logger.js';
|
|
16
|
+
import { formatLogMessage, getLogLevelName } from './util.js';
|
|
17
|
+
|
|
18
|
+
test('formatLogMessage()', () => {
|
|
19
|
+
expect(
|
|
20
|
+
formatLogMessage(new Logger('loggerName'), Date.now(), LOG_LEVEL_INFO, 'logMessage').endsWith(
|
|
21
|
+
'[loggerName] logMessage'
|
|
22
|
+
)
|
|
23
|
+
).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('getLogLevelName()', () => {
|
|
27
|
+
expect(getLogLevelName(LOG_LEVEL_DEBUG)).toBe(LOG_LEVEL_NAME_DEBUG);
|
|
28
|
+
expect(getLogLevelName(LOG_LEVEL_INFO)).toBe(LOG_LEVEL_NAME_INFO);
|
|
29
|
+
expect(getLogLevelName(LOG_LEVEL_WARNING)).toBe(LOG_LEVEL_NAME_WARNING);
|
|
30
|
+
expect(getLogLevelName(LOG_LEVEL_ERROR)).toBe(LOG_LEVEL_NAME_ERROR);
|
|
31
|
+
expect(getLogLevelName(LOG_LEVEL_FATAL)).toBe(LOG_LEVEL_NAME_FATAL);
|
|
32
|
+
expect(getLogLevelName(LOG_LEVEL_SILENT)).toBe(LOG_LEVEL_NAME_SILENT);
|
|
33
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Logger } from './Logger.js';
|
|
2
|
+
import { LOG_LEVEL_NAMES } from './const.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Format log message.
|
|
6
|
+
*/
|
|
7
|
+
export const formatLogMessage = (logger: Logger, timestamp: number, _level: number, message: string): string =>
|
|
8
|
+
`${timestamp} [${logger.name}] ${message}`;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get log level name.
|
|
12
|
+
*/
|
|
13
|
+
export const getLogLevelName = (level: number): string | undefined => LOG_LEVEL_NAMES[level];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns the application environment identifier.
|
|
17
|
+
*/
|
|
18
|
+
export const getAppEnvironment = (): string => {
|
|
19
|
+
let appEnvironment = 'local';
|
|
20
|
+
try {
|
|
21
|
+
const { env } = import.meta as unknown as { env?: Record<string, string | undefined> };
|
|
22
|
+
if (env?.['VITE_APP_ENVIRONMENT']) {
|
|
23
|
+
appEnvironment = env['VITE_APP_ENVIRONMENT'];
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
26
|
+
// pass
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
if (process.env['APP_ENVIRONMENT']) {
|
|
30
|
+
appEnvironment = process.env['APP_ENVIRONMENT'];
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// pass
|
|
34
|
+
}
|
|
35
|
+
return appEnvironment;
|
|
36
|
+
};
|
|
@@ -4,7 +4,7 @@ export const PAGE_LIFECYCLE_STATE_PASSIVE = 'passive';
|
|
|
4
4
|
export const PAGE_LIFECYCLE_STATE_FROZEN = 'frozen';
|
|
5
5
|
export const PAGE_LIFECYCLE_STATE_TERMINATED = 'terminated';
|
|
6
6
|
|
|
7
|
-
export const PAGE_LIFECYCLE_STATES = new Set([
|
|
7
|
+
export const PAGE_LIFECYCLE_STATES: ReadonlySet<string> = new Set([
|
|
8
8
|
PAGE_LIFECYCLE_STATE_ACTIVE,
|
|
9
9
|
PAGE_LIFECYCLE_STATE_FROZEN,
|
|
10
10
|
PAGE_LIFECYCLE_STATE_HIDDEN,
|
|
@@ -18,7 +18,7 @@ export const DOCUMENT_STATE_COMPLETE = 'complete';
|
|
|
18
18
|
export const DOCUMENT_STATE_INTERACTIVE = 'interactive';
|
|
19
19
|
export const DOCUMENT_STATE_LOADING = 'loading';
|
|
20
20
|
|
|
21
|
-
export const DOCUMENT_STATES = new Set([
|
|
21
|
+
export const DOCUMENT_STATES: ReadonlySet<string> = new Set([
|
|
22
22
|
DOCUMENT_STATE_COMPLETE,
|
|
23
23
|
DOCUMENT_STATE_DOM_LOADED,
|
|
24
24
|
DOCUMENT_STATE_FULLY_LOADED,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
DOCUMENT_STATE_CHANGE_EVENT,
|
|
4
|
+
DOCUMENT_STATES,
|
|
5
|
+
PAGE_LIFECYCLE_STATE_CHANGE_EVENT,
|
|
6
|
+
PAGE_LIFECYCLE_STATES,
|
|
7
|
+
} from './const.js';
|
|
8
|
+
import {
|
|
9
|
+
getDocumentState,
|
|
10
|
+
getPageLifecycleEventEmitter,
|
|
11
|
+
getPageLifecycleState,
|
|
12
|
+
initPageLifecycle,
|
|
13
|
+
isPageLifecycleInitialized,
|
|
14
|
+
} from './util.js';
|
|
15
|
+
|
|
16
|
+
describe('Page Lifecycle', () => {
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
initPageLifecycle();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should initialize page lifecycle', () => {
|
|
22
|
+
expect(isPageLifecycleInitialized()).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should return current page lifecycle state', () => {
|
|
26
|
+
const state = getPageLifecycleState();
|
|
27
|
+
expect(state).toBeOneOf([...PAGE_LIFECYCLE_STATES]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should return current document state', () => {
|
|
31
|
+
const state = getDocumentState();
|
|
32
|
+
expect(state).toBeOneOf([...DOCUMENT_STATES]);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return event emitter instance', () => {
|
|
36
|
+
const emitter = getPageLifecycleEventEmitter();
|
|
37
|
+
expect(emitter).toBeDefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle page lifecycle state changes', () => {
|
|
41
|
+
const emitter = getPageLifecycleEventEmitter();
|
|
42
|
+
let stateChanged = false;
|
|
43
|
+
|
|
44
|
+
emitter.on(PAGE_LIFECYCLE_STATE_CHANGE_EVENT, (data: unknown) => {
|
|
45
|
+
stateChanged = true;
|
|
46
|
+
expect(data).toHaveProperty('previousState');
|
|
47
|
+
expect(data).toHaveProperty('nextState');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Trigger visibility change
|
|
51
|
+
const originalVisibilityState = document.visibilityState;
|
|
52
|
+
Object.defineProperty(document, 'visibilityState', {
|
|
53
|
+
value: originalVisibilityState === 'visible' ? 'hidden' : 'visible',
|
|
54
|
+
writable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
document.dispatchEvent(new Event('visibilitychange'));
|
|
59
|
+
|
|
60
|
+
// Restore original state
|
|
61
|
+
Object.defineProperty(document, 'visibilityState', {
|
|
62
|
+
value: originalVisibilityState,
|
|
63
|
+
writable: true,
|
|
64
|
+
configurable: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(stateChanged).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle document state changes', () => {
|
|
71
|
+
const emitter = getPageLifecycleEventEmitter();
|
|
72
|
+
let stateChanged = false;
|
|
73
|
+
|
|
74
|
+
emitter.on(DOCUMENT_STATE_CHANGE_EVENT, (data: unknown) => {
|
|
75
|
+
stateChanged = true;
|
|
76
|
+
expect(data).toHaveProperty('previousState');
|
|
77
|
+
expect(data).toHaveProperty('nextState');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Trigger ready state change
|
|
81
|
+
const originalReadyState = document.readyState;
|
|
82
|
+
Object.defineProperty(document, 'readyState', {
|
|
83
|
+
value: originalReadyState === 'complete' ? 'interactive' : 'complete',
|
|
84
|
+
writable: true,
|
|
85
|
+
configurable: true,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
document.dispatchEvent(new Event('readystatechange'));
|
|
89
|
+
|
|
90
|
+
// Restore original state
|
|
91
|
+
Object.defineProperty(document, 'readyState', {
|
|
92
|
+
value: originalReadyState,
|
|
93
|
+
writable: true,
|
|
94
|
+
configurable: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(stateChanged).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -16,27 +16,24 @@ import {
|
|
|
16
16
|
PAGE_LIFECYCLE_STATE_PASSIVE,
|
|
17
17
|
PAGE_LIFECYCLE_STATE_TERMINATED,
|
|
18
18
|
} from './const.js';
|
|
19
|
+
import type { DocumentState, PageLifecycleState } from './typedef.js';
|
|
19
20
|
|
|
20
21
|
const logger = new Logger('pagelifecycle');
|
|
21
22
|
|
|
22
23
|
const eventEmitter = new EventEmitter();
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
let currentPageLifecycleState = null;
|
|
25
|
+
let currentPageLifecycleState: PageLifecycleState | null | undefined = null;
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
let currentDocumentState = null;
|
|
27
|
+
let currentDocumentState: DocumentState | null | undefined = null;
|
|
29
28
|
|
|
30
29
|
let isInitialized = false;
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
const callbacks = {};
|
|
31
|
+
const callbacks: Record<string, (() => void)[]> = {};
|
|
34
32
|
|
|
35
33
|
/**
|
|
36
34
|
* Run callbacks for a specific state change.
|
|
37
|
-
* @param {import('./typedef.js').DocumentState | import('./typedef.js').PageLifecycleState} state - Callback state condition.
|
|
38
35
|
*/
|
|
39
|
-
const processCallbacks = (state) => {
|
|
36
|
+
const processCallbacks = (state: DocumentState | PageLifecycleState): void => {
|
|
40
37
|
const stateCallbacks = callbacks[state];
|
|
41
38
|
if (!stateCallbacks) {
|
|
42
39
|
return;
|
|
@@ -49,9 +46,8 @@ const processCallbacks = (state) => {
|
|
|
49
46
|
|
|
50
47
|
/**
|
|
51
48
|
* Detects the current page lifecycle state.
|
|
52
|
-
* @returns {import('./typedef.js').PageLifecycleState} Current page lifecycle state.
|
|
53
49
|
*/
|
|
54
|
-
const detectPageLifecycleState = () => {
|
|
50
|
+
const detectPageLifecycleState = (): PageLifecycleState => {
|
|
55
51
|
if (document.visibilityState === 'hidden') {
|
|
56
52
|
return PAGE_LIFECYCLE_STATE_HIDDEN;
|
|
57
53
|
}
|
|
@@ -63,9 +59,8 @@ const detectPageLifecycleState = () => {
|
|
|
63
59
|
|
|
64
60
|
/**
|
|
65
61
|
* Handles page lifecycle state change.
|
|
66
|
-
* @param {import('./typedef.js').PageLifecycleState} nextState - Next page lifecycle state.
|
|
67
62
|
*/
|
|
68
|
-
const onPageLifecycleStateChange = (nextState) => {
|
|
63
|
+
const onPageLifecycleStateChange = (nextState: PageLifecycleState): void => {
|
|
69
64
|
const previousState = currentPageLifecycleState;
|
|
70
65
|
if (nextState !== previousState) {
|
|
71
66
|
currentPageLifecycleState = nextState;
|
|
@@ -78,9 +73,8 @@ const onPageLifecycleStateChange = (nextState) => {
|
|
|
78
73
|
|
|
79
74
|
/**
|
|
80
75
|
* Handles document state change.
|
|
81
|
-
* @param {import('./typedef.js').DocumentState} nextState - Next document state.
|
|
82
76
|
*/
|
|
83
|
-
const onDocumentStateChange = (nextState) => {
|
|
77
|
+
const onDocumentStateChange = (nextState: DocumentState): void => {
|
|
84
78
|
const previousState = currentDocumentState;
|
|
85
79
|
if (nextState !== previousState) {
|
|
86
80
|
currentDocumentState = nextState;
|
|
@@ -94,7 +88,7 @@ const onDocumentStateChange = (nextState) => {
|
|
|
94
88
|
/**
|
|
95
89
|
* Initialize page lifecycle observer.
|
|
96
90
|
*/
|
|
97
|
-
export const initPageLifecycle = () => {
|
|
91
|
+
export const initPageLifecycle = (): void => {
|
|
98
92
|
if (isInitialized) {
|
|
99
93
|
return;
|
|
100
94
|
}
|
|
@@ -103,7 +97,6 @@ export const initPageLifecycle = () => {
|
|
|
103
97
|
onDocumentStateChange(document.readyState);
|
|
104
98
|
const options = { capture: true };
|
|
105
99
|
document.addEventListener('visibilitychange', () => onPageLifecycleStateChange(detectPageLifecycleState()), options);
|
|
106
|
-
// globalThis.addEventListener('popstate', () => onPageLifecycleStateChange(detectPageLifecycleState()), options);
|
|
107
100
|
globalThis.addEventListener('pageshow', () => onPageLifecycleStateChange(detectPageLifecycleState()), options);
|
|
108
101
|
globalThis.addEventListener('focus', () => onPageLifecycleStateChange(detectPageLifecycleState()), options);
|
|
109
102
|
globalThis.addEventListener('blur', () => onPageLifecycleStateChange(detectPageLifecycleState()), options);
|
|
@@ -123,21 +116,18 @@ export const initPageLifecycle = () => {
|
|
|
123
116
|
|
|
124
117
|
/**
|
|
125
118
|
* Returns the current page lifecycle state.
|
|
126
|
-
* @returns {string | null | undefined} Current page lifecycle state.
|
|
127
119
|
*/
|
|
128
|
-
export const getPageLifecycleState = () => currentPageLifecycleState;
|
|
120
|
+
export const getPageLifecycleState = (): PageLifecycleState | null | undefined => currentPageLifecycleState;
|
|
129
121
|
|
|
130
122
|
/**
|
|
131
123
|
* Returns the current document state.
|
|
132
|
-
* @returns {import('./typedef.js').DocumentState | null | undefined} Current document state.
|
|
133
124
|
*/
|
|
134
|
-
export const getDocumentState = () => currentDocumentState;
|
|
125
|
+
export const getDocumentState = (): DocumentState | null | undefined => currentDocumentState;
|
|
135
126
|
|
|
136
127
|
/**
|
|
137
128
|
* Returns the event emitter instance.
|
|
138
|
-
* @returns {EventEmitter} Event emitter instance.
|
|
139
129
|
*/
|
|
140
|
-
export const getPageLifecycleEventEmitter = () => {
|
|
130
|
+
export const getPageLifecycleEventEmitter = (): EventEmitter => {
|
|
141
131
|
if (!isInitialized) {
|
|
142
132
|
initPageLifecycle();
|
|
143
133
|
}
|
|
@@ -146,16 +136,13 @@ export const getPageLifecycleEventEmitter = () => {
|
|
|
146
136
|
|
|
147
137
|
/**
|
|
148
138
|
* Returns the page lifecycle observer initialized state.
|
|
149
|
-
* @returns {boolean} Page lifecycle observer initialized flag.
|
|
150
139
|
*/
|
|
151
|
-
export const isPageLifecycleInitialized = () => isInitialized;
|
|
140
|
+
export const isPageLifecycleInitialized = (): boolean => isInitialized;
|
|
152
141
|
|
|
153
142
|
/**
|
|
154
143
|
* Add callback for a specific state change.
|
|
155
|
-
* @param {import('./typedef.js').DocumentState | import('./typedef.js').PageLifecycleState} state - Callback state condition.
|
|
156
|
-
* @param {() => void} callback - Callback function.
|
|
157
144
|
*/
|
|
158
|
-
export const addPageLifecycleCallback = (state, callback) => {
|
|
145
|
+
export const addPageLifecycleCallback = (state: DocumentState | PageLifecycleState, callback: () => void): void => {
|
|
159
146
|
const stateCallbacks = callbacks[state] ?? [];
|
|
160
147
|
stateCallbacks.push(callback);
|
|
161
148
|
callbacks[state] = stateCallbacks;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
export interface TypeCheckErrorOptions extends ErrorOptions {
|
|
2
|
+
value?: unknown;
|
|
3
|
+
}
|
|
4
|
+
|
|
1
5
|
export class TypeCheckError extends TypeError {
|
|
6
|
+
value: unknown;
|
|
7
|
+
|
|
2
8
|
/**
|
|
3
9
|
* Creates a new `TypeCheckError` instance.
|
|
4
|
-
* @param {string} message - Error message.
|
|
5
|
-
* @param {{ cause?: unknown, value?: unknown }} [options] - Error options.
|
|
6
10
|
*/
|
|
7
|
-
constructor(message, options) {
|
|
11
|
+
constructor(message: string, options?: TypeCheckErrorOptions) {
|
|
8
12
|
super(message, options);
|
|
9
13
|
this.name = 'TypeCheckError';
|
|
10
14
|
this.value = options?.value;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { typeChecker } from './TypeChecker.js';
|
|
3
|
+
import { TypeCheckError } from './TypeCheckError.js';
|
|
4
|
+
|
|
5
|
+
const stringValidator = (value: unknown): value is string => typeof value === 'string';
|
|
6
|
+
|
|
7
|
+
describe('TypeChecker', () => {
|
|
8
|
+
it('should check value type correctly', () => {
|
|
9
|
+
const result = typeChecker.check('test', stringValidator);
|
|
10
|
+
expect(result).toBe('test');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should throw TypeCheckError for invalid type', () => {
|
|
14
|
+
expect(() => typeChecker.check(123, stringValidator)).toThrow(TypeCheckError);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should swallow errors when enabled', () => {
|
|
18
|
+
typeChecker.setSwallowErrors(true);
|
|
19
|
+
const result = typeChecker.check(123, stringValidator);
|
|
20
|
+
expect(result).toBe(123);
|
|
21
|
+
typeChecker.setSwallowErrors(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should check array type correctly', () => {
|
|
25
|
+
const result = typeChecker.checkArray(['a', 'b', 'c'], stringValidator);
|
|
26
|
+
expect(result).toEqual(['a', 'b', 'c']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should throw TypeCheckError for invalid array type', () => {
|
|
30
|
+
expect(() => typeChecker.checkArray([1, 2, 3], stringValidator)).toThrow(TypeCheckError);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should swallow array errors when enabled', () => {
|
|
34
|
+
typeChecker.setSwallowErrors(true);
|
|
35
|
+
const result = typeChecker.checkArray([1, 2, 3], stringValidator);
|
|
36
|
+
expect(result).toEqual([1, 2, 3]);
|
|
37
|
+
typeChecker.setSwallowErrors(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should check enum value correctly', () => {
|
|
41
|
+
const choices = ['option1', 'option2', 'option3'];
|
|
42
|
+
const result = typeChecker.checkEnum('option2', choices);
|
|
43
|
+
expect(result).toBe('option2');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should throw TypeCheckError for invalid enum value', () => {
|
|
47
|
+
const choices = ['option1', 'option2', 'option3'];
|
|
48
|
+
expect(() => typeChecker.checkEnum('invalid', choices)).toThrow(TypeCheckError);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should swallow enum errors when enabled', () => {
|
|
52
|
+
typeChecker.setSwallowErrors(true);
|
|
53
|
+
const choices = ['option1', 'option2', 'option3'];
|
|
54
|
+
const result = typeChecker.checkEnum('invalid', choices);
|
|
55
|
+
expect(result).toBe('invalid');
|
|
56
|
+
typeChecker.setSwallowErrors(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle number enum values', () => {
|
|
60
|
+
const choices = [1, 2, 3];
|
|
61
|
+
const result = typeChecker.checkEnum(2, choices);
|
|
62
|
+
expect(result).toBe(2);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle mixed string and number enum values', () => {
|
|
66
|
+
const choices = ['option1', 2, 'option3'];
|
|
67
|
+
const result = typeChecker.checkEnum(2, choices);
|
|
68
|
+
expect(result).toBe(2);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { TypeCheckError } from './TypeCheckError.js';
|
|
2
1
|
import { Logger } from '../logging/Logger.js';
|
|
3
|
-
import {
|
|
2
|
+
import { TypeCheckError } from './TypeCheckError.js';
|
|
3
|
+
import { typeCheck, typeCheckArray, typeCheckEnum, type EnumChoices } from './util.js';
|
|
4
4
|
|
|
5
5
|
const logger = new Logger('typechecker');
|
|
6
6
|
|
|
7
7
|
class TypeChecker {
|
|
8
|
-
|
|
9
|
-
static #instance;
|
|
8
|
+
static #instance: TypeChecker | undefined;
|
|
10
9
|
|
|
11
|
-
/** @type {boolean} */
|
|
12
10
|
#swallowErrors = false;
|
|
13
11
|
|
|
14
12
|
constructor() {
|
|
@@ -19,27 +17,21 @@ class TypeChecker {
|
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
19
|
* Enable or disable swallowing of TypeCheckErrors.
|
|
22
|
-
* @param {boolean} value - Swallow errors flag.
|
|
23
20
|
*/
|
|
24
|
-
setSwallowErrors(value) {
|
|
21
|
+
setSwallowErrors(value: boolean): void {
|
|
25
22
|
this.#swallowErrors = Boolean(value);
|
|
26
23
|
}
|
|
27
24
|
|
|
28
25
|
/**
|
|
29
26
|
* Type check a single value.
|
|
30
|
-
* @template T
|
|
31
|
-
* @param {unknown} value - The value to check.
|
|
32
|
-
* @param {(value: unknown) => value is T} validator - The validator to check with.
|
|
33
|
-
* @returns {T} - The type checked value.
|
|
34
27
|
*/
|
|
35
|
-
check(value, validator) {
|
|
28
|
+
check<T>(value: unknown, validator: (value: unknown) => value is T): T {
|
|
36
29
|
try {
|
|
37
30
|
return typeCheck(value, validator);
|
|
38
31
|
} catch (error) {
|
|
39
32
|
if (this.#swallowErrors && error instanceof TypeCheckError) {
|
|
40
33
|
logger.exception('check', error);
|
|
41
|
-
|
|
42
|
-
return value;
|
|
34
|
+
return value as T;
|
|
43
35
|
}
|
|
44
36
|
throw error;
|
|
45
37
|
}
|
|
@@ -47,32 +39,23 @@ class TypeChecker {
|
|
|
47
39
|
|
|
48
40
|
/**
|
|
49
41
|
* Type check an array of values.
|
|
50
|
-
* @template T
|
|
51
|
-
* @param {unknown[]} value - The value to check.
|
|
52
|
-
* @param {(value: unknown) => value is T} validator - The validator to check the array with.
|
|
53
|
-
* @returns {T[]} - The type checked value.
|
|
54
42
|
*/
|
|
55
|
-
checkArray(value, validator) {
|
|
43
|
+
checkArray<T>(value: unknown[], validator: (value: unknown) => value is T): T[] {
|
|
56
44
|
try {
|
|
57
45
|
return typeCheckArray(value, validator);
|
|
58
46
|
} catch (error) {
|
|
59
47
|
if (this.#swallowErrors && error instanceof TypeCheckError) {
|
|
60
48
|
logger.exception('checkArray', error);
|
|
61
|
-
|
|
62
|
-
return value;
|
|
49
|
+
return value as T[];
|
|
63
50
|
}
|
|
64
51
|
throw error;
|
|
65
52
|
}
|
|
66
53
|
}
|
|
67
54
|
|
|
68
55
|
/**
|
|
69
|
-
* Type check an
|
|
70
|
-
* @template T
|
|
71
|
-
* @param {string | number} value - The value to check.
|
|
72
|
-
* @param {(string | number)[] | Set<string | number> | Record<string | number, string | number>} choices - Enum list.
|
|
73
|
-
* @returns {string | number} - The type checked value.
|
|
56
|
+
* Type check an enum.
|
|
74
57
|
*/
|
|
75
|
-
checkEnum(value, choices) {
|
|
58
|
+
checkEnum(value: string | number, choices: EnumChoices): string | number {
|
|
76
59
|
try {
|
|
77
60
|
return typeCheckEnum(value, choices);
|
|
78
61
|
} catch (error) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isNumber, isPositiveInteger } from '../util/validate.js';
|
|
2
|
+
import { TypeCheckError } from './TypeCheckError.js';
|
|
3
|
+
import { typeCheck, typeCheckArray, typeCheckEnum } from './util.js';
|
|
4
|
+
|
|
5
|
+
describe('typecheck', () => {
|
|
6
|
+
test('typeCheck', () => {
|
|
7
|
+
expect(() => typeCheck(0.1, isNumber)).not.toThrowError(TypeCheckError);
|
|
8
|
+
expect(() => typeCheck(-0.1, isPositiveInteger)).toThrowError(TypeCheckError);
|
|
9
|
+
expect(() => typeCheck('string', isNumber)).toThrowError(TypeCheckError);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('typeCheckArray', () => {
|
|
13
|
+
expect(() => typeCheckArray([0.1], isNumber)).not.toThrowError(TypeCheckError);
|
|
14
|
+
expect(() => typeCheckArray(['string'], isNumber)).toThrowError(TypeCheckError);
|
|
15
|
+
// @ts-expect-error
|
|
16
|
+
expect(() => typeCheckArray(-0.1, isPositiveInteger)).toThrowError(TypeCheckError);
|
|
17
|
+
// @ts-expect-error
|
|
18
|
+
expect(() => typeCheckArray('string', isNumber)).toThrowError(TypeCheckError);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('typeCheckEnum', () => {
|
|
22
|
+
expect(() => typeCheckEnum('AA', ['AA'])).not.toThrowError(TypeCheckError);
|
|
23
|
+
expect(() => typeCheckEnum('AA', ['BB'])).toThrowError(TypeCheckError);
|
|
24
|
+
// @ts-expect-error
|
|
25
|
+
expect(() => typeCheckEnum(null, ['BB'])).toThrowError(TypeCheckError);
|
|
26
|
+
// @ts-expect-error
|
|
27
|
+
expect(() => typeCheckEnum(['AA'], null)).toThrowError(TypeCheckError);
|
|
28
|
+
try {
|
|
29
|
+
typeCheckEnum('AA', ['BB']);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error instanceof Error) {
|
|
32
|
+
expect(error.message).toBe('Validation failed: isEnum - "AA" (string)');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { getDisplayValue, getTypeFromValue } from '../util/string.js';
|
|
2
|
+
import { isArrayOf, isEnum } from '../util/validate.js';
|
|
3
|
+
import { TypeCheckError } from './TypeCheckError.js';
|
|
4
|
+
|
|
5
|
+
export type EnumChoices =
|
|
6
|
+
| ReadonlyArray<string | number>
|
|
7
|
+
| ReadonlySet<string | number>
|
|
8
|
+
| Readonly<Record<string | number, string | number>>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get error message for validator exceptions.
|
|
12
|
+
*/
|
|
13
|
+
const getErrorMessage = (validatorName: string, value: unknown): never => {
|
|
14
|
+
const displayValue = getDisplayValue(value);
|
|
15
|
+
const displayType = getTypeFromValue(value);
|
|
16
|
+
throw new TypeCheckError(`Validation failed: ${validatorName || '<anonymous>'} - ${displayValue} (${displayType})`);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Type check a value using a validator.
|
|
21
|
+
* @throws {TypeCheckError}
|
|
22
|
+
*/
|
|
23
|
+
export const typeCheck = <T>(value: unknown, validator: (value: unknown) => value is T): T => {
|
|
24
|
+
if (!validator(value)) {
|
|
25
|
+
getErrorMessage(validator.name, value);
|
|
26
|
+
}
|
|
27
|
+
return value as T;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Type check an array of values using a validator.
|
|
32
|
+
* @throws {TypeCheckError}
|
|
33
|
+
*/
|
|
34
|
+
export const typeCheckArray = <T>(value: unknown[], validator: (value: unknown) => value is T): T[] => {
|
|
35
|
+
if (!isArrayOf(value, validator)) {
|
|
36
|
+
getErrorMessage(validator.name, value);
|
|
37
|
+
}
|
|
38
|
+
return value as T[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Type check an enum.
|
|
43
|
+
* @throws {TypeCheckError}
|
|
44
|
+
*/
|
|
45
|
+
export const typeCheckEnum = (value: string | number, choices: EnumChoices): string | number => {
|
|
46
|
+
if (!isEnum(value, choices)) {
|
|
47
|
+
getErrorMessage('isEnum', value);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { delayPromise, loadJSON, retryAsync } from './async.js';
|
|
2
|
+
|
|
3
|
+
describe('delayPromise', () => {
|
|
4
|
+
test('Returns a promise that resolves after specified delay', async () => {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
await delayPromise(10);
|
|
7
|
+
const end = Date.now();
|
|
8
|
+
|
|
9
|
+
expect(end - start).toBeGreaterThanOrEqual(9);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('delayPromise with zero delay', async () => {
|
|
13
|
+
const start = Date.now();
|
|
14
|
+
await delayPromise(0);
|
|
15
|
+
const end = Date.now();
|
|
16
|
+
|
|
17
|
+
expect(end - start).toBeGreaterThanOrEqual(0);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('loadJSON', () => {
|
|
22
|
+
test('Load JSON data', async () => {
|
|
23
|
+
const data = await loadJSON('/test.json');
|
|
24
|
+
expect(data).toMatchObject({
|
|
25
|
+
method: 'GET',
|
|
26
|
+
success: true,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('retryAsync', () => {
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.useFakeTimers({ shouldAdvanceTime: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
vi.useRealTimers();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('returns result when method succeeds immediately', async () => {
|
|
41
|
+
const method = vi.fn().mockResolvedValue('success');
|
|
42
|
+
const result = await retryAsync(method, 1, 100);
|
|
43
|
+
expect(result).toBe('success');
|
|
44
|
+
expect(method).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('retries once and then succeeds', async () => {
|
|
48
|
+
const method = vi.fn().mockRejectedValueOnce(new Error('fail')).mockResolvedValueOnce('success');
|
|
49
|
+
const promise = retryAsync(method, 1, 100);
|
|
50
|
+
const result = await promise;
|
|
51
|
+
expect(result).toBe('success');
|
|
52
|
+
expect(method).toHaveBeenCalledTimes(2);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('throws after exceeding retries', async () => {
|
|
56
|
+
const method = vi.fn().mockRejectedValue(new Error('fail'));
|
|
57
|
+
const promise = retryAsync(method, 1, 100);
|
|
58
|
+
await expect(promise).rejects.toThrow('fail');
|
|
59
|
+
expect(method).toHaveBeenCalledTimes(2);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('does not retry when numTries is 0', async () => {
|
|
63
|
+
const method = vi.fn().mockRejectedValue(new Error('fail'));
|
|
64
|
+
await expect(retryAsync(method, 0, 100)).rejects.toThrow('fail');
|
|
65
|
+
expect(method).toHaveBeenCalledTimes(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('does not wait when delayMs is 0', async () => {
|
|
69
|
+
const method = vi.fn().mockRejectedValueOnce(new Error('fail')).mockResolvedValueOnce('success');
|
|
70
|
+
const result = await retryAsync(method, 1, 0);
|
|
71
|
+
expect(result).toBe('success');
|
|
72
|
+
expect(method).toHaveBeenCalledTimes(2);
|
|
73
|
+
});
|
|
74
|
+
});
|