diff --git a/readme.md b/readme.md index ff3840c2b..8db535e69 100644 --- a/readme.md +++ b/readme.md @@ -61,6 +61,7 @@ Click the type names for complete docs. - [`JsonArray`](source/basic.d.ts) - Matches a JSON array. - [`JsonValue`](source/basic.d.ts) - Matches any valid JSON value. - [`ObservableLike`](source/basic.d.ts) - Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable). +- [`Opaque`](source/basic.d.ts) - Creates an [opaque type](https://codemix.com/opaque-types-in-javascript/). ### Utilities diff --git a/source/basic.d.ts b/source/basic.d.ts index 5969ce59c..17e4b9958 100644 --- a/source/basic.d.ts +++ b/source/basic.d.ts @@ -65,3 +65,44 @@ export interface ObservableLike { subscribe(observer: (value: unknown) => void): void; [Symbol.observable](): ObservableLike; } + +/** +Creates an opaque type, which hides its internal details from the public, and can only be created by being used explicitly. + +The generic type parameter can be anything. It doesn't have to be an object. + +Read more about opaque types [here](https://codemix.com/opaque-types-in-javascript/). + +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: + 1. [](https://github.com/Microsoft/TypeScript/issues/15408) + 2. [](https://github.com/Microsoft/TypeScript/issues/15807) + +@example +``` +import {Opaque} from 'type-fest'; + +type AccountNumber = Opaque; +type AccountBalance = Opaque; + +function createAccountNumber(): AccountNumber { + return 2 as AccountNumber; +} + +function getMoneyForAccount(accountNumber: AccountNumber): AccountBalance { + return 4 as AccountBalance; +} + +// This will compile successfully: +getMoneyForAccount(createAccountNumber()); + +// But this won't, because it has to be explicitly passed as an AccountNumber. +getMoneyForAccount(2); + +// You can use opaque values like they aren't opaque too, like this: +const accountNumber = createAccountNumber(); + +// This will compile successfully: +accountNumber + 2; +``` + */ +type Opaque = Type & {readonly __opaque__: unique symbol}; diff --git a/test-d/opaque.ts b/test-d/opaque.ts new file mode 100644 index 000000000..44c161f3a --- /dev/null +++ b/test-d/opaque.ts @@ -0,0 +1,13 @@ +import {expectType} from 'tsd'; +import {Opaque} from '..'; + +type Value = Opaque; + +// We make an explicit cast so we can test the value. +const value: Value = 2 as Value; + +// Every opaque type should have a private symbol member, so the compiler can differentiate separate opaque types. +expectType(value.__opaque__); + +// The underlying type of the value is still a number. +expectType(value);