@turndown/library 0.1.16 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/date/index.d.ts +113 -0
- package/dist/helpers/date/index.js +171 -0
- package/dist/helpers/index.d.ts +3 -1
- package/dist/helpers/index.js +2 -1
- package/dist/helpers/object/index.d.ts +160 -75
- package/dist/helpers/object/index.js +355 -168
- package/dist/helpers/string/index.d.ts +227 -74
- package/dist/helpers/string/index.js +448 -114
- package/dist/types/api/index.d.ts +22 -11
- package/dist/types/api/index.js +9 -2
- package/dist/types/auth/index.d.ts +56 -18
- package/dist/types/auth/index.js +7 -0
- package/dist/types/auth/routes.d.ts +76 -47
- package/dist/types/auth/routes.js +0 -1
- package/dist/types/base/index.d.ts +200 -69
- package/dist/types/base/index.js +116 -0
- package/dist/types/base/paging.types.d.ts +11 -11
- package/dist/types/checklist-template/index.d.ts +8 -8
- package/dist/types/checklist-template/routes.d.ts +57 -28
- package/dist/types/checklist-template/routes.js +0 -1
- package/dist/types/company/index.d.ts +5 -5
- package/dist/types/company/routes.d.ts +81 -33
- package/dist/types/company/routes.js +0 -1
- package/dist/types/damage-report/index.d.ts +9 -9
- package/dist/types/damage-report/routes.d.ts +128 -51
- package/dist/types/damage-report/routes.js +0 -1
- package/dist/types/errors/index.d.ts +15 -16
- package/dist/types/errors/index.js +17 -7
- package/dist/types/health/index.d.ts +20 -0
- package/dist/types/health/index.js +1 -0
- package/dist/types/health/routes.d.ts +10 -0
- package/dist/types/health/routes.js +1 -0
- package/dist/types/image/index.d.ts +34 -0
- package/dist/types/image/index.js +11 -0
- package/dist/types/image/routes.d.ts +32 -0
- package/dist/types/image/routes.js +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/inventory/index.d.ts +13 -87
- package/dist/types/inventory/routes.d.ts +73 -36
- package/dist/types/inventory/routes.js +0 -1
- package/dist/types/job/index.d.ts +7 -0
- package/dist/types/job/index.js +1 -0
- package/dist/types/property/index.d.ts +88 -29
- package/dist/types/property/index.js +23 -23
- package/dist/types/property/routes.d.ts +40 -14
- package/dist/types/property/routes.js +0 -1
- package/dist/types/room/index.d.ts +4 -4
- package/dist/types/room/routes.d.ts +27 -8
- package/dist/types/room/routes.js +0 -1
- package/dist/types/room-checklist/index.d.ts +9 -9
- package/dist/types/room-checklist/routes.d.ts +50 -28
- package/dist/types/room-checklist/routes.js +0 -1
- package/dist/types/user/index.d.ts +12 -12
- package/dist/types/user/routes.d.ts +26 -16
- package/dist/types/user/routes.js +0 -1
- package/dist/types/work-session/index.d.ts +12 -12
- package/dist/types/work-session/routes.d.ts +55 -34
- package/dist/types/work-session/routes.js +0 -1
- package/package.json +14 -5
|
@@ -1,61 +1,87 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
const unsafePathSegments = new Set(["__proto__", "prototype", "constructor"]);
|
|
2
|
+
const isRecord = (value) => {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
4
|
+
};
|
|
5
|
+
const isSafePathSegment = (segment) => {
|
|
6
|
+
return segment.length > 0 && !unsafePathSegments.has(segment);
|
|
7
|
+
};
|
|
8
|
+
const getPathSegments = (path) => {
|
|
9
|
+
return path.split(".").filter((segment) => segment.length > 0);
|
|
10
|
+
};
|
|
11
|
+
export function parseJSON(jsonString, fallbackValue) {
|
|
12
|
+
if (!jsonString) {
|
|
13
|
+
return fallbackValue ?? {};
|
|
14
|
+
}
|
|
13
15
|
try {
|
|
14
|
-
|
|
16
|
+
const parsedValue = JSON.parse(jsonString);
|
|
17
|
+
return parsedValue;
|
|
15
18
|
}
|
|
16
|
-
catch
|
|
17
|
-
return {};
|
|
19
|
+
catch {
|
|
20
|
+
return fallbackValue ?? {};
|
|
18
21
|
}
|
|
19
|
-
}
|
|
22
|
+
}
|
|
20
23
|
/**
|
|
21
|
-
* Stringify
|
|
24
|
+
* Stringify a value to JSON while skipping circular references.
|
|
22
25
|
*
|
|
23
26
|
* Uses an internal cache to omit repeated object references that would
|
|
24
27
|
* normally cause `JSON.stringify` to throw.
|
|
25
28
|
*
|
|
26
|
-
* @param {
|
|
27
|
-
* @returns {string} JSON string with circulars omitted.
|
|
29
|
+
* @param {unknown} value - Value to stringify.
|
|
30
|
+
* @returns {string | undefined} JSON string with circulars omitted.
|
|
28
31
|
* @example
|
|
29
|
-
* const
|
|
30
|
-
* JSONStringify(
|
|
32
|
+
* const value: Record<string, unknown> = {}; value.self = value;
|
|
33
|
+
* JSONStringify(value) // => "{}"
|
|
31
34
|
*/
|
|
32
|
-
export const JSONStringify = (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
return;
|
|
35
|
+
export const JSONStringify = (value) => {
|
|
36
|
+
const seenValues = new WeakSet();
|
|
37
|
+
try {
|
|
38
|
+
return JSON.stringify(value, (_key, nestedValue) => {
|
|
39
|
+
if (typeof nestedValue === "bigint") {
|
|
40
|
+
return nestedValue.toString();
|
|
38
41
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
if (typeof nestedValue === "object" && nestedValue !== null) {
|
|
43
|
+
if (seenValues.has(nestedValue)) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
seenValues.add(nestedValue);
|
|
47
|
+
}
|
|
48
|
+
return nestedValue;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
45
54
|
};
|
|
46
55
|
/**
|
|
47
|
-
* Deep-remove `undefined` properties
|
|
56
|
+
* Deep-remove `undefined` properties while preserving Dates and arrays.
|
|
48
57
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
58
|
+
* Object properties with `undefined` values are removed. Array items are
|
|
59
|
+
* preserved so array indexes do not shift.
|
|
60
|
+
*
|
|
61
|
+
* @typeParam TValue - Input value type.
|
|
62
|
+
* @param {TValue} value - Input value.
|
|
63
|
+
* @returns {TValue} Cleaned clone with `undefined` object properties removed.
|
|
51
64
|
*/
|
|
52
|
-
export const removeUndefined = (
|
|
53
|
-
|
|
65
|
+
export const removeUndefined = (value) => {
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
return value.map((item) => removeUndefined(item));
|
|
68
|
+
}
|
|
69
|
+
if (isRecord(value)) {
|
|
70
|
+
const updatedValue = Object.entries(value).reduce((accumulator, [key, nestedValue]) => {
|
|
71
|
+
if (nestedValue !== undefined) {
|
|
72
|
+
accumulator[key] = removeUndefined(nestedValue);
|
|
73
|
+
}
|
|
74
|
+
return accumulator;
|
|
75
|
+
}, {});
|
|
76
|
+
return updatedValue;
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
54
79
|
};
|
|
55
80
|
/**
|
|
56
81
|
* Test whether a location object's `pathname` equals a key.
|
|
57
82
|
*
|
|
58
|
-
* @param {
|
|
83
|
+
* @param {{ pathname?: string } | null | undefined} location - Object expected
|
|
84
|
+
* to have a `pathname`.
|
|
59
85
|
* @param {string} key - Path to compare.
|
|
60
86
|
* @returns {boolean}
|
|
61
87
|
* @example
|
|
@@ -83,19 +109,14 @@ export const returnObject = (input) => {
|
|
|
83
109
|
* @typeParam T - Object type with an `id` field.
|
|
84
110
|
* @param {T[]} [array1] - Source array.
|
|
85
111
|
* @param {T[]} [array2] - Items whose `id`s should be excluded.
|
|
86
|
-
* @returns {T[]} Filtered array (or `[]` on
|
|
112
|
+
* @returns {T[]} Filtered array (or `[]` on invalid input).
|
|
87
113
|
*/
|
|
88
114
|
export const filterArrayById = (array1, array2) => {
|
|
89
|
-
|
|
90
|
-
if (!array1 || !array2)
|
|
91
|
-
return [];
|
|
92
|
-
const idsToExclude = new Set(array2.map((item) => item.id));
|
|
93
|
-
return array1.filter((item) => !idsToExclude.has(item.id));
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
console.error("An error occurred:", error);
|
|
115
|
+
if (!Array.isArray(array1) || !Array.isArray(array2)) {
|
|
97
116
|
return [];
|
|
98
117
|
}
|
|
118
|
+
const idsToExclude = new Set(array2.map((item) => item.id));
|
|
119
|
+
return array1.filter((item) => !idsToExclude.has(item.id));
|
|
99
120
|
};
|
|
100
121
|
/**
|
|
101
122
|
* Sort an array of objects by a given property (ascending).
|
|
@@ -103,17 +124,26 @@ export const filterArrayById = (array1, array2) => {
|
|
|
103
124
|
* Mutates the original array (uses `Array.prototype.sort`).
|
|
104
125
|
*
|
|
105
126
|
* @typeParam T - Object type.
|
|
127
|
+
* @typeParam TKey - Sortable property key.
|
|
106
128
|
* @param {T[]} array - Array to sort.
|
|
107
|
-
* @param {
|
|
129
|
+
* @param {TKey} property - Property name to sort by.
|
|
108
130
|
* @returns {T[]} The same array instance, sorted (or empty array if input invalid).
|
|
109
131
|
*/
|
|
110
132
|
export const sortArrayByProperty = (array, property) => {
|
|
111
|
-
if (!array || array.length === 0)
|
|
133
|
+
if (!Array.isArray(array) || array.length === 0)
|
|
112
134
|
return [];
|
|
113
135
|
return array.sort((a, b) => {
|
|
114
|
-
|
|
136
|
+
const firstValue = a[property];
|
|
137
|
+
const secondValue = b[property];
|
|
138
|
+
if (firstValue === secondValue)
|
|
139
|
+
return 0;
|
|
140
|
+
if (firstValue === null || firstValue === undefined)
|
|
141
|
+
return 1;
|
|
142
|
+
if (secondValue === null || secondValue === undefined)
|
|
115
143
|
return -1;
|
|
116
|
-
if (
|
|
144
|
+
if (firstValue < secondValue)
|
|
145
|
+
return -1;
|
|
146
|
+
if (firstValue > secondValue)
|
|
117
147
|
return 1;
|
|
118
148
|
return 0;
|
|
119
149
|
});
|
|
@@ -121,109 +151,99 @@ export const sortArrayByProperty = (array, property) => {
|
|
|
121
151
|
/**
|
|
122
152
|
* Recursively replace `null` values with empty strings.
|
|
123
153
|
*
|
|
124
|
-
* Works on primitives, arrays, and plain objects.
|
|
154
|
+
* Works on primitives, arrays, Dates, and plain objects.
|
|
125
155
|
*
|
|
126
|
-
* @
|
|
127
|
-
* @
|
|
156
|
+
* @typeParam TValue - Input value type.
|
|
157
|
+
* @param {TValue} value - Input value.
|
|
158
|
+
* @returns {TReplaceNulls<TValue>} Value with all `null` replaced by `""`.
|
|
128
159
|
*/
|
|
129
|
-
export const replaceNulls = (
|
|
130
|
-
if (
|
|
160
|
+
export const replaceNulls = (value) => {
|
|
161
|
+
if (value === null) {
|
|
131
162
|
return "";
|
|
132
163
|
}
|
|
133
|
-
|
|
134
|
-
return
|
|
164
|
+
if (Array.isArray(value)) {
|
|
165
|
+
return value.map((item) => replaceNulls(item));
|
|
135
166
|
}
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
return newObj;
|
|
167
|
+
if (isRecord(value)) {
|
|
168
|
+
const updatedValue = Object.entries(value).reduce((accumulator, [key, nestedValue]) => {
|
|
169
|
+
accumulator[key] = replaceNulls(nestedValue);
|
|
170
|
+
return accumulator;
|
|
171
|
+
}, {});
|
|
172
|
+
return updatedValue;
|
|
144
173
|
}
|
|
145
|
-
return
|
|
174
|
+
return value;
|
|
146
175
|
};
|
|
147
176
|
/**
|
|
148
177
|
* Recursively remove object keys that contain a dot (`.`).
|
|
149
178
|
*
|
|
150
|
-
* @typeParam
|
|
151
|
-
* @param {
|
|
152
|
-
* @returns {
|
|
179
|
+
* @typeParam TValue - Input value type.
|
|
180
|
+
* @param {TValue} value - Input object or array.
|
|
181
|
+
* @returns {TValue} New value with dotted keys removed at all levels.
|
|
153
182
|
*/
|
|
154
|
-
export const removeFormProperties = (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
return newObj;
|
|
170
|
-
}
|
|
171
|
-
return input;
|
|
172
|
-
};
|
|
173
|
-
return recursiveRemove(obj);
|
|
183
|
+
export const removeFormProperties = (value) => {
|
|
184
|
+
if (Array.isArray(value)) {
|
|
185
|
+
return value.map((item) => removeFormProperties(item));
|
|
186
|
+
}
|
|
187
|
+
if (isRecord(value)) {
|
|
188
|
+
const updatedValue = Object.entries(value).reduce((accumulator, [key, nestedValue]) => {
|
|
189
|
+
if (!key.includes(".")) {
|
|
190
|
+
accumulator[key] = removeFormProperties(nestedValue);
|
|
191
|
+
}
|
|
192
|
+
return accumulator;
|
|
193
|
+
}, {});
|
|
194
|
+
return updatedValue;
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
174
197
|
};
|
|
175
198
|
/**
|
|
176
199
|
* Recursively convert string booleans `"true"`/`"false"` to actual booleans.
|
|
177
200
|
*
|
|
178
201
|
* Leaves all other values unchanged.
|
|
179
202
|
*
|
|
180
|
-
* @typeParam
|
|
181
|
-
* @param {
|
|
182
|
-
* @returns {
|
|
203
|
+
* @typeParam TValue - Input value type.
|
|
204
|
+
* @param {TValue} value - Input object or array.
|
|
205
|
+
* @returns {TValue} New value with boolean-like strings converted.
|
|
183
206
|
*/
|
|
184
|
-
export const convertStringBooleans = (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
return input;
|
|
206
|
-
};
|
|
207
|
-
return recursiveConvert(obj);
|
|
207
|
+
export const convertStringBooleans = (value) => {
|
|
208
|
+
if (Array.isArray(value)) {
|
|
209
|
+
return value.map((item) => convertStringBooleans(item));
|
|
210
|
+
}
|
|
211
|
+
if (isRecord(value)) {
|
|
212
|
+
const updatedValue = Object.entries(value).reduce((accumulator, [key, nestedValue]) => {
|
|
213
|
+
if (nestedValue === "true") {
|
|
214
|
+
accumulator[key] = true;
|
|
215
|
+
}
|
|
216
|
+
else if (nestedValue === "false") {
|
|
217
|
+
accumulator[key] = false;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
accumulator[key] = convertStringBooleans(nestedValue);
|
|
221
|
+
}
|
|
222
|
+
return accumulator;
|
|
223
|
+
}, {});
|
|
224
|
+
return updatedValue;
|
|
225
|
+
}
|
|
226
|
+
return value;
|
|
208
227
|
};
|
|
209
228
|
/**
|
|
210
229
|
* Convenience helper to clean form-like data:
|
|
211
230
|
* - Removes `undefined` properties
|
|
212
231
|
* - Converts string booleans to booleans
|
|
213
|
-
* - Removes keys containing a dot (
|
|
232
|
+
* - Removes keys containing a dot (`.`)
|
|
214
233
|
*
|
|
215
|
-
* @
|
|
216
|
-
* @
|
|
234
|
+
* @typeParam TObject - Form data object type.
|
|
235
|
+
* @param {TObject} objectToClean - Input data.
|
|
236
|
+
* @returns {Partial<TObject>} Cleaned clone.
|
|
217
237
|
*/
|
|
218
|
-
export const cleanFormData = (
|
|
219
|
-
return removeFormProperties(convertStringBooleans(removeUndefined(
|
|
238
|
+
export const cleanFormData = (objectToClean) => {
|
|
239
|
+
return removeFormProperties(convertStringBooleans(removeUndefined(objectToClean)));
|
|
220
240
|
};
|
|
221
241
|
/**
|
|
222
242
|
* Return a default pagination object, allowing optional sort and filters.
|
|
223
243
|
*
|
|
224
|
-
* @param {
|
|
225
|
-
* @param {
|
|
226
|
-
* @returns {{ page: number; size: number; sort:
|
|
244
|
+
* @param {ISortCondition[]} [sort] - Optional sort conditions.
|
|
245
|
+
* @param {IFilterCondition[]} [filters] - Optional filter conditions.
|
|
246
|
+
* @returns {{ page: number; size: number; sort: ISortCondition[]; filters: IFilterCondition[] }}
|
|
227
247
|
* @example
|
|
228
248
|
* resetPagination() // => { page:1, size:25, sort:[], filters:[] }
|
|
229
249
|
*/
|
|
@@ -238,8 +258,9 @@ export const resetPagination = (sort, filters) => {
|
|
|
238
258
|
/**
|
|
239
259
|
* Format a string of digits into a U.S. phone number.
|
|
240
260
|
*
|
|
241
|
-
* Strips non-numeric characters and formats as `(XXX) XXX-XXXX`.
|
|
242
|
-
*
|
|
261
|
+
* Strips non-numeric characters and formats 10 digits as `(XXX) XXX-XXXX`.
|
|
262
|
+
* Strips a leading US country code when 11 digits are provided.
|
|
263
|
+
* If a value cannot be formatted, returns the original value as a string.
|
|
243
264
|
*
|
|
244
265
|
* @param {string | number} value - Phone number digits (string or number).
|
|
245
266
|
* @returns {string} Formatted phone number, or original input if invalid length.
|
|
@@ -249,12 +270,16 @@ export const resetPagination = (sort, filters) => {
|
|
|
249
270
|
* formatPhoneNumber("555") // "555"
|
|
250
271
|
*/
|
|
251
272
|
export const formatPhoneNumber = (value) => {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
273
|
+
const originalValue = value.toString();
|
|
274
|
+
const digits = originalValue.replace(/\D/g, "");
|
|
275
|
+
const normalizedDigits = digits.length === 11 && digits.startsWith("1")
|
|
276
|
+
? digits.slice(1)
|
|
277
|
+
: digits;
|
|
278
|
+
if (normalizedDigits.length !== 10)
|
|
279
|
+
return originalValue;
|
|
280
|
+
const area = normalizedDigits.slice(0, 3);
|
|
281
|
+
const prefix = normalizedDigits.slice(3, 6);
|
|
282
|
+
const line = normalizedDigits.slice(6);
|
|
258
283
|
return `(${area}) ${prefix}-${line}`;
|
|
259
284
|
};
|
|
260
285
|
/**
|
|
@@ -269,29 +294,25 @@ export const formatNumber = (value) => {
|
|
|
269
294
|
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
270
295
|
};
|
|
271
296
|
/**
|
|
272
|
-
*
|
|
297
|
+
* Remove comma separators from a number-like value.
|
|
273
298
|
*
|
|
274
|
-
* @
|
|
275
|
-
*
|
|
276
|
-
* commas; it inserts them. If you intended to *strip* separators, consider:
|
|
277
|
-
* `value.toString().replace(/,/g, "")`.
|
|
278
|
-
*
|
|
279
|
-
* @param {number} value - Number to "parse".
|
|
280
|
-
* @returns {string} Currently returns a comma-formatted string.
|
|
299
|
+
* @param {number | string} value - Number-like value.
|
|
300
|
+
* @returns {string} Value without comma separators.
|
|
281
301
|
*/
|
|
282
302
|
export const parseNumber = (value) => {
|
|
283
|
-
return value.toString().replace(
|
|
303
|
+
return value.toString().replace(/,/g, "");
|
|
284
304
|
};
|
|
285
305
|
/**
|
|
286
306
|
* Delete a property from an object if it exists (no-op if it doesn't).
|
|
287
307
|
*
|
|
288
|
-
* @
|
|
289
|
-
* @param {
|
|
308
|
+
* @typeParam TObject - Object type.
|
|
309
|
+
* @param {TObject} objectToUpdate - Target object (mutated).
|
|
310
|
+
* @param {keyof TObject | string} propertyName - Property to delete.
|
|
290
311
|
* @returns {void}
|
|
291
312
|
*/
|
|
292
|
-
export const deletePropertyIfExists = (
|
|
293
|
-
if (Object.prototype.hasOwnProperty.call(
|
|
294
|
-
delete
|
|
313
|
+
export const deletePropertyIfExists = (objectToUpdate, propertyName) => {
|
|
314
|
+
if (Object.prototype.hasOwnProperty.call(objectToUpdate, propertyName)) {
|
|
315
|
+
delete objectToUpdate[propertyName];
|
|
295
316
|
}
|
|
296
317
|
};
|
|
297
318
|
/**
|
|
@@ -299,52 +320,218 @@ export const deletePropertyIfExists = (obj, propertyName) => {
|
|
|
299
320
|
*
|
|
300
321
|
* @typeParam T - Element type.
|
|
301
322
|
* @param {T[]} array - Source array.
|
|
302
|
-
* @param {number} chunkSize - Size of each chunk
|
|
323
|
+
* @param {number} chunkSize - Size of each chunk.
|
|
303
324
|
* @returns {T[][]} Array of chunks (last one may be smaller).
|
|
304
325
|
* @example
|
|
305
326
|
* chunkArray([1,2,3,4,5], 2) // [[1,2],[3,4],[5]]
|
|
306
327
|
*/
|
|
307
328
|
export const chunkArray = (array, chunkSize) => {
|
|
329
|
+
if (!Array.isArray(array) || chunkSize <= 0 || !Number.isFinite(chunkSize)) {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
308
332
|
const result = [];
|
|
309
|
-
|
|
310
|
-
|
|
333
|
+
const normalizedChunkSize = Math.floor(chunkSize);
|
|
334
|
+
for (let i = 0; i < array.length; i += normalizedChunkSize) {
|
|
335
|
+
result.push(array.slice(i, i + normalizedChunkSize));
|
|
311
336
|
}
|
|
312
337
|
return result;
|
|
313
338
|
};
|
|
314
339
|
/**
|
|
315
|
-
* Return a shallow clone of `
|
|
340
|
+
* Return a shallow clone of `objectToOmitFrom` without the listed properties.
|
|
316
341
|
*
|
|
317
|
-
* @
|
|
318
|
-
* @
|
|
319
|
-
* @
|
|
342
|
+
* @typeParam TObject - Source object type.
|
|
343
|
+
* @typeParam TKey - Keys to omit.
|
|
344
|
+
* @param {TObject} objectToOmitFrom - Source object.
|
|
345
|
+
* @param {readonly TKey[]} propsToOmit - Property names to omit.
|
|
346
|
+
* @returns {Omit<TObject, TKey>} New object without omitted props.
|
|
320
347
|
* @example
|
|
321
348
|
* omitProperties({a:1,b:2}, ["b"]) // { a:1 }
|
|
322
349
|
*/
|
|
323
|
-
export const omitProperties = (
|
|
324
|
-
const
|
|
325
|
-
propsToOmit.forEach((
|
|
326
|
-
delete
|
|
350
|
+
export const omitProperties = (objectToOmitFrom, propsToOmit) => {
|
|
351
|
+
const updatedObject = { ...objectToOmitFrom };
|
|
352
|
+
propsToOmit.forEach((propertyName) => {
|
|
353
|
+
delete updatedObject[propertyName];
|
|
327
354
|
});
|
|
328
|
-
return
|
|
355
|
+
return updatedObject;
|
|
329
356
|
};
|
|
330
357
|
/**
|
|
331
358
|
* Safe `hasOwnProperty` check.
|
|
332
359
|
*
|
|
333
|
-
* @param {
|
|
334
|
-
* @param {
|
|
360
|
+
* @param {unknown} value - Value to test.
|
|
361
|
+
* @param {PropertyKey} key - Property name.
|
|
335
362
|
* @returns {boolean}
|
|
336
363
|
*/
|
|
337
|
-
export const hasProperty = (
|
|
338
|
-
if (
|
|
364
|
+
export const hasProperty = (value, key) => {
|
|
365
|
+
if (value === null || value === undefined)
|
|
339
366
|
return false;
|
|
340
|
-
return Object.prototype.hasOwnProperty.call(
|
|
367
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* Safe `hasOwnProperty` alias from the reference utilities.
|
|
371
|
+
*
|
|
372
|
+
* @param {unknown} value - Value to test.
|
|
373
|
+
* @param {PropertyKey} key - Property name.
|
|
374
|
+
* @returns {boolean}
|
|
375
|
+
*/
|
|
376
|
+
export const hasOwnProp = (value, key) => {
|
|
377
|
+
return hasProperty(value, key);
|
|
341
378
|
};
|
|
342
379
|
/**
|
|
343
380
|
* Determine if an object has at least one own enumerable property.
|
|
344
381
|
*
|
|
345
|
-
* @param {
|
|
382
|
+
* @param {unknown} value - Object to test.
|
|
346
383
|
* @returns {boolean} `true` if there is at least one key.
|
|
347
384
|
*/
|
|
348
|
-
export const hasProperties = (
|
|
349
|
-
return Object.keys(
|
|
385
|
+
export const hasProperties = (value) => {
|
|
386
|
+
return isRecord(value) && Object.keys(value).length > 0;
|
|
387
|
+
};
|
|
388
|
+
/**
|
|
389
|
+
* Get the first own enumerable property value from an object.
|
|
390
|
+
*
|
|
391
|
+
* @typeParam TObject - Source object type.
|
|
392
|
+
* @param {TObject | null | undefined} value - Source object.
|
|
393
|
+
* @returns {TObject[keyof TObject] | null} First value, or null for empty/non-object input.
|
|
394
|
+
*/
|
|
395
|
+
export const getFirstPropertyValue = (value) => {
|
|
396
|
+
if (value === null || value === undefined || !hasProperties(value)) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const source = value;
|
|
400
|
+
const keys = Object.keys(source);
|
|
401
|
+
return source[keys[0]];
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Get a nested value from an object using dot notation.
|
|
405
|
+
*
|
|
406
|
+
* @param {unknown} value - Source object.
|
|
407
|
+
* @param {string} path - Dot-delimited path.
|
|
408
|
+
* @returns {unknown} Nested value, or undefined when the path cannot be resolved.
|
|
409
|
+
* @example
|
|
410
|
+
* getNestedValue({ user: { name: "John" } }, "user.name") // "John"
|
|
411
|
+
*/
|
|
412
|
+
export const getNestedValue = (value, path) => {
|
|
413
|
+
const pathSegments = getPathSegments(path);
|
|
414
|
+
if (pathSegments.length === 0) {
|
|
415
|
+
return value;
|
|
416
|
+
}
|
|
417
|
+
let currentValue = value;
|
|
418
|
+
for (const pathSegment of pathSegments) {
|
|
419
|
+
if (!isSafePathSegment(pathSegment)) {
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
if (currentValue === null || currentValue === undefined) {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
if (typeof currentValue !== "object" && typeof currentValue !== "function") {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
currentValue = currentValue[pathSegment];
|
|
429
|
+
}
|
|
430
|
+
return currentValue;
|
|
431
|
+
};
|
|
432
|
+
/**
|
|
433
|
+
* Set a nested value on an object using dot notation.
|
|
434
|
+
*
|
|
435
|
+
* Mutates and returns the provided object. Unsafe path segments are ignored to
|
|
436
|
+
* prevent prototype pollution.
|
|
437
|
+
*
|
|
438
|
+
* @typeParam TObject - Target object type.
|
|
439
|
+
* @param {TObject} objectToUpdate - Target object.
|
|
440
|
+
* @param {string} path - Dot-delimited path.
|
|
441
|
+
* @param {unknown} value - Value to set.
|
|
442
|
+
* @returns {TObject} The mutated target object.
|
|
443
|
+
* @example
|
|
444
|
+
* setNestedValue({}, "user.name", "John") // { user: { name: "John" } }
|
|
445
|
+
*/
|
|
446
|
+
export const setNestedValue = (objectToUpdate, path, value) => {
|
|
447
|
+
const pathSegments = getPathSegments(path);
|
|
448
|
+
if (pathSegments.length === 0 || pathSegments.some((pathSegment) => !isSafePathSegment(pathSegment))) {
|
|
449
|
+
return objectToUpdate;
|
|
450
|
+
}
|
|
451
|
+
let currentValue = objectToUpdate;
|
|
452
|
+
for (let i = 0; i < pathSegments.length - 1; i += 1) {
|
|
453
|
+
const pathSegment = pathSegments[i];
|
|
454
|
+
const nextValue = currentValue[pathSegment];
|
|
455
|
+
if (!isRecord(nextValue)) {
|
|
456
|
+
currentValue[pathSegment] = {};
|
|
457
|
+
}
|
|
458
|
+
currentValue = currentValue[pathSegment];
|
|
459
|
+
}
|
|
460
|
+
currentValue[pathSegments[pathSegments.length - 1]] = value;
|
|
461
|
+
return objectToUpdate;
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* Deep clone a value while preserving Dates and circular references.
|
|
465
|
+
*
|
|
466
|
+
* @typeParam TValue - Input value type.
|
|
467
|
+
* @param {TValue} value - Value to clone.
|
|
468
|
+
* @returns {TValue} Deep clone of the input.
|
|
469
|
+
*/
|
|
470
|
+
export const deepClone = (value) => {
|
|
471
|
+
const cloneValue = (nestedValue, seenValues) => {
|
|
472
|
+
if (nestedValue === null || typeof nestedValue !== "object") {
|
|
473
|
+
return nestedValue;
|
|
474
|
+
}
|
|
475
|
+
if (nestedValue instanceof Date) {
|
|
476
|
+
return new Date(nestedValue.getTime());
|
|
477
|
+
}
|
|
478
|
+
if (seenValues.has(nestedValue)) {
|
|
479
|
+
return seenValues.get(nestedValue);
|
|
480
|
+
}
|
|
481
|
+
if (Array.isArray(nestedValue)) {
|
|
482
|
+
const clonedArray = [];
|
|
483
|
+
seenValues.set(nestedValue, clonedArray);
|
|
484
|
+
nestedValue.forEach((item) => {
|
|
485
|
+
clonedArray.push(cloneValue(item, seenValues));
|
|
486
|
+
});
|
|
487
|
+
return clonedArray;
|
|
488
|
+
}
|
|
489
|
+
const clonedObject = Object.create(Object.getPrototypeOf(nestedValue));
|
|
490
|
+
seenValues.set(nestedValue, clonedObject);
|
|
491
|
+
Reflect.ownKeys(nestedValue).forEach((key) => {
|
|
492
|
+
clonedObject[key] = cloneValue(nestedValue[key], seenValues);
|
|
493
|
+
});
|
|
494
|
+
return clonedObject;
|
|
495
|
+
};
|
|
496
|
+
return cloneValue(value, new WeakMap());
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* Flatten a nested object into dot notation.
|
|
500
|
+
*
|
|
501
|
+
* Arrays and Dates are treated as leaf values.
|
|
502
|
+
*
|
|
503
|
+
* @param {TRecord} value - Source object.
|
|
504
|
+
* @param {string} [prefix] - Internal prefix for recursion.
|
|
505
|
+
* @returns {TRecord} Flattened object.
|
|
506
|
+
* @example
|
|
507
|
+
* flatten({ user: { name: "John" } }) // { "user.name": "John" }
|
|
508
|
+
*/
|
|
509
|
+
export const flatten = (value, prefix = "") => {
|
|
510
|
+
const result = {};
|
|
511
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
512
|
+
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
513
|
+
if (isRecord(nestedValue)) {
|
|
514
|
+
Object.assign(result, flatten(nestedValue, newKey));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
result[newKey] = nestedValue;
|
|
518
|
+
});
|
|
519
|
+
return result;
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* Convert a dot-notation object into a nested object.
|
|
523
|
+
*
|
|
524
|
+
* Unsafe path segments are ignored to prevent prototype pollution.
|
|
525
|
+
*
|
|
526
|
+
* @param {TRecord} value - Dot-notation source object.
|
|
527
|
+
* @returns {TRecord} Nested object.
|
|
528
|
+
* @example
|
|
529
|
+
* unflatten({ "user.name": "John" }) // { user: { name: "John" } }
|
|
530
|
+
*/
|
|
531
|
+
export const unflatten = (value) => {
|
|
532
|
+
const result = {};
|
|
533
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
534
|
+
setNestedValue(result, key, nestedValue);
|
|
535
|
+
});
|
|
536
|
+
return result;
|
|
350
537
|
};
|