@strapi/types
Version:
Shared typescript types for Strapi internal use
490 lines • 21.5 kB
TypeScript
import type { Array, Constants, Pretty } from '.';
/**
* The `IsNever` type checks if a given type {@link TValue} strictly equals to `never`.
*
* @template TValue - The type variable to be checked against `never`.
*
* @example
* type A = IsNever<number>; // This will resolve to 'false' because number is not `never`
* type B = IsNever<Cast<'foo', number>>; // This will resolve to 'true' because Cast<'foo', number> strictly equals to never.
*
* @see {@link StrictEqual} - The Type used internally to make the comparison.
* @remark
* Please make sure to understand the difference between `never` and other types in TypeScript before using `IsNever` for any conditional checks
*/
export type IsNever<TValue> = StrictEqual<TValue, never>;
/**
* The `IsNotNever` type checks if a given type {@link TValue} does not strictly equals to `never`.
*
* It is useful in conditional types to verify if the variable of type {@link TValue} is something other than `never`.
* It complements the {@link IsNever} type by negating the result using the {@link Not} utility type.
*
* @template TValue - The type variable to be checked for inequality against `never`.
*
* @example
* type IsNotNeverNumber = IsNotNever<number>; // Evaluates to 'true' because number is not 'never'.
* type IsNotNeverNever = IsNotNever<never>; // Evaluates to 'false' because `never` equals to 'never'.
*
* @see {@link IsNever} - The type used internally to check if {@link TValue} is `never`.
*/
export type IsNotNever<TValue> = Not<IsNever<TValue>>;
/**
* The `IsTrue` type evaluates if the given {@link TValue} strictly equals {@link Constants.True}.
*
* @template TValue - The type to evaluate.
*
* @example
* type A = IsTrue<true>; // This will resolve to Constants.True
* type B = IsTrue<false>; // This will resolve to Constants.False
*/
export type IsTrue<TValue> = [TValue] extends [Constants.True] ? Constants.True : Constants.False;
/**
* The `IsNotTrue` type evaluates if the given {@link TValue} is not strictly equal to {@link Constants.True}.
*
* It basically negates the output of {@link IsTrue}.
*
* @template TValue - The type to evaluate.
*
* @example
* type A = IsNotTrue<true>; // This will resolve to Constants.False
* type B = IsNotTrue<false>; // This will resolve to Constants.True
*
*/
export type IsNotTrue<TValue> = Not<IsTrue<TValue>>;
/**
* The `IsFalse` type evaluates if the given {@link TValue} strictly equals {@link Constants.False}.
*
* @template TValue - The type to evaluate.
*
* @example
* type A = IsFalse<true>; // This will resolve to Constants.False
* type B = IsFalse<false>; // This will resolve to Constants.True
*/
export type IsFalse<TValue> = [TValue] extends [Constants.False] ? Constants.True : Constants.False;
/**
* The `IsNotFalse` type evaluates if the value provided does not strictly equal {@link Constants.False}.
*
* It basically negates the output of {@link IsFalse}.
*
* @template TValue - The type to be evaluated.
*
* @example
* type A = IsNotFalse<false>; // This will resolve to Constants.False
* type B = IsNotFalse<true>; // This will resolve to Constants.True
*/
export type IsNotFalse<TValue> = Not<IsFalse<TValue>>;
/**
* The `StrictEqual` type evaluates if two types, {@link TValue} and {@link TMatch}, are strictly the same.
*
* In other words, it checks if {@link TValue} extends {@link TMatch} and if {@link TMatch} extends {@link TValue} at the same time,
* hence ensuring complete type match.
*
* @template TValue - The first type to be compared.
* @template TMatch - The second type to be compared.
*
* @returns Either {@link Constants.True} or {@link Constants.False}.
*
* @example
* // With a regular extends
* type A = "string" extends string ? true : false; // Result: true
*
* // With `StrictEqual`
* type B = StrictEqual<"string", string>; // Result: false
*/
export type StrictEqual<TValue, TMatch> = And<Extends<TValue, TMatch>, Extends<TMatch, TValue>>;
/**
* The `NotStrictEqual` type is a utility type that checks if two types, {@link TValue} and {@link TMatch}, are different using strict equality comparison.
*
*
* @template TValue - The first type to be compared.
* @template TMatch - The second type to be compared against the first one.
*
* @returns Either {@link Constants.True} or {@link Constants.False}
*
* @see {@link StrictEqual}
*
* @example
* // Comparing basic types
* type BasicTypeCheck = NotStrictEqual<number, string>; // Result: Constants.True (because `number` and `string` types are not the same)
*
* // Comparing complex types
* type MyType = { a: number, b: string };
* type OtherType = { a: number, c: boolean };
* type ComplexTypeCheck = NotStrictEqual<MyType, OtherType>; // Result: Constants.True (because `MyType` and `OtherType` do not have the same structure)
*
*/
export type NotStrictEqual<TValue, TMatch> = Not<StrictEqual<TValue, TMatch>>;
/**
* The `Extends` type evaluates if a type, identified by {@link TLeft}, extends another one, identified by {@link TRight}.
*
* @template TLeft - The type to be tested if it extends {@link TRight}.
* @template TRight - The base type used for comparison.
*
* @note To understand more about conditional types and the `extends` keyword in TypeScript see {@link https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types}
*
* @remark
* The check `[TLeft] extends [TRight]` is wrapped in a tuple because TypeScript's `extends` behaves differently with unions in the context of distributivity.
*
* Wrapping in a tuple deactivates this distributive behavior and makes the check behave as expected in all cases.
*
* @example
* The type `"hello"` is a subtype of `string` so it extends `string`
* ```typescript
* type isString = Extends<"hello", string>;
* // output: Constants.True
*```
*
* The type `string` does not extend `"hello"`.
* ```typescript
* type notSpecificString = Extends<string, "hello">;
* // output: Constants.False
* ```
*/
export type Extends<TLeft, TRight> = [TLeft] extends [TRight] ? Constants.True : Constants.False;
/**
* The `DoesNotExtends` type checks if {@link TLeft} does not extend {@link TRight}.
*
* @template TLeft - The type to be tested if it does not extend {@link TRight}.
* @template TRight - The base type used for comparing if it is not extended by {@link TLeft}.
*
* @see {@link Extends}
*/
export type DoesNotExtends<TLeft, TRight> = Not<Extends<TLeft, TRight>>;
/**
* The `Not` type defines a type-level boolean negation operation.
*
* More concretely, if {@link TExpression} strictly equates to {@link Constants.True} then the result of `Not<TExpression>` would be {@link Constants.False}, and vice versa.
*
* @template TExpression - The type level boolean expression to be negated. It should extend {@link Constants.BooleanValue}.
*
* @see {@link Constants.BooleanValue}
*
* @example
* ```typescript
* // Using expression that equates to `true`
* type A = Not<Constants.True>; // Results in Constants.False
*
* // Using `true` wrapped inside another type
* type B = Not<IsTrue<true>>; // Results in Constants.False
*
* // Using expression that equates to `false`
* type C = Not<Constants.False>; // Results in Constants.True
*
* // Using `false` wrapped inside another type
* type D = Not<IsFalse<false>>; // Results in Constants.True
* ```
*/
export type Not<TExpression extends Constants.BooleanValue> = If<TExpression, Constants.False, Constants.True>;
/**
* The `If` type is a conditional type that accepts a type level boolean expression (`true` or `false` represented as {@link Constants.BooleanValue}),
* and two result types, one if the expression is {@link Constants.True} and the other if it's {@link Constants.False}.
*
* It's an implementation of the traditional 'if/then/else' logic, but at the type level.
*
* @template TExpression - The type level boolean expression to evaluate. It should extend {@link Constants.BooleanValue}.
* @template TOnTrue - The type returned if {@link TExpression} resolves to {@link Constants.True}.
* @template TOnFalse - The type returned if {@link TExpression} resolves to {@link Constants.False}. It defaults to `never`.
*
* @example
* Here's an example using `If` with {@link TExpression} that's resolved to {@link Constants.False}.
*
* As a result, the type applied will be 'FalseCase'.
* ```typescript
* type FalseCase = 'This is False';
* type TrueCase = 'This is True';
*
* type Result = If<Constants.False, TrueCase, FalseCase>; // Result: 'This is False'
* ```
*
* Conversely, if we replace {@link TExpression} with {@link Constants.True}, the applicable type will be 'TrueCase'.
* ```typescript
* type ExampleTrue = If<Constants.True, TrueCase, FalseCase>; // Result: 'This is True case'
* ```
*
* If the third type argument is omitted, and the expression is false, the `If` type will resolve to `never`.
* ```typescript
* type ExampleNever = If<Constants.False, TrueCase>; // Result: never
* ```
*/
export type If<TExpression extends Constants.BooleanValue, TOnTrue, TOnFalse = never> = [
TExpression
] extends [Constants.True] ? TOnTrue : TOnFalse;
/**
* The `MatchFirst` type is a type-level logic that matches the first truthy `Test` in a given array of {@link Test} types
* and resolves to corresponding type value ('TValue') of that {@link Test}.
*
* If no truthy {@link Test} is found, it resolves to a default type {@link TDefault}.
*
* @note This type is particularly useful for checking multiple conditions and matching the type to
* whichever condition proves true first, similar to a switch-case or a series of if-else statements in traditional programming.
*
* @template TTests - An array of {@link Test} types that the type checker will iterate over to find the first truthy test.
* @template TDefault - The default value that will be used if none of the {@link Test} types in {@link TTests} prove true. Defaults to `never`.
*
* @see {@link Test}
* @see {@link MatchAllIntersect}
*
* @example
* Here's an example showing how `MatchFirst` can be used with series of {@link Test} types.
*
* We have declared a Test array containing two Test types.
* - The first Test type checks if 'T' is a string.
*
* If true, it will return 'string type', else it moves to the next Test type.
* - The next Test type checks if 'T' is a number.
*
* If true, it will return 'number type'.
* - The third argument is the default type which would be returned if all the conditions fail. In our case its 'unknown type'.
*
* ```typescript
* type T = string; // you can replace 'T' with 'number' or 'boolean' to test.
*
* type IsString = Test<Extends<T, string>, 'string type'>;
* type IsNumber = Test<Extends<T, number>, 'number type'>;
* type Tests = [IsString, IsNumber];
*
* type Result = MatchFirst<Tests, 'unknown type'>; // Result would be 'string type' as 'T' is string.
* ```
*
*/
export type MatchFirst<TTests extends Test[], TDefault = never> = TTests extends [
infer THead extends Test,
...infer TTail extends Test[]
] ? THead extends Test<infer TExpression, infer TValue> ? If<TExpression, TValue, If<Array.IsNotEmpty<TTail>, MatchFirst<TTail, TDefault>, TDefault>> : never : never;
/**
* The `MatchAllIntersect` type enables the creation of an intersection type from a sequence of conditional types.
*
* It is useful in scenarios where the properties of an object are to be picked conditionally, based on evaluated boolean expressions.
*
* @template TTests - A tuple type where each member extends {@link Test}.
*
* It's this sequence of tests that determine the properties to be picked.
*
* @template TDefault - This type is used whenever a member of `TTests` doesn't match the expected type or when the
* tuple is empty, meaning that no conditions were provided.
*
* This defaults to `unknown`.
*
* @see {@link Test}
* @see {@link MatchFirst}
*
* @example
* ```typescript
* type Test1 = Test<Constants.True, { sort?: string }>;
* type Test2 = Test<Constants.False, { fields?: number[] }>;
* type Test3 = Test<Constants.True, { populate?: string[] }>;
*
* type Result = MatchAllIntersect<[Test1, Test2, Test3]>;
* // The Result will be { sort?: string } & { populate?: string[] }
* ```
*
* In the example above, only Test1 and Test3 resolves to true case and thus the result excludes the type `{ fields?: number[] }`.
*
* There is also a default case `{}` that would be returned if *all* the tests in `TTests` were false.
*
* This can be customized by using the second type parameter {@link TDefault}.
* ```typescript
* type Test3 = Test<Constants.False, { sort?: string }>;
* type Test4 = Test<Constants.False, { fields?: number[] }>;
*
* type ResultDefault = MatchAllIntersect<[Test3, Test4], {}>; // The Result will be {}
* ```
*/
export type MatchAllIntersect<TTests extends Test[], TDefault = unknown> = TTests extends [
infer THead extends Test,
...infer TTail extends Test[]
] ? THead extends Test<infer TExpression, infer TValue> ? // Actual test case evaluation
If<TExpression, TValue, TDefault> & If<Array.IsNotEmpty<TTail>, MatchAllIntersect<TTail, TDefault>, TDefault> : TDefault : TDefault;
/**
* The `Test` type pairs a boolean expression and a corresponding value.
*
* The elements of the type pair are:
* 1. A boolean value ({@link TExpression}), which acts as the conditional expression.
* 2. A corresponding value ({@link TValue}), which is usually returned/read when the conditional expression is `true`.
*
* @template TExpression - A boolean value that will be used as the conditional expression. It extends from {@link Constants.BooleanValue}.
* @template TValue - The corresponding value that will be returned when the `TExpression` is `true`.
*
* @see {@link Constants.BooleanValue}
* @see {@link MatchFirst}
* @see {@link MatchAllIntersect}
*
* @example
* Suppose we're writing a type level function that behaves differently based on whether the generic type parameter extends a string or a number.
*
* We can represent these two conditions using the `Test` type, like this:
*
* ```typescript
* type T = number; // replace this with different types to see the outcome
*
* // Defining two Test conditions
* type IsString = Test<Extends<T, string>, 'Input is a string'>;
* type IsNumber = Test<Extends<T, number>, 'Input is a number'>;
*
* type DetectedType = MatchFirst<[IsString, IsNumber], 'unknown type'>; // The Result will be 'Input is a number'
* ```
*/
export type Test<TExpression extends Constants.BooleanValue = Constants.BooleanValue, TValue = unknown> = [TExpression, TValue];
/**
* The `Some` type is used for performing a boolean OR operation at the type level over all elements of {@link TExpressions}.
*
* The OR operation is applied between every two adjacent types in the array from left to right until a resulting type is derived.
*
* It's conceptually similar to the `Array.prototype.some()` method, but at the type level rather than the value level.
*
* If the array is empty, it returns {@link Constants.False}.
*
* @template TExpressions - An array of types extending {@link Constants.BooleanValue}. Use this to specify the types to apply the OR operation on.
*
* @see {@link Every}
* @see {@link Constants.BooleanValue}
*
*
* @example
* ```typescript
* type Example1 = Some<[Constants.False, Constants.False, Constants.False]>; // Result: Constants.False
* type Example2 = Some<[Constants.False, Constants.True, Constants.False]>; // Result: Constants.True
* type Example3 = Some<[Constants.True, Constants.True, Constants.True]>; // Result: Constants.True
* type Example4 = Some<[Constants.False]>; // Result: Constants.False
* type Example5 = Some<[Constants.True]>; // Result: Constants.True
* type Example6 = Some<[]>; // Result: Constants.False
* ```
*/
export type Some<TExpressions extends Constants.BooleanValue[]> = TExpressions extends [
infer THead extends Constants.BooleanValue,
...infer TTail extends Constants.BooleanValue[]
] ? If<Array.IsNotEmpty<TTail>, Or<THead, Some<TTail>>, Or<THead, false>> : never;
/**
* The `Every` type is used to perform a logical AND operation on a sequence of type-level boolean values represented as {@link Constants.BooleanValue}.
*
* The AND operation is applied between every two adjacent types in the array from left to right until a resulting type is derived.
*
* It's conceptually similar to the `Array.prototype.every()` method, but at the type level rather than the value level.
*
* If the array is empty, it returns {@link Constants.True}.
*
* @template TExpressions - An array of types extending {@link Constants.BooleanValue}. Use this to specify the types to apply the AND operation on.
*
* @example
* ```typescript
* type Example1 = Every<[Constants.False, Constants.False, Constants.False]>; // Result: Constants.False
* type Example2 = Every<[Constants.False, Constants.True, Constants.False]>; // Result: Constants.False
* type Example3 = Every<[Constants.True, Constants.True, Constants.True]>; // Result: Constants.True
* type Example4 = Every<[Constants.False]>; // Result: Constants.False
* type Example5 = Every<[Constants.True]>; // Result: Constants.True
* type Example6 = Every<[]>; // Result: Constants.True
* ```
*
* @see {@link Some}
* @see {@link Constants.BooleanValue}
*/
export type Every<TExpressions extends Constants.BooleanValue[]> = TExpressions extends [
infer THead extends Constants.BooleanValue,
...infer TTail extends Constants.BooleanValue[]
] ? If<Array.IsNotEmpty<TTail>, And<THead, Every<TTail>>, And<THead, Constants.True>> : never;
/**
* The `And` type is a type-level logical conjugation (AND) operator.
*
* It calculates boolean AND operation of {@link IsTrue} derived from the input types {@link TLeft} and {@link TRight}.
*
* @template TLeft - The left hand operand of the AND operation. It should extend {@link Constants.BooleanValue}.
* @template TRight - The right hand operand of the AND operation. It should extend {@link Constants.BooleanValue}.
*
* @see {@link IsTrue}
*
* @example
* ```typescript
* // Constants.True AND Constants.True
* type Example1 = And<Constants.True, Constants.True>; // Result: Constants.True
*
* // Constants.False AND Constants.True
* type Example2 = And<Constants.False, Constants.True>; // Result: Constants.False
*
* // Constants.False AND Constants.False
* type Example3 = And<Constants.False, Constants.False>; // Result: Constants.False
* ```
*/
export type And<TLeft extends Constants.BooleanValue, TRight extends Constants.BooleanValue> = IsTrue<IsTrue<TLeft> | IsTrue<TRight>>;
/**
* The `Or` type is a type-level logical conjugation (OR) operator.
*
* It calculates boolean OR operation of {@link IsTrue} derived from the input types {@link TLeft} and {@link TRight}.
*
* @template TLeft - The left hand operand of the OR operation. It should extend {@link Constants.BooleanValue}.
* @template TRight - The right hand operand of the OR operation. It should extend {@link Constants.BooleanValue}.
*
* @see {@link IsTrue}
*
* @example
* ```typescript
* // Constants.True OR Constants.True
* type Example1 = Or<Constants.True, Constants.True>; // Result: Constants.True
*
* // Constants.False OR Constants.True
* type Example2 = Or<Constants.False, Constants.True>; // Result: Constants.True
*
* // Constants.False OR Constants.False
* type Example3 = Or<Constants.False, Constants.False>; // Result: Constants.False
* ```
*/
export type Or<TLeft extends Constants.BooleanValue, TRight extends Constants.BooleanValue> = Not<IsFalse<IsTrue<TLeft> | IsTrue<TRight>>>;
/**
* The `Intersect` type constructs a new type by intersecting a list of types.
*
* @template TValues - The tuple of types to be intersected extending from `unknown[]`.
*
* @remark This type can easily be replaced by a regular intersection in most scenario.
*
* The main use-case would be when dealing with a list of types of unknown length.
*
* In the codebase, it's used mainly for aesthetics reasons (formatting of type params vs intersection members).
*
* @example
* ```typescript
* // Defining Attribute Options
* interface ConfigurableOption {
* configurable?: boolean;
* }
*
* interface RequiredOption {
* required?: boolean;
* }
*
* // Intersecting Attribute Options
* type AttributeOptions = Intersect<
* [ ConfigurableOption, RequiredOption ]
* >
*
* // Now `AttributeOptions` contains properties from both `ConfigurableOption` and `RequiredOption`.
* ```
*
* @example
* ```typescript
* // Using `Intersect` to define a complete Attribute type
* interface BasicAttribute {
* name: string;
* type: string;
* }
*
* interface AttributeProperties {
* minLength?: number;
* maxLength?: number;
* }
*
* type Attribute = Intersect<[
* BasicAttribute,
* AttributeProperties,
* AttributeOptions
* ]>
*
* // Now, `Attribute` type contains
* // - name and type fields from `BasicAttribute`
* // - minLength and maxLength fields from AttributeProperties
* // - configurable and required fields from `AttributeOptions`
* ```
*/
export type Intersect<TValues extends unknown[]> = TValues extends [
infer THead,
...infer TTail extends unknown[]
] ? Pretty<THead & If<Array.IsNotEmpty<TTail>, Intersect<TTail>, unknown>> : never;
//# sourceMappingURL=expression.d.ts.map