ts-data-forge 6.7.0 → 6.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/array/impl/array-utils-set-op.d.mts.map +1 -1
- package/dist/array/impl/array-utils-set-op.mjs +1 -3
- package/dist/array/impl/array-utils-set-op.mjs.map +1 -1
- package/dist/array/impl/array-utils-transformation.d.mts +25 -0
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs +36 -1
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +2 -6
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/array/impl/index.mjs +1 -1
- package/dist/object/object.d.mts +56 -0
- package/dist/object/object.d.mts.map +1 -1
- package/dist/object/object.mjs +107 -1
- package/dist/object/object.mjs.map +1 -1
- package/dist/others/fast-deep-equal.d.mts.map +1 -1
- package/dist/others/fast-deep-equal.mjs +0 -1
- package/dist/others/fast-deep-equal.mjs.map +1 -1
- package/package.json +14 -14
- package/src/array/impl/array-utils-overload-type-error.test.mts +2 -4
- package/src/array/impl/array-utils-set-op.mts +0 -1
- package/src/array/impl/array-utils-transformation.mts +40 -0
- package/src/array/impl/array-utils-transformation.test.mts +54 -0
- package/src/array/impl/array-utils-validation.mts +2 -6
- package/src/functional/optional.test.mts +5 -7
- package/src/functional/result.test.mts +8 -10
- package/src/functional/ternary-result.test.mts +4 -4
- package/src/json/json.test.mts +5 -5
- package/src/number/branded-types/finite-number.test.mts +11 -15
- package/src/number/branded-types/int.test.mts +13 -13
- package/src/number/branded-types/int16.test.mts +15 -15
- package/src/number/branded-types/int32.test.mts +15 -15
- package/src/number/branded-types/non-negative-finite-number.test.mts +15 -19
- package/src/number/branded-types/non-negative-int16.test.mts +15 -15
- package/src/number/branded-types/non-negative-int32.test.mts +15 -15
- package/src/number/branded-types/non-zero-finite-number.test.mts +18 -18
- package/src/number/branded-types/non-zero-int.test.mts +14 -18
- package/src/number/branded-types/non-zero-int16.test.mts +15 -19
- package/src/number/branded-types/non-zero-int32.test.mts +15 -19
- package/src/number/branded-types/non-zero-safe-int.test.mts +18 -22
- package/src/number/branded-types/non-zero-uint16.test.mts +15 -15
- package/src/number/branded-types/non-zero-uint32.test.mts +15 -15
- package/src/number/branded-types/positive-finite-number.test.mts +18 -18
- package/src/number/branded-types/positive-int.test.mts +16 -22
- package/src/number/branded-types/positive-int16.test.mts +14 -14
- package/src/number/branded-types/positive-int32.test.mts +14 -14
- package/src/number/branded-types/positive-safe-int.test.mts +18 -20
- package/src/number/branded-types/positive-uint16.test.mts +15 -15
- package/src/number/branded-types/positive-uint32.test.mts +15 -15
- package/src/number/branded-types/safe-int.test.mts +17 -21
- package/src/number/branded-types/safe-uint.test.mts +16 -22
- package/src/number/branded-types/uint.test.mts +14 -14
- package/src/number/branded-types/uint16.test.mts +14 -14
- package/src/number/branded-types/uint32.test.mts +14 -14
- package/src/number/enum/int8.test.mts +1 -1
- package/src/number/enum/uint8.test.mts +1 -1
- package/src/object/object.mts +174 -1
- package/src/object/object.test.mts +172 -0
- package/src/others/fast-deep-equal.mts +0 -1
- package/src/others/unknown-to-string.test.mts +1 -1
|
@@ -6,37 +6,37 @@ import { asUint16, isUint16, Uint16 } from './uint16.mjs';
|
|
|
6
6
|
describe('Uint16 test', () => {
|
|
7
7
|
describe(asUint16, () => {
|
|
8
8
|
test('accepts valid uint16 values', () => {
|
|
9
|
-
expect(() => asUint16(0)).not.
|
|
9
|
+
expect(() => asUint16(0)).not.toThrow();
|
|
10
10
|
|
|
11
|
-
expect(() => asUint16(1)).not.
|
|
11
|
+
expect(() => asUint16(1)).not.toThrow();
|
|
12
12
|
|
|
13
|
-
expect(() => asUint16(65_535)).not.
|
|
13
|
+
expect(() => asUint16(65_535)).not.toThrow(); // 2^16 - 1
|
|
14
14
|
|
|
15
|
-
expect(() => asUint16(32_768)).not.
|
|
15
|
+
expect(() => asUint16(32_768)).not.toThrow(); // 2^15
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
test('rejects values outside uint16 range', () => {
|
|
19
|
-
expect(() => asUint16(65_536)).
|
|
19
|
+
expect(() => asUint16(65_536)).toThrow(TypeError); // 2^16
|
|
20
20
|
|
|
21
|
-
expect(() => asUint16(100_000)).
|
|
21
|
+
expect(() => asUint16(100_000)).toThrow(TypeError);
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
test('rejects negative integers', () => {
|
|
25
|
-
expect(() => asUint16(-1)).
|
|
25
|
+
expect(() => asUint16(-1)).toThrow(TypeError);
|
|
26
26
|
|
|
27
|
-
expect(() => asUint16(-42)).
|
|
27
|
+
expect(() => asUint16(-42)).toThrow(TypeError);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
test('rejects non-integers', () => {
|
|
31
|
-
expect(() => asUint16(Number.NaN)).
|
|
31
|
+
expect(() => asUint16(Number.NaN)).toThrow(TypeError);
|
|
32
32
|
|
|
33
|
-
expect(() => asUint16(Number.POSITIVE_INFINITY)).
|
|
33
|
+
expect(() => asUint16(Number.POSITIVE_INFINITY)).toThrow(TypeError);
|
|
34
34
|
|
|
35
|
-
expect(() => asUint16(Number.NEGATIVE_INFINITY)).
|
|
35
|
+
expect(() => asUint16(Number.NEGATIVE_INFINITY)).toThrow(TypeError);
|
|
36
36
|
|
|
37
|
-
expect(() => asUint16(1.2)).
|
|
37
|
+
expect(() => asUint16(1.2)).toThrow(TypeError);
|
|
38
38
|
|
|
39
|
-
expect(() => asUint16(-3.4)).
|
|
39
|
+
expect(() => asUint16(-3.4)).toThrow(TypeError);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test('returns the same value for valid inputs', () => {
|
|
@@ -55,7 +55,7 @@ describe('Uint16 test', () => {
|
|
|
55
55
|
{ name: '-3.4', value: -3.4 },
|
|
56
56
|
{ name: '-1', value: -1 },
|
|
57
57
|
] as const)(`asUint16($name) should throw a TypeError`, ({ value }) => {
|
|
58
|
-
expect(() => asUint16(value)).
|
|
58
|
+
expect(() => asUint16(value)).toThrow(
|
|
59
59
|
new TypeError(
|
|
60
60
|
`Expected a non-negative integer less than 2^16, got: ${value}`,
|
|
61
61
|
),
|
|
@@ -6,37 +6,37 @@ import { asUint32, isUint32, Uint32 } from './uint32.mjs';
|
|
|
6
6
|
describe('Uint32 test', () => {
|
|
7
7
|
describe(asUint32, () => {
|
|
8
8
|
test('accepts valid uint32 values', () => {
|
|
9
|
-
expect(() => asUint32(0)).not.
|
|
9
|
+
expect(() => asUint32(0)).not.toThrow();
|
|
10
10
|
|
|
11
|
-
expect(() => asUint32(1)).not.
|
|
11
|
+
expect(() => asUint32(1)).not.toThrow();
|
|
12
12
|
|
|
13
|
-
expect(() => asUint32(4_294_967_295)).not.
|
|
13
|
+
expect(() => asUint32(4_294_967_295)).not.toThrow(); // 2^32 - 1
|
|
14
14
|
|
|
15
|
-
expect(() => asUint32(2_147_483_648)).not.
|
|
15
|
+
expect(() => asUint32(2_147_483_648)).not.toThrow(); // 2^31
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
test('rejects values outside uint32 range', () => {
|
|
19
|
-
expect(() => asUint32(4_294_967_296)).
|
|
19
|
+
expect(() => asUint32(4_294_967_296)).toThrow(TypeError); // 2^32
|
|
20
20
|
|
|
21
|
-
expect(() => asUint32(10_000_000_000)).
|
|
21
|
+
expect(() => asUint32(10_000_000_000)).toThrow(TypeError);
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
test('rejects negative integers', () => {
|
|
25
|
-
expect(() => asUint32(-1)).
|
|
25
|
+
expect(() => asUint32(-1)).toThrow(TypeError);
|
|
26
26
|
|
|
27
|
-
expect(() => asUint32(-42)).
|
|
27
|
+
expect(() => asUint32(-42)).toThrow(TypeError);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
test('rejects non-integers', () => {
|
|
31
|
-
expect(() => asUint32(Number.NaN)).
|
|
31
|
+
expect(() => asUint32(Number.NaN)).toThrow(TypeError);
|
|
32
32
|
|
|
33
|
-
expect(() => asUint32(Number.POSITIVE_INFINITY)).
|
|
33
|
+
expect(() => asUint32(Number.POSITIVE_INFINITY)).toThrow(TypeError);
|
|
34
34
|
|
|
35
|
-
expect(() => asUint32(Number.NEGATIVE_INFINITY)).
|
|
35
|
+
expect(() => asUint32(Number.NEGATIVE_INFINITY)).toThrow(TypeError);
|
|
36
36
|
|
|
37
|
-
expect(() => asUint32(1.2)).
|
|
37
|
+
expect(() => asUint32(1.2)).toThrow(TypeError);
|
|
38
38
|
|
|
39
|
-
expect(() => asUint32(-3.4)).
|
|
39
|
+
expect(() => asUint32(-3.4)).toThrow(TypeError);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test('returns the same value for valid inputs', () => {
|
|
@@ -55,7 +55,7 @@ describe('Uint32 test', () => {
|
|
|
55
55
|
{ name: '-3.4', value: -3.4 },
|
|
56
56
|
{ name: '-1', value: -1 },
|
|
57
57
|
] as const)(`asUint32($name) should throw a TypeError`, ({ value }) => {
|
|
58
|
-
expect(() => asUint32(value)).
|
|
58
|
+
expect(() => asUint32(value)).toThrow(
|
|
59
59
|
new TypeError(
|
|
60
60
|
`Expected a non-negative integer less than 2^32, got: ${value}`,
|
|
61
61
|
),
|
|
@@ -35,7 +35,7 @@ describe('Int8 test', () => {
|
|
|
35
35
|
{ value: Number.POSITIVE_INFINITY, name: 'Infinity' },
|
|
36
36
|
{ value: Number.NEGATIVE_INFINITY, name: '-Infinity' },
|
|
37
37
|
])('asInt8($name) should throw TypeError', ({ value }) => {
|
|
38
|
-
expect(() => asInt8(value)).
|
|
38
|
+
expect(() => asInt8(value)).toThrow(TypeError);
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
|
|
@@ -35,7 +35,7 @@ describe('Uint8 test', () => {
|
|
|
35
35
|
{ value: Number.POSITIVE_INFINITY, name: 'Infinity' },
|
|
36
36
|
{ value: Number.NEGATIVE_INFINITY, name: '-Infinity' },
|
|
37
37
|
])('asUint8($name) should throw TypeError', ({ value }) => {
|
|
38
|
-
expect(() => asUint8(value)).
|
|
38
|
+
expect(() => asUint8(value)).toThrow(TypeError);
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
|
package/src/object/object.mts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Arr } from '../array/index.mjs';
|
|
2
|
+
import { hasKey, isRecord } from '../guard/index.mjs';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* A collection of type-safe object utility functions providing functional
|
|
3
6
|
* programming patterns for object manipulation, including pick, omit, shallow
|
|
@@ -58,7 +61,6 @@ export namespace Obj {
|
|
|
58
61
|
|
|
59
62
|
const bEntries = Object.entries(b);
|
|
60
63
|
|
|
61
|
-
// eslint-disable-next-line ts-data-forge/prefer-arr-is-array-of-length
|
|
62
64
|
if (aEntries.length !== bEntries.length) return false;
|
|
63
65
|
|
|
64
66
|
return aEntries.every(([k, v]) => eq(b[k], v));
|
|
@@ -311,6 +313,8 @@ export namespace Obj {
|
|
|
311
313
|
*
|
|
312
314
|
* @example
|
|
313
315
|
*
|
|
316
|
+
* <!-- doc:embed:jsdoc:example:./samples/src/object/merge-example.mts -->
|
|
317
|
+
*
|
|
314
318
|
* ```ts
|
|
315
319
|
* const a = { a: 0, b: 0 } as const;
|
|
316
320
|
* const b = { b: 1, c: 0 } as const;
|
|
@@ -320,6 +324,8 @@ export namespace Obj {
|
|
|
320
324
|
* assert.deepStrictEqual(result, { a: 0, b: 1, c: 0 });
|
|
321
325
|
* ```
|
|
322
326
|
*
|
|
327
|
+
* <!-- /doc:embed:jsdoc:example:./samples/src/object/merge-example.mts -->
|
|
328
|
+
*
|
|
323
329
|
* @param records - The records to merge
|
|
324
330
|
* @returns A new record with all properties merged
|
|
325
331
|
*/
|
|
@@ -328,8 +334,175 @@ export namespace Obj {
|
|
|
328
334
|
): TsDataForgeInternals.MergeAll<Records> =>
|
|
329
335
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
330
336
|
Object.fromEntries(records.flatMap((r) => Object.entries(r))) as never;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Deeply picks a nested property from an object along the specified key path.
|
|
340
|
+
* Supports both direct and curried usage.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
*
|
|
344
|
+
* ```ts
|
|
345
|
+
* const data = { a: { b: { c: 1, d: 2 }, e: 3 }, f: 4 } as const;
|
|
346
|
+
*
|
|
347
|
+
* // Direct usage
|
|
348
|
+
* const result = Obj.deepPick(data, ['a', 'b', 'c']);
|
|
349
|
+
* assert.deepStrictEqual(result, { a: { b: { c: 1 } } });
|
|
350
|
+
*
|
|
351
|
+
* // Curried usage with pipe
|
|
352
|
+
* const pickName = Obj.deepPick(['user', 'name']);
|
|
353
|
+
* const result2 = pipe(data).map(pickName).value;
|
|
354
|
+
* ```
|
|
355
|
+
*
|
|
356
|
+
* @template R - The type of the input record
|
|
357
|
+
* @template Path - The key path tuple
|
|
358
|
+
* @param record - The source record
|
|
359
|
+
* @param path - A readonly tuple of keys representing the nested path
|
|
360
|
+
* @returns A new record containing only the nested property at the path
|
|
361
|
+
*/
|
|
362
|
+
export function deepPick<
|
|
363
|
+
const R extends UnknownRecord,
|
|
364
|
+
const Path extends readonly (string | number)[],
|
|
365
|
+
>(record: R, path: Path): DeepPick<R, Path>;
|
|
366
|
+
|
|
367
|
+
// Curried version
|
|
368
|
+
export function deepPick<const Path extends readonly (string | number)[]>(
|
|
369
|
+
path: Path,
|
|
370
|
+
): <const R extends UnknownRecord>(record: R) => DeepPick<R, Path>;
|
|
371
|
+
|
|
372
|
+
export function deepPick<
|
|
373
|
+
const R extends UnknownRecord,
|
|
374
|
+
const Path extends readonly (string | number)[],
|
|
375
|
+
>(
|
|
376
|
+
...args: readonly [record: R, path: Path] | readonly [path: Path]
|
|
377
|
+
): DeepPick<R, Path> | ((record: R) => DeepPick<R, Path>) {
|
|
378
|
+
switch (args.length) {
|
|
379
|
+
case 2: {
|
|
380
|
+
const [record, path] = args;
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
384
|
+
deepPickImpl(record, path) as never
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
case 1: {
|
|
389
|
+
const [path] = args;
|
|
390
|
+
|
|
391
|
+
return (record: R) => deepPick(record, path);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Deeply omits a nested property from an object along the specified key path.
|
|
398
|
+
* Supports both direct and curried usage.
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
*
|
|
402
|
+
* ```ts
|
|
403
|
+
* const data = { a: { b: { c: 1, d: 2 }, e: 3 }, f: 4 } as const;
|
|
404
|
+
*
|
|
405
|
+
* // Direct usage
|
|
406
|
+
* const result = Obj.deepOmit(data, ['a', 'b', 'c']);
|
|
407
|
+
* assert.deepStrictEqual(result, { a: { b: { d: 2 }, e: 3 }, f: 4 });
|
|
408
|
+
*
|
|
409
|
+
* // Curried usage with pipe
|
|
410
|
+
* const omitPassword = Obj.deepOmit(['user', 'password']);
|
|
411
|
+
* const result2 = pipe(data).map(omitPassword).value;
|
|
412
|
+
* ```
|
|
413
|
+
*
|
|
414
|
+
* @template R - The type of the input record
|
|
415
|
+
* @template Path - The key path tuple
|
|
416
|
+
* @param record - The source record
|
|
417
|
+
* @param path - A readonly tuple of keys representing the nested path to omit
|
|
418
|
+
* @returns A new record with the nested property at the path removed
|
|
419
|
+
*/
|
|
420
|
+
export function deepOmit<
|
|
421
|
+
const R extends UnknownRecord,
|
|
422
|
+
const Path extends readonly (string | number)[],
|
|
423
|
+
>(record: R, path: Path): DeepOmit<R, Path>;
|
|
424
|
+
|
|
425
|
+
// Curried version
|
|
426
|
+
export function deepOmit<const Path extends readonly (string | number)[]>(
|
|
427
|
+
path: Path,
|
|
428
|
+
): <const R extends UnknownRecord>(record: R) => DeepOmit<R, Path>;
|
|
429
|
+
|
|
430
|
+
export function deepOmit<
|
|
431
|
+
const R extends UnknownRecord,
|
|
432
|
+
const Path extends readonly (string | number)[],
|
|
433
|
+
>(
|
|
434
|
+
...args: readonly [record: R, path: Path] | readonly [path: Path]
|
|
435
|
+
): DeepOmit<R, Path> | ((record: R) => DeepOmit<R, Path>) {
|
|
436
|
+
switch (args.length) {
|
|
437
|
+
case 2: {
|
|
438
|
+
const [record, path] = args;
|
|
439
|
+
|
|
440
|
+
return (
|
|
441
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
442
|
+
deepOmitImpl(record, path) as never
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
case 1: {
|
|
447
|
+
const [path] = args;
|
|
448
|
+
|
|
449
|
+
return (record: R) => deepOmit(record, path);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
331
453
|
}
|
|
332
454
|
|
|
455
|
+
const deepPickImpl = (
|
|
456
|
+
record: UnknownRecord,
|
|
457
|
+
path: readonly (string | number)[],
|
|
458
|
+
): UnknownRecord => {
|
|
459
|
+
if (!Arr.isNonEmpty(path)) return record;
|
|
460
|
+
|
|
461
|
+
const head = path[0];
|
|
462
|
+
|
|
463
|
+
if (!hasKey(record, head)) return {};
|
|
464
|
+
|
|
465
|
+
const value = record[head];
|
|
466
|
+
|
|
467
|
+
const tail = path.slice(1);
|
|
468
|
+
|
|
469
|
+
if (!Arr.isNonEmpty(tail)) return { [head]: value };
|
|
470
|
+
|
|
471
|
+
if (!isRecord(value)) return { [head]: {} };
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
[head]: deepPickImpl(value, tail),
|
|
475
|
+
};
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const deepOmitImpl = (
|
|
479
|
+
record: UnknownRecord,
|
|
480
|
+
path: readonly (string | number)[],
|
|
481
|
+
): UnknownRecord => {
|
|
482
|
+
if (!Arr.isNonEmpty(path)) return record;
|
|
483
|
+
|
|
484
|
+
const head = path[0];
|
|
485
|
+
|
|
486
|
+
const tail = path.slice(1);
|
|
487
|
+
|
|
488
|
+
if (!hasKey(record, head)) return record;
|
|
489
|
+
|
|
490
|
+
if (!Arr.isNonEmpty(tail)) {
|
|
491
|
+
return Object.fromEntries(
|
|
492
|
+
Object.entries(record).filter(([k]) => k !== head),
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const value = record[head];
|
|
497
|
+
|
|
498
|
+
if (!isRecord(value)) return record;
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
...record,
|
|
502
|
+
[head]: deepOmitImpl(value, tail),
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
|
|
333
506
|
/**
|
|
334
507
|
* @internal
|
|
335
508
|
* Internal type utilities for the Obj module.
|
|
@@ -472,3 +472,175 @@ describe('merge', () => {
|
|
|
472
472
|
assert.deepStrictEqual(result, { a: 1, b: 'text', c: true, d: 42 });
|
|
473
473
|
});
|
|
474
474
|
});
|
|
475
|
+
|
|
476
|
+
describe('deepPick', () => {
|
|
477
|
+
test('should deeply pick a nested property', () => {
|
|
478
|
+
const data = { a: { b: { c: 1, d: 2 }, e: 3 }, f: 4 } as const;
|
|
479
|
+
|
|
480
|
+
const result = Obj.deepPick(data, ['a', 'b', 'c']);
|
|
481
|
+
|
|
482
|
+
assert.deepStrictEqual(result, { a: { b: { c: 1 } } });
|
|
483
|
+
|
|
484
|
+
expectType<
|
|
485
|
+
typeof result,
|
|
486
|
+
Readonly<{ a: Readonly<{ b: Readonly<{ c: 1 }> }> }>
|
|
487
|
+
>('=');
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test('should pick at depth 1', () => {
|
|
491
|
+
const data = { a: 1, b: 2, c: 3 } as const;
|
|
492
|
+
|
|
493
|
+
const result = Obj.deepPick(data, ['a']);
|
|
494
|
+
|
|
495
|
+
assert.deepStrictEqual(result, { a: 1 });
|
|
496
|
+
|
|
497
|
+
expectType<typeof result, Readonly<{ a: 1 }>>('=');
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test('should pick at depth 2', () => {
|
|
501
|
+
const data = { a: { b: 10, c: 20 }, d: 30 } as const;
|
|
502
|
+
|
|
503
|
+
const result = Obj.deepPick(data, ['a', 'b']);
|
|
504
|
+
|
|
505
|
+
assert.deepStrictEqual(result, { a: { b: 10 } });
|
|
506
|
+
|
|
507
|
+
expectType<typeof result, Readonly<{ a: Readonly<{ b: 10 }> }>>('=');
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test('should return empty object for non-existent key', () => {
|
|
511
|
+
const data = { a: 1 } as const;
|
|
512
|
+
|
|
513
|
+
const result = Obj.deepPick(data, ['x']);
|
|
514
|
+
|
|
515
|
+
assert.deepStrictEqual(result, {});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
test('should return empty nested for non-existent nested key', () => {
|
|
519
|
+
const data = { a: { b: 1 } } as const;
|
|
520
|
+
|
|
521
|
+
const result = Obj.deepPick(data, ['a', 'x']);
|
|
522
|
+
|
|
523
|
+
assert.deepStrictEqual(result, { a: {} });
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test('should support curried form with correct type inference', () => {
|
|
527
|
+
const pickABC = Obj.deepPick(['a', 'b', 'c']);
|
|
528
|
+
|
|
529
|
+
const data = { a: { b: { c: 42, d: 99 } } } as const;
|
|
530
|
+
|
|
531
|
+
const result = pickABC(data);
|
|
532
|
+
|
|
533
|
+
assert.deepStrictEqual(result, { a: { b: { c: 42 } } });
|
|
534
|
+
|
|
535
|
+
expectType<
|
|
536
|
+
typeof result,
|
|
537
|
+
Readonly<{ a: Readonly<{ b: Readonly<{ c: 42 }> }> }>
|
|
538
|
+
>('=');
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test('should work with pipe when curried with correct type inference', () => {
|
|
542
|
+
const pickNested = Obj.deepPick(['a', 'b']);
|
|
543
|
+
|
|
544
|
+
const data = { a: { b: 1, c: 2 }, d: 3 } as const;
|
|
545
|
+
|
|
546
|
+
const result = pipe(data).map(pickNested).value;
|
|
547
|
+
|
|
548
|
+
assert.deepStrictEqual(result, { a: { b: 1 } });
|
|
549
|
+
|
|
550
|
+
expectType<typeof result, Readonly<{ a: Readonly<{ b: 1 }> }>>('=');
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test('should return {} for path through primitive', () => {
|
|
554
|
+
const data = { a: 42 } as const;
|
|
555
|
+
|
|
556
|
+
const result = Obj.deepPick(data, ['a', 'b']);
|
|
557
|
+
|
|
558
|
+
assert.deepStrictEqual(result, { a: {} });
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
describe('deepOmit', () => {
|
|
563
|
+
test('should deeply omit a nested property', () => {
|
|
564
|
+
const data = { a: { b: { c: 1, d: 2 }, e: 3 }, f: 4 } as const;
|
|
565
|
+
|
|
566
|
+
const result = Obj.deepOmit(data, ['a', 'b', 'c']);
|
|
567
|
+
|
|
568
|
+
assert.deepStrictEqual(result, { a: { b: { d: 2 }, e: 3 }, f: 4 });
|
|
569
|
+
|
|
570
|
+
expectType<
|
|
571
|
+
typeof result,
|
|
572
|
+
Readonly<{ a: Readonly<{ b: Readonly<{ d: 2 }>; e: 3 }>; f: 4 }>
|
|
573
|
+
>('=');
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test('should omit at depth 1', () => {
|
|
577
|
+
const data = { a: 1, b: 2, c: 3 } as const;
|
|
578
|
+
|
|
579
|
+
const result = Obj.deepOmit(data, ['a']);
|
|
580
|
+
|
|
581
|
+
assert.deepStrictEqual(result, { b: 2, c: 3 });
|
|
582
|
+
|
|
583
|
+
expectType<typeof result, Readonly<{ b: 2; c: 3 }>>('=');
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
test('should omit at depth 2', () => {
|
|
587
|
+
const data = { a: { b: 10, c: 20 }, d: 30 } as const;
|
|
588
|
+
|
|
589
|
+
const result = Obj.deepOmit(data, ['a', 'b']);
|
|
590
|
+
|
|
591
|
+
assert.deepStrictEqual(result, { a: { c: 20 }, d: 30 });
|
|
592
|
+
|
|
593
|
+
expectType<typeof result, Readonly<{ a: Readonly<{ c: 20 }>; d: 30 }>>('=');
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
test('should return unchanged for non-existent key', () => {
|
|
597
|
+
const data = { a: 1, b: 2 } as const;
|
|
598
|
+
|
|
599
|
+
const result = Obj.deepOmit(data, ['x']);
|
|
600
|
+
|
|
601
|
+
assert.deepStrictEqual(result, { a: 1, b: 2 });
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
test('should return unchanged for non-existent nested key', () => {
|
|
605
|
+
const data = { a: { b: 1 } } as const;
|
|
606
|
+
|
|
607
|
+
const result = Obj.deepOmit(data, ['a', 'x']);
|
|
608
|
+
|
|
609
|
+
assert.deepStrictEqual(result, { a: { b: 1 } });
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
test('should return unchanged for path through primitive', () => {
|
|
613
|
+
const data = { a: 42, b: 'hello' } as const;
|
|
614
|
+
|
|
615
|
+
const result = Obj.deepOmit(data, ['a', 'toString']);
|
|
616
|
+
|
|
617
|
+
assert.deepStrictEqual(result, { a: 42, b: 'hello' });
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test('should support curried form with correct type inference', () => {
|
|
621
|
+
const omitABC = Obj.deepOmit(['a', 'b', 'c']);
|
|
622
|
+
|
|
623
|
+
const data = { a: { b: { c: 1, d: 2 } } } as const;
|
|
624
|
+
|
|
625
|
+
const result = omitABC(data);
|
|
626
|
+
|
|
627
|
+
assert.deepStrictEqual(result, { a: { b: { d: 2 } } });
|
|
628
|
+
|
|
629
|
+
expectType<
|
|
630
|
+
typeof result,
|
|
631
|
+
Readonly<{ a: Readonly<{ b: Readonly<{ d: 2 }> }> }>
|
|
632
|
+
>('=');
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test('should work with pipe when curried with correct type inference', () => {
|
|
636
|
+
const omitNested = Obj.deepOmit(['a', 'b']);
|
|
637
|
+
|
|
638
|
+
const data = { a: { b: 1, c: 2 }, d: 3 } as const;
|
|
639
|
+
|
|
640
|
+
const result = pipe(data).map(omitNested).value;
|
|
641
|
+
|
|
642
|
+
assert.deepStrictEqual(result, { a: { c: 2 }, d: 3 });
|
|
643
|
+
|
|
644
|
+
expectType<typeof result, Readonly<{ a: Readonly<{ c: 2 }>; d: 3 }>>('=');
|
|
645
|
+
});
|
|
646
|
+
});
|