ts-data-forge 7.1.1 → 8.0.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/guard/is-non-null-object.d.mts +3 -3
- package/dist/guard/is-non-null-object.mjs +3 -3
- package/dist/guard/is-record.d.mts +47 -29
- package/dist/guard/is-record.d.mts.map +1 -1
- package/dist/guard/is-record.mjs +48 -30
- package/dist/guard/is-record.mjs.map +1 -1
- package/dist/others/fast-deep-equal.d.mts.map +1 -1
- package/dist/others/fast-deep-equal.mjs +7 -5
- package/dist/others/fast-deep-equal.mjs.map +1 -1
- package/package.json +4 -4
- package/src/guard/is-non-null-object.mts +3 -3
- package/src/guard/is-record.mts +49 -30
- package/src/guard/is-record.test.mts +133 -61
- package/src/others/fast-deep-equal.mts +10 -8
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* - Excludes functions (they have `typeof` === `"function"`, not `"object"`)
|
|
14
14
|
* - Includes arrays, dates, regex, and other object instances
|
|
15
15
|
*
|
|
16
|
-
* **Note:** This function returns `true` for arrays. If you need
|
|
17
|
-
*
|
|
16
|
+
* **Note:** This function returns `true` for arrays. If you need
|
|
17
|
+
* record-oriented narrowing that excludes arrays, use `isRecord()` instead.
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
*
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @param u - The value to check
|
|
30
30
|
* @returns `true` if `u` is an object and not `null`, `false` otherwise. When
|
|
31
31
|
* `true`, TypeScript narrows the type to `object`.
|
|
32
|
-
* @see {@link isRecord} - For
|
|
32
|
+
* @see {@link isRecord} - For record-oriented type narrowing (excludes arrays)
|
|
33
33
|
*/
|
|
34
34
|
export declare const isNonNullObject: (u: unknown) => u is object;
|
|
35
35
|
//# sourceMappingURL=is-non-null-object.d.mts.map
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* - Excludes functions (they have `typeof` === `"function"`, not `"object"`)
|
|
14
14
|
* - Includes arrays, dates, regex, and other object instances
|
|
15
15
|
*
|
|
16
|
-
* **Note:** This function returns `true` for arrays. If you need
|
|
17
|
-
*
|
|
16
|
+
* **Note:** This function returns `true` for arrays. If you need
|
|
17
|
+
* record-oriented narrowing that excludes arrays, use `isRecord()` instead.
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
*
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @param u - The value to check
|
|
30
30
|
* @returns `true` if `u` is an object and not `null`, `false` otherwise. When
|
|
31
31
|
* `true`, TypeScript narrows the type to `object`.
|
|
32
|
-
* @see {@link isRecord} - For
|
|
32
|
+
* @see {@link isRecord} - For record-oriented type narrowing (excludes arrays)
|
|
33
33
|
*/
|
|
34
34
|
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
|
35
35
|
const isNonNullObject = (u) => typeof u === 'object' && u !== null;
|
|
@@ -1,30 +1,45 @@
|
|
|
1
1
|
import { type MutableRecord, type UnknownRecord } from 'ts-type-forge';
|
|
2
2
|
/**
|
|
3
|
-
* Type guard that checks if a value is a
|
|
4
|
-
* object
|
|
3
|
+
* Type guard that checks if a value is a record — a non-null, non-array
|
|
4
|
+
* object — and narrows it to `UnknownRecord` (= `ReadonlyRecord<string,
|
|
5
|
+
* unknown>`).
|
|
5
6
|
*
|
|
6
|
-
* This function is
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* types like Date, RegExp, etc., focusing on objects that can be safely treated
|
|
10
|
-
* as property collections.
|
|
7
|
+
* This function is designed to be combined with {@link hasKey} to probe
|
|
8
|
+
* values of type `unknown` (e.g. parsed JSON, caught errors, external data)
|
|
9
|
+
* in a type-safe way: `if (isRecord(u) && hasKey(u, 'some-key')) { ... }`.
|
|
11
10
|
*
|
|
12
11
|
* **Type Narrowing Behavior:**
|
|
13
12
|
*
|
|
14
|
-
* - Narrows `unknown` to `UnknownRecord` (
|
|
15
|
-
*
|
|
16
|
-
* - Excludes
|
|
17
|
-
*
|
|
18
|
-
* with
|
|
19
|
-
* - Returns `
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
13
|
+
* - Narrows `unknown` to `UnknownRecord` (= `ReadonlyRecord<string, unknown>`)
|
|
14
|
+
* - Excludes `null`, `undefined`, primitives, and functions
|
|
15
|
+
* - Excludes arrays: arrays have no string index signature, so they are not
|
|
16
|
+
* assignable to `UnknownRecord`; admitting them at runtime would be
|
|
17
|
+
* inconsistent with the narrowed type
|
|
18
|
+
* - Returns `true` for every other non-null object, including dictionary
|
|
19
|
+
* objects created with `Object.create(null)` and instances such as `Date`,
|
|
20
|
+
* `RegExp`, `Map`, `Set`, `Error`, and user-defined classes
|
|
21
|
+
*
|
|
22
|
+
* **Why non-plain objects are included:** since every property of
|
|
23
|
+
* `UnknownRecord` is typed `unknown`, reading properties from any object
|
|
24
|
+
* through this narrowing is type-safe, and `hasKey` (backed by
|
|
25
|
+
* `Object.hasOwn`) answers own-property questions correctly on any object
|
|
26
|
+
* (e.g. `Map` entries are not own properties, so `hasKey` correctly returns
|
|
27
|
+
* `false` for them). Restricting the guard to plain objects would require
|
|
28
|
+
* prototype checks that misjudge cross-realm objects and would prevent
|
|
29
|
+
* probing useful instances such as `Error` subclasses.
|
|
30
|
+
*
|
|
31
|
+
* **Implementation:** `isNonNullObject(u) && !Array.isArray(u)`.
|
|
23
32
|
*
|
|
24
33
|
* @example
|
|
25
34
|
*
|
|
26
35
|
* ```ts
|
|
27
|
-
* const entries: readonly unknown[] = [
|
|
36
|
+
* const entries: readonly unknown[] = [
|
|
37
|
+
* { id: 1 },
|
|
38
|
+
* [1, 2],
|
|
39
|
+
* 'str',
|
|
40
|
+
* 0,
|
|
41
|
+
* null,
|
|
42
|
+
* ] as const;
|
|
28
43
|
*
|
|
29
44
|
* const records = entries.filter(isRecord);
|
|
30
45
|
*
|
|
@@ -32,29 +47,31 @@ import { type MutableRecord, type UnknownRecord } from 'ts-type-forge';
|
|
|
32
47
|
* ```
|
|
33
48
|
*
|
|
34
49
|
* @param u - The value to check
|
|
35
|
-
* @returns `true` if `u` is a non-null object
|
|
36
|
-
*
|
|
37
|
-
* @see {@link isNonNullObject} - For checking any object
|
|
50
|
+
* @returns `true` if `u` is a non-null, non-array object, `false` otherwise.
|
|
51
|
+
* When `true`, TypeScript narrows the type to `UnknownRecord`.
|
|
52
|
+
* @see {@link isNonNullObject} - For checking any non-null object (includes arrays)
|
|
38
53
|
* @see {@link hasKey} - For checking if a record has specific keys
|
|
39
54
|
*/
|
|
40
55
|
export declare const isRecord: (u: unknown) => u is UnknownRecord;
|
|
41
56
|
/**
|
|
42
|
-
* Type guard that checks if a value is a mutable record
|
|
57
|
+
* Type guard that checks if a value is a mutable record.
|
|
43
58
|
*
|
|
44
|
-
* This function
|
|
45
|
-
*
|
|
46
|
-
*
|
|
59
|
+
* This function has the same runtime behavior as {@link isRecord} (non-null,
|
|
60
|
+
* non-array object check), but narrows the type to `MutableRecord<string,
|
|
61
|
+
* unknown>`, which is useful when you need to add or overwrite properties on
|
|
62
|
+
* the narrowed object.
|
|
47
63
|
*
|
|
48
64
|
* **Type Narrowing Behavior:**
|
|
49
65
|
*
|
|
50
66
|
* - Narrows `unknown` to `MutableRecord<string, unknown>`
|
|
51
|
-
* - Excludes `null`, `undefined`, primitives,
|
|
52
|
-
* - Returns `true` for
|
|
53
|
-
* -
|
|
67
|
+
* - Excludes `null`, `undefined`, primitives, functions, and arrays
|
|
68
|
+
* - Returns `true` for every other non-null object (see {@link isRecord} for
|
|
69
|
+
* details on non-plain objects such as `Date`, `Map`, and `Set`)
|
|
54
70
|
*
|
|
55
71
|
* **Implementation:** This is a type alias for {@link isRecord}.
|
|
56
72
|
*
|
|
57
73
|
* @example
|
|
74
|
+
*
|
|
58
75
|
* ```ts
|
|
59
76
|
* const obj: unknown = { foo: 1 } as const;
|
|
60
77
|
*
|
|
@@ -64,8 +81,9 @@ export declare const isRecord: (u: unknown) => u is UnknownRecord;
|
|
|
64
81
|
* ```
|
|
65
82
|
*
|
|
66
83
|
* @param u - The value to check
|
|
67
|
-
* @returns `true` if `u` is a
|
|
68
|
-
* When `true`, TypeScript narrows the type to `MutableRecord<string,
|
|
84
|
+
* @returns `true` if `u` is a non-null, non-array object, `false` otherwise.
|
|
85
|
+
* When `true`, TypeScript narrows the type to `MutableRecord<string,
|
|
86
|
+
* unknown>`.
|
|
69
87
|
* @see {@link isRecord} - For the underlying implementation and more details
|
|
70
88
|
*/
|
|
71
89
|
export declare const isMutableRecord: (u: unknown) => u is MutableRecord<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-record.d.mts","sourceRoot":"","sources":["../../src/guard/is-record.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAGvE
|
|
1
|
+
{"version":3,"file":"is-record.d.mts","sourceRoot":"","sources":["../../src/guard/is-record.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAGvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,eAAO,MAAM,QAAQ,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,aACF,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,eAAe,EAAE,CAC5B,CAAC,EAAE,OAAO,KACP,CAAC,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,CAAY,CAAC"}
|
package/dist/guard/is-record.mjs
CHANGED
|
@@ -1,31 +1,46 @@
|
|
|
1
1
|
import { isNonNullObject } from './is-non-null-object.mjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Type guard that checks if a value is a
|
|
5
|
-
* object
|
|
4
|
+
* Type guard that checks if a value is a record — a non-null, non-array
|
|
5
|
+
* object — and narrows it to `UnknownRecord` (= `ReadonlyRecord<string,
|
|
6
|
+
* unknown>`).
|
|
6
7
|
*
|
|
7
|
-
* This function is
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* types like Date, RegExp, etc., focusing on objects that can be safely treated
|
|
11
|
-
* as property collections.
|
|
8
|
+
* This function is designed to be combined with {@link hasKey} to probe
|
|
9
|
+
* values of type `unknown` (e.g. parsed JSON, caught errors, external data)
|
|
10
|
+
* in a type-safe way: `if (isRecord(u) && hasKey(u, 'some-key')) { ... }`.
|
|
12
11
|
*
|
|
13
12
|
* **Type Narrowing Behavior:**
|
|
14
13
|
*
|
|
15
|
-
* - Narrows `unknown` to `UnknownRecord` (
|
|
16
|
-
*
|
|
17
|
-
* - Excludes
|
|
18
|
-
*
|
|
19
|
-
* with
|
|
20
|
-
* - Returns `
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
14
|
+
* - Narrows `unknown` to `UnknownRecord` (= `ReadonlyRecord<string, unknown>`)
|
|
15
|
+
* - Excludes `null`, `undefined`, primitives, and functions
|
|
16
|
+
* - Excludes arrays: arrays have no string index signature, so they are not
|
|
17
|
+
* assignable to `UnknownRecord`; admitting them at runtime would be
|
|
18
|
+
* inconsistent with the narrowed type
|
|
19
|
+
* - Returns `true` for every other non-null object, including dictionary
|
|
20
|
+
* objects created with `Object.create(null)` and instances such as `Date`,
|
|
21
|
+
* `RegExp`, `Map`, `Set`, `Error`, and user-defined classes
|
|
22
|
+
*
|
|
23
|
+
* **Why non-plain objects are included:** since every property of
|
|
24
|
+
* `UnknownRecord` is typed `unknown`, reading properties from any object
|
|
25
|
+
* through this narrowing is type-safe, and `hasKey` (backed by
|
|
26
|
+
* `Object.hasOwn`) answers own-property questions correctly on any object
|
|
27
|
+
* (e.g. `Map` entries are not own properties, so `hasKey` correctly returns
|
|
28
|
+
* `false` for them). Restricting the guard to plain objects would require
|
|
29
|
+
* prototype checks that misjudge cross-realm objects and would prevent
|
|
30
|
+
* probing useful instances such as `Error` subclasses.
|
|
31
|
+
*
|
|
32
|
+
* **Implementation:** `isNonNullObject(u) && !Array.isArray(u)`.
|
|
24
33
|
*
|
|
25
34
|
* @example
|
|
26
35
|
*
|
|
27
36
|
* ```ts
|
|
28
|
-
* const entries: readonly unknown[] = [
|
|
37
|
+
* const entries: readonly unknown[] = [
|
|
38
|
+
* { id: 1 },
|
|
39
|
+
* [1, 2],
|
|
40
|
+
* 'str',
|
|
41
|
+
* 0,
|
|
42
|
+
* null,
|
|
43
|
+
* ] as const;
|
|
29
44
|
*
|
|
30
45
|
* const records = entries.filter(isRecord);
|
|
31
46
|
*
|
|
@@ -33,29 +48,31 @@ import { isNonNullObject } from './is-non-null-object.mjs';
|
|
|
33
48
|
* ```
|
|
34
49
|
*
|
|
35
50
|
* @param u - The value to check
|
|
36
|
-
* @returns `true` if `u` is a non-null object
|
|
37
|
-
*
|
|
38
|
-
* @see {@link isNonNullObject} - For checking any object
|
|
51
|
+
* @returns `true` if `u` is a non-null, non-array object, `false` otherwise.
|
|
52
|
+
* When `true`, TypeScript narrows the type to `UnknownRecord`.
|
|
53
|
+
* @see {@link isNonNullObject} - For checking any non-null object (includes arrays)
|
|
39
54
|
* @see {@link hasKey} - For checking if a record has specific keys
|
|
40
55
|
*/
|
|
41
|
-
const isRecord = (u) => isNonNullObject(u);
|
|
56
|
+
const isRecord = (u) => isNonNullObject(u) && !Array.isArray(u);
|
|
42
57
|
/**
|
|
43
|
-
* Type guard that checks if a value is a mutable record
|
|
58
|
+
* Type guard that checks if a value is a mutable record.
|
|
44
59
|
*
|
|
45
|
-
* This function
|
|
46
|
-
*
|
|
47
|
-
*
|
|
60
|
+
* This function has the same runtime behavior as {@link isRecord} (non-null,
|
|
61
|
+
* non-array object check), but narrows the type to `MutableRecord<string,
|
|
62
|
+
* unknown>`, which is useful when you need to add or overwrite properties on
|
|
63
|
+
* the narrowed object.
|
|
48
64
|
*
|
|
49
65
|
* **Type Narrowing Behavior:**
|
|
50
66
|
*
|
|
51
67
|
* - Narrows `unknown` to `MutableRecord<string, unknown>`
|
|
52
|
-
* - Excludes `null`, `undefined`, primitives,
|
|
53
|
-
* - Returns `true` for
|
|
54
|
-
* -
|
|
68
|
+
* - Excludes `null`, `undefined`, primitives, functions, and arrays
|
|
69
|
+
* - Returns `true` for every other non-null object (see {@link isRecord} for
|
|
70
|
+
* details on non-plain objects such as `Date`, `Map`, and `Set`)
|
|
55
71
|
*
|
|
56
72
|
* **Implementation:** This is a type alias for {@link isRecord}.
|
|
57
73
|
*
|
|
58
74
|
* @example
|
|
75
|
+
*
|
|
59
76
|
* ```ts
|
|
60
77
|
* const obj: unknown = { foo: 1 } as const;
|
|
61
78
|
*
|
|
@@ -65,8 +82,9 @@ const isRecord = (u) => isNonNullObject(u);
|
|
|
65
82
|
* ```
|
|
66
83
|
*
|
|
67
84
|
* @param u - The value to check
|
|
68
|
-
* @returns `true` if `u` is a
|
|
69
|
-
* When `true`, TypeScript narrows the type to `MutableRecord<string,
|
|
85
|
+
* @returns `true` if `u` is a non-null, non-array object, `false` otherwise.
|
|
86
|
+
* When `true`, TypeScript narrows the type to `MutableRecord<string,
|
|
87
|
+
* unknown>`.
|
|
70
88
|
* @see {@link isRecord} - For the underlying implementation and more details
|
|
71
89
|
*/
|
|
72
90
|
const isMutableRecord = isRecord;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-record.mjs","sources":["../../src/guard/is-record.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA
|
|
1
|
+
{"version":3,"file":"is-record.mjs","sources":["../../src/guard/is-record.mts"],"sourcesContent":[null],"names":[],"mappings":";;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDG;MACU,QAAQ,GAAG,CAAC,CAAU,KACjC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCG;AACI,MAAM,eAAe,GAEe;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fast-deep-equal.d.mts","sourceRoot":"","sources":["../../src/others/fast-deep-equal.mts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAG,GAAG,CAAC,EAAE,GAAG,CAAC,KAAG,
|
|
1
|
+
{"version":3,"file":"fast-deep-equal.d.mts","sourceRoot":"","sources":["../../src/others/fast-deep-equal.mts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAG,GAAG,CAAC,EAAE,GAAG,CAAC,KAAG,OAuF9C,CAAC"}
|
|
@@ -15,6 +15,13 @@ const fastDeepEqual = (a, b) => {
|
|
|
15
15
|
if (typeof a !== typeof b) {
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
|
+
// Arrays are handled before `isRecord`, which excludes them.
|
|
19
|
+
if (Array.isArray(a)) {
|
|
20
|
+
return (Array.isArray(b) &&
|
|
21
|
+
a.constructor === b.constructor &&
|
|
22
|
+
a.length === b.length &&
|
|
23
|
+
a.every((ai, index) => fastDeepEqual(ai, b[index])));
|
|
24
|
+
}
|
|
18
25
|
if (isRecord(a)) {
|
|
19
26
|
if (!isRecord(b)) {
|
|
20
27
|
return false;
|
|
@@ -22,11 +29,6 @@ const fastDeepEqual = (a, b) => {
|
|
|
22
29
|
if (a.constructor !== b.constructor) {
|
|
23
30
|
return false;
|
|
24
31
|
}
|
|
25
|
-
if (Array.isArray(a)) {
|
|
26
|
-
return (Array.isArray(b) &&
|
|
27
|
-
a.length === b.length &&
|
|
28
|
-
a.every((ai, index) => fastDeepEqual(ai, b[index])));
|
|
29
|
-
}
|
|
30
32
|
if (isMap(a)) {
|
|
31
33
|
return (isMap(b) &&
|
|
32
34
|
a.size === b.size &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fast-deep-equal.mjs","sources":["../../src/others/fast-deep-equal.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;AAGA;;;;;AAKG;MACU,aAAa,GAAG,CAAK,CAAI,EAAE,CAAI,KAAa;AACvD,IAAA,IAAI,CAAC,KAAK,CAAC,EAAE;AACX,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,IAAI,OAAO,CAAC,KAAK,OAAO,CAAC,EAAE;AACzB,QAAA,OAAO,KAAK;IACd
|
|
1
|
+
{"version":3,"file":"fast-deep-equal.mjs","sources":["../../src/others/fast-deep-equal.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-data-forge",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"devDependencies": {
|
|
92
92
|
"@emotion/react": "11.14.0",
|
|
93
93
|
"@emotion/styled": "11.14.1",
|
|
94
|
-
"@mui/material": "9.1.
|
|
94
|
+
"@mui/material": "9.1.2",
|
|
95
95
|
"@rollup/plugin-replace": "6.0.3",
|
|
96
96
|
"@rollup/plugin-strip": "3.0.4",
|
|
97
97
|
"@rollup/plugin-typescript": "12.3.0",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"@vitest/browser-playwright": "4.1.9",
|
|
108
108
|
"@vitest/coverage-v8": "4.1.9",
|
|
109
109
|
"@vitest/ui": "4.1.9",
|
|
110
|
-
"conventional-changelog-conventionalcommits": "
|
|
110
|
+
"conventional-changelog-conventionalcommits": "10.2.0",
|
|
111
111
|
"cspell": "10.0.1",
|
|
112
112
|
"dedent": "1.7.2",
|
|
113
113
|
"eslint": "9.39.4",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"typedoc": "0.28.19",
|
|
133
133
|
"typedoc-github-theme": "0.4.0",
|
|
134
134
|
"typescript": "5.9.3",
|
|
135
|
-
"vite": "8.0
|
|
135
|
+
"vite": "8.1.0",
|
|
136
136
|
"vitest": "4.1.9"
|
|
137
137
|
},
|
|
138
138
|
"peerDependencies": {
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* - Excludes functions (they have `typeof` === `"function"`, not `"object"`)
|
|
14
14
|
* - Includes arrays, dates, regex, and other object instances
|
|
15
15
|
*
|
|
16
|
-
* **Note:** This function returns `true` for arrays. If you need
|
|
17
|
-
*
|
|
16
|
+
* **Note:** This function returns `true` for arrays. If you need
|
|
17
|
+
* record-oriented narrowing that excludes arrays, use `isRecord()` instead.
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
*
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @param u - The value to check
|
|
30
30
|
* @returns `true` if `u` is an object and not `null`, `false` otherwise. When
|
|
31
31
|
* `true`, TypeScript narrows the type to `object`.
|
|
32
|
-
* @see {@link isRecord} - For
|
|
32
|
+
* @see {@link isRecord} - For record-oriented type narrowing (excludes arrays)
|
|
33
33
|
*/
|
|
34
34
|
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
|
35
35
|
export const isNonNullObject = (u: unknown): u is object =>
|
package/src/guard/is-record.mts
CHANGED
|
@@ -2,31 +2,46 @@ import { type MutableRecord, type UnknownRecord } from 'ts-type-forge';
|
|
|
2
2
|
import { isNonNullObject } from './is-non-null-object.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Type guard that checks if a value is a
|
|
6
|
-
* object
|
|
5
|
+
* Type guard that checks if a value is a record — a non-null, non-array
|
|
6
|
+
* object — and narrows it to `UnknownRecord` (= `ReadonlyRecord<string,
|
|
7
|
+
* unknown>`).
|
|
7
8
|
*
|
|
8
|
-
* This function is
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* types like Date, RegExp, etc., focusing on objects that can be safely treated
|
|
12
|
-
* as property collections.
|
|
9
|
+
* This function is designed to be combined with {@link hasKey} to probe
|
|
10
|
+
* values of type `unknown` (e.g. parsed JSON, caught errors, external data)
|
|
11
|
+
* in a type-safe way: `if (isRecord(u) && hasKey(u, 'some-key')) { ... }`.
|
|
13
12
|
*
|
|
14
13
|
* **Type Narrowing Behavior:**
|
|
15
14
|
*
|
|
16
|
-
* - Narrows `unknown` to `UnknownRecord` (
|
|
17
|
-
*
|
|
18
|
-
* - Excludes
|
|
19
|
-
*
|
|
20
|
-
* with
|
|
21
|
-
* - Returns `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
15
|
+
* - Narrows `unknown` to `UnknownRecord` (= `ReadonlyRecord<string, unknown>`)
|
|
16
|
+
* - Excludes `null`, `undefined`, primitives, and functions
|
|
17
|
+
* - Excludes arrays: arrays have no string index signature, so they are not
|
|
18
|
+
* assignable to `UnknownRecord`; admitting them at runtime would be
|
|
19
|
+
* inconsistent with the narrowed type
|
|
20
|
+
* - Returns `true` for every other non-null object, including dictionary
|
|
21
|
+
* objects created with `Object.create(null)` and instances such as `Date`,
|
|
22
|
+
* `RegExp`, `Map`, `Set`, `Error`, and user-defined classes
|
|
23
|
+
*
|
|
24
|
+
* **Why non-plain objects are included:** since every property of
|
|
25
|
+
* `UnknownRecord` is typed `unknown`, reading properties from any object
|
|
26
|
+
* through this narrowing is type-safe, and `hasKey` (backed by
|
|
27
|
+
* `Object.hasOwn`) answers own-property questions correctly on any object
|
|
28
|
+
* (e.g. `Map` entries are not own properties, so `hasKey` correctly returns
|
|
29
|
+
* `false` for them). Restricting the guard to plain objects would require
|
|
30
|
+
* prototype checks that misjudge cross-realm objects and would prevent
|
|
31
|
+
* probing useful instances such as `Error` subclasses.
|
|
32
|
+
*
|
|
33
|
+
* **Implementation:** `isNonNullObject(u) && !Array.isArray(u)`.
|
|
25
34
|
*
|
|
26
35
|
* @example
|
|
27
36
|
*
|
|
28
37
|
* ```ts
|
|
29
|
-
* const entries: readonly unknown[] = [
|
|
38
|
+
* const entries: readonly unknown[] = [
|
|
39
|
+
* { id: 1 },
|
|
40
|
+
* [1, 2],
|
|
41
|
+
* 'str',
|
|
42
|
+
* 0,
|
|
43
|
+
* null,
|
|
44
|
+
* ] as const;
|
|
30
45
|
*
|
|
31
46
|
* const records = entries.filter(isRecord);
|
|
32
47
|
*
|
|
@@ -34,30 +49,33 @@ import { isNonNullObject } from './is-non-null-object.mjs';
|
|
|
34
49
|
* ```
|
|
35
50
|
*
|
|
36
51
|
* @param u - The value to check
|
|
37
|
-
* @returns `true` if `u` is a non-null object
|
|
38
|
-
*
|
|
39
|
-
* @see {@link isNonNullObject} - For checking any object
|
|
52
|
+
* @returns `true` if `u` is a non-null, non-array object, `false` otherwise.
|
|
53
|
+
* When `true`, TypeScript narrows the type to `UnknownRecord`.
|
|
54
|
+
* @see {@link isNonNullObject} - For checking any non-null object (includes arrays)
|
|
40
55
|
* @see {@link hasKey} - For checking if a record has specific keys
|
|
41
56
|
*/
|
|
42
|
-
export const isRecord = (u: unknown): u is UnknownRecord =>
|
|
57
|
+
export const isRecord = (u: unknown): u is UnknownRecord =>
|
|
58
|
+
isNonNullObject(u) && !Array.isArray(u);
|
|
43
59
|
|
|
44
60
|
/**
|
|
45
|
-
* Type guard that checks if a value is a mutable record
|
|
61
|
+
* Type guard that checks if a value is a mutable record.
|
|
46
62
|
*
|
|
47
|
-
* This function
|
|
48
|
-
*
|
|
49
|
-
*
|
|
63
|
+
* This function has the same runtime behavior as {@link isRecord} (non-null,
|
|
64
|
+
* non-array object check), but narrows the type to `MutableRecord<string,
|
|
65
|
+
* unknown>`, which is useful when you need to add or overwrite properties on
|
|
66
|
+
* the narrowed object.
|
|
50
67
|
*
|
|
51
68
|
* **Type Narrowing Behavior:**
|
|
52
69
|
*
|
|
53
70
|
* - Narrows `unknown` to `MutableRecord<string, unknown>`
|
|
54
|
-
* - Excludes `null`, `undefined`, primitives,
|
|
55
|
-
* - Returns `true` for
|
|
56
|
-
* -
|
|
71
|
+
* - Excludes `null`, `undefined`, primitives, functions, and arrays
|
|
72
|
+
* - Returns `true` for every other non-null object (see {@link isRecord} for
|
|
73
|
+
* details on non-plain objects such as `Date`, `Map`, and `Set`)
|
|
57
74
|
*
|
|
58
75
|
* **Implementation:** This is a type alias for {@link isRecord}.
|
|
59
76
|
*
|
|
60
77
|
* @example
|
|
78
|
+
*
|
|
61
79
|
* ```ts
|
|
62
80
|
* const obj: unknown = { foo: 1 } as const;
|
|
63
81
|
*
|
|
@@ -67,8 +85,9 @@ export const isRecord = (u: unknown): u is UnknownRecord => isNonNullObject(u);
|
|
|
67
85
|
* ```
|
|
68
86
|
*
|
|
69
87
|
* @param u - The value to check
|
|
70
|
-
* @returns `true` if `u` is a
|
|
71
|
-
* When `true`, TypeScript narrows the type to `MutableRecord<string,
|
|
88
|
+
* @returns `true` if `u` is a non-null, non-array object, `false` otherwise.
|
|
89
|
+
* When `true`, TypeScript narrows the type to `MutableRecord<string,
|
|
90
|
+
* unknown>`.
|
|
72
91
|
* @see {@link isRecord} - For the underlying implementation and more details
|
|
73
92
|
*/
|
|
74
93
|
export const isMutableRecord: (
|
|
@@ -1,123 +1,195 @@
|
|
|
1
1
|
import { type UnknownRecord } from 'ts-type-forge';
|
|
2
2
|
import { expectType } from '../expect-type.mjs';
|
|
3
|
+
import { hasKey } from './has-key.mjs';
|
|
3
4
|
import { isRecord } from './is-record.mjs';
|
|
4
5
|
|
|
5
6
|
describe(isRecord, () => {
|
|
6
|
-
describe('
|
|
7
|
-
test('
|
|
8
|
-
const
|
|
7
|
+
describe('plain objects are records', () => {
|
|
8
|
+
test('{ x: 1 } is a record', () => {
|
|
9
|
+
const unk: unknown = { x: 1 } as const;
|
|
10
|
+
|
|
11
|
+
if (isRecord(unk)) {
|
|
12
|
+
expectType<typeof unk, UnknownRecord>('=');
|
|
13
|
+
} else {
|
|
14
|
+
assert.fail();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
test('{} is a record', () => {
|
|
19
|
+
const unk: unknown = {} as const;
|
|
11
20
|
|
|
12
21
|
assert.isTrue(isRecord(unk));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('Object.create(null) is a record', () => {
|
|
25
|
+
const unk: unknown = Object.create(null);
|
|
13
26
|
|
|
14
|
-
|
|
27
|
+
assert.isTrue(isRecord(unk));
|
|
15
28
|
});
|
|
29
|
+
});
|
|
16
30
|
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
describe('arrays are not records', () => {
|
|
32
|
+
test('type-level: arrays are not assignable to UnknownRecord', () => {
|
|
33
|
+
// Arrays have a number index signature but no string index signature,
|
|
34
|
+
// so they are assignable to `Record<number, unknown>` but not to
|
|
35
|
+
// `UnknownRecord` (= `ReadonlyRecord<string, unknown>`).
|
|
36
|
+
expectType<readonly number[], Record<number, unknown>>('<=');
|
|
19
37
|
|
|
20
|
-
|
|
38
|
+
expectType<readonly number[], UnknownRecord>('!<=');
|
|
21
39
|
|
|
22
|
-
|
|
40
|
+
expectType<readonly string[], Record<number, unknown>>('<=');
|
|
41
|
+
|
|
42
|
+
expectType<readonly string[], UnknownRecord>('!<=');
|
|
23
43
|
|
|
24
|
-
expectType<
|
|
44
|
+
expectType<readonly [], UnknownRecord>('!<=');
|
|
25
45
|
});
|
|
26
46
|
|
|
27
|
-
test('[] is a record', () => {
|
|
28
|
-
const
|
|
47
|
+
test('[1, 2, 3] is not a record', () => {
|
|
48
|
+
const unk: unknown = [1, 2, 3] as const;
|
|
29
49
|
|
|
30
|
-
|
|
50
|
+
assert.isFalse(isRecord(unk));
|
|
51
|
+
});
|
|
31
52
|
|
|
32
|
-
|
|
53
|
+
test('["1", "2", "3"] is not a record', () => {
|
|
54
|
+
const unk: unknown = ['1', '2', '3'] as const;
|
|
33
55
|
|
|
34
|
-
|
|
56
|
+
assert.isFalse(isRecord(unk));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('[] is not a record', () => {
|
|
60
|
+
const unk: unknown = [] as const;
|
|
61
|
+
|
|
62
|
+
assert.isFalse(isRecord(unk));
|
|
35
63
|
});
|
|
36
64
|
});
|
|
37
65
|
|
|
38
|
-
describe('non-null objects are
|
|
39
|
-
|
|
40
|
-
|
|
66
|
+
describe('other non-null objects are records', () => {
|
|
67
|
+
// These are not statically assignable to `UnknownRecord` either, but the
|
|
68
|
+
// guard intentionally admits them: every property read through
|
|
69
|
+
// `UnknownRecord` is typed `unknown`, so the narrowing is type-safe, and
|
|
70
|
+
// it keeps `isRecord(u) && hasKey(u, k)` usable for probing instances
|
|
71
|
+
// such as caught `Error` objects.
|
|
72
|
+
test('Date is a record', () => {
|
|
73
|
+
expectType<Date, UnknownRecord>('!<=');
|
|
74
|
+
|
|
75
|
+
const unk: unknown = new Date('2020-01-01');
|
|
76
|
+
|
|
77
|
+
assert.isTrue(isRecord(unk));
|
|
78
|
+
});
|
|
41
79
|
|
|
42
|
-
|
|
80
|
+
test('RegExp is a record', () => {
|
|
81
|
+
const unk: unknown = /test/u;
|
|
43
82
|
|
|
44
83
|
assert.isTrue(isRecord(unk));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('Map is a record (but its entries are not own properties)', () => {
|
|
87
|
+
expectType<ReadonlyMap<string, number>, UnknownRecord>('!<=');
|
|
88
|
+
|
|
89
|
+
const unk: unknown = new Map([['a', 1]]);
|
|
45
90
|
|
|
46
|
-
|
|
91
|
+
if (isRecord(unk)) {
|
|
92
|
+
// Map entries are not own properties, so hasKey correctly rejects them.
|
|
93
|
+
assert.isFalse(hasKey(unk, 'a'));
|
|
94
|
+
} else {
|
|
95
|
+
assert.fail();
|
|
96
|
+
}
|
|
47
97
|
});
|
|
48
98
|
|
|
49
|
-
test('
|
|
50
|
-
|
|
99
|
+
test('Set is a record', () => {
|
|
100
|
+
expectType<ReadonlySet<number>, UnknownRecord>('!<=');
|
|
101
|
+
|
|
102
|
+
const unk: unknown = new Set([1, 2, 3]);
|
|
103
|
+
|
|
104
|
+
assert.isTrue(isRecord(unk));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('Error is a record and its own properties can be probed', () => {
|
|
108
|
+
const unk: unknown = Object.assign(new Error('failure'), {
|
|
109
|
+
code: 'ENOENT',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (isRecord(unk) && hasKey(unk, 'code')) {
|
|
113
|
+
expectType<(typeof unk)['code'], unknown>('=');
|
|
51
114
|
|
|
52
|
-
|
|
115
|
+
assert.strictEqual(unk.code, 'ENOENT');
|
|
116
|
+
} else {
|
|
117
|
+
assert.fail();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('class instance is a record', () => {
|
|
122
|
+
class Foo {
|
|
123
|
+
readonly x = 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const unk: unknown = new Foo();
|
|
53
127
|
|
|
54
128
|
assert.isTrue(isRecord(unk));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('functions are not records', () => {
|
|
133
|
+
test('arrow function is not a record', () => {
|
|
134
|
+
const unk: unknown = () => 0;
|
|
55
135
|
|
|
56
|
-
|
|
136
|
+
assert.isFalse(isRecord(unk));
|
|
57
137
|
});
|
|
58
138
|
});
|
|
59
139
|
|
|
60
|
-
describe('primitives are not
|
|
140
|
+
describe('primitives are not records', () => {
|
|
61
141
|
test('null is not a record', () => {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
const unk: unknown = obj;
|
|
142
|
+
const unk: unknown = null;
|
|
65
143
|
|
|
66
144
|
assert.isFalse(isRecord(unk));
|
|
67
|
-
|
|
68
|
-
expectType<typeof unk, UnknownRecord>('!<=');
|
|
69
145
|
});
|
|
70
146
|
|
|
71
147
|
test('undefined is not a record', () => {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
const unk: unknown = prm;
|
|
148
|
+
const unk: unknown = undefined;
|
|
75
149
|
|
|
76
150
|
assert.isFalse(isRecord(unk));
|
|
77
|
-
|
|
78
|
-
expectType<typeof unk, UnknownRecord>('!<=');
|
|
79
151
|
});
|
|
80
152
|
|
|
81
153
|
test('3 is not a record', () => {
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
const unk: unknown = prm;
|
|
154
|
+
const unk: unknown = 3;
|
|
85
155
|
|
|
86
156
|
assert.isFalse(isRecord(unk));
|
|
87
|
-
|
|
88
|
-
expectType<typeof unk, UnknownRecord>('!<=');
|
|
89
157
|
});
|
|
90
158
|
|
|
91
159
|
test('"str" is not a record', () => {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const unk: unknown = prm;
|
|
160
|
+
const unk: unknown = 'str';
|
|
95
161
|
|
|
96
162
|
assert.isFalse(isRecord(unk));
|
|
163
|
+
});
|
|
97
164
|
|
|
98
|
-
|
|
165
|
+
test('true is not a record', () => {
|
|
166
|
+
const unk: unknown = true;
|
|
167
|
+
|
|
168
|
+
assert.isFalse(isRecord(unk));
|
|
99
169
|
});
|
|
100
|
-
});
|
|
101
170
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// const unk: unknown = obj;
|
|
105
|
-
// const res = isRecord(unk);
|
|
171
|
+
test('symbol is not a record', () => {
|
|
172
|
+
const unk: unknown = Symbol('sym');
|
|
106
173
|
|
|
107
|
-
|
|
108
|
-
|
|
174
|
+
assert.isFalse(isRecord(unk));
|
|
175
|
+
});
|
|
109
176
|
|
|
110
|
-
|
|
111
|
-
|
|
177
|
+
test('bigint is not a record', () => {
|
|
178
|
+
const unk: unknown = 1n;
|
|
112
179
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
180
|
+
assert.isFalse(isRecord(unk));
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('combined with hasKey', () => {
|
|
185
|
+
const unk: unknown = { 'some-key': 42 } as const;
|
|
117
186
|
|
|
118
|
-
|
|
119
|
-
|
|
187
|
+
if (isRecord(unk) && hasKey(unk, 'some-key')) {
|
|
188
|
+
expectType<(typeof unk)['some-key'], unknown>('=');
|
|
120
189
|
|
|
121
|
-
|
|
122
|
-
|
|
190
|
+
assert.strictEqual(unk['some-key'], 42);
|
|
191
|
+
} else {
|
|
192
|
+
assert.fail();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
123
195
|
});
|
|
@@ -16,6 +16,16 @@ export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
|
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Arrays are handled before `isRecord`, which excludes them.
|
|
20
|
+
if (Array.isArray(a)) {
|
|
21
|
+
return (
|
|
22
|
+
Array.isArray(b) &&
|
|
23
|
+
a.constructor === b.constructor &&
|
|
24
|
+
a.length === b.length &&
|
|
25
|
+
a.every((ai, index) => fastDeepEqual(ai, b[index]))
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
if (isRecord(a)) {
|
|
20
30
|
if (!isRecord(b)) {
|
|
21
31
|
return false;
|
|
@@ -25,14 +35,6 @@ export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
|
|
|
25
35
|
return false;
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
if (Array.isArray(a)) {
|
|
29
|
-
return (
|
|
30
|
-
Array.isArray(b) &&
|
|
31
|
-
a.length === b.length &&
|
|
32
|
-
a.every((ai, index) => fastDeepEqual(ai, b[index]))
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
38
|
if (isMap(a)) {
|
|
37
39
|
return (
|
|
38
40
|
isMap(b) &&
|