@vpmedia/simplify 1.54.0 → 1.55.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 (112) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/package.json +14 -14
  3. package/src/index.js +9 -19
  4. package/src/logging/Logger.js +1 -1
  5. package/src/logging/OpenTelemetryLogHandler.js +1 -1
  6. package/src/logging/util.js +2 -0
  7. package/src/logging/util.test.js +4 -1
  8. package/src/pagelifecycle/util.js +17 -21
  9. package/src/util/async.js +20 -0
  10. package/src/util/{delayPromise.test.js → async.test.js} +1 -1
  11. package/src/util/{getErrorDetails.test.js → error.test.js} +1 -1
  12. package/src/util/{fetchRetry.js → fetch.js} +6 -4
  13. package/src/util/number.js +116 -0
  14. package/src/util/number.test.js +115 -0
  15. package/src/util/object.js +97 -0
  16. package/src/util/object.test.js +205 -0
  17. package/src/util/{getURLParam.js → query.js} +12 -2
  18. package/src/util/{sanitizeURLParam.test.js → query.test.js} +18 -1
  19. package/src/util/{serverDataToState.js → state.js} +1 -1
  20. package/src/util/{serverDataToState.test.js → state.test.js} +1 -1
  21. package/src/util/string.js +54 -0
  22. package/src/util/{capitalize.test.js → string.test.js} +19 -1
  23. package/src/util/validate.js +272 -0
  24. package/src/util/validate.test.js +325 -0
  25. package/tsconfig.build.json +24 -0
  26. package/types/index.d.ts +9 -19
  27. package/types/logging/util.d.ts.map +1 -1
  28. package/types/pagelifecycle/util.d.ts +1 -1
  29. package/types/pagelifecycle/util.d.ts.map +1 -1
  30. package/types/util/async.d.ts +3 -0
  31. package/types/util/async.d.ts.map +1 -0
  32. package/types/util/{getErrorDetails.d.ts → error.d.ts} +1 -1
  33. package/types/util/error.d.ts.map +1 -0
  34. package/types/util/{fetchRetry.d.ts → fetch.d.ts} +1 -1
  35. package/types/util/fetch.d.ts.map +1 -0
  36. package/types/util/number.d.ts +13 -0
  37. package/types/util/number.d.ts.map +1 -0
  38. package/types/util/object.d.ts +6 -0
  39. package/types/util/object.d.ts.map +1 -0
  40. package/types/util/{getURLParam.d.ts → query.d.ts} +2 -1
  41. package/types/util/query.d.ts.map +1 -0
  42. package/types/util/{serverDataToState.d.ts → state.d.ts} +1 -1
  43. package/types/util/state.d.ts.map +1 -0
  44. package/types/util/string.d.ts +5 -0
  45. package/types/util/string.d.ts.map +1 -0
  46. package/types/util/validate.d.ts +54 -0
  47. package/types/util/validate.d.ts.map +1 -0
  48. package/src/util/addLeadingZero.js +0 -16
  49. package/src/util/addLeadingZero.test.js +0 -11
  50. package/src/util/capitalize.js +0 -15
  51. package/src/util/deepMerge.js +0 -24
  52. package/src/util/deepMerge.test.js +0 -103
  53. package/src/util/deg2rad.js +0 -8
  54. package/src/util/deg2rad.test.js +0 -5
  55. package/src/util/delayPromise.js +0 -6
  56. package/src/util/fixFloatPrecision.js +0 -16
  57. package/src/util/fixFloatPrecision.test.js +0 -27
  58. package/src/util/getObjArrayPropSum.js +0 -11
  59. package/src/util/getObjValueByPath.js +0 -20
  60. package/src/util/getObjValueByPath.test.js +0 -51
  61. package/src/util/getRandomInt.js +0 -9
  62. package/src/util/getRandomInt.test.js +0 -24
  63. package/src/util/getURLParam.test.js +0 -18
  64. package/src/util/loadJSON.js +0 -10
  65. package/src/util/purgeObject.js +0 -13
  66. package/src/util/purgeObject.test.js +0 -8
  67. package/src/util/safeFloat.js +0 -31
  68. package/src/util/safeFloat.test.js +0 -13
  69. package/src/util/sanitizeURLParam.js +0 -11
  70. package/src/util/saveAsFile.js +0 -14
  71. package/src/util/setObjValueByPath.js +0 -26
  72. package/src/util/setObjValueByPath.test.js +0 -47
  73. package/src/util/underscoreToCamelCase.js +0 -10
  74. package/src/util/underscoreToCamelCase.test.js +0 -7
  75. package/typedefs/global.d.ts +0 -5
  76. package/types/util/addLeadingZero.d.ts +0 -2
  77. package/types/util/addLeadingZero.d.ts.map +0 -1
  78. package/types/util/capitalize.d.ts +0 -2
  79. package/types/util/capitalize.d.ts.map +0 -1
  80. package/types/util/deepMerge.d.ts +0 -2
  81. package/types/util/deepMerge.d.ts.map +0 -1
  82. package/types/util/deg2rad.d.ts +0 -2
  83. package/types/util/deg2rad.d.ts.map +0 -1
  84. package/types/util/delayPromise.d.ts +0 -2
  85. package/types/util/delayPromise.d.ts.map +0 -1
  86. package/types/util/fetchRetry.d.ts.map +0 -1
  87. package/types/util/fixFloatPrecision.d.ts +0 -7
  88. package/types/util/fixFloatPrecision.d.ts.map +0 -1
  89. package/types/util/getErrorDetails.d.ts.map +0 -1
  90. package/types/util/getObjArrayPropSum.d.ts +0 -2
  91. package/types/util/getObjArrayPropSum.d.ts.map +0 -1
  92. package/types/util/getObjValueByPath.d.ts +0 -2
  93. package/types/util/getObjValueByPath.d.ts.map +0 -1
  94. package/types/util/getRandomInt.d.ts +0 -2
  95. package/types/util/getRandomInt.d.ts.map +0 -1
  96. package/types/util/getURLParam.d.ts.map +0 -1
  97. package/types/util/loadJSON.d.ts +0 -7
  98. package/types/util/loadJSON.d.ts.map +0 -1
  99. package/types/util/purgeObject.d.ts +0 -2
  100. package/types/util/purgeObject.d.ts.map +0 -1
  101. package/types/util/safeFloat.d.ts +0 -22
  102. package/types/util/safeFloat.d.ts.map +0 -1
  103. package/types/util/sanitizeURLParam.d.ts +0 -2
  104. package/types/util/sanitizeURLParam.d.ts.map +0 -1
  105. package/types/util/saveAsFile.d.ts +0 -2
  106. package/types/util/saveAsFile.d.ts.map +0 -1
  107. package/types/util/serverDataToState.d.ts.map +0 -1
  108. package/types/util/setObjValueByPath.d.ts +0 -2
  109. package/types/util/setObjValueByPath.d.ts.map +0 -1
  110. package/types/util/underscoreToCamelCase.d.ts +0 -2
  111. package/types/util/underscoreToCamelCase.d.ts.map +0 -1
  112. /package/src/util/{getErrorDetails.js → error.js} +0 -0
@@ -0,0 +1,205 @@
1
+ import { getObjValueByPath, setObjValueByPath, purgeObject, deepMerge } from './object.js';
2
+
3
+ describe('deepMerge', () => {
4
+ test('should override deep properties correctly', () => {
5
+ const defaultObj = { a: { b: 1, c: 2 }, d: 3 };
6
+ const overrideObj = { a: { b: 42 } };
7
+ const expectedResult = { a: { b: 42, c: 2 }, d: 3 };
8
+
9
+ expect(deepMerge({ ...defaultObj }, overrideObj)).toEqual(expectedResult);
10
+ });
11
+
12
+ test('should not modify the original target object', () => {
13
+ const target = { x: { y: 10 } };
14
+ const source = { x: { y: 20 } };
15
+ const copy = { ...target };
16
+
17
+ deepMerge(target, source);
18
+ expect(target).toEqual(copy);
19
+ });
20
+
21
+ test('should handle non-object values correctly', () => {
22
+ expect(deepMerge(null, { a: 1 })).toEqual({ a: 1 });
23
+ expect(deepMerge({ a: 1 }, null)).toEqual({ a: 1 });
24
+ });
25
+
26
+ test('should handle arrays correctly', () => {
27
+ const target = { arr: [1, 2] };
28
+ const source = { arr: [3, 4] };
29
+
30
+ expect(deepMerge(target, source)).toEqual({ arr: [3, 4] });
31
+ });
32
+
33
+ test('should handle nested arrays correctly', () => {
34
+ const target = { obj: { arr: [1, 2] } };
35
+ const source = { obj: { arr: [3, 4] } };
36
+
37
+ expect(deepMerge(target, source)).toEqual({ obj: { arr: [3, 4] } });
38
+ });
39
+
40
+ test('should handle string values', () => {
41
+ const target = { str: 'hello' };
42
+ const source = { str: 'world' };
43
+
44
+ expect(deepMerge(target, source)).toEqual({ str: 'world' });
45
+ });
46
+
47
+ test('should handle number values', () => {
48
+ const target = { num: 42 };
49
+ const source = { num: 100 };
50
+
51
+ expect(deepMerge(target, source)).toEqual({ num: 100 });
52
+ });
53
+
54
+ test('should handle boolean values', () => {
55
+ const target = { bool: true };
56
+ const source = { bool: false };
57
+
58
+ expect(deepMerge(target, source)).toEqual({ bool: false });
59
+ });
60
+
61
+ test('should handle undefined values', () => {
62
+ const target = { undef: undefined };
63
+ const source = { undef: 'value' };
64
+
65
+ expect(deepMerge(target, source)).toEqual({ undef: 'value' });
66
+ });
67
+
68
+ test('should handle null values', () => {
69
+ const target = { nullVal: null };
70
+ const source = { nullVal: 'value' };
71
+
72
+ expect(deepMerge(target, source)).toEqual({ nullVal: 'value' });
73
+ });
74
+
75
+ test('should handle mixed property types', () => {
76
+ const target = {
77
+ str: 'hello',
78
+ num: 42,
79
+ arr: [1, 2],
80
+ obj: { nested: 'value' },
81
+ };
82
+ const source = {
83
+ str: 'world',
84
+ num: 100,
85
+ arr: [3, 4],
86
+ obj: { nested: 'newValue' },
87
+ };
88
+
89
+ expect(deepMerge(target, source)).toEqual({
90
+ str: 'world',
91
+ num: 100,
92
+ arr: [3, 4],
93
+ obj: { nested: 'newValue' },
94
+ });
95
+ });
96
+
97
+ test('should handle constructor and __proto__ protection', () => {
98
+ const target = { a: 1 };
99
+ const source = { b: 2 };
100
+
101
+ expect(deepMerge(target, source)).toEqual({ a: 1, b: 2 });
102
+ });
103
+ });
104
+
105
+ test('Purges object of null and undefined values', () => {
106
+ const a = { k: 'v' };
107
+ expect(a.k).toBe('v');
108
+ purgeObject(a);
109
+ expect(a.k).toBe(null);
110
+ });
111
+
112
+ describe('getObjValueByPath', () => {
113
+ test('Gets object value by path', () => {
114
+ const source = { a: { b: { c: 'd' } } };
115
+ expect(getObjValueByPath(source, 'a.b.c')).toBe('d');
116
+ });
117
+
118
+ test('Returns null when object is null or undefined', () => {
119
+ expect(getObjValueByPath(null, 'a.b.c')).toBeNull();
120
+ expect(getObjValueByPath(undefined, 'a.b.c')).toBeNull();
121
+ });
122
+
123
+ test('Returns null when path is empty or null', () => {
124
+ expect(getObjValueByPath({ a: 'b' }, '')).toBeNull();
125
+ expect(getObjValueByPath({ a: 'b' }, null)).toBeNull();
126
+ });
127
+
128
+ test('Returns null when property does not exist', () => {
129
+ const source = { a: { b: 'c' } };
130
+ expect(getObjValueByPath(source, 'a.b.c')).toBeNull();
131
+ });
132
+
133
+ test('Returns null when property is undefined', () => {
134
+ const source = { a: { b: undefined } };
135
+ expect(getObjValueByPath(source, 'a.b')).toBeNull();
136
+ });
137
+
138
+ test('Handles single-level paths correctly', () => {
139
+ const source = { a: 'value' };
140
+ expect(getObjValueByPath(source, 'a')).toBe('value');
141
+ });
142
+
143
+ test('Handles nested paths correctly', () => {
144
+ const source = {
145
+ level1: {
146
+ level2: {
147
+ level3: 'deepValue',
148
+ },
149
+ },
150
+ };
151
+ expect(getObjValueByPath(source, 'level1.level2.level3')).toBe('deepValue');
152
+ });
153
+
154
+ test('Handles arrays in paths', () => {
155
+ const source = {
156
+ items: [{ name: 'item1' }, { name: 'item2' }],
157
+ };
158
+ expect(getObjValueByPath(source, 'items.0.name')).toBe('item1');
159
+ });
160
+ });
161
+
162
+ describe('setObjValueByPath', () => {
163
+ test('Sets object value by path', () => {
164
+ const source = { a: { b: { c: 'd' } } };
165
+ expect(getObjValueByPath(source, 'a.b.c')).toBe('d');
166
+ setObjValueByPath(source, 'a.b.c', 'newValue');
167
+ expect(getObjValueByPath(source, 'a.b.c')).toBe('newValue');
168
+ });
169
+
170
+ test('Handles null or undefined object', () => {
171
+ // Should not throw error
172
+ setObjValueByPath(null, 'a.b.c', 'value');
173
+ setObjValueByPath(undefined, 'a.b.c', 'value');
174
+ });
175
+
176
+ test('Handles null or undefined path', () => {
177
+ const source = { a: 'b' };
178
+ // Should not throw error
179
+ setObjValueByPath(source, null, 'value');
180
+ setObjValueByPath(source, undefined, 'value');
181
+ });
182
+
183
+ test('Sets value at root level', () => {
184
+ const source = { a: 'oldValue' };
185
+ setObjValueByPath(source, 'a', 'newValue');
186
+ expect(source.a).toBe('newValue');
187
+ });
188
+
189
+ test('Creates new nested properties', () => {
190
+ const source = { a: { b: 'existing' } };
191
+ setObjValueByPath(source, 'a.c.d', 'newNestedValue');
192
+ expect(getObjValueByPath(source, 'a.c.d')).toBe('newNestedValue');
193
+ });
194
+
195
+ test('Handles array paths', () => {
196
+ const source = { items: [{ name: 'item1' }] };
197
+ setObjValueByPath(source, 'items.0.name', 'updatedItem');
198
+ expect(getObjValueByPath(source, 'items.0.name')).toBe('updatedItem');
199
+ });
200
+
201
+ test('Throws error for __proto__ path', () => {
202
+ const source = { a: 'b' };
203
+ expect(() => setObjValueByPath(source, '__proto__.test', 'value')).toThrow(SyntaxError);
204
+ });
205
+ });
@@ -1,6 +1,16 @@
1
- import { sanitizeURLParam } from './sanitizeURLParam.js';
1
+ const urlSearchParams = new URLSearchParams(globalThis.location.search);
2
2
 
3
- const urlSearchParams = new URLSearchParams(window.location.search);
3
+ /**
4
+ * Sanitizes URL parameters allowing only alpha-numeric characters and dash.
5
+ * @param {string} input - The input string to be sanitized.
6
+ * @returns {string} The sanitized output string.
7
+ */
8
+ export const sanitizeURLParam = (input) => {
9
+ if (!input) {
10
+ return input;
11
+ }
12
+ return input.replaceAll(/[^\w-]/giu, '');
13
+ };
4
14
 
5
15
  /**
6
16
  * Get a URL parameter value.
@@ -1,4 +1,21 @@
1
- import { sanitizeURLParam } from './sanitizeURLParam.js';
1
+ import { getURLParam, sanitizeURLParam } from './query.js';
2
+
3
+ describe('getURLParam', () => {
4
+ test('Returns fallback value when parameter is not found', () => {
5
+ const result = getURLParam('nonexistent', 'fallback');
6
+ expect(result).toBe('fallback');
7
+ });
8
+
9
+ test('Handles null/undefined input gracefully', () => {
10
+ const result = getURLParam(null, 'fallback');
11
+ expect(result).toBe('fallback');
12
+ });
13
+
14
+ test('Returns default value when param is null', () => {
15
+ const result = getURLParam('key', 'default');
16
+ expect(result).toBe('default');
17
+ });
18
+ });
2
19
 
3
20
  describe('sanitizeURLParam', () => {
4
21
  test('Sanitizes URL parameter correctly', () => {
@@ -1,4 +1,4 @@
1
- import { underscoreToCamelCase } from './underscoreToCamelCase.js';
1
+ import { underscoreToCamelCase } from './string.js';
2
2
 
3
3
  /**
4
4
  * Maps server data to client data.
@@ -1,4 +1,4 @@
1
- import { serverDataToState } from './serverDataToState.js';
1
+ import { serverDataToState } from './state.js';
2
2
 
3
3
  test('serverDataToState() recursive', () => {
4
4
  const state = serverDataToState(
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Add leading zeros to a value to ensure it has a minimum width.
3
+ * @param {number | string | null | undefined} value - The value to pad with leading zeros.
4
+ * @param {number} size - The minimum width of the resulting string.
5
+ * @returns {string | null} The value padded with leading zeros or null if the input is null/undefined.
6
+ */
7
+ export const addLeadingZero = (value, size = 2) => {
8
+ if (value === null || value === undefined) {
9
+ return null;
10
+ }
11
+ value = value.toString();
12
+ while (value.length < size) {
13
+ value = `0${value}`;
14
+ }
15
+ return value;
16
+ };
17
+
18
+ /**
19
+ * Capitalize a string.
20
+ * @param {string | null | undefined} value - The input string to capitalize.
21
+ * @returns {string | null} The capitalized string or null if the input is null/undefined.
22
+ */
23
+ export const capitalize = (value) => {
24
+ if (value === null || value === undefined) {
25
+ return null;
26
+ }
27
+ if (!value || value?.length === 0) {
28
+ return value;
29
+ }
30
+ const normValue = value.toLowerCase();
31
+ return normValue.charAt(0).toUpperCase() + normValue.slice(1);
32
+ };
33
+
34
+ /**
35
+ * Converts underscore case string to camel case.
36
+ * @param {string} value - The input string in underscore case.
37
+ * @returns {string} The output string in camel case.
38
+ */
39
+ export const underscoreToCamelCase = (value) => value.replaceAll(/(_\w)/gu, (m) => m[1].toUpperCase());
40
+
41
+ /**
42
+ * Saves text file.
43
+ * @param {string} filename - File name.
44
+ * @param {string} text - File content.
45
+ */
46
+ export const saveAsFile = (filename, text) => {
47
+ const element = document.createElement('a');
48
+ element.setAttribute('href', `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`);
49
+ element.setAttribute('download', filename);
50
+ element.style.display = 'none';
51
+ document.body.append(element);
52
+ element.click();
53
+ element.remove();
54
+ };
@@ -1,4 +1,16 @@
1
- import { capitalize } from './capitalize.js';
1
+ /* eslint-disable unicorn/no-useless-undefined */
2
+
3
+ import { underscoreToCamelCase, capitalize, addLeadingZero } from './string.js';
4
+
5
+ test('Tests add leading zero', () => {
6
+ expect(addLeadingZero(1)).toBe('01');
7
+ expect(addLeadingZero('1')).toBe('01');
8
+ expect(addLeadingZero(1, 3)).toBe('001');
9
+ expect(addLeadingZero('21')).toBe('21');
10
+ expect(addLeadingZero(21)).toBe('21');
11
+ expect(addLeadingZero(null)).toBe(null);
12
+ expect(addLeadingZero(undefined)).toBe(null);
13
+ });
2
14
 
3
15
  describe('capitalize', () => {
4
16
  test('Capitalizes first letter of string', () => {
@@ -28,3 +40,9 @@ describe('capitalize', () => {
28
40
  expect(capitalize('test123')).toBe('Test123');
29
41
  });
30
42
  });
43
+
44
+ test('Converts underscore to camelCase', () => {
45
+ expect(underscoreToCamelCase('test')).toBe('test');
46
+ expect(underscoreToCamelCase('test_variable')).toBe('testVariable');
47
+ expect(underscoreToCamelCase('test_variable_name')).toBe('testVariableName');
48
+ });
@@ -0,0 +1,272 @@
1
+ /* eslint-disable jsdoc/reject-any-type */
2
+ /* eslint-disable jsdoc/no-undefined-types */
3
+
4
+ import { isEq, isGt, isGtOrEq, isInRange, isLe, isLeOrEq } from './number.js';
5
+
6
+ /**
7
+ * Validates `value` as `boolean`.
8
+ * @param {unknown} value - Input value.
9
+ * @returns {value is boolean} `true` if `value` is `boolean` type.
10
+ */
11
+ export const isBoolean = (value) => typeof value === 'boolean';
12
+
13
+ /**
14
+ * Validates `value` as `function`.
15
+ * @param {unknown} value - Input value.
16
+ * @returns {value is (...args: any[]) => any} `true` if `value` is `function` type.
17
+ */
18
+ export const isFunction = (value) => typeof value === 'function';
19
+
20
+ /**
21
+ * Validates `value` as `number`.
22
+ * @param {unknown} value - Input value.
23
+ * @returns {value is number} `true` if `value` is `number` type.
24
+ */
25
+ export const isNumber = (value) => typeof value === 'number' && Number.isFinite(value);
26
+
27
+ /**
28
+ * Validates `value` as positive `number`.
29
+ * @param {unknown} value - Input value.
30
+ * @returns {value is number} `true` if `value` is `number` type with positive value.
31
+ */
32
+ export const isPositiveNumber = (value) => isNumber(value) && value > 0;
33
+
34
+ /**
35
+ * Validates `value` as non-negative `number`.
36
+ * @param {unknown} value - Input value.
37
+ * @returns {value is number} `true` if `value` is `number` type with non-negative value.
38
+ */
39
+ export const isNonNegativeNumber = (value) => isNumber(value) && value >= 0;
40
+
41
+ /**
42
+ * Validates `value` as `integer`.
43
+ * @param {unknown} value - Input value.
44
+ * @returns {value is number} `true` if `value` is `integer` type.
45
+ */
46
+ export const isInteger = (value) => isNumber(value) && Number.isInteger(value);
47
+
48
+ /**
49
+ * Validates `value` as positive `integer`.
50
+ * @param {unknown} value - Input value.
51
+ * @returns {value is number} `true` if `value` is `integer` type with positive value.
52
+ */
53
+ export const isPositiveInteger = (value) => isInteger(value) && value > 0;
54
+
55
+ /**
56
+ * Validates `value` as non-negative `integer`.
57
+ * @param {unknown} value - Input value.
58
+ * @returns {value is number} `true` if `value` is `integer` type with non-negative value.
59
+ */
60
+ export const isNonNegativeInteger = (value) => isInteger(value) && value >= 0;
61
+
62
+ /**
63
+ * Validates `value` as `string`.
64
+ * @param {unknown} value - Input value.
65
+ * @returns {value is string} `true` if `value` is `string` type.
66
+ */
67
+ export const isString = (value) => typeof value === 'string';
68
+
69
+ /**
70
+ * Validates `value` as `array`.
71
+ * @template T
72
+ * @param {unknown} value - Input value.
73
+ * @returns {value is T[]} `true` if `value` is `array` type.
74
+ */
75
+ export const isArray = (value) => Array.isArray(value);
76
+
77
+ /**
78
+ * Validates `value` as `null`
79
+ * @param {unknown} value - Input value.
80
+ * @returns {value is null} `true` if `value` is `null` type.
81
+ */
82
+ export const isNull = (value) => value === null;
83
+
84
+ /**
85
+ * Validates `value` as `undefined`
86
+ * @param {unknown} value - Input value.
87
+ * @returns {value is undefined} `true` if `value` is `undefined` type.
88
+ */
89
+ export const isUndefined = (value) => value === undefined;
90
+
91
+ /**
92
+ * Validates `value` as `null` or `undefined`
93
+ * @param {unknown} value - Input value.
94
+ * @returns {value is null | undefined} `true` if `value` is `null` or `undefined` type.
95
+ */
96
+ export const isNullOrUndefined = (value) => isNull(value) || isUndefined(value);
97
+
98
+ /**
99
+ * Validates `value` as plain `object`.
100
+ * @param {unknown} value - Input value.
101
+ * @returns {value is Record<string, unknown>} `true` if `value` is `object` type.
102
+ */
103
+ export const isPlainObject = (value) => Object.prototype.toString.call(value) === '[object Object]';
104
+
105
+ /**
106
+ * Validates `value` as `type`
107
+ * @template T
108
+ * @param {unknown} value - The value to test.
109
+ * @param {new (...args: any[]) => T} type - A class or constructor function.
110
+ * @returns {value is T} `true` if `value` is an instance of `type`.
111
+ */
112
+ export const isInstance = (value, type) => isFunction(type) && value instanceof type;
113
+
114
+ /**
115
+ * Validates `value` as `enum`.
116
+ * @param {unknown} value - Input value.
117
+ * @param {unknown[] | Set<string | number> | Record<string | number, unknown>} choices - Input value.
118
+ * @returns {boolean} `true` if `value` is `enum` type.
119
+ */
120
+ export const isEnum = (value, choices) => {
121
+ if (!isString(value) && !isNumber(value)) {
122
+ return false;
123
+ }
124
+ return (
125
+ (isArray(choices) && choices.includes(value)) ||
126
+ (isPlainObject(choices) && Object.values(choices).includes(value)) ||
127
+ (isInstance(choices, Set) && choices.has(value))
128
+ );
129
+ };
130
+
131
+ /**
132
+ * Type check an array of values using a validator.
133
+ * @template T
134
+ * @param {unknown} values - The value to check.
135
+ * @param {(value: unknown) => value is T} validator - The validator to check with.
136
+ * @returns {values is T[]} `true` if `values` has only `validator` checked types.
137
+ */
138
+ export const isArrayOf = (values, validator) => {
139
+ if (!isArray(values)) {
140
+ return false;
141
+ }
142
+ for (const value of values) {
143
+ if (!validator(value)) {
144
+ return false;
145
+ }
146
+ }
147
+ return true;
148
+ };
149
+
150
+ /**
151
+ * Type check a plain object of values using a validator.
152
+ * @template T
153
+ * @param {Record<string | number, unknown>} record - The value to check.
154
+ * @param {(value: unknown) => value is T} validator - The validator to check with.
155
+ * @returns {record is Record<string | number, T>} `true` if `values` has only `validator` checked types.
156
+ */
157
+ export const isPlainObjectOf = (record, validator) => {
158
+ if (!isPlainObject(record)) {
159
+ return false;
160
+ }
161
+ for (const value of Object.values(record)) {
162
+ if (!validator(value)) {
163
+ return false;
164
+ }
165
+ }
166
+ return true;
167
+ };
168
+
169
+ /**
170
+ * Refine a base validator with an extra condition.
171
+ * @template T
172
+ * @param {(value: unknown) => value is T} base - The base validator.
173
+ * @param {(value: T) => boolean} predicate - Extra condition validator.
174
+ * @param {string | null} name - Refined validator name.
175
+ * @returns {(value: unknown) => value is T} The refined validator.
176
+ */
177
+ export const refineValidator = (base, predicate, name = null) => {
178
+ const refinedValidator = (value) => base(value) && predicate(value);
179
+ Object.defineProperty(refinedValidator, 'name', {
180
+ value: isString(name) ? name : `${base.name}Refined`,
181
+ });
182
+ // @ts-expect-error
183
+ return refinedValidator;
184
+ };
185
+
186
+ //
187
+ // Refined validators
188
+ //
189
+
190
+ /**
191
+ * Logical OR of two validators.
192
+ * @template A, B
193
+ * @param {(value: unknown) => value is A} a - Validator A.
194
+ * @param {(value: unknown) => value is B} b - Validator B.
195
+ * @returns {(value: unknown) => value is A | B} `true` if `value` is any of the checked types.
196
+ */
197
+ export const isAnyOf = (a, b) => (value) => a(value) || b(value);
198
+
199
+ export const isNumberGreater = (min) => refineValidator(isNumber, (value) => isGt(value, min));
200
+ export const isNumberGreaterOrEqual = (min) => refineValidator(isNumber, (value) => isGtOrEq(value, min));
201
+ export const isNumberLess = (min) => refineValidator(isNumber, (value) => isLe(value, min));
202
+ export const isNumberLessOrEqual = (min) => refineValidator(isNumber, (value) => isLeOrEq(value, min));
203
+ export const isNumberInRange = (min, max) => refineValidator(isNumber, (value) => isInRange(value, min, max));
204
+ export const isNumberEqual = (expected) => refineValidator(isNumber, (value) => isEq(value, expected));
205
+
206
+ export const isIntegerGreater = (min) => refineValidator(isInteger, (value) => isGt(value, min));
207
+ export const isIntegerGreaterOrEqual = (min) => refineValidator(isInteger, (value) => isGtOrEq(value, min));
208
+ export const isIntegerLess = (min) => refineValidator(isInteger, (value) => isLe(value, min));
209
+ export const isIntegerLessOrEqual = (min) => refineValidator(isInteger, (value) => isLeOrEq(value, min));
210
+ export const isIntegerInRange = (min, max) => refineValidator(isInteger, (value) => isInRange(value, min, max));
211
+ export const isIntegerEqual = (expected) => refineValidator(isNumber, (value) => isEq(value, expected));
212
+
213
+ export const isStringLengthGreater = (min) => refineValidator(isString, (value) => isGt(value.length, min));
214
+ export const isStringLengthGreaterOrEqual = (min) => refineValidator(isString, (value) => isGtOrEq(value.length, min));
215
+ export const isStringLengthLess = (min) => refineValidator(isString, (value) => isLe(value.length, min));
216
+ export const isStringLengthLessOrEqual = (min) => refineValidator(isString, (value) => isLeOrEq(value.length, min));
217
+ export const isStringLengthInRange = (min, max) =>
218
+ refineValidator(isString, (value) => isInRange(value.length, min, max));
219
+ export const isStringLengthEqual = (expected) => refineValidator(isString, (value) => isEq(value.length, expected));
220
+
221
+ export const isArrayLengthGreater = (min) => refineValidator(isArray, (value) => isGt(value.length, min));
222
+ export const isArrayLengthGreaterOrEqual = (min) => refineValidator(isArray, (value) => isGtOrEq(value.length, min));
223
+ export const isArrayLengthLess = (min) => refineValidator(isArray, (value) => isLe(value.length, min));
224
+ export const isArrayLengthLessOrEqual = (min) => refineValidator(isArray, (value) => isLeOrEq(value.length, min));
225
+ export const isArrayLengthInRange = (min, max) =>
226
+ refineValidator(isArray, (value) => isInRange(value.length, min, max));
227
+ export const isArrayLengthEqual = (expected) => refineValidator(isArray, (value) => isEq(value.length, expected));
228
+
229
+ export class TypeCheckError extends TypeError {
230
+ /**
231
+ * Creates a new `TypeCheckError` instance.
232
+ * @param {string} message - Error message.
233
+ */
234
+ constructor(message) {
235
+ super(message);
236
+ this.name = 'TypeCheckError';
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Type check a value using a validator.
242
+ * @template T
243
+ * @param {unknown} value - The value to check.
244
+ * @param {(value: unknown) => value is T} validator - The validator to check with.
245
+ * @returns {T} The type checked value.
246
+ * @throws {TypeCheckError}
247
+ */
248
+ export const typeCheck = (value, validator) => {
249
+ if (!validator(value)) {
250
+ const name = validator.name || '<anonymous>';
251
+ const display = typeof value === 'string' ? `"${value}"` : Object.prototype.toString.call(value);
252
+ throw new TypeCheckError(`Validation failed: ${name} (${display})`);
253
+ }
254
+ return value;
255
+ };
256
+
257
+ /**
258
+ * Type check a value using a validator.
259
+ * @template T
260
+ * @param {unknown[]} value - The value to check.
261
+ * @param {(value: unknown) => value is T} validator - The validator to check the array with.
262
+ * @returns {T[]} The type checked value.
263
+ * @throws {TypeCheckError}
264
+ */
265
+ export const typeCheckArray = (value, validator) => {
266
+ if (!isArrayOf(value, validator)) {
267
+ const name = validator.name || '<anonymous>';
268
+ const display = typeof value === 'string' ? `"${value}"` : Object.prototype.toString.call(value);
269
+ throw new TypeCheckError(`Validation failed: ${name} (${display})`);
270
+ }
271
+ return value;
272
+ };