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.
- package/README.md +114 -2
- package/docs/api/combine.md +48 -0
- package/docs/api/dto.md +115 -0
- package/docs/api/helpers.md +97 -0
- package/docs/api/logical.md +48 -0
- package/docs/api/mutable.md +164 -0
- package/docs/api/non-nullable.md +68 -0
- package/docs/api/nullish.md +66 -0
- package/docs/api/omit-never.md +62 -0
- package/docs/api/omit-undefined.md +60 -0
- package/docs/api/omit.md +99 -0
- package/docs/api/opaque.md +56 -0
- package/docs/api/partial.md +135 -0
- package/docs/api/pick.md +115 -0
- package/docs/api/readonly.md +150 -0
- package/docs/api/required.md +146 -0
- package/docs/api/type-check.md +246 -0
- package/docs/api/types.md +238 -0
- package/docs/api.md +113 -0
- package/docs/logo.svg +43 -0
- package/lib/dto.d.ts +14 -12
- package/lib/helpers.d.ts +16 -15
- package/lib/mutable.d.ts +15 -17
- package/lib/non-nullable.d.ts +31 -23
- package/lib/nullish.d.ts +15 -11
- package/lib/omit-never.d.ts +15 -11
- package/lib/omit-undefined.d.ts +12 -10
- package/lib/omit.d.ts +37 -28
- package/lib/partial.d.ts +12 -11
- package/lib/pick.d.ts +46 -38
- package/lib/readonly.d.ts +91 -77
- package/lib/required.d.ts +91 -75
- package/lib/type-check.d.ts +10 -9
- package/package.json +17 -15
package/README.md
CHANGED
|
@@ -1,18 +1,130 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/logo.svg" alt="ts-gems logo" width="200" height="200" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# ts-gems
|
|
2
6
|
|
|
3
7
|
[![NPM Version][npm-image]][npm-url]
|
|
4
8
|
[![NPM Downloads][downloads-image]][downloads-url]
|
|
5
9
|
[![CI Tests][ci-test-image]][ci-test-url]
|
|
6
10
|
|
|
11
|
+
**Think of it as lodash — but every function operates on `types`, not values, and runs at compile time for free.**
|
|
12
|
+
|
|
13
|
+
Quick question: what's the type of `Partial<{ user: { name: string } }>['user']`?
|
|
14
|
+
|
|
15
|
+
If you guessed `{ name?: string }`, that's the intuitive answer — and it's wrong.
|
|
16
|
+
TypeScript's built-in `Partial<T>` only reaches the *first* level. The real
|
|
17
|
+
answer is `{ name: string }` — still fully required underneath, still ready
|
|
18
|
+
to blow up at runtime the moment you try to build one incrementally.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
type Config = { user: { name: string; age: number } };
|
|
22
|
+
|
|
23
|
+
type Shallow = Partial<Config>;
|
|
24
|
+
// { user?: { name: string; age: number } } <- still required inside!
|
|
25
|
+
|
|
26
|
+
type Deep = DeepPartial<Config>;
|
|
27
|
+
// { user?: { name?: string; age?: number } } <- actually usable
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
That one gap is the whole reason this library exists. `Partial`, `Required`,
|
|
31
|
+
`Readonly`, and `Pick`/`Omit` all share the same blind spot: they stop at the
|
|
32
|
+
surface. ts-gems finishes what TypeScript started — and then keeps going into
|
|
33
|
+
places you didn't know needed a type yet.
|
|
34
|
+
|
|
35
|
+
## A few things that might surprise you
|
|
36
|
+
|
|
37
|
+
**Freeze a config tree, arrays included, in one line:**
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import type { DeeperReadonly } from 'ts-gems';
|
|
41
|
+
|
|
42
|
+
type FrozenConfig = DeeperReadonly<{
|
|
43
|
+
servers: { host: string; port: number }[];
|
|
44
|
+
}>;
|
|
45
|
+
// every level is readonly — including inside the array elements
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Turn any interface into an API-safe DTO — no functions, no symbols, recursively:**
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import type { DTO } from 'ts-gems';
|
|
52
|
+
|
|
53
|
+
class User {
|
|
54
|
+
name = '';
|
|
55
|
+
greet() {}
|
|
56
|
+
[Symbol.iterator]() {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type UserResponse = DTO<User>;
|
|
60
|
+
// { name: string } — methods and symbol keys are gone, nested objects too
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Stop mixing up two `number`s that were never meant to meet:**
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import type { Opaque } from 'ts-gems';
|
|
67
|
+
|
|
68
|
+
type UserId = Opaque<number, 'UserId'>;
|
|
69
|
+
type OrderId = Opaque<number, 'OrderId'>;
|
|
70
|
+
|
|
71
|
+
function cancelOrder(id: OrderId) {
|
|
72
|
+
/* ... */
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
declare const userId: UserId;
|
|
76
|
+
cancelOrder(userId); // ✗ compile error — nominally different, even though both are `number`
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Merge types without the intersection trap:**
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
type A = { name: string };
|
|
83
|
+
type B = { name: Date }; // overlapping key, different type
|
|
84
|
+
|
|
85
|
+
type Bad = A & B; // name: string & Date — a type nothing can satisfy. Oops.
|
|
86
|
+
type Good = Combine<A, B>; // { name: string } — A simply wins
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
None of this is magic. It's ~60 small, focused utility types, each solving
|
|
90
|
+
one specific gap — composable, dependency-free, and fully documented with
|
|
91
|
+
runnable examples.
|
|
92
|
+
|
|
93
|
+
## Explore the full toolkit
|
|
94
|
+
|
|
95
|
+
📖 **[Browse the complete API reference →](docs/api.md)**
|
|
96
|
+
|
|
97
|
+
| Category | What it does |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| [`Deep*` / `Deeper*` family](docs/api.md#the-deep--deeper-convention) | `Mutable`, `Readonly`, `Partial`, `Required`, `Nullish` — that finally reach nested objects and arrays |
|
|
100
|
+
| [`DTO` / `PartialDTO` / `PatchDTO`](docs/api/dto.md) | Turn any class or interface into a clean transfer-object shape |
|
|
101
|
+
| [`Pick` / `Omit` family](docs/api/pick.md) | Select by key, by function-vs-data, or by matching value type |
|
|
102
|
+
| [`Opaque`](docs/api/opaque.md) | Nominal typing / branded primitives for TypeScript's structural type system |
|
|
103
|
+
| [`Combine`](docs/api/combine.md) | Merge types without the `&` intersection trap |
|
|
104
|
+
| [20+ type guards](docs/api/type-check.md) | `IfAny`, `IfNever`, `IfEquals`, `IfTuple`, `IfCompatible`, and more, for building your own conditional types |
|
|
105
|
+
| [`And` / `Or`](docs/api/logical.md) | Compile-time boolean logic to combine several guards into one |
|
|
106
|
+
|
|
7
107
|
## Installation
|
|
8
108
|
|
|
9
109
|
```bash
|
|
10
|
-
|
|
110
|
+
npm install ts-gems --save
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { DeepPartial, DTO, Opaque, StrictOmit } from 'ts-gems';
|
|
11
115
|
```
|
|
116
|
+
|
|
117
|
+
Everything is exported from the package root — no sub-path imports, no
|
|
118
|
+
runtime cost. It's types all the way down (with twelve tiny `as*` cast
|
|
119
|
+
helpers thrown in, purely for ergonomics).
|
|
120
|
+
|
|
12
121
|
## Node Compatibility
|
|
122
|
+
|
|
13
123
|
- node >= 16.x
|
|
124
|
+
|
|
14
125
|
## License
|
|
15
|
-
|
|
126
|
+
|
|
127
|
+
ts-gems is available under the [MIT](LICENSE) license.
|
|
16
128
|
|
|
17
129
|
[npm-image]: https://img.shields.io/npm/v/ts-gems
|
|
18
130
|
[npm-url]: https://npmjs.org/package/ts-gems
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Combine
|
|
2
|
+
|
|
3
|
+
Source: [`lib/combine.d.ts`](../../lib/combine.d.ts)
|
|
4
|
+
|
|
5
|
+
## `Combine<T1, T2, T3 = {}, T4 = {}>`
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
type Combine<T1, T2, T3 = {}, T4 = {}> = T1 &
|
|
9
|
+
Omit<T2, keyof T1> &
|
|
10
|
+
Omit<T3, keyof T1 | keyof T2> &
|
|
11
|
+
Omit<T4, keyof T1 | keyof T2 | keyof T3>;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Merges up to four object types into one, **without** merging the types of
|
|
15
|
+
overlapping properties — unlike a plain intersection (`T1 & T2`), which
|
|
16
|
+
would combine both types of a shared key into `T1[K] & T2[K]`, `Combine`
|
|
17
|
+
lets the earliest argument win outright for any key present in more than
|
|
18
|
+
one input.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import type { Combine } from 'ts-gems';
|
|
22
|
+
|
|
23
|
+
interface Base {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Timestamps {
|
|
29
|
+
name: Date; // overlaps with `Base.name` - Base wins
|
|
30
|
+
createdAt: Date;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type Entity = Combine<Base, Timestamps>;
|
|
34
|
+
// { id: string; name: string; createdAt: Date }
|
|
35
|
+
// (compare to `Base & Timestamps`, where `name` would be `string & Date` = `never`)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Precedence follows argument order — `T1` wins over `T2`, which wins over
|
|
39
|
+
`T3`, which wins over `T4`:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
type A = { x: 1 };
|
|
43
|
+
type B = { x: 2; y: 2 };
|
|
44
|
+
type C = { x: 3; y: 3; z: 3 };
|
|
45
|
+
|
|
46
|
+
type Result = Combine<A, B, C>;
|
|
47
|
+
// { x: 1; y: 2; z: 3 }
|
|
48
|
+
```
|
package/docs/api/dto.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# DTO
|
|
2
|
+
|
|
3
|
+
Source: [`lib/dto.d.ts`](../../lib/dto.d.ts)
|
|
4
|
+
|
|
5
|
+
Turns any type into a plain Data Transfer Object shape: strips function
|
|
6
|
+
properties and symbol-keyed properties, and always deep-processes nested
|
|
7
|
+
objects and arrays (there's no shallow/`Deep` variant — `DTO` behaves like
|
|
8
|
+
the `Deeper*` members of [the convention](../api.md#the-deep--deeper-convention)).
|
|
9
|
+
Tuples are left untouched, same as everywhere else in the library.
|
|
10
|
+
|
|
11
|
+
## `DTO<T, X = never>`
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import type { DTO } from 'ts-gems';
|
|
15
|
+
|
|
16
|
+
interface User {
|
|
17
|
+
name: string;
|
|
18
|
+
age?: number;
|
|
19
|
+
greet(): void; // function - removed
|
|
20
|
+
[Symbol.iterator]: () => Iterator<unknown>; // symbol key - removed
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type UserDTO = DTO<User>;
|
|
24
|
+
// { name: string; age?: number }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Nested objects and arrays are always processed, recursively:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
interface Order {
|
|
31
|
+
id: string;
|
|
32
|
+
createdBy: { name: string; greet(): void };
|
|
33
|
+
items: { sku: string; validate(): boolean }[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type OrderDTO = DTO<Order>;
|
|
37
|
+
// {
|
|
38
|
+
// id: string;
|
|
39
|
+
// createdBy: { name: string };
|
|
40
|
+
// items: { sku: string }[];
|
|
41
|
+
// }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### The `X` parameter
|
|
45
|
+
|
|
46
|
+
The second type parameter, `X`, is unioned into **every** remaining
|
|
47
|
+
property's value type (through `NonNullable<T[K] | X>`, applied at every
|
|
48
|
+
nesting level). This is handy for tagging every field of the DTO with a
|
|
49
|
+
shared extra type — for example, a sentinel used to represent "field
|
|
50
|
+
explicitly not selected" in a partial-response GraphQL/RPC layer:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import type { DTO } from 'ts-gems';
|
|
54
|
+
|
|
55
|
+
const NOT_SELECTED = Symbol('not-selected');
|
|
56
|
+
type NotSelected = typeof NOT_SELECTED;
|
|
57
|
+
|
|
58
|
+
interface User {
|
|
59
|
+
name: string;
|
|
60
|
+
age?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type SparseUserDTO = DTO<User, NotSelected>;
|
|
64
|
+
// { name: string | NotSelected; age?: number | NotSelected }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
> Because `X` is combined via `NonNullable<T[K] | X>`, passing `null` or
|
|
68
|
+
> `undefined` as `X` has no visible effect — both are immediately stripped
|
|
69
|
+
> again. Use a non-nullish sentinel (a literal, a branded/[`Opaque`](opaque.md)
|
|
70
|
+
> type, or a `unique symbol` as above) if you need one.
|
|
71
|
+
|
|
72
|
+
## `PartialDTO<T, X = never>`
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
type PartialDTO<T, X = never> = DeeperPartial<DTO<T, X>>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`DTO<T, X>` with every property (including nested ones and array elements)
|
|
79
|
+
made optional via [`DeeperPartial`](partial.md#deeperpartialt) — a common
|
|
80
|
+
shape for "create" endpoint request bodies where every field may be omitted.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import type { PartialDTO } from 'ts-gems';
|
|
84
|
+
|
|
85
|
+
interface User {
|
|
86
|
+
name: string;
|
|
87
|
+
profile: { bio: string };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type CreateUserBody = PartialDTO<User>;
|
|
91
|
+
// { name?: string; profile?: { bio?: string } }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## `PatchDTO<T, X = never>`
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
type PatchDTO<T, X = never> = DeeperNullish<DTO<T, X>>;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`DTO<T, X>` with every property (including nested ones and array elements)
|
|
101
|
+
made optional **and** nullable via [`DeeperNullish`](nullish.md#deepernullisht)
|
|
102
|
+
— a common shape for "patch"/"update" endpoint request bodies, where `null`
|
|
103
|
+
means "clear this field" and omitting the key means "leave it unchanged".
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import type { PatchDTO } from 'ts-gems';
|
|
107
|
+
|
|
108
|
+
interface User {
|
|
109
|
+
name: string;
|
|
110
|
+
profile: { bio: string };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type UpdateUserBody = PatchDTO<User>;
|
|
114
|
+
// { name?: string | null; profile?: { bio?: string | null } | null }
|
|
115
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Helpers
|
|
2
|
+
|
|
3
|
+
Source: [`lib/helpers.d.ts`](../../lib/helpers.d.ts)
|
|
4
|
+
|
|
5
|
+
## `IfNoDeepValue<T>`
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
type IfNoDeepValue<T> = /* ... */; // true | false
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`true` when `T` should be treated as a **leaf** by every `Deep*`/`Deeper*`
|
|
12
|
+
transform in this library — i.e. left untouched instead of being recursed
|
|
13
|
+
into. This is the type that defines "leaf" for [the `Deep*`/`Deeper*`
|
|
14
|
+
convention](../api.md#the-deep--deeper-convention): `true` for `any`, every
|
|
15
|
+
[`Builtin`](types.md#builtin) type, a [tuple](type-check.md#iftuplet-y-n), a
|
|
16
|
+
`Function`, a class/constructor reference, `Map`/`ReadonlyMap`/`WeakMap`/
|
|
17
|
+
`Set`/`ReadonlySet`/`WeakSet`, or any array; `false` for a plain object.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import type { IfNoDeepValue } from 'ts-gems';
|
|
21
|
+
|
|
22
|
+
type A = IfNoDeepValue<string>; // true
|
|
23
|
+
type B = IfNoDeepValue<any>; // true
|
|
24
|
+
type C = IfNoDeepValue<[string, number]>; // true - a tuple
|
|
25
|
+
type D = IfNoDeepValue<Date>; // true
|
|
26
|
+
type E = IfNoDeepValue<Map<string, number>>; // true
|
|
27
|
+
type F = IfNoDeepValue<{ a: string }>; // false - a plain object, gets recursed into
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You'll rarely need this directly unless you're writing your own `Deep*`-style
|
|
31
|
+
recursive type and want it to treat leaves consistently with the rest of the
|
|
32
|
+
library.
|
|
33
|
+
|
|
34
|
+
## `ValuesOf<T>`
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
type ValuesOf<T> = T[keyof T];
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Returns the union of every property value type in `T` — the value-side
|
|
41
|
+
equivalent of `keyof`.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import type { ValuesOf } from 'ts-gems';
|
|
45
|
+
|
|
46
|
+
interface Row {
|
|
47
|
+
a: number;
|
|
48
|
+
b: string;
|
|
49
|
+
c?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type Values = ValuesOf<Row>; // number | string | boolean | undefined
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Runtime cast helpers
|
|
56
|
+
|
|
57
|
+
Twelve tiny runtime functions, declared in
|
|
58
|
+
[`lib/index.d.ts`](../../lib/index.d.ts) and implemented as a no-op identity
|
|
59
|
+
function at runtime (`lib/index.js`). They exist purely so you can _cast_ a
|
|
60
|
+
value's static type without an `as` expression or reaching for a type
|
|
61
|
+
import at the call site:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
declare function asMutable<T>(x: T): Mutable<T>;
|
|
65
|
+
declare function asDeepMutable<T>(x: T): DeepMutable<T>;
|
|
66
|
+
declare function asDeeperMutable<T>(x: T): DeeperMutable<T>;
|
|
67
|
+
|
|
68
|
+
declare function asReadonly<T>(x: T): Readonly<T>;
|
|
69
|
+
declare function asDeepReadonly<T>(x: T): DeepReadonly<T>;
|
|
70
|
+
declare function asDeeperReadonly<T>(x: T): DeeperReadonly<T>;
|
|
71
|
+
|
|
72
|
+
declare function asPartial<T>(x: T): Partial<T>;
|
|
73
|
+
declare function asDeepPartial<T>(x: T): DeepPartial<T>;
|
|
74
|
+
declare function asDeeperPartial<T>(x: T): DeeperPartial<T>;
|
|
75
|
+
|
|
76
|
+
declare function asRequired<T>(x: T): Required<T>;
|
|
77
|
+
declare function asDeepRequired<T>(x: T): DeepRequired<T>;
|
|
78
|
+
declare function asDeeperRequired<T>(x: T): DeeperRequired<T>;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { asMutable } from 'ts-gems';
|
|
83
|
+
|
|
84
|
+
interface Point {
|
|
85
|
+
readonly x: number;
|
|
86
|
+
readonly y: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function move(p: Point) {
|
|
90
|
+
const mutablePoint = asMutable(p); // same object reference, `Mutable<Point>` type
|
|
91
|
+
mutablePoint.x += 1; // ok - no cast needed, and no runtime cost
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Because these are real functions (not just types), they also work well with
|
|
96
|
+
plain JavaScript editors/tools that only understand runtime code — the
|
|
97
|
+
identity behavior at runtime means they're always safe to call.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Logical
|
|
2
|
+
|
|
3
|
+
Source: [`lib/logical.d.ts`](../../lib/logical.d.ts)
|
|
4
|
+
|
|
5
|
+
Compile-time boolean logic over up to six type-level "truthy" values, useful
|
|
6
|
+
when composing several [type guards](type-check.md) into one condition. A
|
|
7
|
+
value counts as "falsy" when it is `undefined`, `null`, or the literal type
|
|
8
|
+
`false`; anything else (including `never`... see below) counts as "truthy".
|
|
9
|
+
|
|
10
|
+
Unused trailing parameters default to the operator's identity value (`true`
|
|
11
|
+
for `And`, `false` for `Or`), so you can pass anywhere from 2 to 6 arguments.
|
|
12
|
+
|
|
13
|
+
## `And<T1, T2, T3?, T4?, T5?, T6?>`
|
|
14
|
+
|
|
15
|
+
`true` only if every provided argument is truthy.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import type { And } from 'ts-gems';
|
|
19
|
+
|
|
20
|
+
type A = And<true, true>; // true
|
|
21
|
+
type B = And<true, false>; // false
|
|
22
|
+
type C = And<true, true, true, true, true, true>; // true
|
|
23
|
+
type D = And<true, never>; // false - `never` counts as falsy here
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
A common use is combining several `If*` guards:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import type { And, IfObject, IfNever } from 'ts-gems';
|
|
30
|
+
|
|
31
|
+
type IsPlainRequiredObject<T> = And<IfObject<T>, IfNever<T, false, true>>;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## `Or<T1, T2, T3?, T4?, T5?, T6?>`
|
|
35
|
+
|
|
36
|
+
`true` if **any** provided argument is truthy.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import type { Or } from 'ts-gems';
|
|
40
|
+
|
|
41
|
+
type A = Or<false, false>; // false
|
|
42
|
+
type B = Or<false, true>; // true
|
|
43
|
+
type C = Or<false, false, false, false, false, true>; // true
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This is the exact building block the [`Pick`](pick.md)/[`Omit`](omit.md)
|
|
47
|
+
family uses internally to combine several exclusion criteria into one key
|
|
48
|
+
filter — e.g. "omit this key if it's `never` **or** if it's a function".
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Mutable
|
|
2
|
+
|
|
3
|
+
Source: [`lib/mutable.d.ts`](../../lib/mutable.d.ts)
|
|
4
|
+
|
|
5
|
+
Strips `readonly` modifiers. See [the `Deep*`/`Deeper*` convention](../api.md#the-deep--deeper-convention)
|
|
6
|
+
for what "deeply" means here, and the runtime [`asMutable` cast
|
|
7
|
+
helpers](helpers.md#runtime-cast-helpers).
|
|
8
|
+
|
|
9
|
+
## `Mutable<T>`
|
|
10
|
+
|
|
11
|
+
Removes `readonly` from every top-level property. Properties whose value is
|
|
12
|
+
`never` (after stripping `undefined`) are dropped, matching the rest of the
|
|
13
|
+
library's "never means absent" convention.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import type { Mutable } from 'ts-gems';
|
|
17
|
+
|
|
18
|
+
interface Point {
|
|
19
|
+
readonly x: number;
|
|
20
|
+
readonly y: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type MutablePoint = Mutable<Point>;
|
|
24
|
+
// { x: number; y: number }
|
|
25
|
+
|
|
26
|
+
const p: MutablePoint = { x: 1, y: 2 };
|
|
27
|
+
p.x = 3; // ok - no longer readonly
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## `MutableSome<T, K>`
|
|
31
|
+
|
|
32
|
+
Removes `readonly` from only the properties named in `K`; every other
|
|
33
|
+
property is untouched.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import type { MutableSome } from 'ts-gems';
|
|
37
|
+
|
|
38
|
+
interface Row {
|
|
39
|
+
readonly id: number;
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly createdAt: Date;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type EditableRow = MutableSome<Row, 'name'>;
|
|
45
|
+
// { readonly id: number; name: string; readonly createdAt: Date }
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## `DeepMutable<T>`
|
|
49
|
+
|
|
50
|
+
Like `Mutable`, but also strips `readonly` from nested object properties.
|
|
51
|
+
Array-typed properties are left exactly as they are (see the convention
|
|
52
|
+
table) — use `DeeperMutable` to also transform array element types.
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import type { DeepMutable } from 'ts-gems';
|
|
56
|
+
|
|
57
|
+
interface Config {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
readonly server: {
|
|
60
|
+
readonly host: string;
|
|
61
|
+
readonly port: number;
|
|
62
|
+
};
|
|
63
|
+
readonly tags: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type MutableConfig = DeepMutable<Config>;
|
|
67
|
+
// {
|
|
68
|
+
// name: string;
|
|
69
|
+
// server: { host: string; port: number };
|
|
70
|
+
// tags: string[]; // untouched - it's an array
|
|
71
|
+
// }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## `DeeperMutable<T>`
|
|
75
|
+
|
|
76
|
+
Like `DeepMutable`, but also makes the element type of array-typed
|
|
77
|
+
properties mutable. Tuples are left untouched (their fixed shape is
|
|
78
|
+
preserved as-is).
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import type { DeeperMutable } from 'ts-gems';
|
|
82
|
+
|
|
83
|
+
interface Config {
|
|
84
|
+
readonly server: { readonly host: string };
|
|
85
|
+
readonly servers: { readonly host: string }[];
|
|
86
|
+
readonly range: readonly [number, number]; // tuple
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type MutableConfig = DeeperMutable<Config>;
|
|
90
|
+
// {
|
|
91
|
+
// server: { host: string };
|
|
92
|
+
// servers: { host: string }[]; // element type made mutable too
|
|
93
|
+
// range: readonly [number, number]; // tuple - unchanged
|
|
94
|
+
// }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## `MutableKeys<T>`
|
|
98
|
+
|
|
99
|
+
Returns the union of property names in `T` that are **not** `readonly`.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import type { MutableKeys } from 'ts-gems';
|
|
103
|
+
|
|
104
|
+
interface Row {
|
|
105
|
+
readonly id: number;
|
|
106
|
+
name: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type Editable = MutableKeys<Row>; // 'name'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## `PickMutable<T>` / `OmitMutable<T>`
|
|
113
|
+
|
|
114
|
+
Pick (or omit) only the properties that are **not** `readonly`. These are
|
|
115
|
+
implemented in terms of [`OmitReadonly`/`PickReadonly`](readonly.md), since
|
|
116
|
+
"mutable" and "readonly" partition every property into exactly two sets.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import type { OmitMutable, PickMutable } from 'ts-gems';
|
|
120
|
+
|
|
121
|
+
interface Row {
|
|
122
|
+
readonly id: number;
|
|
123
|
+
name: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type OnlyMutable = PickMutable<Row>; // { name: string }
|
|
127
|
+
type OnlyReadonly = OmitMutable<Row>; // { readonly id: number }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## `DeepPickMutable<T>` / `DeepOmitMutable<T>`
|
|
131
|
+
|
|
132
|
+
The deep versions of `PickMutable`/`OmitMutable` — nested objects are
|
|
133
|
+
filtered the same way, recursively. Delegates to
|
|
134
|
+
[`DeepOmitReadonly`/`DeepPickReadonly`](readonly.md).
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import type { DeepPickMutable } from 'ts-gems';
|
|
138
|
+
|
|
139
|
+
interface Row {
|
|
140
|
+
readonly id: number;
|
|
141
|
+
meta: { readonly createdBy: string; label: string };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
type Editable = DeepPickMutable<Row>;
|
|
145
|
+
// { meta: { label: string } }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## `DeeperPickMutable<T>` / `DeeperOmitMutable<T>`
|
|
149
|
+
|
|
150
|
+
Like `DeepPickMutable`/`DeepOmitMutable`, but also recurses into array
|
|
151
|
+
elements. Delegates to
|
|
152
|
+
[`DeeperOmitReadonly`/`DeeperPickReadonly`](readonly.md).
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import type { DeeperPickMutable } from 'ts-gems';
|
|
156
|
+
|
|
157
|
+
interface Row {
|
|
158
|
+
readonly id: number;
|
|
159
|
+
tags: { readonly createdBy: string; label: string }[];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
type Editable = DeeperPickMutable<Row>;
|
|
163
|
+
// { tags: { label: string }[] }
|
|
164
|
+
```
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# UnNullish
|
|
2
|
+
|
|
3
|
+
Source: [`lib/non-nullable.d.ts`](../../lib/non-nullable.d.ts)
|
|
4
|
+
|
|
5
|
+
Removes `null` and `undefined` from every property's value type (via
|
|
6
|
+
`NonNullable`), and drops the key entirely if nothing is left afterwards.
|
|
7
|
+
See [the `Deep*`/`Deeper*` convention](../api.md#the-deep--deeper-convention).
|
|
8
|
+
|
|
9
|
+
## `UnNullish<T>`
|
|
10
|
+
|
|
11
|
+
**Shallow** — only top-level properties are un-nullished. Nested `null`/
|
|
12
|
+
`undefined` values, inside an object or array property, are left exactly as
|
|
13
|
+
they are. Use [`DeepUnNullish`](#deepunnullisht) or
|
|
14
|
+
[`DeeperUnNullish`](#deeperunnullisht) to reach into nested structures too.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import type { UnNullish } from 'ts-gems';
|
|
18
|
+
|
|
19
|
+
type MyType = {
|
|
20
|
+
a: string | null;
|
|
21
|
+
b?: number | null;
|
|
22
|
+
nested: { c: string | null } | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type Result = UnNullish<MyType>;
|
|
26
|
+
// {
|
|
27
|
+
// a: string;
|
|
28
|
+
// b?: number;
|
|
29
|
+
// nested: { c: string | null }; // the outer `| null` is gone, the inner one isn't
|
|
30
|
+
// }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## `DeepUnNullish<T>`
|
|
34
|
+
|
|
35
|
+
Like `UnNullish`, but also un-nullishes nested object properties,
|
|
36
|
+
recursively. Array-typed properties are left as-is.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import type { DeepUnNullish } from 'ts-gems';
|
|
40
|
+
|
|
41
|
+
type MyType = {
|
|
42
|
+
nested: { c: string | null } | null;
|
|
43
|
+
list: { c: string | null }[] | null;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type Result = DeepUnNullish<MyType>;
|
|
47
|
+
// {
|
|
48
|
+
// nested: { c: string };
|
|
49
|
+
// list: { c: string | null }[]; // untouched - it's an array
|
|
50
|
+
// }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## `DeeperUnNullish<T>`
|
|
54
|
+
|
|
55
|
+
Like `DeepUnNullish`, but also recurses into array elements. Tuples are
|
|
56
|
+
preserved as-is (their own `| null` is still stripped).
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import type { DeeperUnNullish } from 'ts-gems';
|
|
60
|
+
|
|
61
|
+
type MyType = {
|
|
62
|
+
list: { c: string | null }[] | null;
|
|
63
|
+
pair: [string, number] | null; // tuple
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type Result = DeeperUnNullish<MyType>;
|
|
67
|
+
// { list: { c: string }[]; pair: [string, number] } - tuple untouched
|
|
68
|
+
```
|