Skip to content
8 changes: 8 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './sour
export type {UndefinedOnPartialDeep} from './source/undefined-on-partial-deep.d.ts';
export type {ReadonlyDeep} from './source/readonly-deep.d.ts';
export type {LiteralUnion} from './source/literal-union.d.ts';
export type {ExtractLiterals} from './source/extract-literals.js';
export type {Promisable} from './source/promisable.d.ts';
export type {Arrayable} from './source/arrayable.d.ts';
export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged.d.ts';
Expand Down Expand Up @@ -122,6 +123,13 @@ export type {
IsBooleanLiteral,
IsSymbolLiteral,
} from './source/is-literal.d.ts';
export type {
IsPrimitive,
IsStringPrimitive,
IsNumericPrimitive,
IsBooleanPrimitive,
IsSymbolPrimitive,
} from './source/is-primitive.d.ts';
export type {IsAny} from './source/is-any.d.ts';
export type {IfAny} from './source/if-any.d.ts';
export type {IsNever} from './source/is-never.d.ts';
Expand Down
6 changes: 6 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Click the type names for complete docs.
- [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`.
- [`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.
- [`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).
- [`ExtractLiterals`](source/literal-of.d.ts) - Creates a union of the literal members from a given union type, removing wide primitive or infinite types, and optionally specify the Strictness.
- [`Tagged`](source/tagged.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) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/tagged.d.ts) type, which is now deprecated.)
- [`UnwrapTagged`](source/tagged.d.ts) - Get the untagged portion of a tagged type created with `Tagged`. (This replaces the previous [`UnwrapOpaque`](source/tagged.d.ts) type, which is now deprecated.)
- [`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.
Expand Down Expand Up @@ -189,10 +190,15 @@ Click the type names for complete docs.

- [`If`](source/if.d.ts) - An if-else-like type that resolves depending on whether the given `boolean` type is `true` or `false`.
- [`IsLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean).
- [`IsStringLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsNumericLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsBooleanLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsSymbolLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
- [`IsStringPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `string` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean).
- [`IsNumericPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `number` or `bigint` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean).
- [`IsBooleanPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `true` or `false` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean).
- [`IsSymbolPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `symbol` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean).
- [`IsAny`](source/is-any.d.ts) - Returns a boolean for whether the given type is `any`.
- [`IsNever`](source/is-never.d.ts) - Returns a boolean for whether the given type is `never`.
- [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`.
Expand Down
97 changes: 97 additions & 0 deletions source/extract-literals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type {ApplyDefaultOptions} from './internal/object.js';
import type {UnwrapTagged, TagContainer} from './tagged.js';
import type {IsPrimitive} from './is-primitive.js';
import type {IsLiteral} from './is-literal.js';
import type {Primitive} from './primitive.js';
import type {Not} from './internal/type.js';

/**
ExtactLiterals options.

@see {@link ExtactLiterals}
*/
export type ExtractLiteralsOptions = {
/**
Whether to remove infinite signature types (e.g., `abc${string}`).

@default true
*/
strict?: boolean;
};

export type DefaultExtractLiteralsOptions = {
strict: true;
};

/**
Creates a union of the literal members from a given union type, removing wide primitive or infinite types.

This utility helps you extract only the literal members from a "literal union" type
(e.g., `'foo' | 'bar' | string` becomes `'foo' | 'bar'`), saving you from defining separate types for literals and unions.

It works with all primitive and tagged types, and supports two strictness modes:

- **Strict mode (`Strict = true`)**: Removes any infinite signature type (e.g., `abc${string}`, `123${number}`).
- **Non-strict mode (`Strict = false`)**: Removes only wide primitive types (e.g., `string`, `number`).

@default true

@example
```ts
import type { ExtractLiterals } from 'type-fest';

// String example:
type Pet = LiteralUnion<'dog' | 'cat' | `${string}Dog`, string>;
// ^? type Pet = 'dog' | 'cat' | `${string}Dog` | (string & {})
type PetLiteralStrict = ExtractLiterals<Pet>;
// ^? type PetLiteralNonStrict = 'dog' | 'cat'
type PetLiteralNonStrict = ExtractLiterals<Pet, false>;
// ^? type PetLiteralStrict = 'dog' | 'cat' | `${string}Dog`

// Number example:
type Nums = LiteralUnion<0 | 1 | 2, number>;
// ^? type Nums = 0 | 1 | 2 | (number & {})
type NumsLiteral = ExtractLiterals<Nums>;
// ^? type NumsLiteral = 0 | 1 | 2

// Symbol example:
declare const sym1: unique symbol;
declare const sym2: unique symbol;
type Symbols = LiteralUnion<typeof sym1 | typeof sym2, symbol>;
// ^? type Symbols = typeof sym1 | typeof sym2 | (symbol & {})
type SymbolsLiteral = ExtractLiterals<Symbols>;
// ^? type SymbolsLiteral = typeof sym1 | typeof sym2

// BigInt example:
type Big = LiteralUnion<1n | 2n, bigint>;
// ^? type Big = 1n | 2n | (bigint & {})
type BigLiteral = ExtractLiterals<Big>;
// ^? type BigLiteral = 1n | 2n

```

@author benzaria
@see LiteralUnion
@category Type
*/
export type ExtractLiterals<
LiteralUnion extends Primitive,
Options extends ExtractLiteralsOptions = {},
> = LiteralUnion extends infer Member
? ((
Member extends TagContainer<any>
? UnwrapTagged<Member>
: Member
) extends infer Type
? ApplyDefaultOptions<
ExtractLiteralsOptions,
DefaultExtractLiteralsOptions,
Options
>['strict'] extends true
? Not<IsLiteral<Type>>
: IsPrimitive<Type>
: never
) extends true
? never
: Member
: never;
44 changes: 19 additions & 25 deletions source/internal/type.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type {If} from '../if.js';
import type {IsAny} from '../is-any.d.ts';
import type {IsNever} from '../is-never.d.ts';
import type {Primitive} from '../primitive.d.ts';
import type {IsNever} from '../is-never.d.ts';
import type {IsAny} from '../is-any.d.ts';
import type {And} from '../and.js';
import type {If} from '../if.js';

/**
Matches any primitive, `void`, `Date`, or `RegExp` value.
Expand All @@ -13,14 +14,24 @@ Matches non-recursive types.
*/
export type NonRecursiveType = BuiltIns | Function | (new (...arguments_: any[]) => unknown);

/**
Checks if one type extends another. Note: this is not quite the same as `Left extends Right` because:
1. If either type is `never`, the result is `true` iff the other type is also `never`.
2. Types are wrapped in a 1-tuple so that union types are not distributed - instead we consider `string | number` to _not_ extend `number`. If we used `Left extends Right` directly you would get `Extends<string | number, number>` => `false | true` => `boolean`.
*/
export type Extends<Left, Right> = IsNever<Left> extends true ? IsNever<Right> : [Left] extends [Right] ? true : false;

/**
Returns a boolean for whether the two given types extends the base type.
*/
export type IsBothExtends<BaseType, FirstType, SecondType> = FirstType extends BaseType
? SecondType extends BaseType
? true
: false
: false;
export type IsBothExtends<BaseType, FirstType, SecondType> = (
IsNotFalse<
And<
Extends<FirstType, BaseType>,
Extends<SecondType, BaseType>
>
>
);

/**
Test if the given function has multiple call signatures.
Expand All @@ -44,23 +55,6 @@ Returns a boolean for whether the given `boolean` is not `false`.
*/
export type IsNotFalse<T extends boolean> = [T] extends [false] ? false : true;

/**
Returns a boolean for whether the given type is primitive value or primitive type.

@example
```
IsPrimitive<'string'>
//=> true

IsPrimitive<string>
//=> true

IsPrimitive<Object>
//=> false
```
*/
export type IsPrimitive<T> = [T] extends [Primitive] ? true : false;

/**
Returns a boolean for whether A is false.

Expand Down
59 changes: 35 additions & 24 deletions source/is-literal.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type {Extends, IsNotFalse, Not} from './internal/type.d.ts';
import type {CollapseLiterals} from './internal/object.d.ts';
import type {TagContainer, UnwrapTagged} from './tagged.js';
import type {Primitive} from './primitive.d.ts';
import type {Numeric} from './numeric.d.ts';
import type {CollapseLiterals, IfNotAnyOrNever, IsNotFalse, IsPrimitive} from './internal/index.d.ts';
import type {IsNever} from './is-never.d.ts';
import type {TagContainer, UnwrapTagged} from './tagged.js';
import type {Numeric} from './numeric.d.ts';
import type {And} from './and.js';

/**
Returns a boolean for whether the given type `T` is the specified `LiteralType`.
Expand All @@ -24,11 +26,10 @@ LiteralCheck<1, string>
type LiteralCheck<T, LiteralType extends Primitive> = (
IsNever<T> extends false // Must be wider than `never`
? [T] extends [LiteralType & infer U] // Remove any branding
? [U] extends [LiteralType] // Must be narrower than `LiteralType`
? [LiteralType] extends [U] // Cannot be wider than `LiteralType`
? false
: true
: false
? And<
Extends<U, LiteralType>, // Must be narrower than `LiteralType`
Not<Extends<LiteralType, U>> // Cannot be wider than `LiteralType`
>
: false
: false
);
Expand All @@ -52,7 +53,8 @@ type LiteralChecks<T, LiteralUnionType> = (
// Conditional type to force union distribution.
// If `T` is none of the literal types in the union `LiteralUnionType`, then `LiteralCheck<T, LiteralType>` will evaluate to `false` for the whole union.
// If `T` is one of the literal types in the union, it will evaluate to `boolean` (i.e. `true | false`)
IsNotFalse<LiteralUnionType extends Primitive
IsNotFalse<
LiteralUnionType extends Primitive
? LiteralCheck<T, LiteralUnionType>
: never
>
Expand Down Expand Up @@ -111,22 +113,24 @@ type L2 = Length<`${number}`>;
//=> number
```

@see IsStringPrimitive
@category Type Guard
@category Utilities
*/
export type IsStringLiteral<S> = IfNotAnyOrNever<S,
_IsStringLiteral<CollapseLiterals<S extends TagContainer<any> ? UnwrapTagged<S> : S>>,
false, false>;

export type _IsStringLiteral<S> =
// If `T` is an infinite string type (e.g., `on${string}`), `Record<T, never>` produces an index signature,
// and since `{}` extends index signatures, the result becomes `false`.
S extends string
? {} extends Record<S, never>
? false
: true
export type IsStringLiteral<T> = IsNever<T> extends false
? _IsStringLiteral<CollapseLiterals<T extends TagContainer<any> ? UnwrapTagged<T> : T>>
: false;

export type _IsStringLiteral<S> = (
// If `T` is an infinite string type (e.g., `on${string}`), `Record<T, never>` produces an index signature,
// and since `{}` extends index signatures, the result becomes `false`.
S extends string
? {} extends Record<S, never>
? false
: true
: false
);

/**
Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).

Expand Down Expand Up @@ -170,10 +174,13 @@ endsWith('abc123', end);
//=> boolean
```

@see IsNumericPrimitive
@category Type Guard
@category Utilities
*/
export type IsNumericLiteral<T> = LiteralChecks<T, Numeric>;
export type IsNumericLiteral<T> = T extends Numeric
? LiteralChecks<T, Numeric>
: false;

/**
Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).
Expand Down Expand Up @@ -210,6 +217,7 @@ const eitherId = getId({asString: runtimeBoolean});
//=> number | string
```

@see IsBooleanPrimitive
@category Type Guard
@category Utilities
*/
Expand Down Expand Up @@ -245,6 +253,7 @@ get({[symbolValue]: 1} as const, symbolValue);
//=> number
```

@see IsSymbolPrimitive
@category Type Guard
@category Utilities
*/
Expand Down Expand Up @@ -291,10 +300,12 @@ stripLeading(str, 'abc');
//=> string
```

@see IsPrimitive
@category Type Guard
@category Utilities
*/
export type IsLiteral<T> =
IsPrimitive<T> extends true
export type IsLiteral<T> = (
Extends<T, Primitive> extends true
? IsNotFalse<IsLiteralUnion<T>>
: false;
: false
);
Loading