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.
@@ -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 to check for
17
- * plain objects specifically (excluding arrays), use `isRecord()` instead.
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 checking plain objects specifically (excludes arrays)
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 to check for
17
- * plain objects specifically (excluding arrays), use `isRecord()` instead.
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 checking plain objects specifically (excludes arrays)
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 plain object (record) - a non-null
4
- * object that is not an array.
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 useful for identifying "plain" JavaScript objects (also
7
- * called records or dictionaries) - objects that are typically used as
8
- * key-value collections. It excludes arrays, functions, and special object
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` (equivalent to `Record<PropertyKey,
15
- * unknown>`)
16
- * - Excludes `null`, `undefined`, primitives, arrays, and functions
17
- * - Returns `true` for plain objects `{}`, object literals, and objects created
18
- * with `Object.create()`
19
- * - Returns `false` for arrays, even though they are technically objects
20
- *
21
- * **Implementation:** Uses `isNonNullObject()` to check for objects, then
22
- * `Array.isArray()` to exclude arrays.
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[] = [{ id: 1 }, 'str', 0, null] as const;
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 and not an array, `false`
36
- * otherwise. When `true`, TypeScript narrows the type to `UnknownRecord`.
37
- * @see {@link isNonNullObject} - For checking any object type (includes arrays)
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 (an object with mutable string keys).
57
+ * Type guard that checks if a value is a mutable record.
43
58
  *
44
- * This function is an alias for {@link isRecord}, but narrows the type to
45
- * `MutableRecord<string, unknown>`, which is useful when you need to ensure
46
- * that the object can be safely mutated (i.e., its properties can be added or changed).
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, arrays, and functions
52
- * - Returns `true` for plain objects with mutable string keys
53
- * - Returns `false` for arrays and other non-record types
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 mutable record (plain object with string keys), `false` otherwise.
68
- * When `true`, TypeScript narrows the type to `MutableRecord<string, unknown>`.
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,eAAO,MAAM,QAAQ,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,aAAmC,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,eAAe,EAAE,CAC5B,CAAC,EAAE,OAAO,KACP,CAAC,IAAI,aAAa,CAAC,MAAM,EAAE,OAAO,CAAY,CAAC"}
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"}
@@ -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 plain object (record) - a non-null
5
- * object that is not an array.
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 useful for identifying "plain" JavaScript objects (also
8
- * called records or dictionaries) - objects that are typically used as
9
- * key-value collections. It excludes arrays, functions, and special object
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` (equivalent to `Record<PropertyKey,
16
- * unknown>`)
17
- * - Excludes `null`, `undefined`, primitives, arrays, and functions
18
- * - Returns `true` for plain objects `{}`, object literals, and objects created
19
- * with `Object.create()`
20
- * - Returns `false` for arrays, even though they are technically objects
21
- *
22
- * **Implementation:** Uses `isNonNullObject()` to check for objects, then
23
- * `Array.isArray()` to exclude arrays.
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[] = [{ id: 1 }, 'str', 0, null] as const;
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 and not an array, `false`
37
- * otherwise. When `true`, TypeScript narrows the type to `UnknownRecord`.
38
- * @see {@link isNonNullObject} - For checking any object type (includes arrays)
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 (an object with mutable string keys).
58
+ * Type guard that checks if a value is a mutable record.
44
59
  *
45
- * This function is an alias for {@link isRecord}, but narrows the type to
46
- * `MutableRecord<string, unknown>`, which is useful when you need to ensure
47
- * that the object can be safely mutated (i.e., its properties can be added or changed).
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, arrays, and functions
53
- * - Returns `true` for plain objects with mutable string keys
54
- * - Returns `false` for arrays and other non-record types
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 mutable record (plain object with string keys), `false` otherwise.
69
- * When `true`, TypeScript narrows the type to `MutableRecord<string, unknown>`.
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCG;AACI,MAAM,QAAQ,GAAG,CAAC,CAAU,KAAyB,eAAe,CAAC,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BG;AACI,MAAM,eAAe,GAEe;;;;"}
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,OAqF9C,CAAC"}
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;AAEA,IAAA,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE;AACf,QAAA,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;AAChB,YAAA,OAAO,KAAK;QACd;QAEA,IAAI,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,WAAW,EAAE;AACnC,YAAA,OAAO,KAAK;QACd;AAEA,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;AACpB,YAAA,QACE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAChB,gBAAA,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;gBACrB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,KAAK,KAAK,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAEvD;AAEA,QAAA,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE;AACZ,YAAA,QACE,KAAK,CAAC,CAAC,CAAC;AACR,gBAAA,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;AACjB,gBAAA,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAC3B,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CACjE;QAEL;AAEA,QAAA,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE;AACZ,YAAA,QACE,KAAK,CAAC,CAAC,CAAC;AACR,gBAAA,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;gBACjB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAE5D;AAEA,QAAA,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE;AACnB,YAAA,QACE,YAAY,CAAC,CAAC,CAAC;AACf,gBAAA,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;AACrB,gBAAA,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;QAEjD;AAEA,QAAA,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE;YACf,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QACpE;QAEA,IAAI,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE;YAC1C,OAAO,CAAC,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE;QACpC;QAEA,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE;YAC5C,OAAO,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE;QACtC;QAEA,MAAM,KAAK,GAAsB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAE/C,MAAM,KAAK,GAAsB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAE/C,QAAA,QACE,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;AAC7B,YAAA,KAAK,CAAC,KAAK,CACT,CAAC,GAAG,KACF,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC;;;;;gBAKd,EAAE,GAAG,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC;AACpC,gBAAA,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAChC;IAEL;;AAGA,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3C;;;;"}
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": "7.1.1",
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.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": "9.3.1",
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.16",
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 to check for
17
- * plain objects specifically (excluding arrays), use `isRecord()` instead.
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 checking plain objects specifically (excludes arrays)
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 =>
@@ -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 plain object (record) - a non-null
6
- * object that is not an array.
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 useful for identifying "plain" JavaScript objects (also
9
- * called records or dictionaries) - objects that are typically used as
10
- * key-value collections. It excludes arrays, functions, and special object
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` (equivalent to `Record<PropertyKey,
17
- * unknown>`)
18
- * - Excludes `null`, `undefined`, primitives, arrays, and functions
19
- * - Returns `true` for plain objects `{}`, object literals, and objects created
20
- * with `Object.create()`
21
- * - Returns `false` for arrays, even though they are technically objects
22
- *
23
- * **Implementation:** Uses `isNonNullObject()` to check for objects, then
24
- * `Array.isArray()` to exclude arrays.
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[] = [{ id: 1 }, 'str', 0, null] as const;
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 and not an array, `false`
38
- * otherwise. When `true`, TypeScript narrows the type to `UnknownRecord`.
39
- * @see {@link isNonNullObject} - For checking any object type (includes arrays)
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 => isNonNullObject(u);
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 (an object with mutable string keys).
61
+ * Type guard that checks if a value is a mutable record.
46
62
  *
47
- * This function is an alias for {@link isRecord}, but narrows the type to
48
- * `MutableRecord<string, unknown>`, which is useful when you need to ensure
49
- * that the object can be safely mutated (i.e., its properties can be added or changed).
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, arrays, and functions
55
- * - Returns `true` for plain objects with mutable string keys
56
- * - Returns `false` for arrays and other non-record types
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 mutable record (plain object with string keys), `false` otherwise.
71
- * When `true`, TypeScript narrows the type to `MutableRecord<string, unknown>`.
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('arrays are Records', () => {
7
- test('number[] is assignable to Record<number, unknown>', () => {
8
- const arr: Record<number, unknown> = [1, 2, 3] as 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
- const unk: unknown = arr;
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
- expectType<typeof unk, UnknownRecord>('<=');
27
+ assert.isTrue(isRecord(unk));
15
28
  });
29
+ });
16
30
 
17
- test('string[] is assignable to Record<number, unknown>', () => {
18
- const arr: Record<number, unknown> = ['1', '2', '3'] as const;
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
- const unk: unknown = arr;
38
+ expectType<readonly number[], UnknownRecord>('!<=');
21
39
 
22
- assert.isTrue(isRecord(unk));
40
+ expectType<readonly string[], Record<number, unknown>>('<=');
41
+
42
+ expectType<readonly string[], UnknownRecord>('!<=');
23
43
 
24
- expectType<typeof unk, UnknownRecord>('<=');
44
+ expectType<readonly [], UnknownRecord>('!<=');
25
45
  });
26
46
 
27
- test('[] is a record', () => {
28
- const arr: unknown = [] as const;
47
+ test('[1, 2, 3] is not a record', () => {
48
+ const unk: unknown = [1, 2, 3] as const;
29
49
 
30
- const unk: unknown = arr;
50
+ assert.isFalse(isRecord(unk));
51
+ });
31
52
 
32
- assert.isTrue(isRecord(unk));
53
+ test('["1", "2", "3"] is not a record', () => {
54
+ const unk: unknown = ['1', '2', '3'] as const;
33
55
 
34
- expectType<typeof unk, UnknownRecord>('<=');
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 Records', () => {
39
- test('{ x: 1 } is a record', () => {
40
- const obj = { x: 1 } as const;
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
- const unk: unknown = obj;
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
- expectType<typeof unk, UnknownRecord>('<=');
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('{} is a record', () => {
50
- const obj = {} as const;
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
- const unk: unknown = obj;
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
- expectType<typeof unk, UnknownRecord>('<=');
136
+ assert.isFalse(isRecord(unk));
57
137
  });
58
138
  });
59
139
 
60
- describe('primitives are not Records', () => {
140
+ describe('primitives are not records', () => {
61
141
  test('null is not a record', () => {
62
- const obj = null;
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 prm = undefined;
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 prm = 3;
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 prm = 'str';
93
-
94
- const unk: unknown = prm;
160
+ const unk: unknown = 'str';
95
161
 
96
162
  assert.isFalse(isRecord(unk));
163
+ });
97
164
 
98
- expectType<typeof unk, UnknownRecord>('!<=');
165
+ test('true is not a record', () => {
166
+ const unk: unknown = true;
167
+
168
+ assert.isFalse(isRecord(unk));
99
169
  });
100
- });
101
170
 
102
- // test('Map is not a record', () => {
103
- // const obj = new MutableMap();
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
- // assertNotType<typeof obj, UnknownRecord>("<=");
108
- // expectType<typeof res, boolean>("=");
174
+ assert.isFalse(isRecord(unk));
175
+ });
109
176
 
110
- // expect(res).toBe(false);
111
- // });
177
+ test('bigint is not a record', () => {
178
+ const unk: unknown = 1n;
112
179
 
113
- // test('Set is not a record', () => {
114
- // const obj = new MutableSet();
115
- // const unk: unknown = obj;
116
- // const res = isRecord(unk);
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
- // assertNotType<typeof obj, UnknownRecord>("<=");
119
- // expectType<typeof res, boolean>("=");
187
+ if (isRecord(unk) && hasKey(unk, 'some-key')) {
188
+ expectType<(typeof unk)['some-key'], unknown>('=');
120
189
 
121
- // expect(res).toBe(false);
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) &&