UNPKG

@strapi/types

Version:

Shared typescript types for Strapi internal use

490 lines • 21.5 kB
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