ts-gems 3.12.0 → 4.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.
@@ -0,0 +1,150 @@
1
+ # Readonly
2
+
3
+ Source: [`lib/readonly.d.ts`](../../lib/readonly.d.ts)
4
+
5
+ Adds `readonly` modifiers — the mirror image of [Mutable](mutable.md). See
6
+ [the `Deep*`/`Deeper*` convention](../api.md#the-deep--deeper-convention) and
7
+ the runtime [`asReadonly` cast helpers](helpers.md#runtime-cast-helpers).
8
+
9
+ ## `ReadonlySome<T, K>`
10
+
11
+ Marks only the properties named in `K` as `readonly`; every other property is
12
+ untouched.
13
+
14
+ ```ts
15
+ import type { ReadonlySome } from 'ts-gems';
16
+
17
+ interface Row {
18
+ id: number;
19
+ name: string;
20
+ }
21
+
22
+ type LockedId = ReadonlySome<Row, 'id'>;
23
+ // { readonly id: number; name: string }
24
+ ```
25
+
26
+ ## `DeepReadonly<T>`
27
+
28
+ Makes every property `readonly`, recursively into nested objects. Array-typed
29
+ properties are left exactly as-is — use `DeeperReadonly` to also make array
30
+ elements readonly.
31
+
32
+ ```ts
33
+ import type { DeepReadonly } from 'ts-gems';
34
+
35
+ interface Config {
36
+ name: string;
37
+ server: { host: string; port: number };
38
+ tags: string[];
39
+ }
40
+
41
+ type FrozenConfig = DeepReadonly<Config>;
42
+ // {
43
+ // readonly name: string;
44
+ // readonly server: { readonly host: string; readonly port: number };
45
+ // readonly tags: string[]; // untouched - it's an array
46
+ // }
47
+ ```
48
+
49
+ ## `DeeperReadonly<T>`
50
+
51
+ Like `DeepReadonly`, but also makes the element type of array-typed
52
+ properties readonly. Tuples are left untouched.
53
+
54
+ ```ts
55
+ import type { DeeperReadonly } from 'ts-gems';
56
+
57
+ interface Config {
58
+ servers: { host: string }[];
59
+ range: [number, number]; // tuple
60
+ }
61
+
62
+ type FrozenConfig = DeeperReadonly<Config>;
63
+ // {
64
+ // readonly servers: { readonly host: string }[];
65
+ // readonly range: [number, number]; // tuple - unchanged
66
+ // }
67
+ ```
68
+
69
+ > Note: `DeeperReadonly` makes the **property** and the **element type**
70
+ > readonly; it does not turn the array itself into a `readonly T[]` — push/
71
+ > pop on the array reference is still possible unless you also apply
72
+ > `Readonly<...>` around the whole property yourself. This also means that
73
+ > if the _input_ property was already a `readonly T[]`, `DeeperReadonly`
74
+ > normalizes it back down to a plain `T[]` wrapper (only the element type's
75
+ > readonly-ness is guaranteed, not the wrapper's).
76
+
77
+ ## `ReadonlyKeys<T>`
78
+
79
+ Returns the union of property names in `T` that **are** `readonly`
80
+ (`keyof PickReadonly<T>`).
81
+
82
+ ```ts
83
+ import type { ReadonlyKeys } from 'ts-gems';
84
+
85
+ interface Row {
86
+ readonly id: number;
87
+ name: string;
88
+ }
89
+
90
+ type Locked = ReadonlyKeys<Row>; // 'id'
91
+ ```
92
+
93
+ ## `PickReadonly<T>` / `OmitReadonly<T>`
94
+
95
+ Pick (or omit) only the properties that **are** `readonly`.
96
+
97
+ ```ts
98
+ import type { OmitReadonly, PickReadonly } from 'ts-gems';
99
+
100
+ interface Row {
101
+ readonly id: number;
102
+ name: string;
103
+ }
104
+
105
+ type OnlyReadonly = PickReadonly<Row>; // { readonly id: number }
106
+ type OnlyMutable = OmitReadonly<Row>; // { name: string }
107
+ ```
108
+
109
+ ## `DeepPickReadonly<T>` / `DeepOmitReadonly<T>`
110
+
111
+ The deep versions of `PickReadonly`/`OmitReadonly` — nested objects are
112
+ filtered the same way, recursively.
113
+
114
+ ```ts
115
+ import type { DeepOmitReadonly } from 'ts-gems';
116
+
117
+ interface Row {
118
+ readonly id: number;
119
+ meta: { readonly createdBy: string; label: string };
120
+ }
121
+
122
+ type Editable = DeepOmitReadonly<Row>;
123
+ // { meta: { label: string } }
124
+ ```
125
+
126
+ A mutable property is always kept by `DeepOmitReadonly`, no matter its value
127
+ type — including `null`:
128
+
129
+ ```ts
130
+ type I1 = { a: null; readonly b: null };
131
+ type Result = DeepOmitReadonly<I1>; // { a: null }
132
+ ```
133
+
134
+ ## `DeeperPickReadonly<T>` / `DeeperOmitReadonly<T>`
135
+
136
+ Like `DeepPickReadonly`/`DeepOmitReadonly`, but also recurses into array
137
+ elements. Tuples are preserved as-is (never split into a homogeneous array).
138
+
139
+ ```ts
140
+ import type { DeeperOmitReadonly } from 'ts-gems';
141
+
142
+ interface Row {
143
+ readonly id: number;
144
+ tags: { readonly createdBy: string; label: string }[];
145
+ point: readonly [number, number];
146
+ }
147
+
148
+ type Editable = DeeperOmitReadonly<Row>;
149
+ // { tags: { label: string }[]; point: readonly [number, number] }
150
+ ```
@@ -0,0 +1,146 @@
1
+ # Required
2
+
3
+ Source: [`lib/required.d.ts`](../../lib/required.d.ts)
4
+
5
+ Makes properties required (and strips `undefined` from their type). See
6
+ [the `Deep*`/`Deeper*` convention](../api.md#the-deep--deeper-convention) and
7
+ the runtime [`asRequired` cast helpers](helpers.md#runtime-cast-helpers).
8
+
9
+ ## `RequiredSome<T, K>`
10
+
11
+ Marks only the properties named in `K` as required; the rest of `T` is left
12
+ exactly as declared.
13
+
14
+ ```ts
15
+ import type { RequiredSome } from 'ts-gems';
16
+
17
+ interface UpdateUserInput {
18
+ name?: string;
19
+ email?: string;
20
+ }
21
+
22
+ type CreateUserInput = RequiredSome<UpdateUserInput, 'name'>;
23
+ // { name: string; email?: string }
24
+ ```
25
+
26
+ ## `DeepRequired<T>`
27
+
28
+ Like the built-in `Required<T>`, but also makes nested object properties
29
+ required, recursively. Array-typed properties are left as-is.
30
+
31
+ ```ts
32
+ import type { DeepRequired } from 'ts-gems';
33
+
34
+ interface Config {
35
+ name?: string;
36
+ server?: { host?: string; port?: number };
37
+ tags?: string[];
38
+ }
39
+
40
+ type FullConfig = DeepRequired<Config>;
41
+ // {
42
+ // name: string;
43
+ // server: { host: string; port: number };
44
+ // tags: string[]; // required now, but its element type is untouched
45
+ // }
46
+ ```
47
+
48
+ ## `DeeperRequired<T>`
49
+
50
+ Like `DeepRequired`, but also makes array element properties required.
51
+ Tuples are left untouched.
52
+
53
+ ```ts
54
+ import type { DeeperRequired } from 'ts-gems';
55
+
56
+ interface Config {
57
+ servers?: { host?: string }[];
58
+ range?: [number, number]; // tuple
59
+ }
60
+
61
+ type FullConfig = DeeperRequired<Config>;
62
+ // {
63
+ // servers: { host: string }[];
64
+ // range: [number, number]; // tuple - unchanged
65
+ // }
66
+ ```
67
+
68
+ `DeeperRequired` keeps its array-awareness through every level of nesting —
69
+ an array several objects deep is still processed, not just one at the top:
70
+
71
+ ```ts
72
+ interface Nested {
73
+ a?: { b?: { c?: number }[] };
74
+ }
75
+
76
+ type Result = DeeperRequired<Nested>;
77
+ // { a: { b: { c: number }[] } }
78
+ ```
79
+
80
+ ## `RequiredKeys<T>`
81
+
82
+ Returns the union of property names in `T` that are required
83
+ (`keyof PickRequired<T>`).
84
+
85
+ ```ts
86
+ import type { RequiredKeys } from 'ts-gems';
87
+
88
+ interface Row {
89
+ id: number;
90
+ nickname?: string;
91
+ }
92
+
93
+ type Required = RequiredKeys<Row>; // 'id'
94
+ ```
95
+
96
+ ## `PickRequired<T>` / `OmitRequired<T>`
97
+
98
+ Pick (or omit) only the properties that are required.
99
+
100
+ ```ts
101
+ import type { OmitRequired, PickRequired } from 'ts-gems';
102
+
103
+ interface Row {
104
+ id: number;
105
+ nickname?: string;
106
+ }
107
+
108
+ type OnlyRequired = PickRequired<Row>; // { id: number }
109
+ type OnlyOptional = OmitRequired<Row>; // { nickname?: string }
110
+ ```
111
+
112
+ ## `DeepPickRequired<T>` / `DeepOmitRequired<T>`
113
+
114
+ The deep versions of `PickRequired`/`OmitRequired`. A property is only ever
115
+ recursed into when **it is itself optional** — a required property is
116
+ dropped by `DeepOmitRequired` outright, without inspecting what's inside it
117
+ (there would be nothing to "omit the required parts of", since the whole
118
+ property is required in the first place).
119
+
120
+ ```ts
121
+ import type { DeepOmitRequired } from 'ts-gems';
122
+
123
+ interface Row {
124
+ id: number; // required -> dropped entirely
125
+ meta?: { label?: string; note: string }; // optional -> kept & recursed
126
+ }
127
+
128
+ type Optional = DeepOmitRequired<Row>;
129
+ // { meta?: { label?: string } } - `note` was required, so it's dropped too
130
+ ```
131
+
132
+ ## `DeeperPickRequired<T>` / `DeeperOmitRequired<T>`
133
+
134
+ Like `DeepPickRequired`/`DeepOmitRequired`, but also recurses into array
135
+ elements. Tuples are preserved as-is.
136
+
137
+ ```ts
138
+ import type { DeeperOmitRequired } from 'ts-gems';
139
+
140
+ interface Row {
141
+ tags?: { label?: string; note: string }[];
142
+ }
143
+
144
+ type Optional = DeeperOmitRequired<Row>;
145
+ // { tags?: { label?: string }[] }
146
+ ```
@@ -0,0 +1,246 @@
1
+ # Type Guards
2
+
3
+ Source: [`lib/type-check.d.ts`](../../lib/type-check.d.ts)
4
+
5
+ Every type below follows the same shape:
6
+
7
+ ```ts
8
+ type IfX<T, Y = true, N = false> = /* ... */;
9
+ ```
10
+
11
+ It checks a condition about `T` and resolves to `Y` when the condition holds,
12
+ `N` otherwise. `Y`/`N` default to the literal types `true`/`false`, which lets
13
+ you use them directly as compile-time assertions:
14
+
15
+ ```ts
16
+ import type { IfNever } from 'ts-gems';
17
+
18
+ type Check = IfNever<never>; // true
19
+ type Check2 = IfNever<string>; // false
20
+ ```
21
+
22
+ ...or pass real types through `Y`/`N` to build a conditional transform:
23
+
24
+ ```ts
25
+ type OrString<T> = IfNever<T, string, T>; // replace `never` with `string`
26
+ ```
27
+
28
+ ## The `*OrAny` variants
29
+
30
+ `IfTupleOrAny`, `IfPrimitiveOrAny`, `IfObjectOrAny`, `IfFunctionOrAny`, and
31
+ `IfClassOrAny` are convenience wrappers that treat `any` as an automatic
32
+ match, since `any` would otherwise fail every structural check:
33
+
34
+ ```ts
35
+ type IfXOrAny<T, Y = true, N = false> =
36
+ IfAny<T> extends true ? Y : IfX<T, Y, N>;
37
+ ```
38
+
39
+ ```ts
40
+ import type { IfObject, IfObjectOrAny } from 'ts-gems';
41
+
42
+ type A = IfObject<any>; // false - `any` doesn't structurally match "object"
43
+ type B = IfObjectOrAny<any>; // true
44
+ ```
45
+
46
+ ## `IfAny<T, Y, N>`
47
+
48
+ Detects the `any` type specifically — distinct from `unknown`, `never`, and
49
+ every other type. Uses the well-known `0 extends 1 & T` trick, since `any` is
50
+ the only type where this holds.
51
+
52
+ ```ts
53
+ import type { IfAny } from 'ts-gems';
54
+
55
+ type A = IfAny<any>; // true
56
+ type B = IfAny<unknown>; // false
57
+ type C = IfAny<never>; // false
58
+ type D = IfAny<string>; // false
59
+ ```
60
+
61
+ ## `IfNever<T, Y, N>`
62
+
63
+ ```ts
64
+ import type { IfNever } from 'ts-gems';
65
+
66
+ type A = IfNever<never>; // true
67
+ type B = IfNever<undefined>; // false
68
+ type C = IfNever<any>; // false
69
+ ```
70
+
71
+ ## `IfUndefined<T, Y, N>` / `IfNull<T, Y, N>` / `IfUnknown<T, Y, N>`
72
+
73
+ Exact-type checks for `undefined`, `null`, and `unknown` respectively, each
74
+ implemented via [`IfEquals`](#ifequalst1-t2-y-n).
75
+
76
+ ```ts
77
+ import type { IfNull, IfUndefined, IfUnknown } from 'ts-gems';
78
+
79
+ type A = IfUndefined<undefined>; // true
80
+ type B = IfUndefined<null>; // false
81
+
82
+ type C = IfNull<null>; // true
83
+ type D = IfNull<undefined>; // false
84
+
85
+ type E = IfUnknown<unknown>; // true
86
+ type F = IfUnknown<any>; // false - `any` is not `unknown`
87
+ ```
88
+
89
+ ## `IfNullish<T, Y, N>`
90
+
91
+ `true` for `null` **or** `undefined`.
92
+
93
+ ```ts
94
+ import type { IfNullish } from 'ts-gems';
95
+
96
+ type A = IfNullish<null>; // true
97
+ type B = IfNullish<undefined>; // true
98
+ type C = IfNullish<0>; // false
99
+ ```
100
+
101
+ ## `IfSymbol<T, Y, N>`
102
+
103
+ `true` only for the general `symbol` type — **not** for a specific `unique
104
+ symbol` (e.g. the type of a `const x = Symbol('x')`), which is a distinct,
105
+ narrower type.
106
+
107
+ ```ts
108
+ import type { IfSymbol } from 'ts-gems';
109
+
110
+ const uniqueSym = Symbol('x');
111
+
112
+ type A = IfSymbol<symbol>; // true
113
+ type B = IfSymbol<typeof uniqueSym>; // false - a *specific* unique symbol
114
+ type C = IfSymbol<string>; // false
115
+ ```
116
+
117
+ ## `IfTuple<T, Y, N>`
118
+
119
+ `true` for a fixed-length tuple (`[string, number]`, `[]`, ...); `false` for
120
+ a variable-length array (`string[]`) or anything else. Distinguishes the two
121
+ by checking whether `T['length']` is a literal number (tuple) or the general
122
+ `number` type (array).
123
+
124
+ ```ts
125
+ import type { IfTuple } from 'ts-gems';
126
+
127
+ type A = IfTuple<[string, number]>; // true
128
+ type B = IfTuple<[]>; // true
129
+ type C = IfTuple<string[]>; // false
130
+ type D = IfTuple<any>; // false
131
+ ```
132
+
133
+ > This is the type that decides, throughout the whole library, whether a
134
+ > tuple gets left untouched by `Deeper*` transforms instead of being
135
+ > collapsed into a homogeneous array — see [the `Deep*`/`Deeper*`
136
+ > convention](../api.md#the-deep--deeper-convention).
137
+
138
+ ## `IfPrimitive<T, Y, N>`
139
+
140
+ `true` for any [`Primitive`](types.md#primitive): `string | number | bigint |
141
+ boolean | symbol | null | undefined`, plus `any`. `false` for objects,
142
+ arrays, functions, and classes.
143
+
144
+ ```ts
145
+ import type { IfPrimitive } from 'ts-gems';
146
+
147
+ type A = IfPrimitive<string>; // true
148
+ type B = IfPrimitive<null>; // true
149
+ type C = IfPrimitive<{}>; // false
150
+ type D = IfPrimitive<Function>; // false
151
+ ```
152
+
153
+ ## `IfEmptyObject<T, Y, N>`
154
+
155
+ `true` only for the exact type `{}`.
156
+
157
+ ```ts
158
+ import type { IfEmptyObject } from 'ts-gems';
159
+
160
+ type A = IfEmptyObject<{}>; // true
161
+ type B = IfEmptyObject<{ x: 1 }>; // false
162
+ type C = IfEmptyObject<undefined>; // false
163
+ ```
164
+
165
+ ## `IfObject<T, Y, N>`
166
+
167
+ `true` for anything structurally an `object` that isn't a primitive,
168
+ `Function`, or array (arrays get their own check via [`IfTuple`](#iftuplet-y-n)
169
+ or a plain `T[]` test elsewhere in the library).
170
+
171
+ ```ts
172
+ import type { IfObject } from 'ts-gems';
173
+
174
+ type A = IfObject<{ x: 1 }>; // true
175
+ type B = IfObject<object>; // true
176
+ type C = IfObject<any[]>; // false
177
+ type D = IfObject<Function>; // false
178
+ type E = IfObject<null>; // false
179
+ ```
180
+
181
+ ## `IfFunction<T, Y, N>`
182
+
183
+ `true` for any function type (including arrow functions), `false` for a
184
+ constructor/class reference (see [`IfClass`](#ifclasst-y-n) for that case).
185
+
186
+ ```ts
187
+ import type { IfFunction } from 'ts-gems';
188
+
189
+ class Foo {}
190
+
191
+ type A = IfFunction<() => void>; // true
192
+ type B = IfFunction<(a: string) => boolean>; // true
193
+ type C = IfFunction<typeof Foo>; // false - a constructor, not a plain function
194
+ type D = IfFunction<Foo>; // false - an instance
195
+ ```
196
+
197
+ ## `IfClass<T, Y, N>`
198
+
199
+ `true` when `T` is a constructor/class reference — i.e. it matches
200
+ [`Type<any>`](types.md#typet--any) (`new (...args: any[]) => any`).
201
+
202
+ ```ts
203
+ import type { IfClass } from 'ts-gems';
204
+
205
+ class Foo {}
206
+ function bar() {}
207
+
208
+ type A = IfClass<typeof Foo>; // true
209
+ type B = IfClass<typeof bar>; // false
210
+ type C = IfClass<Foo>; // false - an instance is not a constructor
211
+ ```
212
+
213
+ ## `IfEquals<T1, T2, Y, N>`
214
+
215
+ Exact, deeply-structural type equality — the classic
216
+ [Matt McCutchen conditional-type trick](https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650),
217
+ extended in this library to also compare object shapes recursively. Two
218
+ types are equal only if they are mutually and exactly assignable, including
219
+ modifiers (`?`, `readonly`).
220
+
221
+ ```ts
222
+ import type { IfEquals } from 'ts-gems';
223
+
224
+ type A = IfEquals<number, number>; // true
225
+ type B = IfEquals<number, number | string>; // false
226
+ type C = IfEquals<{ x: 1 }, { x?: 1 }>; // false - optionality differs
227
+ type D = IfEquals<any, any>; // true
228
+ type E = IfEquals<any, unknown>; // false
229
+ ```
230
+
231
+ ## `IfCompatible<T1, T2, Y, N>`
232
+
233
+ A looser, one- or two-way _assignability_ check (unlike `IfEquals`, which
234
+ requires exact equality). Used internally to compare function parameter/return
235
+ compatibility, and handles `any`/`unknown`/`never`/`null`/`undefined`
236
+ specially so they behave as you'd expect at the call site rather than by
237
+ strict structural rules.
238
+
239
+ ```ts
240
+ import type { IfCompatible } from 'ts-gems';
241
+
242
+ type A = IfCompatible<number, number | string>; // true
243
+ type B = IfCompatible<any, number>; // true
244
+ type C = IfCompatible<{}, number>; // false
245
+ type D = IfCompatible<() => void, (a: string) => void>; // true
246
+ ```