@simplysm/core-common 13.0.76 → 13.0.78
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/README.md +64 -21
- package/dist/extensions/arr-ext.d.ts +1 -1
- package/dist/extensions/arr-ext.d.ts.map +1 -1
- package/dist/extensions/arr-ext.helpers.d.ts +8 -0
- package/dist/extensions/arr-ext.helpers.d.ts.map +1 -1
- package/dist/extensions/arr-ext.helpers.js +65 -0
- package/dist/extensions/arr-ext.helpers.js.map +2 -2
- package/dist/extensions/arr-ext.js +16 -124
- package/dist/extensions/arr-ext.js.map +2 -2
- package/dist/extensions/arr-ext.types.d.ts +40 -32
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/map-ext.js.map +1 -1
- package/dist/extensions/set-ext.js.map +1 -1
- package/dist/features/event-emitter.d.ts +4 -4
- package/dist/features/event-emitter.d.ts.map +1 -1
- package/dist/features/event-emitter.js.map +1 -1
- package/dist/features/serial-queue.js +2 -2
- package/dist/features/serial-queue.js.map +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -13
- package/dist/index.js.map +1 -1
- package/dist/types/date-only.js +2 -2
- package/dist/types/date-only.js.map +1 -1
- package/dist/types/date-time.js +2 -2
- package/dist/types/date-time.js.map +1 -1
- package/dist/types/time.js +2 -2
- package/dist/types/time.js.map +1 -1
- package/dist/types/uuid.d.ts +2 -2
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +1 -1
- package/dist/types/uuid.js.map +1 -1
- package/dist/utils/bytes.d.ts +10 -10
- package/dist/utils/bytes.d.ts.map +1 -1
- package/dist/utils/bytes.js +10 -10
- package/dist/utils/bytes.js.map +1 -1
- package/dist/utils/date-format.d.ts +1 -1
- package/dist/utils/date-format.d.ts.map +1 -1
- package/dist/utils/date-format.js +2 -2
- package/dist/utils/date-format.js.map +1 -1
- package/dist/utils/error.d.ts +1 -1
- package/dist/utils/error.d.ts.map +1 -1
- package/dist/utils/error.js +2 -2
- package/dist/utils/error.js.map +1 -1
- package/dist/utils/json.d.ts +4 -2
- package/dist/utils/json.d.ts.map +1 -1
- package/dist/utils/json.js +9 -9
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/num.d.ts +10 -10
- package/dist/utils/num.d.ts.map +1 -1
- package/dist/utils/num.js +11 -11
- package/dist/utils/num.js.map +1 -1
- package/dist/utils/obj.d.ts +40 -40
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +102 -99
- package/dist/utils/obj.js.map +1 -1
- package/dist/utils/path.d.ts +3 -3
- package/dist/utils/path.d.ts.map +1 -1
- package/dist/utils/path.js +6 -6
- package/dist/utils/path.js.map +1 -1
- package/dist/utils/primitive.d.ts +1 -1
- package/dist/utils/primitive.d.ts.map +1 -1
- package/dist/utils/primitive.js +2 -2
- package/dist/utils/primitive.js.map +1 -1
- package/dist/utils/str.d.ts +16 -16
- package/dist/utils/str.d.ts.map +1 -1
- package/dist/utils/str.js +16 -16
- package/dist/utils/str.js.map +1 -1
- package/dist/utils/transferable.d.ts +3 -3
- package/dist/utils/transferable.d.ts.map +1 -1
- package/dist/utils/transferable.js +10 -10
- package/dist/utils/transferable.js.map +1 -1
- package/dist/utils/wait.d.ts +2 -2
- package/dist/utils/wait.d.ts.map +1 -1
- package/dist/utils/wait.js +5 -5
- package/dist/utils/wait.js.map +1 -1
- package/dist/utils/xml.d.ts +2 -2
- package/dist/utils/xml.d.ts.map +1 -1
- package/dist/utils/xml.js +4 -4
- package/dist/utils/xml.js.map +1 -1
- package/dist/{zip/sd-zip.d.ts → utils/zip.d.ts} +1 -1
- package/dist/utils/zip.d.ts.map +1 -0
- package/dist/{zip/sd-zip.js → utils/zip.js} +1 -1
- package/dist/{zip/sd-zip.js.map → utils/zip.js.map} +1 -1
- package/package.json +1 -1
- package/src/extensions/arr-ext.helpers.ts +86 -0
- package/src/extensions/arr-ext.ts +22 -170
- package/src/extensions/arr-ext.types.ts +76 -48
- package/src/extensions/map-ext.ts +3 -3
- package/src/extensions/set-ext.ts +2 -2
- package/src/features/event-emitter.ts +6 -6
- package/src/features/serial-queue.ts +2 -2
- package/src/index.ts +16 -16
- package/src/types/date-only.ts +2 -2
- package/src/types/date-time.ts +2 -2
- package/src/types/time.ts +2 -2
- package/src/types/uuid.ts +2 -2
- package/src/utils/bytes.ts +15 -15
- package/src/utils/date-format.ts +1 -1
- package/src/utils/error.ts +1 -1
- package/src/utils/json.ts +9 -7
- package/src/utils/num.ts +15 -15
- package/src/utils/obj.ts +119 -116
- package/src/utils/path.ts +3 -3
- package/src/utils/primitive.ts +1 -1
- package/src/utils/str.ts +16 -16
- package/src/utils/transferable.ts +9 -9
- package/src/utils/wait.ts +3 -3
- package/src/utils/xml.ts +2 -2
- package/tests/extensions/array-extension.spec.ts +7 -5
- package/tests/types/uuid.spec.ts +4 -4
- package/tests/utils/bytes-utils.spec.ts +42 -49
- package/tests/utils/date-format.spec.ts +89 -88
- package/tests/utils/debounce-queue.spec.ts +3 -1
- package/tests/utils/json.spec.ts +61 -68
- package/tests/utils/number.spec.ts +41 -46
- package/tests/utils/object.spec.ts +120 -139
- package/tests/utils/path.spec.ts +19 -19
- package/tests/utils/primitive.spec.ts +12 -12
- package/tests/utils/string.spec.ts +66 -74
- package/tests/utils/transferable.spec.ts +55 -62
- package/tests/utils/wait.spec.ts +10 -10
- package/tests/utils/xml.spec.ts +25 -25
- package/dist/zip/sd-zip.d.ts.map +0 -1
- /package/src/{zip/sd-zip.ts → utils/zip.ts} +0 -0
- /package/tests/{zip/sd-zip.spec.ts → utils/zip.spec.ts} +0 -0
package/src/utils/obj.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Time } from "../types/time";
|
|
|
4
4
|
import { Uuid } from "../types/uuid";
|
|
5
5
|
import { ArgumentError } from "../errors/argument-error";
|
|
6
6
|
|
|
7
|
-
//#region
|
|
7
|
+
//#region clone
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Deep clone
|
|
@@ -16,11 +16,11 @@ import { ArgumentError } from "../errors/argument-error";
|
|
|
16
16
|
* @note Prototype chain is maintained (using Object.setPrototypeOf)
|
|
17
17
|
* @note Getters/setters are evaluated as current values and copied (accessor properties themselves are not copied)
|
|
18
18
|
*/
|
|
19
|
-
export function
|
|
20
|
-
return
|
|
19
|
+
export function clone<TObj>(source: TObj): TObj {
|
|
20
|
+
return cloneImpl(source) as TObj;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function
|
|
23
|
+
function cloneImpl(source: unknown, prevClones?: WeakMap<object, unknown>): unknown {
|
|
24
24
|
// Primitives are returned as-is
|
|
25
25
|
if (typeof source !== "object" || source === null) {
|
|
26
26
|
return source;
|
|
@@ -67,15 +67,18 @@ function objCloneImpl(source: unknown, prevClones?: WeakMap<object, unknown>): u
|
|
|
67
67
|
cloned.name = source.name;
|
|
68
68
|
cloned.stack = source.stack;
|
|
69
69
|
if (source.cause !== undefined) {
|
|
70
|
-
cloned.cause =
|
|
70
|
+
cloned.cause = cloneImpl(source.cause, currPrevClones);
|
|
71
71
|
}
|
|
72
72
|
// Copy custom Error properties
|
|
73
73
|
for (const key of Object.keys(source)) {
|
|
74
74
|
if (!["message", "name", "stack", "cause"].includes(key)) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
const desc = Object.getOwnPropertyDescriptor(source, key);
|
|
76
|
+
if (desc !== undefined) {
|
|
77
|
+
Object.defineProperty(cloned, key, {
|
|
78
|
+
...desc,
|
|
79
|
+
value: "value" in desc ? cloneImpl(desc.value, currPrevClones) : desc.value,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
return cloned;
|
|
@@ -91,7 +94,7 @@ function objCloneImpl(source: unknown, prevClones?: WeakMap<object, unknown>): u
|
|
|
91
94
|
const result: unknown[] = [];
|
|
92
95
|
currPrevClones.set(source, result);
|
|
93
96
|
for (const item of source) {
|
|
94
|
-
result.push(
|
|
97
|
+
result.push(cloneImpl(item, currPrevClones));
|
|
95
98
|
}
|
|
96
99
|
return result;
|
|
97
100
|
}
|
|
@@ -100,7 +103,7 @@ function objCloneImpl(source: unknown, prevClones?: WeakMap<object, unknown>): u
|
|
|
100
103
|
const result = new Map();
|
|
101
104
|
currPrevClones.set(source, result);
|
|
102
105
|
for (const [key, value] of source) {
|
|
103
|
-
result.set(
|
|
106
|
+
result.set(cloneImpl(key, currPrevClones), cloneImpl(value, currPrevClones));
|
|
104
107
|
}
|
|
105
108
|
return result;
|
|
106
109
|
}
|
|
@@ -109,7 +112,7 @@ function objCloneImpl(source: unknown, prevClones?: WeakMap<object, unknown>): u
|
|
|
109
112
|
const result = new Set();
|
|
110
113
|
currPrevClones.set(source, result);
|
|
111
114
|
for (const item of source) {
|
|
112
|
-
result.add(
|
|
115
|
+
result.add(cloneImpl(item, currPrevClones));
|
|
113
116
|
}
|
|
114
117
|
return result;
|
|
115
118
|
}
|
|
@@ -121,7 +124,7 @@ function objCloneImpl(source: unknown, prevClones?: WeakMap<object, unknown>): u
|
|
|
121
124
|
|
|
122
125
|
for (const key of Object.keys(source)) {
|
|
123
126
|
const value = (source as Record<string, unknown>)[key];
|
|
124
|
-
result[key] =
|
|
127
|
+
result[key] = cloneImpl(value, currPrevClones);
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
return result;
|
|
@@ -129,9 +132,9 @@ function objCloneImpl(source: unknown, prevClones?: WeakMap<object, unknown>): u
|
|
|
129
132
|
|
|
130
133
|
//#endregion
|
|
131
134
|
|
|
132
|
-
//#region
|
|
135
|
+
//#region equal
|
|
133
136
|
|
|
134
|
-
/**
|
|
137
|
+
/** equal options type */
|
|
135
138
|
export interface EqualOptions {
|
|
136
139
|
/** List of keys to compare. When specified, only those keys are compared (applies only to top level) */
|
|
137
140
|
topLevelIncludes?: string[];
|
|
@@ -140,7 +143,7 @@ export interface EqualOptions {
|
|
|
140
143
|
/** Whether to ignore array order. O(n²) complexity when true */
|
|
141
144
|
ignoreArrayIndex?: boolean;
|
|
142
145
|
/** Whether to do shallow comparison. Only compare 1 level (reference comparison) when true */
|
|
143
|
-
|
|
146
|
+
shallow?: boolean;
|
|
144
147
|
}
|
|
145
148
|
|
|
146
149
|
/**
|
|
@@ -154,7 +157,7 @@ export interface EqualOptions {
|
|
|
154
157
|
* @param options.topLevelExcludes List of keys to exclude from comparison (applies only to top level)
|
|
155
158
|
* @example `{ topLevelExcludes: ["updatedAt"] }` - Compare excluding updatedAt key
|
|
156
159
|
* @param options.ignoreArrayIndex Whether to ignore array order. O(n²) complexity when true
|
|
157
|
-
* @param options.
|
|
160
|
+
* @param options.shallow Whether to do shallow comparison. Only compare 1 level (reference comparison) when true
|
|
158
161
|
*
|
|
159
162
|
* @note topLevelIncludes/topLevelExcludes options apply only to object property keys.
|
|
160
163
|
* All keys in Map are always included in comparison.
|
|
@@ -166,7 +169,7 @@ export interface EqualOptions {
|
|
|
166
169
|
* - Ignore array order and check if elements are permutations of the same set
|
|
167
170
|
* - Example: `[1,2,3]` and `[3,2,1]` → true, `[1,1,1]` and `[1,2,3]` → false
|
|
168
171
|
*/
|
|
169
|
-
export function
|
|
172
|
+
export function equal(source: unknown, target: unknown, options?: EqualOptions): boolean {
|
|
170
173
|
if (source === target) return true;
|
|
171
174
|
if (source == null || target == null) return false;
|
|
172
175
|
if (typeof source !== typeof target) return false;
|
|
@@ -192,19 +195,19 @@ export function objEqual(source: unknown, target: unknown, options?: EqualOption
|
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
if (source instanceof Array && target instanceof Array) {
|
|
195
|
-
return
|
|
198
|
+
return equalArray(source, target, options);
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
if (source instanceof Map && target instanceof Map) {
|
|
199
|
-
return
|
|
202
|
+
return equalMap(source, target, options);
|
|
200
203
|
}
|
|
201
204
|
|
|
202
205
|
if (source instanceof Set && target instanceof Set) {
|
|
203
|
-
return
|
|
206
|
+
return equalSet(source, target, options);
|
|
204
207
|
}
|
|
205
208
|
|
|
206
209
|
if (typeof source === "object" && typeof target === "object") {
|
|
207
|
-
return
|
|
210
|
+
return equalObject(
|
|
208
211
|
source as Record<string, unknown>,
|
|
209
212
|
target as Record<string, unknown>,
|
|
210
213
|
options,
|
|
@@ -214,7 +217,7 @@ export function objEqual(source: unknown, target: unknown, options?: EqualOption
|
|
|
214
217
|
return false;
|
|
215
218
|
}
|
|
216
219
|
|
|
217
|
-
function
|
|
220
|
+
function equalArray(source: unknown[], target: unknown[], options?: EqualOptions): boolean {
|
|
218
221
|
if (source.length !== target.length) {
|
|
219
222
|
return false;
|
|
220
223
|
}
|
|
@@ -222,7 +225,7 @@ function objEqualArray(source: unknown[], target: unknown[], options?: EqualOpti
|
|
|
222
225
|
if (options?.ignoreArrayIndex) {
|
|
223
226
|
const matchedIndices = new Set<number>();
|
|
224
227
|
|
|
225
|
-
if (options.
|
|
228
|
+
if (options.shallow) {
|
|
226
229
|
return source.every((sourceItem) => {
|
|
227
230
|
const idx = target.findIndex((t, i) => !matchedIndices.has(i) && t === sourceItem);
|
|
228
231
|
if (idx !== -1) {
|
|
@@ -235,11 +238,11 @@ function objEqualArray(source: unknown[], target: unknown[], options?: EqualOpti
|
|
|
235
238
|
// On recursive calls, topLevelIncludes/topLevelExcludes options apply only to top level, so exclude them
|
|
236
239
|
const recursiveOptions = {
|
|
237
240
|
ignoreArrayIndex: options.ignoreArrayIndex,
|
|
238
|
-
|
|
241
|
+
shallow: options.shallow,
|
|
239
242
|
};
|
|
240
243
|
return source.every((sourceItem) => {
|
|
241
244
|
const idx = target.findIndex(
|
|
242
|
-
(t, i) => !matchedIndices.has(i) &&
|
|
245
|
+
(t, i) => !matchedIndices.has(i) && equal(t, sourceItem, recursiveOptions),
|
|
243
246
|
);
|
|
244
247
|
if (idx !== -1) {
|
|
245
248
|
matchedIndices.add(idx);
|
|
@@ -249,7 +252,7 @@ function objEqualArray(source: unknown[], target: unknown[], options?: EqualOpti
|
|
|
249
252
|
});
|
|
250
253
|
}
|
|
251
254
|
} else {
|
|
252
|
-
if (options?.
|
|
255
|
+
if (options?.shallow) {
|
|
253
256
|
for (let i = 0; i < source.length; i++) {
|
|
254
257
|
if (source[i] !== target[i]) {
|
|
255
258
|
return false;
|
|
@@ -259,9 +262,9 @@ function objEqualArray(source: unknown[], target: unknown[], options?: EqualOpti
|
|
|
259
262
|
// On recursive calls, topLevelIncludes/topLevelExcludes options apply only to top level, so exclude them
|
|
260
263
|
for (let i = 0; i < source.length; i++) {
|
|
261
264
|
if (
|
|
262
|
-
!
|
|
265
|
+
!equal(source[i], target[i], {
|
|
263
266
|
ignoreArrayIndex: options?.ignoreArrayIndex,
|
|
264
|
-
|
|
267
|
+
shallow: options?.shallow,
|
|
265
268
|
})
|
|
266
269
|
) {
|
|
267
270
|
return false;
|
|
@@ -276,9 +279,9 @@ function objEqualArray(source: unknown[], target: unknown[], options?: EqualOpti
|
|
|
276
279
|
/**
|
|
277
280
|
* Map object comparison
|
|
278
281
|
* @note O(n²) complexity when handling non-string keys (objects, arrays, etc.)
|
|
279
|
-
* @note Recommended to use
|
|
282
|
+
* @note Recommended to use shallow: true option for large datasets (improves to O(n) with reference comparison)
|
|
280
283
|
*/
|
|
281
|
-
function
|
|
284
|
+
function equalMap(
|
|
282
285
|
source: Map<unknown, unknown>,
|
|
283
286
|
target: Map<unknown, unknown>,
|
|
284
287
|
options?: EqualOptions,
|
|
@@ -297,13 +300,13 @@ function objEqualMap(
|
|
|
297
300
|
if (typeof sourceKey === "string") {
|
|
298
301
|
const sourceValue = source.get(sourceKey);
|
|
299
302
|
const targetValue = target.get(sourceKey);
|
|
300
|
-
if (options?.
|
|
303
|
+
if (options?.shallow) {
|
|
301
304
|
if (sourceValue !== targetValue) return false;
|
|
302
305
|
} else {
|
|
303
306
|
if (
|
|
304
|
-
!
|
|
307
|
+
!equal(sourceValue, targetValue, {
|
|
305
308
|
ignoreArrayIndex: options?.ignoreArrayIndex,
|
|
306
|
-
|
|
309
|
+
shallow: options?.shallow,
|
|
307
310
|
})
|
|
308
311
|
) {
|
|
309
312
|
return false;
|
|
@@ -315,17 +318,17 @@ function objEqualMap(
|
|
|
315
318
|
for (let i = 0; i < targetKeys.length; i++) {
|
|
316
319
|
const targetKey = targetKeys[i];
|
|
317
320
|
if (typeof targetKey === "string" || usedTargetKeys.has(i)) continue;
|
|
318
|
-
if (options?.
|
|
321
|
+
if (options?.shallow ? sourceKey === targetKey : equal(sourceKey, targetKey)) {
|
|
319
322
|
usedTargetKeys.add(i);
|
|
320
323
|
const sourceValue = source.get(sourceKey);
|
|
321
324
|
const targetValue = target.get(targetKey);
|
|
322
|
-
if (options?.
|
|
325
|
+
if (options?.shallow) {
|
|
323
326
|
if (sourceValue !== targetValue) return false;
|
|
324
327
|
} else {
|
|
325
328
|
if (
|
|
326
|
-
!
|
|
329
|
+
!equal(sourceValue, targetValue, {
|
|
327
330
|
ignoreArrayIndex: options?.ignoreArrayIndex,
|
|
328
|
-
|
|
331
|
+
shallow: options?.shallow,
|
|
329
332
|
})
|
|
330
333
|
) {
|
|
331
334
|
return false;
|
|
@@ -342,7 +345,7 @@ function objEqualMap(
|
|
|
342
345
|
return true;
|
|
343
346
|
}
|
|
344
347
|
|
|
345
|
-
function
|
|
348
|
+
function equalObject(
|
|
346
349
|
source: Record<string, unknown>,
|
|
347
350
|
target: Record<string, unknown>,
|
|
348
351
|
options?: EqualOptions,
|
|
@@ -365,13 +368,13 @@ function objEqualObject(
|
|
|
365
368
|
}
|
|
366
369
|
|
|
367
370
|
for (const key of sourceKeys) {
|
|
368
|
-
if (options?.
|
|
371
|
+
if (options?.shallow) {
|
|
369
372
|
if (source[key] !== target[key]) {
|
|
370
373
|
return false;
|
|
371
374
|
}
|
|
372
375
|
} else {
|
|
373
376
|
if (
|
|
374
|
-
!
|
|
377
|
+
!equal(source[key], target[key], {
|
|
375
378
|
ignoreArrayIndex: options?.ignoreArrayIndex,
|
|
376
379
|
})
|
|
377
380
|
) {
|
|
@@ -385,15 +388,15 @@ function objEqualObject(
|
|
|
385
388
|
|
|
386
389
|
/**
|
|
387
390
|
* Set deep equality comparison
|
|
388
|
-
* @note Deep equal comparison (`
|
|
389
|
-
* Recommended to use `
|
|
391
|
+
* @note Deep equal comparison (`shallow: false`) has O(n²) time complexity.
|
|
392
|
+
* Recommended to use `shallow: true` for primitive Sets or when performance is critical
|
|
390
393
|
*/
|
|
391
|
-
function
|
|
394
|
+
function equalSet(source: Set<unknown>, target: Set<unknown>, options?: EqualOptions): boolean {
|
|
392
395
|
if (source.size !== target.size) {
|
|
393
396
|
return false;
|
|
394
397
|
}
|
|
395
398
|
|
|
396
|
-
if (options?.
|
|
399
|
+
if (options?.shallow) {
|
|
397
400
|
for (const sourceItem of source) {
|
|
398
401
|
if (!target.has(sourceItem)) {
|
|
399
402
|
return false;
|
|
@@ -406,7 +409,7 @@ function objEqualSet(source: Set<unknown>, target: Set<unknown>, options?: Equal
|
|
|
406
409
|
const matchedIndices = new Set<number>();
|
|
407
410
|
for (const sourceItem of source) {
|
|
408
411
|
const idx = targetArr.findIndex(
|
|
409
|
-
(t, i) => !matchedIndices.has(i) &&
|
|
412
|
+
(t, i) => !matchedIndices.has(i) && equal(sourceItem, t, options),
|
|
410
413
|
);
|
|
411
414
|
if (idx === -1) {
|
|
412
415
|
return false;
|
|
@@ -420,10 +423,10 @@ function objEqualSet(source: Set<unknown>, target: Set<unknown>, options?: Equal
|
|
|
420
423
|
|
|
421
424
|
//#endregion
|
|
422
425
|
|
|
423
|
-
//#region
|
|
426
|
+
//#region merge
|
|
424
427
|
|
|
425
|
-
/**
|
|
426
|
-
export interface
|
|
428
|
+
/** merge options type */
|
|
429
|
+
export interface MergeOptions {
|
|
427
430
|
/** Array processing method. "replace": replace with target (default), "concat": merge (deduplicate) */
|
|
428
431
|
arrayProcess?: "replace" | "concat";
|
|
429
432
|
/** Whether to delete the key when target is null */
|
|
@@ -448,23 +451,23 @@ export interface ObjMergeOptions {
|
|
|
448
451
|
* and for object arrays, deduplication is determined by reference (address) comparison
|
|
449
452
|
* @note If types are different, overwrite with target value
|
|
450
453
|
*/
|
|
451
|
-
export function
|
|
454
|
+
export function merge<TSource, TMergeTarget>(
|
|
452
455
|
source: TSource,
|
|
453
456
|
target: TMergeTarget,
|
|
454
|
-
opt?:
|
|
457
|
+
opt?: MergeOptions,
|
|
455
458
|
): TSource & TMergeTarget {
|
|
456
459
|
if (source == null) {
|
|
457
|
-
return
|
|
460
|
+
return clone(target) as TSource & TMergeTarget;
|
|
458
461
|
}
|
|
459
462
|
|
|
460
463
|
if (target === undefined) {
|
|
461
|
-
return
|
|
464
|
+
return clone(source) as TSource & TMergeTarget;
|
|
462
465
|
}
|
|
463
466
|
|
|
464
467
|
if (target === null) {
|
|
465
468
|
return opt?.useDelTargetNull
|
|
466
469
|
? (undefined as TSource & TMergeTarget)
|
|
467
|
-
: (
|
|
470
|
+
: (clone(source) as TSource & TMergeTarget);
|
|
468
471
|
}
|
|
469
472
|
|
|
470
473
|
if (typeof target !== "object") {
|
|
@@ -480,21 +483,21 @@ export function objMerge<TSource, TMergeTarget>(
|
|
|
480
483
|
target instanceof Uint8Array ||
|
|
481
484
|
(opt?.arrayProcess === "replace" && target instanceof Array)
|
|
482
485
|
) {
|
|
483
|
-
return
|
|
486
|
+
return clone(target) as TSource & TMergeTarget;
|
|
484
487
|
}
|
|
485
488
|
|
|
486
489
|
// If source is not an object or source and target are different types of objects, overwrite with target
|
|
487
490
|
if (typeof source !== "object" || source.constructor !== target.constructor) {
|
|
488
|
-
return
|
|
491
|
+
return clone(target) as TSource & TMergeTarget;
|
|
489
492
|
}
|
|
490
493
|
|
|
491
494
|
if (source instanceof Map && target instanceof Map) {
|
|
492
|
-
const result =
|
|
495
|
+
const result = clone(source);
|
|
493
496
|
for (const key of target.keys()) {
|
|
494
497
|
if (result.has(key)) {
|
|
495
|
-
result.set(key,
|
|
498
|
+
result.set(key, merge(result.get(key), target.get(key), opt));
|
|
496
499
|
} else {
|
|
497
|
-
result.set(key,
|
|
500
|
+
result.set(key, clone(target.get(key)));
|
|
498
501
|
}
|
|
499
502
|
}
|
|
500
503
|
return result as TSource & TMergeTarget;
|
|
@@ -510,9 +513,9 @@ export function objMerge<TSource, TMergeTarget>(
|
|
|
510
513
|
|
|
511
514
|
const sourceRec = source as Record<string, unknown>;
|
|
512
515
|
const targetRec = target as Record<string, unknown>;
|
|
513
|
-
const resultRec =
|
|
516
|
+
const resultRec = clone(sourceRec);
|
|
514
517
|
for (const key of Object.keys(target)) {
|
|
515
|
-
resultRec[key] =
|
|
518
|
+
resultRec[key] = merge(sourceRec[key], targetRec[key], opt);
|
|
516
519
|
if (resultRec[key] === undefined) {
|
|
517
520
|
delete resultRec[key];
|
|
518
521
|
}
|
|
@@ -522,7 +525,7 @@ export function objMerge<TSource, TMergeTarget>(
|
|
|
522
525
|
}
|
|
523
526
|
|
|
524
527
|
/** merge3 options type */
|
|
525
|
-
export interface
|
|
528
|
+
export interface Merge3KeyOptions {
|
|
526
529
|
/** List of sub-keys to compare (same as equal's topLevelIncludes) */
|
|
527
530
|
keys?: string[];
|
|
528
531
|
/** List of sub-keys to exclude from comparison */
|
|
@@ -557,7 +560,7 @@ export interface ObjMerge3KeyOptions {
|
|
|
557
560
|
* );
|
|
558
561
|
* // conflict: false, result: { a: 2, b: 2 }
|
|
559
562
|
*/
|
|
560
|
-
export function
|
|
563
|
+
export function merge3<
|
|
561
564
|
S extends Record<string, unknown>,
|
|
562
565
|
O extends Record<string, unknown>,
|
|
563
566
|
T extends Record<string, unknown>,
|
|
@@ -565,21 +568,21 @@ export function objMerge3<
|
|
|
565
568
|
source: S,
|
|
566
569
|
origin: O,
|
|
567
570
|
target: T,
|
|
568
|
-
optionsObj?: Record<string,
|
|
571
|
+
optionsObj?: Record<string, Merge3KeyOptions>,
|
|
569
572
|
): {
|
|
570
573
|
conflict: boolean;
|
|
571
574
|
result: O & S & T;
|
|
572
575
|
} {
|
|
573
576
|
let conflict = false;
|
|
574
|
-
const result =
|
|
577
|
+
const result = clone(origin) as Record<string, unknown>;
|
|
575
578
|
const allKeys = new Set([...Object.keys(source), ...Object.keys(target), ...Object.keys(origin)]);
|
|
576
579
|
for (const key of allKeys) {
|
|
577
|
-
if (
|
|
578
|
-
result[key] =
|
|
579
|
-
} else if (
|
|
580
|
-
result[key] =
|
|
581
|
-
} else if (
|
|
582
|
-
result[key] =
|
|
580
|
+
if (equal(source[key], result[key], optionsObj?.[key])) {
|
|
581
|
+
result[key] = clone(target[key]);
|
|
582
|
+
} else if (equal(target[key], result[key], optionsObj?.[key])) {
|
|
583
|
+
result[key] = clone(source[key]);
|
|
584
|
+
} else if (equal(source[key], target[key], optionsObj?.[key])) {
|
|
585
|
+
result[key] = clone(source[key]);
|
|
583
586
|
} else {
|
|
584
587
|
conflict = true;
|
|
585
588
|
}
|
|
@@ -593,7 +596,7 @@ export function objMerge3<
|
|
|
593
596
|
|
|
594
597
|
//#endregion
|
|
595
598
|
|
|
596
|
-
//#region
|
|
599
|
+
//#region omit / pick
|
|
597
600
|
|
|
598
601
|
/**
|
|
599
602
|
* Exclude specific keys from object
|
|
@@ -602,10 +605,10 @@ export function objMerge3<
|
|
|
602
605
|
* @returns New object with specified keys excluded
|
|
603
606
|
* @example
|
|
604
607
|
* const user = { name: "Alice", age: 30, email: "alice@example.com" };
|
|
605
|
-
*
|
|
608
|
+
* omit(user, ["email"]);
|
|
606
609
|
* // { name: "Alice", age: 30 }
|
|
607
610
|
*/
|
|
608
|
-
export function
|
|
611
|
+
export function omit<T extends Record<string, unknown>, K extends keyof T>(
|
|
609
612
|
item: T,
|
|
610
613
|
omitKeys: K[],
|
|
611
614
|
): Omit<T, K> {
|
|
@@ -626,10 +629,10 @@ export function objOmit<T extends Record<string, unknown>, K extends keyof T>(
|
|
|
626
629
|
* @returns New object with keys matching condition excluded
|
|
627
630
|
* @example
|
|
628
631
|
* const data = { name: "Alice", _internal: "secret", age: 30 };
|
|
629
|
-
*
|
|
632
|
+
* omitByFilter(data, (key) => key.startsWith("_"));
|
|
630
633
|
* // { name: "Alice", age: 30 }
|
|
631
634
|
*/
|
|
632
|
-
export function
|
|
635
|
+
export function omitByFilter<T extends Record<string, unknown>>(
|
|
633
636
|
item: T,
|
|
634
637
|
omitKeyFn: (key: keyof T) => boolean,
|
|
635
638
|
): T {
|
|
@@ -649,15 +652,15 @@ export function objOmitByFilter<T extends Record<string, unknown>>(
|
|
|
649
652
|
* @returns New object containing only specified keys
|
|
650
653
|
* @example
|
|
651
654
|
* const user = { name: "Alice", age: 30, email: "alice@example.com" };
|
|
652
|
-
*
|
|
655
|
+
* pick(user, ["name", "age"]);
|
|
653
656
|
* // { name: "Alice", age: 30 }
|
|
654
657
|
*/
|
|
655
|
-
export function
|
|
658
|
+
export function pick<T extends Record<string, unknown>, K extends keyof T>(
|
|
656
659
|
item: T,
|
|
657
|
-
|
|
660
|
+
pickKeys: K[],
|
|
658
661
|
): Pick<T, K> {
|
|
659
662
|
const result: Record<string, unknown> = {};
|
|
660
|
-
for (const key of
|
|
663
|
+
for (const key of pickKeys) {
|
|
661
664
|
result[key as string] = item[key];
|
|
662
665
|
}
|
|
663
666
|
return result as Pick<T, K>;
|
|
@@ -665,7 +668,7 @@ export function objPick<T extends Record<string, unknown>, K extends keyof T>(
|
|
|
665
668
|
|
|
666
669
|
//#endregion
|
|
667
670
|
|
|
668
|
-
//#region
|
|
671
|
+
//#region getChainValue / setChainValue / deleteChainValue
|
|
669
672
|
|
|
670
673
|
// Regex caching (created once at module load)
|
|
671
674
|
const chainSplitRegex = /[.[\]]/g;
|
|
@@ -691,11 +694,11 @@ function getChainSplits(chain: string): (string | number)[] {
|
|
|
691
694
|
|
|
692
695
|
/**
|
|
693
696
|
* Get value by chain path
|
|
694
|
-
* @example
|
|
697
|
+
* @example getChainValue(obj, "a.b[0].c")
|
|
695
698
|
*/
|
|
696
|
-
export function
|
|
697
|
-
export function
|
|
698
|
-
export function
|
|
699
|
+
export function getChainValue(obj: unknown, chain: string, optional: true): unknown | undefined;
|
|
700
|
+
export function getChainValue(obj: unknown, chain: string): unknown;
|
|
701
|
+
export function getChainValue(
|
|
699
702
|
obj: unknown,
|
|
700
703
|
chain: string,
|
|
701
704
|
optional?: true,
|
|
@@ -720,20 +723,20 @@ export function objGetChainValue(
|
|
|
720
723
|
* @param depth Depth to descend (1 or more)
|
|
721
724
|
* @param optional If true, return undefined without error if null/undefined found in the middle
|
|
722
725
|
* @throws ArgumentError If depth is less than 1
|
|
723
|
-
* @example
|
|
726
|
+
* @example getChainValueByDepth({ parent: { parent: { name: 'a' } } }, 'parent', 2) => { name: 'a' }
|
|
724
727
|
*/
|
|
725
|
-
export function
|
|
728
|
+
export function getChainValueByDepth<TObject, TKey extends keyof TObject>(
|
|
726
729
|
obj: TObject,
|
|
727
730
|
key: TKey,
|
|
728
731
|
depth: number,
|
|
729
732
|
optional: true,
|
|
730
733
|
): TObject[TKey] | undefined;
|
|
731
|
-
export function
|
|
734
|
+
export function getChainValueByDepth<TObject, TKey extends keyof TObject>(
|
|
732
735
|
obj: TObject,
|
|
733
736
|
key: TKey,
|
|
734
737
|
depth: number,
|
|
735
738
|
): TObject[TKey];
|
|
736
|
-
export function
|
|
739
|
+
export function getChainValueByDepth<TObject, TKey extends keyof TObject>(
|
|
737
740
|
obj: TObject,
|
|
738
741
|
key: TKey,
|
|
739
742
|
depth: number,
|
|
@@ -755,9 +758,9 @@ export function objGetChainValueByDepth<TObject, TKey extends keyof TObject>(
|
|
|
755
758
|
|
|
756
759
|
/**
|
|
757
760
|
* Set value by chain path
|
|
758
|
-
* @example
|
|
761
|
+
* @example setChainValue(obj, "a.b[0].c", value)
|
|
759
762
|
*/
|
|
760
|
-
export function
|
|
763
|
+
export function setChainValue(obj: unknown, chain: string, value: unknown): void {
|
|
761
764
|
const splits = getChainSplits(chain);
|
|
762
765
|
if (splits.length === 0) {
|
|
763
766
|
throw new ArgumentError("Chain is empty", { chain });
|
|
@@ -775,9 +778,9 @@ export function objSetChainValue(obj: unknown, chain: string, value: unknown): v
|
|
|
775
778
|
|
|
776
779
|
/**
|
|
777
780
|
* Delete value by chain path
|
|
778
|
-
* @example
|
|
781
|
+
* @example deleteChainValue(obj, "a.b[0].c")
|
|
779
782
|
*/
|
|
780
|
-
export function
|
|
783
|
+
export function deleteChainValue(obj: unknown, chain: string): void {
|
|
781
784
|
const splits = getChainSplits(chain);
|
|
782
785
|
if (splits.length === 0) {
|
|
783
786
|
throw new ArgumentError("Chain is empty", { chain });
|
|
@@ -799,7 +802,7 @@ export function objDeleteChainValue(obj: unknown, chain: string): void {
|
|
|
799
802
|
|
|
800
803
|
//#endregion
|
|
801
804
|
|
|
802
|
-
//#region
|
|
805
|
+
//#region clearUndefined / clear / nullToUndefined / unflatten
|
|
803
806
|
|
|
804
807
|
/**
|
|
805
808
|
* Delete keys with undefined values from object
|
|
@@ -807,7 +810,7 @@ export function objDeleteChainValue(obj: unknown, chain: string): void {
|
|
|
807
810
|
*
|
|
808
811
|
* @mutates Modifies the original object directly
|
|
809
812
|
*/
|
|
810
|
-
export function
|
|
813
|
+
export function clearUndefined<T extends object>(obj: T): T {
|
|
811
814
|
const record = obj as Record<string, unknown>;
|
|
812
815
|
for (const key of Object.keys(record)) {
|
|
813
816
|
if (record[key] === undefined) {
|
|
@@ -823,7 +826,7 @@ export function objClearUndefined<T extends object>(obj: T): T {
|
|
|
823
826
|
*
|
|
824
827
|
* @mutates Modifies the original object directly
|
|
825
828
|
*/
|
|
826
|
-
export function
|
|
829
|
+
export function clear<T extends Record<string, unknown>>(obj: T): Record<string, never> {
|
|
827
830
|
for (const key of Object.keys(obj)) {
|
|
828
831
|
delete obj[key];
|
|
829
832
|
}
|
|
@@ -836,11 +839,11 @@ export function objClear<T extends Record<string, unknown>>(obj: T): Record<stri
|
|
|
836
839
|
*
|
|
837
840
|
* @mutates Modifies the original array/object directly
|
|
838
841
|
*/
|
|
839
|
-
export function
|
|
840
|
-
return
|
|
842
|
+
export function nullToUndefined<TObject>(obj: TObject): TObject | undefined {
|
|
843
|
+
return nullToUndefinedImpl(obj, new WeakSet());
|
|
841
844
|
}
|
|
842
845
|
|
|
843
|
-
function
|
|
846
|
+
function nullToUndefinedImpl<TObject>(obj: TObject, seen: WeakSet<object>): TObject | undefined {
|
|
844
847
|
if (obj == null) {
|
|
845
848
|
return undefined;
|
|
846
849
|
}
|
|
@@ -859,7 +862,7 @@ function objNullToUndefinedImpl<TObject>(obj: TObject, seen: WeakSet<object>): T
|
|
|
859
862
|
if (seen.has(obj)) return obj;
|
|
860
863
|
seen.add(obj);
|
|
861
864
|
for (let i = 0; i < obj.length; i++) {
|
|
862
|
-
obj[i] =
|
|
865
|
+
obj[i] = nullToUndefinedImpl(obj[i], seen);
|
|
863
866
|
}
|
|
864
867
|
return obj;
|
|
865
868
|
}
|
|
@@ -869,7 +872,7 @@ function objNullToUndefinedImpl<TObject>(obj: TObject, seen: WeakSet<object>): T
|
|
|
869
872
|
seen.add(obj as object);
|
|
870
873
|
const objRec = obj as Record<string, unknown>;
|
|
871
874
|
for (const key of Object.keys(obj)) {
|
|
872
|
-
objRec[key] =
|
|
875
|
+
objRec[key] = nullToUndefinedImpl(objRec[key], seen);
|
|
873
876
|
}
|
|
874
877
|
|
|
875
878
|
return obj;
|
|
@@ -881,9 +884,9 @@ function objNullToUndefinedImpl<TObject>(obj: TObject, seen: WeakSet<object>): T
|
|
|
881
884
|
/**
|
|
882
885
|
* Convert flattened object to nested object
|
|
883
886
|
* @internal
|
|
884
|
-
* @example
|
|
887
|
+
* @example unflatten({ "a.b.c": 1 }) => { a: { b: { c: 1 } } }
|
|
885
888
|
*/
|
|
886
|
-
export function
|
|
889
|
+
export function unflatten(flatObj: Record<string, unknown>): Record<string, unknown> {
|
|
887
890
|
const result: Record<string, unknown> = {};
|
|
888
891
|
|
|
889
892
|
for (const key in flatObj) {
|
|
@@ -915,7 +918,7 @@ export function objUnflatten(flatObj: Record<string, unknown>): Record<string, u
|
|
|
915
918
|
* Convert properties with undefined to optional
|
|
916
919
|
* @example { a: string; b: string | undefined } → { a: string; b?: string | undefined }
|
|
917
920
|
*/
|
|
918
|
-
export type
|
|
921
|
+
export type UndefToOptional<TObject> = {
|
|
919
922
|
[K in keyof TObject as undefined extends TObject[K] ? K : never]?: TObject[K];
|
|
920
923
|
} & { [K in keyof TObject as undefined extends TObject[K] ? never : K]: TObject[K] };
|
|
921
924
|
|
|
@@ -923,7 +926,7 @@ export type ObjUndefToOptional<TObject> = {
|
|
|
923
926
|
* Convert optional properties to required + undefined union
|
|
924
927
|
* @example { a: string; b?: string } → { a: string; b: string | undefined }
|
|
925
928
|
*/
|
|
926
|
-
export type
|
|
929
|
+
export type OptionalToUndef<TObject> = {
|
|
927
930
|
[K in keyof TObject]-?: {} extends Pick<TObject, K> ? TObject[K] | undefined : TObject[K];
|
|
928
931
|
};
|
|
929
932
|
|
|
@@ -934,7 +937,7 @@ export type ObjOptionalToUndef<TObject> = {
|
|
|
934
937
|
* @param obj Object to extract keys from
|
|
935
938
|
* @returns Array of object keys
|
|
936
939
|
*/
|
|
937
|
-
export function
|
|
940
|
+
export function keys<T extends object>(obj: T): (keyof T)[] {
|
|
938
941
|
return Object.keys(obj) as (keyof T)[];
|
|
939
942
|
}
|
|
940
943
|
|
|
@@ -943,8 +946,8 @@ export function objKeys<T extends object>(obj: T): (keyof T)[] {
|
|
|
943
946
|
* @param obj Object to extract entries from
|
|
944
947
|
* @returns Array of [key, value] tuples
|
|
945
948
|
*/
|
|
946
|
-
export function
|
|
947
|
-
return Object.entries(obj) as
|
|
949
|
+
export function entries<T extends object>(obj: T): Entries<T> {
|
|
950
|
+
return Object.entries(obj) as Entries<T>;
|
|
948
951
|
}
|
|
949
952
|
|
|
950
953
|
/**
|
|
@@ -952,11 +955,11 @@ export function objEntries<T extends object>(obj: T): ObjEntries<T> {
|
|
|
952
955
|
* @param entries Array of [key, value] tuples
|
|
953
956
|
* @returns Created object
|
|
954
957
|
*/
|
|
955
|
-
export function
|
|
956
|
-
return Object.fromEntries(
|
|
958
|
+
export function fromEntries<T extends [string, unknown]>(entryPairs: T[]): { [K in T[0]]: T[1] } {
|
|
959
|
+
return Object.fromEntries(entryPairs) as { [K in T[0]]: T[1] };
|
|
957
960
|
}
|
|
958
961
|
|
|
959
|
-
type
|
|
962
|
+
type Entries<TObject> = { [K in keyof TObject]: [K, TObject[K]] }[keyof TObject][];
|
|
960
963
|
|
|
961
964
|
/**
|
|
962
965
|
* Transform each entry of object and return new object
|
|
@@ -967,21 +970,21 @@ type ObjEntries<TObject> = { [K in keyof TObject]: [K, TObject[K]] }[keyof TObje
|
|
|
967
970
|
* const colors = { primary: "255, 0, 0", secondary: "0, 255, 0" };
|
|
968
971
|
*
|
|
969
972
|
* // Transform only values
|
|
970
|
-
*
|
|
973
|
+
* map(colors, (key, rgb) => [null, `rgb(${rgb})`]);
|
|
971
974
|
* // { primary: "rgb(255, 0, 0)", secondary: "rgb(0, 255, 0)" }
|
|
972
975
|
*
|
|
973
976
|
* // Transform both keys and values
|
|
974
|
-
*
|
|
977
|
+
* map(colors, (key, rgb) => [`${key}Light`, `rgb(${rgb})`]);
|
|
975
978
|
* // { primaryLight: "rgb(255, 0, 0)", secondaryLight: "rgb(0, 255, 0)" }
|
|
976
979
|
*/
|
|
977
|
-
export function
|
|
980
|
+
export function map<TSource extends object, TNewKey extends string, TNewValue>(
|
|
978
981
|
obj: TSource,
|
|
979
982
|
fn: (key: keyof TSource, value: TSource[keyof TSource]) => [TNewKey | null, TNewValue],
|
|
980
983
|
): Record<TNewKey | Extract<keyof TSource, string>, TNewValue> {
|
|
981
|
-
return
|
|
984
|
+
return mapImpl(obj, fn);
|
|
982
985
|
}
|
|
983
986
|
|
|
984
|
-
function
|
|
987
|
+
function mapImpl<TSource extends object, TNewKey extends string, TNewValue>(
|
|
985
988
|
obj: TSource,
|
|
986
989
|
fn: (key: keyof TSource, value: TSource[keyof TSource]) => [TNewKey | null, TNewValue],
|
|
987
990
|
): Record<string, TNewValue> {
|