ts-gems 4.0.0 → 4.0.2

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/docs/api/omit.md CHANGED
@@ -9,10 +9,11 @@ convention](../api.md#the-deep--deeper-convention) for `DeepOmitTypes`/
9
9
 
10
10
  ## `StrictOmit<T, X>`
11
11
 
12
- Like the built-in `Omit<T, K>`, but also drops any remaining key whose value
13
- type is `never` (after stripping `undefined`) symmetric with
14
- [`StrictPick`](pick.md#strictpickt-x), so picking and omitting the same key
15
- set never disagree about `never`-typed properties.
12
+ Like the built-in `Omit<T, K>`, but `X` is constrained to `keyof T`, so a
13
+ typo in the key you're omitting is a compile error instead of a silent
14
+ no-op. Unlike [`StrictPick`](pick.md#strictpickt-x), it does **not** also
15
+ drop `never`-typed keys only the key(s) named in `X` are removed, by
16
+ identity, regardless of their value type.
16
17
 
17
18
  ```ts
18
19
  import type { StrictOmit } from 'ts-gems';
@@ -24,7 +25,7 @@ interface Row {
24
25
  }
25
26
 
26
27
  type Result = StrictOmit<Row, 'b'>;
27
- // { a?: number } - `c` is dropped too, it's `never`
28
+ // { a?: number; c: never } - only `b` is removed
28
29
  ```
29
30
 
30
31
  ## `OmitFunctions<T>`
package/docs/api/pick.md CHANGED
@@ -7,9 +7,11 @@ See [Omit](omit.md) for the inverse operations.
7
7
 
8
8
  ## `StrictPick<T, X>`
9
9
 
10
- Like the built-in `Pick<T, K>`, but also drops any selected key whose value
11
- type is `never` (after stripping `undefined`) consistent with how the rest
12
- of the library treats `never`-typed properties as "absent".
10
+ Like the built-in `Pick<T, K>`, but `X` is constrained to `keyof T`, so a
11
+ typo in the key you're picking is a compile error instead of silently
12
+ producing `never`. It only selects by key identity — it does **not** also
13
+ drop `never`-typed keys, so it stays correct when `T` is still an open
14
+ generic type parameter (e.g. used inside another generic function).
13
15
 
14
16
  ```ts
15
17
  import type { StrictPick } from 'ts-gems';
@@ -21,7 +23,7 @@ interface Row {
21
23
  }
22
24
 
23
25
  type Result = StrictPick<Row, 'a' | 'c'>;
24
- // { a?: number } - `c` is dropped, it's `never`
26
+ // { a?: number; c: never } - both named keys are kept
25
27
  ```
26
28
 
27
29
  ## `PickFunctions<T>`
package/docs/api.md CHANGED
@@ -1,10 +1,10 @@
1
1
  <!--
2
2
  docs-baseline
3
- git-commit: bfe8777ee92aea1e8cacecfa8f68e297d4235c19
4
- package-version: 4.0.0
3
+ git-commit: 27bbe999f45fa24fe4e1d7d32d5639defa285bdc
4
+ package-version: 4.0.1
5
5
  date: 2026-09-09
6
6
  verified-against: lib/
7
- diff-command: git diff bfe8777ee92aea1e8cacecfa8f68e297d4235c19..HEAD -- lib/
7
+ diff-command: git diff 27bbe999f45fa24fe4e1d7d32d5639defa285bdc..HEAD -- lib/
8
8
  -->
9
9
 
10
10
  <p align="center">
@@ -98,6 +98,19 @@ type ReadonlyConfig = DeeperReadonly<Config>;
98
98
  // }
99
99
  ```
100
100
 
101
+ A **nullable** object or array property (`SomeObject | null`) still gets
102
+ recursed into — `null` is a leaf on its own, but it is preserved as a
103
+ separate union member alongside the transformed object/array, not lost:
104
+
105
+ ```ts
106
+ type Config = {
107
+ server: { host: string } | null;
108
+ };
109
+
110
+ type ReadonlyConfig = DeeperReadonly<Config>;
111
+ // { readonly server: { readonly host: string } | null }
112
+ ```
113
+
101
114
  ## Naming patterns used across pages
102
115
 
103
116
  - **`Pick*` / `Omit*`** — select or remove properties matching some criterion
package/lib/mutable.d.ts CHANGED
@@ -30,8 +30,8 @@ export type DeepMutable<T> = {
30
30
  K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>
31
31
  ]: IfNoDeepValue<Exclude<T[K], undefined>> extends true // Do not deep process No-Deep values
32
32
  ? T[K]
33
- : // Deep process objects
34
- DeepMutable<NonNullable<T[K]>>;
33
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
34
+ DeepMutable<Exclude<T[K], undefined>>;
35
35
  };
36
36
 
37
37
  /**
@@ -43,12 +43,14 @@ export type DeeperMutable<T> = {
43
43
  ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
44
44
  ? T[K]
45
45
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
46
- ? DeeperMutable<U>[]
46
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
47
+ ? DeeperMutable<U>[] | null
48
+ : DeeperMutable<U>[]
47
49
  : // Do not deep process No-Deep values
48
- IfNoDeepValue<NonNullable<T[K]>> extends true
50
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
49
51
  ? T[K]
50
- : // Deep process objects
51
- DeeperMutable<NonNullable<T[K]>>;
52
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
53
+ DeeperMutable<Exclude<T[K], undefined>>;
52
54
  };
53
55
 
54
56
  /**
@@ -30,11 +30,11 @@ export type OmitNever<T> = {
30
30
  export type DeepOmitNever<T> = {
31
31
  [K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>]: IfNoDeepValue<
32
32
  // Do not deep process No-Deep values
33
- NonNullable<T[K]>
33
+ Exclude<T[K], undefined>
34
34
  > extends true
35
35
  ? T[K]
36
- : // Deep process objects
37
- DeepOmitNever<NonNullable<T[K]>>;
36
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
37
+ DeepOmitNever<Exclude<T[K], undefined>>;
38
38
  };
39
39
 
40
40
  /**
@@ -49,10 +49,12 @@ export type DeeperOmitNever<T> = {
49
49
  // Deep process arrays
50
50
  T[K]
51
51
  > extends readonly (infer U)[]
52
- ? DeeperOmitNever<U>[]
52
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
53
+ ? DeeperOmitNever<U>[] | null
54
+ : DeeperOmitNever<U>[]
53
55
  : // Do not deep process No-Deep values
54
- IfNoDeepValue<NonNullable<T[K]>> extends true
56
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
55
57
  ? T[K]
56
- : // Deep process objects
57
- DeeperOmitNever<NonNullable<T[K]>>;
58
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
59
+ DeeperOmitNever<Exclude<T[K], undefined>>;
58
60
  };
@@ -30,10 +30,12 @@ export type DeeperOmitUndefined<T> = {
30
30
  > extends true // Leave fixed-length tuples untouched
31
31
  ? T[K]
32
32
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
33
- ? DeeperOmitUndefined<U>[]
33
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
34
+ ? DeeperOmitUndefined<U>[] | null
35
+ : DeeperOmitUndefined<U>[]
34
36
  : // Do not deep process No-Deep values
35
- IfNoDeepValue<NonNullable<T[K]>> extends true
37
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
36
38
  ? T[K]
37
- : // Deep process objects
38
- DeeperOmitUndefined<NonNullable<T[K]>>;
39
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
40
+ DeeperOmitUndefined<Exclude<T[K], undefined>>;
39
41
  };
package/lib/omit.d.ts CHANGED
@@ -7,16 +7,7 @@ import { IfFunction, IfNever, IfTuple } from './type-check.js';
7
7
  * while preserving strict type checking.
8
8
  */
9
9
  export type StrictOmit<T, X extends keyof T> = {
10
- [
11
- K in keyof T as Or<
12
- // Omit never keys
13
- IfNever<Exclude<T[K], undefined>>,
14
- // Omit X
15
- K extends X ? true : false
16
- > extends true
17
- ? never
18
- : K
19
- ]: T[K];
10
+ [K in keyof T as K extends X ? never : K]: T[K];
20
11
  };
21
12
 
22
13
  /**
@@ -53,8 +44,8 @@ export type DeepOmitTypes<T, X> = {
53
44
  K in keyof T as IfNever<Exclude<T[K], undefined | X>, never, K>
54
45
  ]: IfNoDeepValue<Exclude<T[K], undefined>> extends true // Do not deep process No-Deep values
55
46
  ? Exclude<T[K], X>
56
- : // Deep process objects
57
- DeepOmitTypes<NonNullable<T[K]>, X>;
47
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
48
+ DeepOmitTypes<Exclude<T[K], undefined>, X>;
58
49
  };
59
50
 
60
51
  /**
@@ -69,9 +60,11 @@ export type DeeperOmitTypes<T, X> = {
69
60
  // Deep process arrays // Do not deep process No-Deep values
70
61
  T[K]
71
62
  > extends readonly (infer U)[]
72
- ? DeeperOmitTypes<U, X>[]
73
- : IfNoDeepValue<NonNullable<T[K]>> extends true
63
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
64
+ ? DeeperOmitTypes<U, X>[] | null
65
+ : DeeperOmitTypes<U, X>[]
66
+ : IfNoDeepValue<Exclude<T[K], undefined>> extends true
74
67
  ? Exclude<T[K], X>
75
- : // Deep process objects
76
- DeeperOmitTypes<NonNullable<T[K]>, X>;
68
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
69
+ DeeperOmitTypes<Exclude<T[K], undefined>, X>;
77
70
  };
package/lib/partial.d.ts CHANGED
@@ -24,8 +24,8 @@ export type DeepPartial<T> = {
24
24
  Exclude<T[K], undefined>
25
25
  > extends true
26
26
  ? T[K]
27
- : // Deep process objects
28
- DeepPartial<NonNullable<T[K]>>;
27
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
28
+ DeepPartial<Exclude<T[K], undefined>>;
29
29
  };
30
30
 
31
31
  /**
@@ -37,12 +37,14 @@ export type DeeperPartial<T> = {
37
37
  > extends true // Leave fixed-length tuples untouched
38
38
  ? T[K]
39
39
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
40
- ? DeeperPartial<U>[]
40
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
41
+ ? DeeperPartial<U>[] | null
42
+ : DeeperPartial<U>[]
41
43
  : // Do not deep process No-Deep values
42
- IfNoDeepValue<NonNullable<T[K]>> extends true
44
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
43
45
  ? T[K]
44
- : // Deep process objects
45
- DeeperPartial<NonNullable<T[K]>>;
46
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
47
+ DeeperPartial<Exclude<T[K], undefined>>;
46
48
  };
47
49
 
48
50
  /**
package/lib/pick.d.ts CHANGED
@@ -13,16 +13,7 @@ import {
13
13
  * while preserving strict type checking.
14
14
  */
15
15
  export type StrictPick<T, X extends keyof T> = {
16
- [
17
- K in keyof T as Or<
18
- // Omit never keys
19
- IfNever<Exclude<T[K], undefined>>,
20
- // Omit X
21
- K extends X ? false : true
22
- > extends true
23
- ? never
24
- : K
25
- ]: T[K];
16
+ [K in keyof T as K extends X ? K : never]: T[K];
26
17
  };
27
18
 
28
19
  /**
package/lib/readonly.d.ts CHANGED
@@ -29,7 +29,9 @@ export type DeeperReadonly<T> = {
29
29
  ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
30
30
  ? T[K]
31
31
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
32
- ? DeeperReadonly<U>[]
32
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
33
+ ? DeeperReadonly<U>[] | null
34
+ : DeeperReadonly<U>[]
33
35
  : // Do not deep process No-Deep values
34
36
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
35
37
  ? T[K]
@@ -87,10 +89,10 @@ export type DeepPickReadonly<T> = {
87
89
  > extends true
88
90
  ? never
89
91
  : K
90
- ]: IfNoDeepValue<NonNullable<T[K]>> extends true // Do not deep process No-Deep values
92
+ ]: IfNoDeepValue<Exclude<T[K], undefined>> extends true // Do not deep process No-Deep values
91
93
  ? T[K]
92
- : // Deep process objects
93
- DeepPickReadonly<NonNullable<T[K]>>;
94
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
95
+ DeepPickReadonly<Exclude<T[K], undefined>>;
94
96
  };
95
97
 
96
98
  /**
@@ -106,10 +108,10 @@ export type DeepOmitReadonly<T> = {
106
108
  > extends true
107
109
  ? never
108
110
  : K
109
- ]: IfNoDeepValue<NonNullable<T[K]>> extends true // Do not deep process No-Deep values
111
+ ]: IfNoDeepValue<Exclude<T[K], undefined>> extends true // Do not deep process No-Deep values
110
112
  ? T[K]
111
- : // Deep process objects
112
- DeepOmitReadonly<NonNullable<T[K]>>;
113
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
114
+ DeepOmitReadonly<Exclude<T[K], undefined>>;
113
115
  };
114
116
 
115
117
  /**
@@ -128,12 +130,14 @@ export type DeeperPickReadonly<T> = {
128
130
  ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
129
131
  ? T[K]
130
132
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
131
- ? DeeperPickReadonly<U>[]
133
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
134
+ ? DeeperPickReadonly<U>[] | null
135
+ : DeeperPickReadonly<U>[]
132
136
  : // Do not deep process No-Deep values
133
- IfNoDeepValue<NonNullable<T[K]>> extends true
137
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
134
138
  ? T[K]
135
- : // Deep process objects
136
- DeeperPickReadonly<NonNullable<T[K]>>;
139
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
140
+ DeeperPickReadonly<Exclude<T[K], undefined>>;
137
141
  };
138
142
 
139
143
  /**
@@ -152,10 +156,12 @@ export type DeeperOmitReadonly<T> = {
152
156
  ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
153
157
  ? T[K]
154
158
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
155
- ? DeeperOmitReadonly<U>[]
159
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
160
+ ? DeeperOmitReadonly<U>[] | null
161
+ : DeeperOmitReadonly<U>[]
156
162
  : // Do not deep process No-Deep values
157
- IfNoDeepValue<NonNullable<T[K]>> extends true
163
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
158
164
  ? T[K]
159
- : // Deep process objects
160
- DeeperOmitReadonly<NonNullable<T[K]>>;
165
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
166
+ DeeperOmitReadonly<Exclude<T[K], undefined>>;
161
167
  };
package/lib/required.d.ts CHANGED
@@ -1,15 +1,11 @@
1
1
  import { IfNoDeepValue } from './helpers.js';
2
2
  import { Or } from './logical.js';
3
- import { OmitTypes } from './omit.js';
4
3
  import { IfEquals, IfNever, IfTuple } from './type-check.js';
5
4
 
6
5
  /**
7
6
  * Marks given keys as required
8
7
  */
9
- export type RequiredSome<T, K extends keyof T> = OmitTypes<
10
- Required<Pick<T, K>>,
11
- null
12
- > &
8
+ export type RequiredSome<T, K extends keyof T> = Required<Pick<T, K>> &
13
9
  Omit<T, K>;
14
10
 
15
11
  /**
@@ -20,8 +16,8 @@ export type DeepRequired<T> = {
20
16
  K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>
21
17
  ]-?: IfNoDeepValue<Exclude<T[K], undefined>> extends true // Do not deep process No-Deep values
22
18
  ? Exclude<T[K], undefined>
23
- : // Deep process objects
24
- DeepRequired<NonNullable<T[K]>>;
19
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
20
+ DeepRequired<Exclude<T[K], undefined>>;
25
21
  };
26
22
 
27
23
  /**
@@ -33,12 +29,14 @@ export type DeeperRequired<T> = {
33
29
  > extends true // Leave fixed-length tuples untouched
34
30
  ? Exclude<T[K], undefined>
35
31
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
36
- ? DeeperRequired<U>[]
32
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
33
+ ? DeeperRequired<U>[] | null
34
+ : DeeperRequired<U>[]
37
35
  : // Do not deep process No-Deep values
38
- IfNoDeepValue<NonNullable<T[K]>> extends true
36
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
39
37
  ? Exclude<T[K], undefined>
40
- : // Deep process objects
41
- DeeperRequired<NonNullable<T[K]>>;
38
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
39
+ DeeperRequired<Exclude<T[K], undefined>>;
42
40
  };
43
41
 
44
42
  /**
@@ -92,10 +90,10 @@ export type DeepPickRequired<T> = {
92
90
  > extends true
93
91
  ? never
94
92
  : K
95
- ]: IfNoDeepValue<NonNullable<T[K]>> extends true // Do not deep process No-Deep values
93
+ ]: IfNoDeepValue<Exclude<T[K], undefined>> extends true // Do not deep process No-Deep values
96
94
  ? T[K]
97
- : // Deep process objects
98
- DeepPickRequired<NonNullable<T[K]>>;
95
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
96
+ DeepPickRequired<Exclude<T[K], undefined>>;
99
97
  };
100
98
 
101
99
  /**
@@ -111,10 +109,10 @@ export type DeepOmitRequired<T> = {
111
109
  > extends true
112
110
  ? never
113
111
  : K
114
- ]?: IfNoDeepValue<NonNullable<T[K]>> extends true // Do not deep process No-Deep values
112
+ ]?: IfNoDeepValue<Exclude<T[K], undefined>> extends true // Do not deep process No-Deep values
115
113
  ? T[K]
116
- : // Deep process objects
117
- DeepOmitRequired<NonNullable<T[K]>>;
114
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
115
+ DeepOmitRequired<Exclude<T[K], undefined>>;
118
116
  };
119
117
 
120
118
  /**
@@ -133,12 +131,14 @@ export type DeeperPickRequired<T> = {
133
131
  ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
134
132
  ? T[K]
135
133
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
136
- ? DeeperPickRequired<U>[]
134
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
135
+ ? DeeperPickRequired<U>[] | null
136
+ : DeeperPickRequired<U>[]
137
137
  : // Do not deep process No-Deep values
138
- IfNoDeepValue<NonNullable<T[K]>> extends true
138
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
139
139
  ? T[K]
140
- : // Deep process objects
141
- DeeperPickRequired<NonNullable<T[K]>>;
140
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
141
+ DeeperPickRequired<Exclude<T[K], undefined>>;
142
142
  };
143
143
 
144
144
  /**
@@ -157,10 +157,12 @@ export type DeeperOmitRequired<T> = {
157
157
  ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
158
158
  ? T[K]
159
159
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
160
- ? DeeperOmitRequired<U>[]
160
+ ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
161
+ ? DeeperOmitRequired<U>[] | null
162
+ : DeeperOmitRequired<U>[]
161
163
  : // Do not deep process No-Deep values
162
- IfNoDeepValue<NonNullable<T[K]>> extends true
164
+ IfNoDeepValue<Exclude<T[K], undefined>> extends true
163
165
  ? T[K]
164
- : // Deep process objects
165
- DeeperOmitRequired<NonNullable<T[K]>>;
166
+ : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
167
+ DeeperOmitRequired<Exclude<T[K], undefined>>;
166
168
  };
@@ -127,6 +127,14 @@ export type IfClassOrAny<T, Y = true, N = false> =
127
127
 
128
128
  /**
129
129
  * Returns "Y" if "T1" is exactly same with "T2", "N" otherwise
130
+ *
131
+ * The `IfObject<T1> | IfObject<T2>` check below looks like it should be
132
+ * `Or<IfObject<T1>, IfObject<T2>>` (this file's usual pattern), but it is
133
+ * not a bug: it deliberately only takes the EqualsWrapped/object-safe path
134
+ * when BOTH sides are objects. Wrapping a non-object side (e.g. `string`)
135
+ * in `EqualsWrapped` maps over its own keys instead of comparing it as
136
+ * itself, which gives wrong answers - switching this to `Or` breaks
137
+ * multiple cases in test/type-check.spec.ts's `IfEquals` suite.
130
138
  */
131
139
  export type IfEquals<T1, T2, Y = true, N = false> =
132
140
  IfObject<T1> | IfObject<T2> extends true
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ts-gems",
3
3
  "description": "Valuable typing extensions for TypeScript",
4
- "version": "4.0.0",
4
+ "version": "4.0.2",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "module": "./lib/index.js",