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 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.2.0",
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.0.4",
41
- "xo": "^0.55.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://img.shields.io/badge/unicorn-approved-ff69b4.svg)](https://giphy.com/gifs/illustration-rainbow-unicorn-26AHG5KGFxSkUWw1i)
61
61
  [![npm dependents](https://badgen.net/npm/dependents/type-fest)](https://www.npmjs.com/package/type-fest?activeTab=dependents)
62
62
  [![npm downloads](https://badgen.net/npm/dt/type-fest)](https://www.npmjs.com/package/type-fest)
63
- [![Docs](https://paka.dev/badges/v0/cute.svg)](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
- - [`Opaque`](source/opaque.d.ts) - Create an [opaque type](https://codemix.com/opaque-types-in-javascript/).
133
- - [`UnwrapOpaque`](source/opaque.d.ts) - Revert an [opaque type](https://codemix.com/opaque-types-in-javascript/) back to its original type.
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
- // Goverment didn't like the fact that you wanted to change your identity.
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 burining!');
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
- type ConditionalPickDeepSymbol = Opaque<symbol, 'conditional-pick-deep-symbol'>;
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
- : ConditionalPickDeepSymbol;
102
- }, (ConditionalPickDeepSymbol | undefined) | Record<PropertyKey, never>>>;
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
- // For Opaque types, internal details are hidden from public, so let's leave it as is.
60
- : ParameterType extends Opaque<infer OpaqueType, infer OpaqueToken> ? ParameterType
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;
@@ -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> = Opaque<Type, (argument: Type) => 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>;
@@ -1,17 +1,25 @@
1
1
  declare const tag: unique symbol;
2
2
 
3
- declare type Tagged<Token> = {
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
- Create an opaque type, which hides its internal details from the public, and can only be created by being used explicitly.
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
- The generic type parameter can be anything. It doesn't have to be an object.
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 opaque types.](https://codemix.com/opaque-types-in-javascript/)
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 this feature to TypeScript via the `opaque type` operator, similar to how Flow does it. Unfortunately, nothing has (yet) moved forward:
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 not compile successfully.
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 & Tagged<Token>;
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 UnwrapOpaque<OpaqueType extends Tagged<unknown>> =
105
- OpaqueType extends Opaque<infer Type, OpaqueType[typeof tag]>
106
- ? Type
107
- : OpaqueType;
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;