ts-gems 4.0.2 → 4.0.4

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/lib/dto.d.ts CHANGED
@@ -4,24 +4,68 @@ import { DeeperPartial } from './partial.js';
4
4
  import { IfNever, IfTuple } from './type-check.js';
5
5
 
6
6
  /**
7
- * Returns the given type as a Data Transfer Object (DTO) interface, Removes symbol keys and function properties.
7
+ * Narrows T to a Data Transfer Object shape: a plain, serializable snapshot
8
+ * of T's data, safe to persist to a database, encode to JSON, or send over
9
+ * an API. It strips whatever only exists in a live JavaScript runtime and
10
+ * cannot survive that round-trip - function properties, and properties
11
+ * whose value or key is a symbol - recursively into nested objects, arrays,
12
+ * and tuples.
13
+ *
14
+ * DTO only removes those non-serializable members. It does not otherwise
15
+ * change a value's mutability, or a property's readonly/optional modifiers
16
+ * - "is this representable as plain data" is orthogonal to "is this
17
+ * mutable/readonly", which is the concern of the
18
+ * Mutable/Readonly/Partial/Required families instead. In particular, a
19
+ * `readonly T[]` property stays a `readonly T[]` after DTO - it is still
20
+ * perfectly serializable, so DTO has no reason to touch it.
21
+ *
8
22
  * @template T - The type of the data being transferred.
23
+ * @template X - An extra type unioned into every remaining property's
24
+ * value, useful for tagging every field with a shared marker (e.g. a
25
+ * sentinel for "field not selected"). See docs/api/dto.md.
9
26
  */
10
27
  export type DTO<T, X = never> = {
11
28
  [
12
29
  K in keyof T as K extends symbol
13
30
  ? never
14
31
  : IfNever<Exclude<NonNullable<T[K]>, Function | symbol>, never, K>
15
- ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
16
- ? NonNullable<T[K] | X>
32
+ ]: IfTuple<NonNullable<T[K]>> extends true
33
+ ? DTOTuple<NonNullable<T[K]>, X>
17
34
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
18
- ? DTO<U>[]
19
- : // Do not deep process No-Deep values
20
- IfNoDeepValue<NonNullable<T[K]>> extends true
21
- ? NonNullable<T[K] | X>
22
- : // Deep process objects
23
- DTO<NonNullable<T[K] | X>>;
35
+ ? NonNullable<T[K]> extends any[]
36
+ ? DTO<U>[] // was a mutable array - stays mutable
37
+ : readonly DTO<U>[] // was a readonly array - DTO doesn't touch mutability
38
+ : DTOValue<T[K], X>;
24
39
  };
25
40
 
41
+ type DTOValue<V, X> =
42
+ IfNoDeepValue<NonNullable<V>> extends true
43
+ ? NonNullable<V | X>
44
+ : DTO<NonNullable<V | X>>;
45
+
46
+ type DTOTuple<T, X> = T extends readonly [infer Head, ...infer Rest]
47
+ ? IfNever<Exclude<NonNullable<Head>, Function | symbol>> extends true
48
+ ? DTOTuple<Rest, X>
49
+ : [DTOValue<Head, X>, ...DTOTuple<Rest, X>]
50
+ : [];
51
+
52
+ /**
53
+ * `DTO<T, X>` with every property (including nested ones and array/tuple
54
+ * elements) made optional. For request bodies where the data is expected to
55
+ * be incomplete - a "create" endpoint where some fields have server-side
56
+ * defaults, or an "update" endpoint where the caller only sends the fields
57
+ * they want to change and omitting a key means "leave it as it is".
58
+ *
59
+ * See `PatchDTO` for the alternative where omitting a key and sending
60
+ * `null` are meant to mean different things.
61
+ */
26
62
  export type PartialDTO<T, X = never> = DeeperPartial<DTO<T, X>>;
63
+
64
+ /**
65
+ * `DTO<T, X>` with every property (including nested ones and array/tuple
66
+ * elements) made optional and nullable. For "patch"-style update endpoints
67
+ * that need to distinguish "omit this key, leave the field unchanged" from
68
+ * "send `null` for this key, clear the field" - `PartialDTO` alone cannot
69
+ * express that second case since it never adds `null`.
70
+ */
27
71
  export type PatchDTO<T, X = never> = DeeperNullish<DTO<T, X>>;
package/lib/helpers.d.ts CHANGED
@@ -4,32 +4,31 @@ import { Builtin } from './types.js';
4
4
  /**
5
5
  * Returns true if T is excluded from deep operations
6
6
  */
7
- export type IfNoDeepValue<T> =
8
- IfAny<T> extends true
9
- ? true
10
- : T extends Builtin
7
+ export type IfNoDeepValue<T> = T extends Builtin
8
+ ? true
9
+ : IfAny<T> extends true
10
+ ? T
11
+ : IfTuple<T> extends true
11
12
  ? true
12
- : IfTuple<T> extends true
13
+ : T extends Function
13
14
  ? true
14
- : T extends Function
15
+ : IfClass<T> extends true
15
16
  ? true
16
- : IfClass<T> extends true
17
+ : T extends Map<any, any>
17
18
  ? true
18
- : T extends Map<any, any>
19
+ : T extends ReadonlyMap<any, any>
19
20
  ? true
20
- : T extends ReadonlyMap<any, any>
21
+ : T extends WeakMap<any, any>
21
22
  ? true
22
- : T extends WeakMap<any, any>
23
+ : T extends Set<any>
23
24
  ? true
24
- : T extends Set<any>
25
+ : T extends ReadonlySet<any>
25
26
  ? true
26
- : T extends ReadonlySet<any>
27
+ : T extends WeakSet<any>
27
28
  ? true
28
- : T extends WeakSet<any>
29
+ : T extends readonly any[]
29
30
  ? true
30
- : T extends readonly any[]
31
- ? true
32
- : false;
31
+ : false;
33
32
 
34
33
  /**
35
34
  * ValuesOf
package/lib/mutable.d.ts CHANGED
@@ -40,10 +40,11 @@ export type DeepMutable<T> = {
40
40
  export type DeeperMutable<T> = {
41
41
  -readonly [
42
42
  K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>
43
- ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
44
- ? T[K]
43
+ ]: IfTuple<NonNullable<T[K]>> extends true // Deep process tuples positionally
44
+ ? DeeperMutableTuple<NonNullable<T[K]>>
45
45
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
46
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
46
+ ? // Always mutable - that is DeeperMutable's whole purpose
47
+ null extends T[K]
47
48
  ? DeeperMutable<U>[] | null
48
49
  : DeeperMutable<U>[]
49
50
  : // Do not deep process No-Deep values
@@ -53,6 +54,15 @@ export type DeeperMutable<T> = {
53
54
  DeeperMutable<Exclude<T[K], undefined>>;
54
55
  };
55
56
 
57
+ type DeeperMutableTuple<T> = T extends readonly [infer Head, ...infer Rest]
58
+ ? [
59
+ IfNoDeepValue<NonNullable<Head>> extends true
60
+ ? Head
61
+ : DeeperMutable<NonNullable<Head>>,
62
+ ...DeeperMutableTuple<Rest>,
63
+ ]
64
+ : [];
65
+
56
66
  /**
57
67
  * Returns mutable keys of an object
58
68
  */
@@ -2,7 +2,7 @@ import { IfNoDeepValue } from './helpers.js';
2
2
  import { IfNever, IfNull, IfTuple } from './type-check.js';
3
3
 
4
4
  /**
5
- * Exclude null and undefined from T
5
+ * Exclude null and undefined from T deeply
6
6
  */
7
7
  export type UnNullish<T> = {
8
8
  [
@@ -43,13 +43,24 @@ export type DeeperUnNullish<T> = {
43
43
  never,
44
44
  IfNull<Exclude<T[K], undefined>, never, K>
45
45
  >
46
- ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
47
- ? NonNullable<T[K]>
46
+ ]: IfTuple<NonNullable<T[K]>> extends true // Deep process tuples positionally
47
+ ? DeeperUnNullishTuple<NonNullable<T[K]>>
48
48
  : NonNullable<NonNullable<T[K]>> extends readonly (infer U)[]
49
- ? DeeperUnNullish<U>[]
49
+ ? NonNullable<T[K]> extends any[]
50
+ ? DeeperUnNullish<U>[] // was mutable - UnNullish doesn't touch mutability
51
+ : readonly DeeperUnNullish<U>[] // was readonly - preserve that
50
52
  : // Do not deep process No-Deep values
51
53
  IfNoDeepValue<NonNullable<T[K]>> extends true
52
54
  ? NonNullable<T[K]>
53
55
  : // Deep process objects
54
56
  DeeperUnNullish<NonNullable<T[K]>>;
55
57
  };
58
+
59
+ type DeeperUnNullishTuple<T> = T extends readonly [infer Head, ...infer Rest]
60
+ ? [
61
+ IfNoDeepValue<NonNullable<Head>> extends true
62
+ ? NonNullable<Head>
63
+ : DeeperUnNullish<NonNullable<Head>>,
64
+ ...DeeperUnNullishTuple<Rest>,
65
+ ]
66
+ : [];
package/lib/nullish.d.ts CHANGED
@@ -27,16 +27,27 @@ export type DeepNullish<T> = {
27
27
  export type DeeperNullish<T> = {
28
28
  [K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>]?: IfTuple<
29
29
  NonNullable<T[K]>
30
- > extends true // Leave fixed-length tuples untouched
31
- ? T[K] | null
30
+ > extends true // Deep process tuples positionally
31
+ ? DeeperNullishTuple<NonNullable<T[K]>> | null
32
32
  : NonNullable<
33
33
  // Deep process arrays
34
34
  T[K]
35
35
  > extends readonly (infer U)[]
36
- ? DeeperNullish<U>[] | null
36
+ ? NonNullable<T[K]> extends any[]
37
+ ? DeeperNullish<U>[] | null // was mutable - DeeperNullish doesn't touch mutability
38
+ : readonly DeeperNullish<U>[] | null // was readonly - preserve that
37
39
  : // Do not deep process No-Deep values
38
40
  IfNoDeepValue<NonNullable<T[K]>> extends true
39
41
  ? T[K] | null
40
42
  : // Deep process objects
41
43
  DeeperNullish<NonNullable<T[K]>> | null;
42
44
  };
45
+
46
+ type DeeperNullishTuple<T> = T extends readonly [infer Head, ...infer Rest]
47
+ ? [
48
+ IfNoDeepValue<NonNullable<Head>> extends true
49
+ ? Head
50
+ : DeeperNullish<NonNullable<Head>>,
51
+ ...DeeperNullishTuple<Rest>,
52
+ ]
53
+ : [];
@@ -43,18 +43,32 @@ export type DeepOmitNever<T> = {
43
43
  export type DeeperOmitNever<T> = {
44
44
  [K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>]: IfTuple<
45
45
  NonNullable<T[K]>
46
- > extends true // Leave fixed-length tuples untouched
47
- ? T[K]
46
+ > extends true // Deep process tuples positionally
47
+ ? DeeperOmitNeverTuple<NonNullable<T[K]>>
48
48
  : NonNullable<
49
49
  // Deep process arrays
50
50
  T[K]
51
51
  > extends readonly (infer U)[]
52
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
53
- ? DeeperOmitNever<U>[] | null
54
- : DeeperOmitNever<U>[]
52
+ ? NonNullable<T[K]> extends any[]
53
+ ? // was mutable - DeeperOmitNever doesn't touch mutability
54
+ null extends T[K]
55
+ ? DeeperOmitNever<U>[] | null
56
+ : DeeperOmitNever<U>[]
57
+ : null extends T[K] // was readonly - preserve that
58
+ ? readonly DeeperOmitNever<U>[] | null
59
+ : readonly DeeperOmitNever<U>[]
55
60
  : // Do not deep process No-Deep values
56
61
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
57
62
  ? T[K]
58
63
  : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
59
64
  DeeperOmitNever<Exclude<T[K], undefined>>;
60
65
  };
66
+
67
+ type DeeperOmitNeverTuple<T> = T extends readonly [infer Head, ...infer Rest]
68
+ ? [
69
+ IfNoDeepValue<NonNullable<Head>> extends true
70
+ ? Head
71
+ : DeeperOmitNever<NonNullable<Head>>,
72
+ ...DeeperOmitNeverTuple<Rest>,
73
+ ]
74
+ : [];
@@ -27,15 +27,32 @@ export type DeepOmitUndefined<T> = {
27
27
  export type DeeperOmitUndefined<T> = {
28
28
  [K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>]: IfTuple<
29
29
  NonNullable<T[K]>
30
- > extends true // Leave fixed-length tuples untouched
31
- ? T[K]
30
+ > extends true // Deep process tuples positionally
31
+ ? DeeperOmitUndefinedTuple<NonNullable<T[K]>>
32
32
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
33
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
34
- ? DeeperOmitUndefined<U>[] | null
35
- : DeeperOmitUndefined<U>[]
33
+ ? NonNullable<T[K]> extends any[]
34
+ ? // was mutable - DeeperOmitUndefined doesn't touch mutability
35
+ null extends T[K]
36
+ ? DeeperOmitUndefined<U>[] | null
37
+ : DeeperOmitUndefined<U>[]
38
+ : null extends T[K] // was readonly - preserve that
39
+ ? readonly DeeperOmitUndefined<U>[] | null
40
+ : readonly DeeperOmitUndefined<U>[]
36
41
  : // Do not deep process No-Deep values
37
42
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
38
43
  ? T[K]
39
44
  : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
40
45
  DeeperOmitUndefined<Exclude<T[K], undefined>>;
41
46
  };
47
+
48
+ type DeeperOmitUndefinedTuple<T> = T extends readonly [
49
+ infer Head,
50
+ ...infer Rest,
51
+ ]
52
+ ? [
53
+ IfNoDeepValue<NonNullable<Head>> extends true
54
+ ? Head
55
+ : DeeperOmitUndefined<NonNullable<Head>>,
56
+ ...DeeperOmitUndefinedTuple<Rest>,
57
+ ]
58
+ : [];
package/lib/omit.d.ts CHANGED
@@ -54,17 +54,31 @@ export type DeepOmitTypes<T, X> = {
54
54
  export type DeeperOmitTypes<T, X> = {
55
55
  [K in keyof T as IfNever<Exclude<T[K], undefined | X>, never, K>]: IfTuple<
56
56
  NonNullable<T[K]>
57
- > extends true // Leave fixed-length tuples untouched
58
- ? Exclude<T[K], X>
57
+ > extends true // Deep process tuples positionally
58
+ ? DeeperOmitTypesTuple<NonNullable<T[K]>, X>
59
59
  : NonNullable<
60
60
  // Deep process arrays // Do not deep process No-Deep values
61
61
  T[K]
62
62
  > extends readonly (infer U)[]
63
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
64
- ? DeeperOmitTypes<U, X>[] | null
65
- : DeeperOmitTypes<U, X>[]
63
+ ? NonNullable<T[K]> extends any[]
64
+ ? // was mutable - DeeperOmitTypes doesn't touch mutability
65
+ null extends T[K]
66
+ ? DeeperOmitTypes<U, X>[] | null
67
+ : DeeperOmitTypes<U, X>[]
68
+ : null extends T[K] // was readonly - preserve that
69
+ ? readonly DeeperOmitTypes<U, X>[] | null
70
+ : readonly DeeperOmitTypes<U, X>[]
66
71
  : IfNoDeepValue<Exclude<T[K], undefined>> extends true
67
72
  ? Exclude<T[K], X>
68
73
  : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
69
74
  DeeperOmitTypes<Exclude<T[K], undefined>, X>;
70
75
  };
76
+
77
+ type DeeperOmitTypesTuple<T, X> = T extends readonly [infer Head, ...infer Rest]
78
+ ? [
79
+ IfNoDeepValue<NonNullable<Head>> extends true
80
+ ? Exclude<Head, X>
81
+ : DeeperOmitTypes<NonNullable<Head>, X>,
82
+ ...DeeperOmitTypesTuple<Rest, X>,
83
+ ]
84
+ : [];
package/lib/partial.d.ts CHANGED
@@ -34,12 +34,20 @@ export type DeepPartial<T> = {
34
34
  export type DeeperPartial<T> = {
35
35
  [K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>]?: IfTuple<
36
36
  NonNullable<T[K]>
37
- > extends true // Leave fixed-length tuples untouched
38
- ? T[K]
39
- : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
40
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
41
- ? DeeperPartial<U>[] | null
42
- : DeeperPartial<U>[]
37
+ > extends true // Deep process tuples positionally
38
+ ? DeeperPartialTuple<NonNullable<T[K]>>
39
+ : NonNullable<
40
+ // Deep process arrays
41
+ T[K]
42
+ > extends readonly (infer U)[]
43
+ ? NonNullable<T[K]> extends any[]
44
+ ? // was mutable - DeeperPartial doesn't touch mutability
45
+ null extends T[K]
46
+ ? DeeperPartial<U>[] | null
47
+ : DeeperPartial<U>[]
48
+ : null extends T[K] // was readonly - preserve that
49
+ ? readonly DeeperPartial<U>[] | null
50
+ : readonly DeeperPartial<U>[]
43
51
  : // Do not deep process No-Deep values
44
52
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
45
53
  ? T[K]
@@ -47,6 +55,15 @@ export type DeeperPartial<T> = {
47
55
  DeeperPartial<Exclude<T[K], undefined>>;
48
56
  };
49
57
 
58
+ type DeeperPartialTuple<T> = T extends readonly [infer Head, ...infer Rest]
59
+ ? [
60
+ IfNoDeepValue<NonNullable<Head>> extends true
61
+ ? Head
62
+ : DeeperPartial<NonNullable<Head>>,
63
+ ...DeeperPartialTuple<Rest>,
64
+ ]
65
+ : [];
66
+
50
67
  /**
51
68
  * OptionalKeys
52
69
  * @desc Returns optional keys of an object
package/lib/readonly.d.ts CHANGED
@@ -26,12 +26,12 @@ export type DeepReadonly<T> = {
26
26
  export type DeeperReadonly<T> = {
27
27
  readonly [
28
28
  K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>
29
- ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
30
- ? T[K]
29
+ ]: IfTuple<NonNullable<T[K]>> extends true // Deep process tuples positionally
30
+ ? readonly [...DeeperReadonlyTuple<NonNullable<T[K]>>]
31
31
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
32
32
  ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
33
- ? DeeperReadonly<U>[] | null
34
- : DeeperReadonly<U>[]
33
+ ? readonly DeeperReadonly<U>[] | null // Always readonly - that is DeeperReadonly's whole purpose
34
+ : readonly DeeperReadonly<U>[]
35
35
  : // Do not deep process No-Deep values
36
36
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
37
37
  ? T[K]
@@ -39,6 +39,15 @@ export type DeeperReadonly<T> = {
39
39
  DeeperReadonly<Exclude<T[K], undefined>>;
40
40
  };
41
41
 
42
+ type DeeperReadonlyTuple<T> = T extends readonly [infer Head, ...infer Rest]
43
+ ? [
44
+ IfNoDeepValue<NonNullable<Head>> extends true
45
+ ? Head
46
+ : DeeperReadonly<NonNullable<Head>>,
47
+ ...DeeperReadonlyTuple<Rest>,
48
+ ]
49
+ : [];
50
+
42
51
  /**
43
52
  * Returns readonly keys of an object
44
53
  */
@@ -127,12 +136,17 @@ export type DeeperPickReadonly<T> = {
127
136
  > extends true
128
137
  ? never
129
138
  : K
130
- ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
131
- ? T[K]
139
+ ]: IfTuple<NonNullable<T[K]>> extends true // Deep process tuples positionally
140
+ ? DeeperPickReadonlyTuple<NonNullable<T[K]>>
132
141
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
133
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
134
- ? DeeperPickReadonly<U>[] | null
135
- : DeeperPickReadonly<U>[]
142
+ ? NonNullable<T[K]> extends any[]
143
+ ? // was mutable - DeeperPickReadonly doesn't touch mutability, unlike DeeperReadonly
144
+ null extends T[K]
145
+ ? DeeperPickReadonly<U>[] | null
146
+ : DeeperPickReadonly<U>[]
147
+ : null extends T[K] // was readonly - preserve that
148
+ ? readonly DeeperPickReadonly<U>[] | null
149
+ : readonly DeeperPickReadonly<U>[]
136
150
  : // Do not deep process No-Deep values
137
151
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
138
152
  ? T[K]
@@ -140,6 +154,15 @@ export type DeeperPickReadonly<T> = {
140
154
  DeeperPickReadonly<Exclude<T[K], undefined>>;
141
155
  };
142
156
 
157
+ type DeeperPickReadonlyTuple<T> = T extends readonly [infer Head, ...infer Rest]
158
+ ? [
159
+ IfNoDeepValue<NonNullable<Head>> extends true
160
+ ? Head
161
+ : DeeperPickReadonly<NonNullable<Head>>,
162
+ ...DeeperPickReadonlyTuple<Rest>,
163
+ ]
164
+ : [];
165
+
143
166
  /**
144
167
  * Pick all readonly properties in T deeply including arrays
145
168
  */
@@ -153,15 +176,29 @@ export type DeeperOmitReadonly<T> = {
153
176
  > extends true
154
177
  ? never
155
178
  : K
156
- ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
157
- ? T[K]
179
+ ]: IfTuple<NonNullable<T[K]>> extends true // Deep process tuples positionally
180
+ ? DeeperOmitReadonlyTuple<NonNullable<T[K]>>
158
181
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
159
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
160
- ? DeeperOmitReadonly<U>[] | null
161
- : DeeperOmitReadonly<U>[]
182
+ ? NonNullable<T[K]> extends any[]
183
+ ? // was mutable - DeeperOmitReadonly doesn't touch mutability, unlike DeeperReadonly
184
+ null extends T[K]
185
+ ? DeeperOmitReadonly<U>[] | null
186
+ : DeeperOmitReadonly<U>[]
187
+ : null extends T[K] // was readonly - preserve that
188
+ ? readonly DeeperOmitReadonly<U>[] | null
189
+ : readonly DeeperOmitReadonly<U>[]
162
190
  : // Do not deep process No-Deep values
163
191
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
164
192
  ? T[K]
165
193
  : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
166
194
  DeeperOmitReadonly<Exclude<T[K], undefined>>;
167
195
  };
196
+
197
+ type DeeperOmitReadonlyTuple<T> = T extends readonly [infer Head, ...infer Rest]
198
+ ? [
199
+ IfNoDeepValue<NonNullable<Head>> extends true
200
+ ? Head
201
+ : DeeperOmitReadonly<NonNullable<Head>>,
202
+ ...DeeperOmitReadonlyTuple<Rest>,
203
+ ]
204
+ : [];
package/lib/required.d.ts CHANGED
@@ -26,12 +26,17 @@ export type DeepRequired<T> = {
26
26
  export type DeeperRequired<T> = {
27
27
  [K in keyof T as IfNever<Exclude<T[K], undefined>, never, K>]-?: IfTuple<
28
28
  NonNullable<T[K]>
29
- > extends true // Leave fixed-length tuples untouched
30
- ? Exclude<T[K], undefined>
29
+ > extends true // Deep process tuples positionally
30
+ ? DeeperRequiredTuple<NonNullable<T[K]>>
31
31
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
32
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
33
- ? DeeperRequired<U>[] | null
34
- : DeeperRequired<U>[]
32
+ ? NonNullable<T[K]> extends any[]
33
+ ? // was mutable - DeeperRequired doesn't touch mutability
34
+ null extends T[K]
35
+ ? DeeperRequired<U>[] | null
36
+ : DeeperRequired<U>[]
37
+ : null extends T[K] // was readonly - preserve that
38
+ ? readonly DeeperRequired<U>[] | null
39
+ : readonly DeeperRequired<U>[]
35
40
  : // Do not deep process No-Deep values
36
41
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
37
42
  ? Exclude<T[K], undefined>
@@ -39,6 +44,15 @@ export type DeeperRequired<T> = {
39
44
  DeeperRequired<Exclude<T[K], undefined>>;
40
45
  };
41
46
 
47
+ type DeeperRequiredTuple<T> = T extends readonly [infer Head, ...infer Rest]
48
+ ? [
49
+ IfNoDeepValue<NonNullable<Head>> extends true
50
+ ? Exclude<Head, undefined>
51
+ : DeeperRequired<NonNullable<Head>>,
52
+ ...DeeperRequiredTuple<Rest>,
53
+ ]
54
+ : [];
55
+
42
56
  /**
43
57
  * RequiredKeys
44
58
  * @desc Returns required keys of an object
@@ -128,12 +142,17 @@ export type DeeperPickRequired<T> = {
128
142
  > extends true
129
143
  ? never
130
144
  : K
131
- ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
132
- ? T[K]
145
+ ]: IfTuple<NonNullable<T[K]>> extends true // Deep process tuples positionally
146
+ ? DeeperPickRequiredTuple<NonNullable<T[K]>>
133
147
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
134
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
135
- ? DeeperPickRequired<U>[] | null
136
- : DeeperPickRequired<U>[]
148
+ ? NonNullable<T[K]> extends any[]
149
+ ? // was mutable - DeeperPickRequired doesn't touch mutability
150
+ null extends T[K]
151
+ ? DeeperPickRequired<U>[] | null
152
+ : DeeperPickRequired<U>[]
153
+ : null extends T[K] // was readonly - preserve that
154
+ ? readonly DeeperPickRequired<U>[] | null
155
+ : readonly DeeperPickRequired<U>[]
137
156
  : // Do not deep process No-Deep values
138
157
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
139
158
  ? T[K]
@@ -141,6 +160,15 @@ export type DeeperPickRequired<T> = {
141
160
  DeeperPickRequired<Exclude<T[K], undefined>>;
142
161
  };
143
162
 
163
+ type DeeperPickRequiredTuple<T> = T extends readonly [infer Head, ...infer Rest]
164
+ ? [
165
+ IfNoDeepValue<NonNullable<Head>> extends true
166
+ ? Head
167
+ : DeeperPickRequired<NonNullable<Head>>,
168
+ ...DeeperPickRequiredTuple<Rest>,
169
+ ]
170
+ : [];
171
+
144
172
  /**
145
173
  * Omit all required properties in T deeply including arrays
146
174
  */
@@ -154,15 +182,29 @@ export type DeeperOmitRequired<T> = {
154
182
  > extends true
155
183
  ? never
156
184
  : K
157
- ]: IfTuple<NonNullable<T[K]>> extends true // Leave fixed-length tuples untouched
158
- ? T[K]
185
+ ]: IfTuple<NonNullable<T[K]>> extends true // Deep process tuples positionally
186
+ ? DeeperOmitRequiredTuple<NonNullable<T[K]>>
159
187
  : NonNullable<T[K]> extends readonly (infer U)[] // Deep process arrays
160
- ? null extends T[K] // Preserve a `| null` member lost by NonNullable above
161
- ? DeeperOmitRequired<U>[] | null
162
- : DeeperOmitRequired<U>[]
188
+ ? NonNullable<T[K]> extends any[]
189
+ ? // was mutable - DeeperOmitRequired doesn't touch mutability
190
+ null extends T[K]
191
+ ? DeeperOmitRequired<U>[] | null
192
+ : DeeperOmitRequired<U>[]
193
+ : null extends T[K] // was readonly - preserve that
194
+ ? readonly DeeperOmitRequired<U>[] | null
195
+ : readonly DeeperOmitRequired<U>[]
163
196
  : // Do not deep process No-Deep values
164
197
  IfNoDeepValue<Exclude<T[K], undefined>> extends true
165
198
  ? T[K]
166
199
  : // Deep process objects (Exclude, not NonNullable - preserves a `| null` member)
167
200
  DeeperOmitRequired<Exclude<T[K], undefined>>;
168
201
  };
202
+
203
+ type DeeperOmitRequiredTuple<T> = T extends readonly [infer Head, ...infer Rest]
204
+ ? [
205
+ IfNoDeepValue<NonNullable<Head>> extends true
206
+ ? Head
207
+ : DeeperOmitRequired<NonNullable<Head>>,
208
+ ...DeeperOmitRequiredTuple<Rest>,
209
+ ]
210
+ : [];
@@ -57,6 +57,7 @@ export type IfTuple<T, Y = true, N = false> =
57
57
  ? N
58
58
  : Y
59
59
  : N;
60
+
60
61
  export type IfTupleOrAny<T, Y = true, N = false> =
61
62
  IfAny<T> extends true ? Y : IfTuple<T, Y, N>;
62
63
 
@@ -127,14 +128,6 @@ export type IfClassOrAny<T, Y = true, N = false> =
127
128
 
128
129
  /**
129
130
  * 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.
138
131
  */
139
132
  export type IfEquals<T1, T2, Y = true, N = false> =
140
133
  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.2",
4
+ "version": "4.0.4",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "module": "./lib/index.js",