@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.
Files changed (167) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/const/http_status.d.ts +66 -0
  3. package/dist/const/http_status.d.ts.map +1 -0
  4. package/dist/index.d.ts +34 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +1119 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/logging/AbstractLogHandler.d.ts +17 -0
  9. package/dist/logging/AbstractLogHandler.d.ts.map +1 -0
  10. package/dist/logging/ConsoleLogHandler.d.ts +13 -0
  11. package/dist/logging/ConsoleLogHandler.d.ts.map +1 -0
  12. package/dist/logging/Logger.d.ts +19 -0
  13. package/dist/logging/Logger.d.ts.map +1 -0
  14. package/dist/logging/OpenTelemetryLogHandler.d.ts +15 -0
  15. package/dist/logging/OpenTelemetryLogHandler.d.ts.map +1 -0
  16. package/dist/logging/SentryLogHandler.d.ts +13 -0
  17. package/dist/logging/SentryLogHandler.d.ts.map +1 -0
  18. package/dist/logging/const.d.ts +14 -0
  19. package/dist/logging/const.d.ts.map +1 -0
  20. package/dist/logging/util.d.ts +14 -0
  21. package/dist/logging/util.d.ts.map +1 -0
  22. package/dist/pagelifecycle/const.d.ts +15 -0
  23. package/dist/pagelifecycle/const.d.ts.map +1 -0
  24. package/dist/pagelifecycle/typedef.d.ts +4 -0
  25. package/dist/pagelifecycle/typedef.d.ts.map +1 -0
  26. package/dist/pagelifecycle/util.d.ts +31 -0
  27. package/dist/pagelifecycle/util.d.ts.map +1 -0
  28. package/dist/typecheck/TypeCheckError.d.ts +11 -0
  29. package/dist/typecheck/TypeCheckError.d.ts.map +1 -0
  30. package/dist/typecheck/TypeChecker.d.ts +27 -0
  31. package/dist/typecheck/TypeChecker.d.ts.map +1 -0
  32. package/dist/typecheck/util.d.ts +17 -0
  33. package/dist/typecheck/util.d.ts.map +1 -0
  34. package/dist/util/async.d.ts +13 -0
  35. package/dist/util/async.d.ts.map +1 -0
  36. package/dist/util/error.d.ts +16 -0
  37. package/dist/util/error.d.ts.map +1 -0
  38. package/dist/util/event_emitter.d.ts +42 -0
  39. package/dist/util/event_emitter.d.ts.map +1 -0
  40. package/dist/util/fetch.d.ts +22 -0
  41. package/dist/util/fetch.d.ts.map +1 -0
  42. package/dist/util/number.d.ts +23 -0
  43. package/dist/util/number.d.ts.map +1 -0
  44. package/dist/util/object.d.ts +24 -0
  45. package/dist/util/object.d.ts.map +1 -0
  46. package/dist/util/query.d.ts +11 -0
  47. package/dist/util/query.d.ts.map +1 -0
  48. package/dist/util/state.d.ts +5 -0
  49. package/dist/util/state.d.ts.map +1 -0
  50. package/dist/util/string.d.ts +25 -0
  51. package/dist/util/string.d.ts.map +1 -0
  52. package/dist/util/uuid.d.ts +13 -0
  53. package/dist/util/uuid.d.ts.map +1 -0
  54. package/dist/util/validate.d.ts +106 -0
  55. package/dist/util/validate.d.ts.map +1 -0
  56. package/package.json +32 -16
  57. package/src/const/http_status.test.ts +7 -0
  58. package/src/const/{http_status.js → http_status.ts} +1 -1
  59. package/src/{index.js → index.ts} +8 -0
  60. package/src/logging/AbstractLogHandler.ts +31 -0
  61. package/src/logging/{ConsoleLogHandler.js → ConsoleLogHandler.ts} +15 -13
  62. package/src/logging/Logger.test.ts +69 -0
  63. package/src/logging/Logger.ts +77 -0
  64. package/src/logging/OpenTelemetryLogHandler.ts +40 -0
  65. package/src/logging/SentryLogHandler.ts +44 -0
  66. package/src/logging/{const.js → const.ts} +1 -1
  67. package/src/logging/util.test.ts +33 -0
  68. package/src/logging/util.ts +36 -0
  69. package/src/pagelifecycle/{const.js → const.ts} +2 -2
  70. package/src/pagelifecycle/typedef.ts +5 -0
  71. package/src/pagelifecycle/util.test.ts +99 -0
  72. package/src/pagelifecycle/{util.js → util.ts} +14 -27
  73. package/src/typecheck/{TypeCheckError.js → TypeCheckError.ts} +7 -3
  74. package/src/typecheck/TypeChecker.test.ts +70 -0
  75. package/src/typecheck/{TypeChecker.js → TypeChecker.ts} +10 -27
  76. package/src/typecheck/util.test.ts +36 -0
  77. package/src/typecheck/util.ts +50 -0
  78. package/src/util/async.test.ts +74 -0
  79. package/src/util/{async.js → async.ts} +3 -12
  80. package/src/util/error.test.ts +32 -0
  81. package/src/util/error.ts +37 -0
  82. package/src/util/event_emitter.test.ts +228 -0
  83. package/src/util/event_emitter.ts +147 -0
  84. package/src/util/fetch.test.ts +62 -0
  85. package/src/util/{fetch.js → fetch.ts} +40 -31
  86. package/src/util/number.test.ts +124 -0
  87. package/src/util/number.ts +58 -0
  88. package/src/util/object.test.ts +203 -0
  89. package/src/util/{object.js → object.ts} +14 -21
  90. package/src/util/query.test.ts +71 -0
  91. package/src/util/query.ts +35 -0
  92. package/src/util/state.test.ts +47 -0
  93. package/src/util/{state.js → state.ts} +3 -6
  94. package/src/util/string.test.ts +64 -0
  95. package/src/util/string.ts +65 -0
  96. package/src/util/uuid.test.ts +53 -0
  97. package/src/util/uuid.ts +31 -0
  98. package/src/util/validate.test.ts +309 -0
  99. package/src/util/validate.ts +230 -0
  100. package/.vscode/extensions.json +0 -6
  101. package/.vscode/settings.json +0 -27
  102. package/src/logging/AbstractLogHandler.js +0 -23
  103. package/src/logging/Logger.js +0 -115
  104. package/src/logging/OpenTelemetryLogHandler.js +0 -30
  105. package/src/logging/SentryLogHandler.js +0 -46
  106. package/src/logging/util.js +0 -41
  107. package/src/pagelifecycle/typedef.js +0 -9
  108. package/src/typecheck/util.js +0 -60
  109. package/src/util/error.js +0 -33
  110. package/src/util/event_emitter.js +0 -196
  111. package/src/util/number.js +0 -118
  112. package/src/util/query.js +0 -32
  113. package/src/util/string.js +0 -76
  114. package/src/util/uuid.js +0 -35
  115. package/src/util/validate.js +0 -247
  116. package/types/const/http_status.d.ts +0 -131
  117. package/types/const/http_status.d.ts.map +0 -1
  118. package/types/index.d.ts +0 -26
  119. package/types/index.d.ts.map +0 -1
  120. package/types/logging/AbstractLogHandler.d.ts +0 -20
  121. package/types/logging/AbstractLogHandler.d.ts.map +0 -1
  122. package/types/logging/ConsoleLogHandler.d.ts +0 -9
  123. package/types/logging/ConsoleLogHandler.d.ts.map +0 -1
  124. package/types/logging/Logger.d.ts +0 -66
  125. package/types/logging/Logger.d.ts.map +0 -1
  126. package/types/logging/OpenTelemetryLogHandler.d.ts +0 -11
  127. package/types/logging/OpenTelemetryLogHandler.d.ts.map +0 -1
  128. package/types/logging/SentryLogHandler.d.ts +0 -9
  129. package/types/logging/SentryLogHandler.d.ts.map +0 -1
  130. package/types/logging/const.d.ts +0 -14
  131. package/types/logging/const.d.ts.map +0 -1
  132. package/types/logging/util.d.ts +0 -4
  133. package/types/logging/util.d.ts.map +0 -1
  134. package/types/pagelifecycle/const.d.ts +0 -15
  135. package/types/pagelifecycle/const.d.ts.map +0 -1
  136. package/types/pagelifecycle/typedef.d.ts +0 -4
  137. package/types/pagelifecycle/typedef.d.ts.map +0 -1
  138. package/types/pagelifecycle/util.d.ts +0 -8
  139. package/types/pagelifecycle/util.d.ts.map +0 -1
  140. package/types/typecheck/TypeCheckError.d.ts +0 -13
  141. package/types/typecheck/TypeCheckError.d.ts.map +0 -1
  142. package/types/typecheck/TypeChecker.d.ts +0 -40
  143. package/types/typecheck/TypeChecker.d.ts.map +0 -1
  144. package/types/typecheck/util.d.ts +0 -4
  145. package/types/typecheck/util.d.ts.map +0 -1
  146. package/types/util/async.d.ts +0 -4
  147. package/types/util/async.d.ts.map +0 -1
  148. package/types/util/error.d.ts +0 -3
  149. package/types/util/error.d.ts.map +0 -1
  150. package/types/util/event_emitter.d.ts +0 -69
  151. package/types/util/event_emitter.d.ts.map +0 -1
  152. package/types/util/fetch.d.ts +0 -22
  153. package/types/util/fetch.d.ts.map +0 -1
  154. package/types/util/number.d.ts +0 -11
  155. package/types/util/number.d.ts.map +0 -1
  156. package/types/util/object.d.ts +0 -6
  157. package/types/util/object.d.ts.map +0 -1
  158. package/types/util/query.d.ts +0 -3
  159. package/types/util/query.d.ts.map +0 -1
  160. package/types/util/state.d.ts +0 -2
  161. package/types/util/state.d.ts.map +0 -1
  162. package/types/util/string.d.ts +0 -7
  163. package/types/util/string.d.ts.map +0 -1
  164. package/types/util/uuid.d.ts +0 -4
  165. package/types/util/uuid.d.ts.map +0 -1
  166. package/types/util/validate.d.ts +0 -45
  167. 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,5 @@
1
+ export const TYPEDEF = true;
2
+
3
+ export type PageLifecycleState = 'hidden' | 'active' | 'passive' | 'frozen' | 'terminated';
4
+
5
+ export type DocumentState = 'loading' | 'interactive' | 'complete' | 'domLoaded' | 'fullyLoaded';
@@ -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
- /** @type {import('./typedef.js').PageLifecycleState | null | undefined} */
25
- let currentPageLifecycleState = null;
25
+ let currentPageLifecycleState: PageLifecycleState | null | undefined = null;
26
26
 
27
- /** @type {import('./typedef.js').DocumentState | null | undefined} */
28
- let currentDocumentState = null;
27
+ let currentDocumentState: DocumentState | null | undefined = null;
29
28
 
30
29
  let isInitialized = false;
31
30
 
32
- /** @type {Record<string, (() => void)[]>} */
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 { typeCheck, typeCheckArray, typeCheckEnum } from './util.js';
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
- /** @type {TypeChecker} */
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
- // @ts-expect-error
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
- // @ts-expect-error
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 array of values.
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
+ });