type-fest 4.2.0 → 4.3.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/index.d.ts +2 -1
- package/package.json +3 -3
- package/readme.md +9 -5
- package/source/conditional-pick-deep.d.ts +3 -4
- package/source/exact.d.ts +3 -3
- package/source/invariant-of.d.ts +3 -1
- package/source/non-empty-object.d.ts +35 -0
- package/source/opaque.d.ts +110 -12
package/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './source/observable-like';
|
|
|
6
6
|
|
|
7
7
|
// Utilities
|
|
8
8
|
export type {EmptyObject, IsEmptyObject} from './source/empty-object';
|
|
9
|
+
export type {NonEmptyObject} from './source/non-empty-object';
|
|
9
10
|
export type {UnknownRecord} from './source/unknown-record';
|
|
10
11
|
export type {Except} from './source/except';
|
|
11
12
|
export type {TaggedUnion} from './source/tagged-union';
|
|
@@ -26,7 +27,7 @@ export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './sour
|
|
|
26
27
|
export type {ReadonlyDeep} from './source/readonly-deep';
|
|
27
28
|
export type {LiteralUnion} from './source/literal-union';
|
|
28
29
|
export type {Promisable} from './source/promisable';
|
|
29
|
-
export type {Opaque, UnwrapOpaque} from './source/opaque';
|
|
30
|
+
export type {Opaque, UnwrapOpaque, Tagged, UnwrapTagged} from './source/opaque';
|
|
30
31
|
export type {InvariantOf} from './source/invariant-of';
|
|
31
32
|
export type {SetOptional} from './source/set-optional';
|
|
32
33
|
export type {SetReadonly} from './source/set-readonly';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "type-fest",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "A collection of essential TypeScript types",
|
|
5
5
|
"license": "(MIT OR CC0-1.0)",
|
|
6
6
|
"repository": "sindresorhus/type-fest",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"@sindresorhus/tsconfig": "~0.7.0",
|
|
38
38
|
"expect-type": "^0.15.0",
|
|
39
39
|
"tsd": "^0.28.1",
|
|
40
|
-
"typescript": "^5.
|
|
41
|
-
"xo": "^0.
|
|
40
|
+
"typescript": "^5.2.2",
|
|
41
|
+
"xo": "^0.56.0"
|
|
42
42
|
},
|
|
43
43
|
"xo": {
|
|
44
44
|
"rules": {
|
package/readme.md
CHANGED
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
[](https://giphy.com/gifs/illustration-rainbow-unicorn-26AHG5KGFxSkUWw1i)
|
|
61
61
|
[](https://www.npmjs.com/package/type-fest?activeTab=dependents)
|
|
62
62
|
[](https://www.npmjs.com/package/type-fest)
|
|
63
|
-
[](https://paka.dev/npm/type-fest)
|
|
64
63
|
|
|
65
64
|
Many of the types here should have been built-in. You can help by suggesting some of them to the [TypeScript project](https://github.com/Microsoft/TypeScript/blob/main/CONTRIBUTING.md).
|
|
66
65
|
|
|
@@ -110,6 +109,7 @@ Click the type names for complete docs.
|
|
|
110
109
|
|
|
111
110
|
- [`EmptyObject`](source/empty-object.d.ts) - Represents a strictly empty plain object, the `{}` value.
|
|
112
111
|
- [`IsEmptyObject`](source/empty-object.d.ts) - Returns a `boolean` for whether the type is strictly equal to an empty plain object, the `{}` value.
|
|
112
|
+
- [`NonEmptyObject`](source/non-empty-object.d.ts) - Represents an object with at least 1 non-optional key.
|
|
113
113
|
- [`UnknownRecord`](source/unknown-record.d.ts) - Represents an object with `unknown` value. You probably want this instead of `{}`.
|
|
114
114
|
- [`Except`](source/except.d.ts) - Create a type from an object type without certain keys. This is a stricter version of [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys).
|
|
115
115
|
- [`Writable`](source/writable.d.ts) - Create a type that strips `readonly` from all or some of an object's keys. The inverse of `Readonly<T>`.
|
|
@@ -129,8 +129,10 @@ Click the type names for complete docs.
|
|
|
129
129
|
- [`PartialOnUndefinedDeep`](source/partial-on-undefined-deep.d.ts) - Create a deep version of another type where all keys accepting `undefined` type are set to optional.
|
|
130
130
|
- [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly<T>`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) if you only need one level deep.
|
|
131
131
|
- [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729).
|
|
132
|
-
- [`
|
|
133
|
-
- [`
|
|
132
|
+
- [`Tagged`](source/opaque.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) if needed.
|
|
133
|
+
- [`UnwrapTagged`](source/opaque.d.ts) - Get the untagged portion of a tagged type created with `Tagged`.
|
|
134
|
+
- [`Opaque`](source/opaque.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d). This implementation only supports a single tag.
|
|
135
|
+
- [`UnwrapOpaque`](source/opaque.d.ts) - Get the untagged portion of a tagged type created with `Opaque` or `Tagged`.
|
|
134
136
|
- [`InvariantOf`](source/invariant-of.d.ts) - Create an [invariant type](https://basarat.gitbook.io/typescript/type-system/type-compatibility#footnote-invariance), which is a type that does not accept supertypes and subtypes.
|
|
135
137
|
- [`SetOptional`](source/set-optional.d.ts) - Create a type that makes the given keys optional.
|
|
136
138
|
- [`SetReadonly`](source/set-readonly.d.ts) - Create a type that makes the given keys readonly.
|
|
@@ -293,6 +295,8 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
|
|
|
293
295
|
- `RecordDeep`- See [`Schema`](https://github.com/sindresorhus/type-fest/blob/main/source/schema.d.ts)
|
|
294
296
|
- `Mutable`- See [`Writable`](https://github.com/sindresorhus/type-fest/blob/main/source/writable.d.ts)
|
|
295
297
|
- `Prettify`- See [`Simplify`](https://github.com/sindresorhus/type-fest/blob/main/source/simplify.d.ts)
|
|
298
|
+
- `RequireOnlyOne` - See [`RequireExactlyOne`](https://github.com/sindresorhus/type-fest/blob/main/source/require-exactly-one.d.ts)
|
|
299
|
+
- `AtMostOne` - See [`RequireOneOrNone`](https://github.com/sindresorhus/type-fest/blob/main/source/require-one-or-none.d.ts)
|
|
296
300
|
|
|
297
301
|
## Tips
|
|
298
302
|
|
|
@@ -600,7 +604,7 @@ There are many advanced types most users don't know about.
|
|
|
600
604
|
// Cool, we're fine with that.
|
|
601
605
|
changePersonData(andrew, 'name', 'Pony');
|
|
602
606
|
|
|
603
|
-
//
|
|
607
|
+
// Government didn't like the fact that you wanted to change your identity.
|
|
604
608
|
changePersonData(andrew, ID, uniqueId());
|
|
605
609
|
```
|
|
606
610
|
</details>
|
|
@@ -716,7 +720,7 @@ There are many advanced types most users don't know about.
|
|
|
716
720
|
}
|
|
717
721
|
|
|
718
722
|
const articleCache = new InstanceCache(ArticleModel);
|
|
719
|
-
const amazonArticle = articleCache.getInstance('Amazon forests
|
|
723
|
+
const amazonArticle = articleCache.getInstance('Amazon forests burning!');
|
|
720
724
|
```
|
|
721
725
|
</details>
|
|
722
726
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {Opaque} from './opaque';
|
|
2
1
|
import type {IsEqual} from './is-equal';
|
|
3
2
|
import type {ConditionalExcept} from './conditional-except';
|
|
4
3
|
import type {ConditionalSimplifyDeep} from './conditional-simplify';
|
|
@@ -6,7 +5,7 @@ import type {ConditionalSimplifyDeep} from './conditional-simplify';
|
|
|
6
5
|
/**
|
|
7
6
|
Used to mark properties that should be excluded.
|
|
8
7
|
*/
|
|
9
|
-
|
|
8
|
+
declare const conditionalPickDeepSymbol: unique symbol;
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
Assert the condition according to the {@link ConditionalPickDeepOptions.condition|condition} option.
|
|
@@ -98,5 +97,5 @@ export type ConditionalPickDeep<
|
|
|
98
97
|
? Type[Key]
|
|
99
98
|
: Type[Key] extends object
|
|
100
99
|
? ConditionalPickDeep<Type[Key], Condition, Options>
|
|
101
|
-
:
|
|
102
|
-
}, (
|
|
100
|
+
: typeof conditionalPickDeepSymbol;
|
|
101
|
+
}, (typeof conditionalPickDeepSymbol | undefined) | Record<PropertyKey, never>>>;
|
package/source/exact.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {KeysOfUnion, ArrayElement, ObjectValue} from './internal';
|
|
2
|
-
import type {Opaque} from './opaque';
|
|
2
|
+
import type {Opaque, TagContainer} from './opaque';
|
|
3
3
|
import type {IsEqual} from './is-equal';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -56,7 +56,7 @@ export type Exact<ParameterType, InputType> =
|
|
|
56
56
|
: ParameterType extends unknown[] ? Array<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
|
|
57
57
|
// In TypeScript, Array is a subtype of ReadonlyArray, so always test Array before ReadonlyArray.
|
|
58
58
|
: ParameterType extends readonly unknown[] ? ReadonlyArray<Exact<ArrayElement<ParameterType>, ArrayElement<InputType>>>
|
|
59
|
-
//
|
|
60
|
-
: ParameterType extends
|
|
59
|
+
// Leave tagged types as-is. We could try to make the untagged part Exact, and just leave the tag as-is, but that seems to create instanitation excessively deep errors.
|
|
60
|
+
: ParameterType extends TagContainer<unknown> ? ParameterType
|
|
61
61
|
: ParameterType extends object ? ExactObject<ParameterType, InputType>
|
|
62
62
|
: ParameterType;
|
package/source/invariant-of.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type {Opaque} from './opaque';
|
|
2
2
|
|
|
3
|
+
declare const invariantBrand: unique symbol;
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
Create an [invariant type](https://basarat.gitbook.io/typescript/type-system/type-compatibility#footnote-invariance), which is a type that does not accept supertypes and subtypes.
|
|
5
7
|
|
|
@@ -73,4 +75,4 @@ keyOfInvariantFooBar(invariantOf(fooBarBaz)); // Error: Argument of type 'Invari
|
|
|
73
75
|
|
|
74
76
|
@category Type
|
|
75
77
|
*/
|
|
76
|
-
export type InvariantOf<Type> =
|
|
78
|
+
export type InvariantOf<Type> = Type & {[invariantBrand]: (_: Type) => Type};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {HasRequiredKeys} from './has-required-keys';
|
|
2
|
+
import type {RequireAtLeastOne} from './require-at-least-one';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Represents an object with at least 1 non-optional key.
|
|
6
|
+
|
|
7
|
+
This is useful when you need an object where all keys are optional, but there must be at least 1 key.
|
|
8
|
+
|
|
9
|
+
@example
|
|
10
|
+
```
|
|
11
|
+
import type {NonEmptyObject} from 'type-fest';
|
|
12
|
+
|
|
13
|
+
type User = {
|
|
14
|
+
name: string;
|
|
15
|
+
surname: string;
|
|
16
|
+
id: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type UpdateRequest<Entity extends object> = NonEmptyObject<Partial<Entity>>;
|
|
20
|
+
|
|
21
|
+
const update1: UpdateRequest<User> = {
|
|
22
|
+
name: 'Alice',
|
|
23
|
+
surname: 'Acme',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// At least 1 key is required, therefore this will report a 2322 error:
|
|
27
|
+
// Type '{}' is not assignable to type 'UpdateRequest<User>'
|
|
28
|
+
const update2: UpdateRequest<User> = {};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
@see Use `IsEmptyObject` to check whether an object is empty.
|
|
32
|
+
|
|
33
|
+
@category Object
|
|
34
|
+
*/
|
|
35
|
+
export type NonEmptyObject<T extends object> = HasRequiredKeys<T> extends true ? T : RequireAtLeastOne<T, keyof T>;
|
package/source/opaque.d.ts
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
declare const tag: unique symbol;
|
|
2
2
|
|
|
3
|
-
declare type
|
|
3
|
+
declare type TagContainer<Token> = {
|
|
4
4
|
readonly [tag]: Token;
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
type MultiTagContainer<Token extends PropertyKey> = {
|
|
8
|
+
readonly [tag]: {[K in Token]: void};
|
|
9
|
+
};
|
|
10
|
+
|
|
7
11
|
/**
|
|
8
|
-
|
|
12
|
+
Attach a "tag" to an arbitrary type. This allows you to create distinct types, that aren't assignable to one another, for runtime values that would otherwise have the same type. (See examples.)
|
|
13
|
+
|
|
14
|
+
The generic type parameters can be anything.
|
|
15
|
+
|
|
16
|
+
Note that `Opaque` is somewhat of a misnomer here, in that, unlike [some alternative implementations](https://github.com/microsoft/TypeScript/issues/4895#issuecomment-425132582), the original, untagged type is not actually hidden. (E.g., functions that accept the untagged type can still be called with the "opaque" version -- but not vice-versa.)
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
Also note that this implementation is limited to a single tag. If you want to allow multiple tags, use `Tagged` instead.
|
|
11
19
|
|
|
12
|
-
[Read more about
|
|
20
|
+
[Read more about tagged types.](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d)
|
|
13
21
|
|
|
14
|
-
There have been several discussions about adding
|
|
22
|
+
There have been several discussions about adding similar features to TypeScript. Unfortunately, nothing has (yet) moved forward:
|
|
15
23
|
- [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
|
|
16
24
|
- [Microsoft/TypeScript#15408](https://github.com/Microsoft/TypeScript/issues/15408)
|
|
17
25
|
- [Microsoft/TypeScript#15807](https://github.com/Microsoft/TypeScript/issues/15807)
|
|
@@ -59,7 +67,7 @@ getMoneyForAccount(2);
|
|
|
59
67
|
// You can use opaque values like they aren't opaque too.
|
|
60
68
|
const accountNumber = createAccountNumber();
|
|
61
69
|
|
|
62
|
-
// This will
|
|
70
|
+
// This will compile successfully.
|
|
63
71
|
const newAccountNumber = accountNumber + 2;
|
|
64
72
|
|
|
65
73
|
// As a side note, you can (and should) use recursive types for your opaque types to make them stronger and hopefully easier to type.
|
|
@@ -71,10 +79,10 @@ type Person = {
|
|
|
71
79
|
|
|
72
80
|
@category Type
|
|
73
81
|
*/
|
|
74
|
-
export type Opaque<Type, Token = unknown> = Type &
|
|
82
|
+
export type Opaque<Type, Token = unknown> = Type & TagContainer<Token>;
|
|
75
83
|
|
|
76
84
|
/**
|
|
77
|
-
Revert an opaque type back to its original type by removing the readonly `[tag]`.
|
|
85
|
+
Revert an opaque or tagged type back to its original type by removing the readonly `[tag]`.
|
|
78
86
|
|
|
79
87
|
Why is this necessary?
|
|
80
88
|
|
|
@@ -97,11 +105,101 @@ const money = moneyByAccountType.SAVINGS; // TS error: Property 'SAVINGS' does n
|
|
|
97
105
|
|
|
98
106
|
// Attempting to pass an non-Opaque type to UnwrapOpaque will raise a type error.
|
|
99
107
|
type WontWork = UnwrapOpaque<string>;
|
|
108
|
+
|
|
109
|
+
// Using a Tagged type will work too.
|
|
110
|
+
type WillWork = UnwrapOpaque<Tagged<number, 'AccountNumber'>>; // number
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
@category Type
|
|
114
|
+
*/
|
|
115
|
+
export type UnwrapOpaque<OpaqueType extends TagContainer<unknown>> =
|
|
116
|
+
OpaqueType extends MultiTagContainer<string | number | symbol>
|
|
117
|
+
? RemoveAllTags<OpaqueType>
|
|
118
|
+
: OpaqueType extends Opaque<infer Type, OpaqueType[typeof tag]>
|
|
119
|
+
? Type
|
|
120
|
+
: OpaqueType;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
Attach a "tag" to an arbitrary type. This allows you to create distinct types, that aren't assignable to one another, for runtime values that would otherwise have the same type. (See examples.)
|
|
124
|
+
|
|
125
|
+
A type returned by `Tagged` can be passed to `Tagged` again, to create a type with multiple tags.
|
|
126
|
+
|
|
127
|
+
[Read more about tagged types.](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d)
|
|
128
|
+
|
|
129
|
+
There have been several discussions about adding similar features to TypeScript. Unfortunately, nothing has (yet) moved forward:
|
|
130
|
+
- [Microsoft/TypeScript#202](https://github.com/microsoft/TypeScript/issues/202)
|
|
131
|
+
- [Microsoft/TypeScript#4895](https://github.com/microsoft/TypeScript/issues/4895)
|
|
132
|
+
- [Microsoft/TypeScript#33290](https://github.com/microsoft/TypeScript/pull/33290)
|
|
133
|
+
|
|
134
|
+
@example
|
|
135
|
+
```
|
|
136
|
+
import type {Tagged} from 'type-fest';
|
|
137
|
+
|
|
138
|
+
type AccountNumber = Tagged<number, 'AccountNumber'>;
|
|
139
|
+
type AccountBalance = Tagged<number, 'AccountBalance'>;
|
|
140
|
+
|
|
141
|
+
function createAccountNumber(): AccountNumber {
|
|
142
|
+
// As you can see, casting from a `number` (the underlying type being tagged) is allowed.
|
|
143
|
+
return 2 as AccountNumber;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance {
|
|
147
|
+
return 4 as AccountBalance;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// This will compile successfully.
|
|
151
|
+
getMoneyForAccount(createAccountNumber());
|
|
152
|
+
|
|
153
|
+
// But this won't, because it has to be explicitly passed as an `AccountNumber` type!
|
|
154
|
+
getMoneyForAccount(2);
|
|
155
|
+
|
|
156
|
+
// You can use opaque values like they aren't opaque too.
|
|
157
|
+
const accountNumber = createAccountNumber();
|
|
158
|
+
|
|
159
|
+
// This will compile successfully.
|
|
160
|
+
const newAccountNumber = accountNumber + 2;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
@category Type
|
|
164
|
+
*/
|
|
165
|
+
export type Tagged<Type, Tag extends PropertyKey> = Type & MultiTagContainer<Tag>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
Revert a tagged type back to its original type by removing the readonly `[tag]`.
|
|
169
|
+
|
|
170
|
+
Why is this necessary?
|
|
171
|
+
|
|
172
|
+
1. Use a `Tagged` type as object keys
|
|
173
|
+
2. Prevent TS4058 error: "Return type of exported function has or is using name X from external module Y but cannot be named"
|
|
174
|
+
|
|
175
|
+
@example
|
|
176
|
+
```
|
|
177
|
+
import type {Tagged, UnwrapTagged} from 'type-fest';
|
|
178
|
+
|
|
179
|
+
type AccountType = Tagged<'SAVINGS' | 'CHECKING', 'AccountType'>;
|
|
180
|
+
|
|
181
|
+
const moneyByAccountType: Record<UnwrapTagged<AccountType>, number> = {
|
|
182
|
+
SAVINGS: 99,
|
|
183
|
+
CHECKING: 0.1
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Without UnwrapTagged, the following expression would throw a type error.
|
|
187
|
+
const money = moneyByAccountType.SAVINGS; // TS error: Property 'SAVINGS' does not exist
|
|
188
|
+
|
|
189
|
+
// Attempting to pass an non-Tagged type to UnwrapTagged will raise a type error.
|
|
190
|
+
type WontWork = UnwrapTagged<string>;
|
|
100
191
|
```
|
|
101
192
|
|
|
102
193
|
@category Type
|
|
103
194
|
*/
|
|
104
|
-
export type
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
195
|
+
export type UnwrapTagged<TaggedType extends MultiTagContainer<PropertyKey>> =
|
|
196
|
+
RemoveAllTags<TaggedType>;
|
|
197
|
+
|
|
198
|
+
type RemoveAllTags<T> = T extends MultiTagContainer<infer ExistingTags>
|
|
199
|
+
? {
|
|
200
|
+
[ThisTag in ExistingTags]:
|
|
201
|
+
T extends Tagged<infer Type, ThisTag>
|
|
202
|
+
? RemoveAllTags<Type>
|
|
203
|
+
: never
|
|
204
|
+
}[ExistingTags]
|
|
205
|
+
: T;
|