diff --git a/packages/core/types/src/utils/array.ts b/packages/core/types/src/utils/array.ts index 5add03e414..c2f94be58a 100644 --- a/packages/core/types/src/utils/array.ts +++ b/packages/core/types/src/utils/array.ts @@ -1,20 +1,84 @@ -import type { Extends, Not, Constants } from './'; +import type { Extends, Not } from './'; /** - * Extract the array values into a union type + * The `Values` type extracts the type of the values stored in arrays or tuples. + * + * @returns A union of every value contained in the array/tuple + * + * @template TCollection - The array-like structure from which the values' type will be extracted. + * + * @example + * Let's suppose we have an array of numbers, and we would like to extract the type its values + * + * ```typescript + * type MyTuple = [1, 2, 3, 4]; + * + * type TupleValues = Values; // TupleValues: 1 | 2 | 3 | 4 + * ``` + * + * @example + * Now, let's suppose we have a regular TypeScript array, and we would like to extract its value + * + * ```typescript + * type MyArray = (string | number)[]; + * + * type ArrayValues = Values; // ArrayValues: string | number + * ``` */ export type Values> = TCollection extends Array ? TValues : never; /** - * Checks if the size of the given collection equals 0 + * Checks if a given array ({@link TCollection}) is empty. + * + * @template TCollection - The array to be checked. It should extend 'Array'. + * + * @example + * Validate an array that is empty: + * ```typescript + * type EmptyArray = []; + * + * type IsEmptyCheck = IsEmpty; + * // Result: Constants.True + * ``` + * + * @example + * Validate an array that is not empty: + * ```typescript + * type NonEmptyArray = [1, 2, 3]; + * + * type IsEmptyCheck = IsEmpty; + * // Result: Constants.False + * ``` */ export type IsEmpty> = Extends; /** - * Checks if the size of the given collection is not 0 + * Checks if a given array ({@link TCollection}) is not empty. * - * Returns a {@link Constants.BooleanValue} expression + * @template TCollection - The collection (array) that needs to be checked if it's not empty. It must extend 'Array'. + * + * @see {@link Not} + * @see {@link IsEmpty} + * + * @example + * Checking non-empty array: + * ```typescript + * type NonEmptyArray = [1, 2, 3]; + * + * // This type checks will result to True because + * // 'NonEmptyArray' is indeed not empty. + * type IsNotEmptyCheck = IsNotEmpty; + * ``` + * + * @example + * Checking empty array: + * ```typescript + * type EmptyArray = []; + * + * // This type checks will result to False because 'EmptyArray' is empty. + * type IsNotEmptyCheck = IsNotEmpty; + * ``` */ export type IsNotEmpty> = Not>; diff --git a/packages/core/types/src/utils/constants.ts b/packages/core/types/src/utils/constants.ts index 43a22b1100..a9ea8dd8da 100644 --- a/packages/core/types/src/utils/constants.ts +++ b/packages/core/types/src/utils/constants.ts @@ -1,7 +1,7 @@ import type * as Internal from '../internal'; import type * as UID from '../uid'; -import type { Or, NotStrictEqual } from '../utils'; +import type { NotStrictEqual, Or } from '../utils'; export type True = true; @@ -9,14 +9,71 @@ export type False = false; export type BooleanValue = True | False; +/** + * Determine if there's been an extension or change in either Component Registry or Content-Type Registry. + * + * It applies the compound boolean OR operation on the results derived from {@link IsComponentRegistryExtended} and {@link IsContentTypeRegistryExtended}. + * + * If either or both results are true (indicating a change or extension in the corresponding registry), it returns {@link True}; otherwise, it returns {@link False}. + * + * @example + * ```typescript + * // A change or extension in both the Component Registry and the Content-Type Registry + * type Example1 = AreSchemaRegistriesExtended; // Result: Constants.True + * + * // A change or extension only in the Component Registry + * type Example2 = IsComponentRegistryExtended; // Result: Constants.True + * + * // A change or extension only in the Content-Type Registry + * type Example3 = IsContentTypeRegistryExtended; // Result: Constants.True + + * // No change or extension in either registries + * type Example4 = AreSchemaRegistriesExtended; // Result: Constants.False + * ``` + * + * @see IsComponentRegistryExtended + * @see IsContentTypeRegistryExtended + */ export type AreSchemaRegistriesExtended = Or< IsComponentRegistryExtended, IsContentTypeRegistryExtended >; +/** + * Evaluate if the internal UIDs ({@link Internal.UID.ContentType}) and public ones ({@link UID.ContentType}) are not identical. + * + * If these two types are not the same, it indicates an extension or change has occurred in the public Content-Type Registry. + * + * The type leverages {@link NotStrictEqual} to perform this comparison accurately. The result is a type-level + * boolean that denotes whether there is a deviation between the two Content-Type representations. + * + * @returns Either [Constants.True](@link Constants.True) if the Content-Type Registry has been extended, else [Constants.False](@link Constants.False). + * + * @remark + * This type is particularly useful when there is a need to verify whether there have been extensions or changes to the Content-Type Registry + * after initially creating it. + * + * It allows developers to perform this check at the type level and decide what type should be resolved depending on the context + */ export type IsContentTypeRegistryExtended = NotStrictEqual< Internal.UID.ContentType, UID.ContentType >; +/** + * Evaluate if the internal UIDs ({@link Internal.UID.Component}) and public ones ({@link UID.Component}) are not identical. + * + * If these two types are not the same, it indicates an extension or change has occurred in the public Component Registry. + * + * The type leverages {@link NotStrictEqual} to perform this comparison accurately. The result is a type-level + * boolean that denotes whether there is a deviation between the two Content-Type representations. + * + * @return - Either {@link True} if the Component Registry has been extended, else {@link False}. + * + * @remark + * This type is particularly useful when there is a need to verify whether there have been extensions or changes to the Component Registry + * after initially creating it. + * + * It allows developers to perform this check at the type level and decide what type should be resolved depending on the context + */ export type IsComponentRegistryExtended = NotStrictEqual; diff --git a/packages/core/types/src/utils/expression.ts b/packages/core/types/src/utils/expression.ts index b71d827737..76e740d076 100644 --- a/packages/core/types/src/utils/expression.ts +++ b/packages/core/types/src/utils/expression.ts @@ -1,37 +1,271 @@ -import type { Array, Constants, Guard, Simplify } from './'; +import type { Array, Constants } 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; // This will resolve to 'false' because number is not `never` + * type B = IsNever>; // 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 = StrictEqual; +/** + * 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; // Evaluates to 'true' because number is not 'never'. + * type IsNotNeverNever = IsNotNever; // 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 = Not>; +/** + * The `IsTrue` type evaluates if the given {@link TValue} strictly equals {@link Constants.True}. + * + * @template TValue - The type to evaluate. + * + * @example + * type A = IsTrue; // This will resolve to Constants.True + * type B = IsTrue; // This will resolve to Constants.False + */ export type IsTrue = [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; // This will resolve to Constants.False + * type B = IsNotTrue; // This will resolve to Constants.True + * + */ export type IsNotTrue = Not>; +/** + * The `IsFalse` type evaluates if the given {@link TValue} strictly equals {@link Constants.False}. + * + * @template TValue - The type to evaluate. + * + * @example + * type A = IsFalse; // This will resolve to Constants.False + * type B = IsFalse; // This will resolve to Constants.True + */ export type IsFalse = [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; // This will resolve to Constants.False + * type B = IsNotFalse; // This will resolve to Constants.True + */ export type IsNotFalse = Not>; +/** + * 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 = And, Extends>; +/** + * 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; // 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; // Result: Constants.True (because `MyType` and `OtherType` do not have the same structure) + * + */ export type NotStrictEqual = Not>; +/** + * 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; + * // output: Constants.False + * ``` + */ export type Extends = [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 = Not>; +/** + * 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` 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; // Results in Constants.False + * + * // Using `true` wrapped inside another type + * type B = Not>; // Results in Constants.False + * + * // Using expression that equates to `false` + * type C = Not; // Results in Constants.True + * + * // Using `false` wrapped inside another type + * type D = Not>; // Results in Constants.True + * ``` + */ export type Not = 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; // Result: 'This is False' + * ``` + * + * Conversely, if we replace {@link TExpression} with {@link Constants.True}, the applicable type will be 'TrueCase'. + * ```typescript + * type ExampleTrue = If; // 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; // Result: never + * ``` + */ export type If = [ 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, 'string type'>; + * type IsNumber = Test, 'number type'>; + * type Tests = [IsString, IsNumber]; + * + * type Result = MatchFirst; // Result would be 'string type' as 'T' is string. + * ``` + * + */ export type MatchFirst = TTests extends [ infer THead extends Test, ...infer TTail extends Test[] @@ -41,6 +275,45 @@ export type MatchFirst = TTests extends : 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; + * type Test2 = Test; + * type Test3 = Test; + * + * 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; + * type Test4 = Test; + * + * type ResultDefault = MatchAllIntersect<[Test3, Test4], {}>; // The Result will be {} + * ``` + */ export type MatchAllIntersect = TTests extends [ infer THead extends Test, ...infer TTail extends Test[] @@ -53,11 +326,65 @@ export type MatchAllIntersect = TTest : 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, 'Input is a string'>; + * type IsNumber = Test, '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 [ infer THead extends Constants.BooleanValue, ...infer TTail extends Constants.BooleanValue[] @@ -65,6 +392,30 @@ export type Some = TExpressions e ? If, Or>, Or> : 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 [ infer THead extends Constants.BooleanValue, ...infer TTail extends Constants.BooleanValue[] @@ -72,15 +423,114 @@ export type Every = TExpressions ? If, And>, And> : 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; // Result: Constants.True + * + * // Constants.False AND Constants.True + * type Example2 = And; // Result: Constants.False + * + * // Constants.False AND Constants.False + * type Example3 = And; // Result: Constants.False + * ``` + */ export type And< TLeft extends Constants.BooleanValue, TRight extends Constants.BooleanValue > = IsTrue | IsTrue>; +/** + * 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; // Result: Constants.True + * + * // Constants.False OR Constants.True + * type Example2 = Or; // Result: Constants.True + * + * // Constants.False OR Constants.False + * type Example3 = Or; // Result: Constants.False + * ``` + */ export type Or = Not< IsFalse | IsTrue> >; +/** + * 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 [ infer THead, ...infer TTail extends unknown[] diff --git a/packages/core/types/src/utils/function.ts b/packages/core/types/src/utils/function.ts index 6191b92361..02d144c612 100644 --- a/packages/core/types/src/utils/function.ts +++ b/packages/core/types/src/utils/function.ts @@ -1,2 +1,21 @@ +/** + * Defines a function parameter in a way that accommodates any number and type of arguments. + * + * The flexibility the type provides makes it a suitable choice for representing generic function in TypeScript whose behaviors heavily rely on the runtime inputs. + * + * This type is primarily used when the function parameter types and return type can't be accurately defined. + * + * @remark + * It's important to understand that while the {@link Any} type provides flexibility, + * it inherently sacrifices the benefits of type-safety. + * + * Therefore, it's suggested to use it sparingly and only in situations where it's unavoidable. + */ export type Any = (...args: any[]) => any; + +/** + * Async version of {@link Any} + * + * @see Any + */ export type AnyPromise = (...args: any[]) => Promise; diff --git a/packages/core/types/src/utils/guard.ts b/packages/core/types/src/utils/guard.ts index 33979fe97d..8d355fad28 100644 --- a/packages/core/types/src/utils/guard.ts +++ b/packages/core/types/src/utils/guard.ts @@ -1,20 +1,91 @@ import type { Array, If, StrictEqual } from '.'; /** - * Assign a default value `TDefault` to `TValue` if `TValue` is of type `never` + * Conditionally assigns a fallback type {@link TFallback} to a type {@link TValue}, if {@link TValue} resolves to `never`. + * + * Otherwise, it assigns the type of {@link TValue}. + * + * @template TValue - The original type which could be any type or `never`. + * @template TFallback - The fallback type that will be assigned to {@link TValue} if {@link TValue} is `never`. It defaults to `unknown`. + * + * @remark + * This type becomes useful when working with conditional types where there are possibilities of ending up with `never` type. + * + * It provides a way to ensure that, in such situations, the type defaults to a more meaningful type rather than `never`. * * @example - * type X = Never<{ foo: 'bar' }, string> - * // { foo: 'bar' } + * ```typescript + * type User = { name: 'John' } * - * type X = Never - * // unknown + * type X = Guard.Never; // X: User + * ``` * - * type X = Never - * // string + * @example + * ```typescript + * type NoType = never; + * + * type X = Guard.Never; // X: unknown + * type Y = Guard.Never; // Y: string + * ``` */ export type Never = OfTypes<[never], TValue, TFallback>; +/** + * Conditionally assigns a fallback type {@link TFallback} to a type {@link TValue}, if {@link TValue} resolves to `{}`. + * + * Otherwise, it assigns the type of {@link TValue}. + * + * @template TValue - The original type which could be any type or `{}`. + * @template TFallback - The fallback type that will be assigned to {@link TValue} if {@link TValue} is `{}`. It defaults to `unknown`. + * + * @remark + * This type becomes useful when working with conditional types where there are possibilities of ending up with `{}` type. + * + * It provides a way to ensure that, in such situations, the type defaults to a more meaningful type rather than `{}`. + * + * @example + * ```typescript + * type User = { name: 'John' } + * + * type X = Guard.EmptyObject; // X: User + * ``` + * + * @example + * ```typescript + * type MyObj = {}; + * + * type X = Guard.EmptyObject; // X: unknown + * type Y = Guard.EmptyObject; // Y: string + * ``` + */ +export type EmptyObject = OfTypes<[{}], TValue, TFallback>; + +/** + * Conditionally assigning a fallback type (@link TFallback) if the value type ({@link TValue}) matches any of the types in {@link TTypes}. + * + * It basically enables conditional type assignments based on type matching. + * + * The value is checked against the list of types iteratively. If it matches any type from the list, the fallback type is assigned. + * + * If it doesn't match, the value's original type is maintained. + * + * If no fallback is provided, unknown type is used by default. + * + * @template TTypes - A tuple of types to match the value against. It must extend Array + * @template TValue - The value whose type is checked against TTypes. + * @template TFallback - The type to be assigned if TValue matches any member of TTypes. It defaults to unknown. + * + * @example + * Here, the `TValue` is `string` which exists in the `TTypes` list. Thus, the `TFallback` which is `null` is returned. + * ```typescript + * type Result = OfTypes<[string, number], string, null>; // Result: null + * ``` + * + * Here, the `TValue` is `boolean` which does not exist in the `TTypes` list. Thus, the `TValue` is returned as no match was found. + * ```typescript + * type Result = OfTypes<[string, number], boolean, number>; // Result: boolean + * ``` + */ export type OfTypes = TTypes extends [ infer THead extends unknown, ...infer TTail extends unknown[] @@ -25,5 +96,3 @@ export type OfTypes = TTy If, OfTypes, TValue> > : never; - -export type EmptyObject = OfTypes<[{}], TValue, TFallback>; diff --git a/packages/core/types/src/utils/index.ts b/packages/core/types/src/utils/index.ts index 908c8f658b..97e1efdd7a 100644 --- a/packages/core/types/src/utils/index.ts +++ b/packages/core/types/src/utils/index.ts @@ -10,33 +10,175 @@ export * from './expression'; export * from './json'; /** - * Get the type of a specific key `TKey` in `TValue` + * `Get` obtains the type of a specific property (key-value pair) within an object or type. + * + * @template TValue The initial object type from which a property's type should be extracted. + * @template TKey A specific key, existing within `TValue`. * * @example + * // Utilizing Get to extract types from an object + * type ExampleObject = { foo: 'bar', 'bar': 'foo' }; * - * type X = Get<{ foo: 'bar', 'bar': 'foo' }, 'foo'> - * // 'bar' + * // Extract `foo`'s type from the object. This infers and outputs the type 'bar'. + * type FooType = Get; + * let fooVar: FooType = 'bar'; // This is valid * - * type X = Get<{ foo: 'bar', 'bar': 'foo' }, 'bar'> - * // 'foo' + * // Similar extraction for `bar` + * type BarType = Get; + * let barVar: BarType = 'foo'; // This is valid */ export type Get = TValue[TKey]; /** - * Represents a simplified version of a given intersection type. + * `Simplify` is a type used to flatten intersection types. * - * It flattens the properties of every component in the intersection {@link T}, and returns a unified object. + * It acts upon each constituent type within the provided intersection, and extracts its properties + * to form a new, unified object type where properties do not retain their original type-specific + * distinctions. * - * @template T The original type to be simplified. + * It’s useful when there's a need to treat an intersection type as a single unified object type, + * while ensuring that the properties of each component type in the intersection are accounted for. + * + * @template T The type parameter indicating the intersection type that is to be simplified. This must extend `unknown`. + * + * @example + * Consider the following example with two distinct types `A` and `B`: + * ```typescript + * type A = { a: number }; + * type B = { b: string }; + * ``` + * If we were to create an intersection of these two types as `C`: + * ```typescript + * type C = A & B; + * ``` + * The usual operations on `C` would account for the distinct types `A` and `B`. However, when we want to treat it as a single unified object type, we can apply `Simplify` in the following way: + * ```typescript + * type D = Simplify; + * ``` + * Now, `D` is a single object type with properties from both `A` and `B`, and we can operate on it as: + * ```typescript + * let obj: D = { a: 5, b: 'hello' }; + * ``` + * + * @remark + * While this type is beneficial in certain contexts where treating intersection types as single unified objects is desirable (e.g. when exposing + * complex types to end-users), it's important to remember that it strips the original type information from the properties. + * + * Thus, it may not be suitable in situations where retaining the distinction between types present in the intersection is important. */ export type Simplify = { [TKey in keyof T]: T[TKey] }; +/** + * Utility type that creates a new type by excluding properties from the left type ({@link TLeft}) that exist in the right type ({@link TRight}). + * + * @template TLeft + * @template TRight + * + * @example + * type User = { + * id: number, + * name: string, + * email: string, + * }; + * + * type Credentials = { + * email: string, + * password: string, + * }; + * + * type UserWithoutCredentials = Without; + * + * const user: UserWithoutCredentials = { + * id: 1, + * name: 'Alice' + * // no email property because it's excluded by the Without type + * }; + */ export type Without = { [key in Exclude]?: never }; +/** + * Creates a type that is mutually exclusive between two given types. + * + * @template TLeft - The first type. + * @template TRight - The second type. + * + * @remarks + * This type is used to create a type that can be either TLeft or TRight, but not both at the same time. + * + * @example + * // Example 1: XOR type with two object types + * type A = { a: number }; + * type B = { b: string }; + * + * const value1: XOR = { a: 1 }; // Valid, TLeft type A is assigned + * const value2: XOR = { b: "hello" }; // Valid, TRight type B is assigned + * const value3: XOR = { a: 1, b: "hello" }; // Invalid, both types A and B cannot be assigned at the same time + * + * // Example 2: XOR type with object type and string type + * type C = XOR; + * + * const value4: C = { a: 1 }; // Valid, object type A is assigned + * const value5: C = "hello"; // Valid, string type is assigned + * const value6: C = { a: 1, b: "hello" }; // Invalid, both object type A and string type cannot be assigned at the same time + */ export type XOR = TLeft | TRight extends object ? (Without & TRight) | (Without & TLeft) : TLeft | TRight; +/** + * The `Cast` type is designed for casting a value of type {@link TValue} into type {@link TType}, thus making sure {@link TValue} extends {@link TType}. + * + * If the casting is impossible ({@link TValue} does not extend {@link TType}), it returns `never`. + * + * @template TValue - The type to cast. + * @template TType - The target type. + * + * @example + * // In this example, the String 'Hello' is attempted to be cast to a number, + * // which is not possible. Thus, the result would be 'never'. + * type ImpossibleCasting = Cast<'Hello', number>; // this will be 'never' + * + * @example + * // In this example, the String 'Hello' is attempted to be cast to a String, + * // which is possible. Thus, the result would be 'Hello'. + * type PossibleCasting = Cast<'Hello', string>; // this will be 'Hello' + * + */ export type Cast = TValue extends TType ? TValue : never; +/** + * The `PartialWithThis` type extends the functionality of two types: {@link Partial} and {@link ThisType}. + * + * It creates a type that represents an object with a subset of properties from the provided + * type {@link T} merged with a pseudo `this` context for methods, based on the same type parameter. + * + * - {@link Partial} makes all properties of the given type optional. + * - {@link ThisType} defines what `this` refers to within a method of the final object. + * + * @template T The type to create a subset from and to use for the pseudo 'this' context. + * + * It can be any TypeScript type such as interface, class, primitive, or even another + * generic type. + * + * @example + * ```typescript + * interface MyObject { + * property1: string; + * property2: number; + * method(): void; + * } + * + * let foo: PartialWithThis = { + * property1: 'Hello', + * method() { + * // Here, `this` refers to `MyObject` + * console.log(this.property1); + * }, + * // `property2` is optional + * }; + * ``` + * + * @remark + * This type can be useful when working with partial data and object methods that contain a pseudo `this` context. + */ export type PartialWithThis = Partial & ThisType; diff --git a/packages/core/types/src/utils/json.ts b/packages/core/types/src/utils/json.ts index 7eaf7a8b30..4adb4b9aba 100644 --- a/packages/core/types/src/utils/json.ts +++ b/packages/core/types/src/utils/json.ts @@ -1,9 +1,121 @@ +/** + * The `JSONValue` type embodies all potential JSON data forms ({@link JSONPrimitive}, {@link JSONObject}, and {@link JSONArray}). + * + * @note `JSONValue` does not introduce any new type parameters; it merely aggregates {@link JSONPrimitive}, {@link JSONObject}, and {@link JSONArray}. + * + * @example + * ```typescript + * function processJSON(jsonData: JSONValue): void { + * if (Array.isArray(jsonData)) { + * console.log('This is a JSONArray: ', JSON.stringify(jsonData, null, 2)); + * } else if (typeof jsonData === 'object') { + * console.log('This is a JSONObject: ', JSON.stringify(jsonData, null, 2)); + * } else { + * console.log('This is a JSONPrimitive: ', jsonData); + * } + * } + * + * processJSON(['hello', { anotherKey: 'anotherValue' }]); + * // This is a JSONArray: ["hello", { "anotherKey": "anotherValue" }] + * + * processJSON({ key: 'value' }); + * // This is a JSONObject: { "key": "value" } + * + * processJSON(10); + * // This is a JSONPrimitive: 10 + * ``` + * + * @see {@link JSONPrimitive} - The simplest form of `JSONValue`, corresponds to basic JSON data types. + * @see {@link JSONObject} - A potential `JSONValue`, encapsulates JSON object structures. + * @see {@link JSONArray} - A potential `JSONValue`, encapsulates JSON arrays. + */ export type JSONValue = JSONPrimitive | JSONObject | JSONArray; +/** + * The `JSONPrimitive` type models the fundamental data types (`string`, `number`, `boolean`, `null`) of JSON in TypeScript. + * + * @example + * ```typescript + * declare function set(key: string, value: JSONPrimitive): void; + * + * set('string', 'foo'); // This is valid + * set('number', 42); // This is valid + * set('boolean', true); // This is valid + * set('null', null); // This is valid + * set('array', []); // Error + * set('undefined', undefined); // Error + * ``` + * + * @see {@link JSONValue} - The potential forms of JSON data, including `JSONPrimitive`. + * @see {@link JSONObject} - Incorporated in {@link JSONValue}, represents JSON objects. + * @see {@link JSONArray} - Incorporated in {@link JSONValue}, represents JSON arrays. + * + * @remarks + * `JSONPrimitive` provides a foundation for describing JSON data. + * Combined with {@link JSONObject} and {@link JSONArray}, they encompass all possible JSON data types. + */ export type JSONPrimitive = string | number | boolean | null; +/** + * The `JSONArray` type models a standard JSON array in TypeScript, which allows manipulation of arrays of {@link JSONValue} elements. + * + * @example + * ```typescript + * // Create a JSONArray consisting of different JSONValues + * let jsonArray: JSONArray = ['hello', 10, true, null, {key: 'value'}, ['nested array']]; + * + * function prettyPrint(jsonArray: JSONArray): void { + * jsonArray.forEach(item => { + * if(typeof item === 'object' && item !== null) { + * // If it's a JSONObject or another JSONArray, stringify it + * console.log(JSON.stringify(item, null, 2)); + * } else { + * // If it's a JSONPrimitive, print it directly + * console.log(item); + * } + * }); + * } + * + * prettyPrint(jsonArray); // Will print all items in a friendly format to the console + * ``` + * + * This type is part of a series of types used for modeling all possible JSON values in TypeScript. + * @see {@link JSONValue} - The supertype of all elements that a `JSONArray` can contain. + * @see {@link JSONPrimitive} - The simplest kind of `JSONValue` that a `JSONArray` can contain. + * @see {@link JSONObject} - A possible `JSONValue` that a `JSONArray` can contain. + * + * @remarks + * The `JSONArray` is a versatile type that can contain various kinds of JSON data, even nested arrays or objects. + */ export type JSONArray = Array; +/** + * The `JSONObject` interface formally defines a JavaScript object with string keys and values of type {@link JSONValue}. + * + * It models standard JSON objects as TypeScript types, facilitating their manipulation. + * + * @example + * ```typescript + * function addToJSONObject(key: string, value: JSONValue, jsonObject: JSONObject): JSONObject { + * // Copy the existing JSONObject + * let updatedObject: JSONObject = { ...jsonObject }; + * + * // Add the new key-value pair + * updatedObject[key] = value; + * + * // Return the updated JSONObject + * return updatedObject; + * } + * ``` + * + * @see {@link JSONValue} - The permitted types for values within the `JSONObject` (primitives, objects, or arrays). + * @see {@link JSONPrimitive} - The basis for JSON data, used in {@link JSONValue}. + * @see {@link JSONArray} - Arrays used in {@link JSONValue}. + * + * @remarks + * The keys of the `JSONObject` are always of type string, as per the standard JSON specification. + * Values may take any valid {@link JSONValue}, allowing nested data structures. + */ export interface JSONObject { [key: string]: JSONValue; } diff --git a/packages/core/types/src/utils/object.ts b/packages/core/types/src/utils/object.ts index 146f4b2ead..45feeb23b6 100644 --- a/packages/core/types/src/utils/object.ts +++ b/packages/core/types/src/utils/object.ts @@ -1,64 +1,164 @@ /** - * Retrieve object's (`TValue`) keys if they extend the given `TTest` type. + * Extracts object's (`TValue`) keys where the key's value type extends the given `TTest` type. + * + * @template TValue - The original object type. + * @template TTest - The test type. Keys of TValue having values that extend this type are extracted. + * @template TExtract - An optional constraint for the keys of TValue. * * @example - * type X = KeysBy<{ foo: 'bar', bar: 'foo', foobar: 2 }, string> - * // 'foo' | 'bar' * - * type Base = { x: 'foo' | 'bar' }; - * type Obj = { foo: { x: 'foo' }, bar: { x: 'bar' }, other: { x: '42' } }; - * type X = KeysBy - * // 'foo' | 'bar' + * // Here TValue is `{ foo: 'bar', bar: 'foo', foobar: 2 }` and TTest is `string`. + * // So it extracts keys `foo` and `bar`, because their values are string type. + * type keys = KeysBy<{ foo: 'bar', bar: 'foo', foobar: 2 }, string> // 'foo' | 'bar' + * + * @example + * + * // Here TValue is `{ foo: { x: 'foo' }, bar: { x: 'bar' }, other: { x: '42' } }` and TTest is `{ x: 'foo' | 'bar' }`. + * // So it extracts keys `foo` and `bar`, because their values are extending `{ x: 'foo' | 'bar' }`. + * type Base = { x: 'foo' | 'bar' }; + * type Obj = { foo: { x: 'foo' }, bar: { x: 'bar' }, other: { x: '42' } }; + * type X = KeysBy // 'foo' | 'bar' + * + * @see {@link KeysExcept} + * @see {@link PickBy} */ export type KeysBy = { [key in keyof TValue & TExtract]: TValue[key] extends TTest ? key : never; }[keyof TValue & TExtract]; /** - * Retrieve object's (`TValue`) keys if they don't extend the given `TTest` type. + * Extracts the keys of a given object ({@link TValue}). It includes only those keys which do not map to a value of type {@link TTest}`. + * + * @template TValue - The object whose keys are to be examined and selectively retrieved + * @template TTest - The type of value to be used as an exclusion criterion for selecting keys from `TValue` + * @template TExtract - An optional union of keys to constrain the keys that are being examined. If not provided, it defaults to examining all keys in `TValue`. * * @example - * type X = KeysExcept<{ foo: 'bar', bar: 'foo', foobar: 2 }, string> - * // foobar + * ```typescript + * // In this example, KeysExcept is used to fetch keys from the object which do not have string type values + * type ExampleType = { foo: 'bar', bar: 'foo', foobar: 2 } + * type ResultType = KeysExcept + * // The resulting type is "foobar" + * ``` * + * @example + * ```typescript + * // In this example, we use a base type to define allowed value types and only fetch those keys from the object that have values not extending the base type * type Base = { x: 'foo' | 'bar' }; * type Obj = { foo: { x: 'foo' }, bar: { x: 'bar' }, other: { x: '42' } }; * type X = KeysBy - * // 'other' + * // The resulting type is "other" + * ``` */ export type KeysExcept = { [key in keyof TValue & TExtract]: TValue[key] extends TTest ? never : key; }[keyof TValue & TExtract]; /** - * Retrieve object's (`TValue`) properties if their value extends the given `TTest` type. + * Select properties from an object ({@link TValue}), only if their types extend a specific test type ({@link TTest}). + * + * @template TValue - The object type from which properties are selected. + * @template TTest - The test type. Properties of TValue extending this type are selected. * * @example - * type X = KeysBy<{ foo: 'bar', bar: 'foo', foobar: 2 }, string> - * // { foo: 'bar', bar: 'foo' } * - * type Base = { x: 'foo' | 'bar' }; - * type Obj = { foo: { x: 'foo' }, bar: { x: 'bar' }, other: { x: '42' } }; - * type X = KeysBy - * // { foo: { x: 'foo' }, bar: { x: 'bar' } } + * // If we have this: + * type FruitAttributes = { color: string, taste: string, weight: number, isOrganic: boolean }; + * + * // And we use `PickBy` like so: + * type StringAttributes = PickBy; + * + * // Then, `StringAttributes` will equal: `{ color: string, taste: string }` */ export type PickBy = Pick>; +/** + * Creates a new type from a given type ({@link TValue}), and select specific + * keys ({@link TKeys}) to be optionally present in the new type. + * + * @template TValue The original type of object. + * @template TKeys A union of selected {@link TValue} object keys that should be partial/optional in the new type. + * + * @example + * ```typescript + * type Person = { + * name: string; + * age: number; + * }; + * + * type PartialAgePerson = PartialBy; + * + * // the type PartialAgePerson is now equivalent to: + * // { + * // name: string; + * // age?: number; + * // } + * ``` + */ export type PartialBy = Omit & Partial>; /** - * Retrieve object's (`TObject`) values + * Extracts all unique values from a given object as a union type. + * + * @template TObject - An object from which values are to be extracted. It must extend the `object` type. + * + * @remark + * It works with non-primitive values as well. Hence, if a value is an object, it is included as is. Primitive types are included directly. * * @example - * type X = Values<{ foo: 'bar', bar: 'foo', foobar: 2 }> - * // 'bar' | 'foo' | 2 + * With a simple object: + * ```TypeScript + * type SimpleExample = Values<{ + * a: 'one', + * b: 'two', + * c: 3 + * }>; + * // Result: 'one' | 'two' | 3 + * ``` * - * type Y = Values<{ foo: { x: 'foo' }, bar: { x: 'bar' }, other: { x: '42' } }> - * // { x: 'foo' } | { x: 'bar' } | { x: '42' } + * @example + * With complex (non-primitive) values in an object: + * ```TypeScript + * type ComplexExample = Values<{ + * a: { x: 10 }, + * b: { y: 'twenty' }, + * c: { z: true } + * }>; + * // Result: { x: 10 } | { y: 'twenty' } | { z: true } + * ``` */ export type Values = TObject[keyof TObject]; +/** + * Provides a way to set deeply-nested properties within `TObject` to optional. + * + * @template TObject Type of the object that will become deeply partial. + * + * @example + * ```typescript + * interface Person { + * name: string; + * age: number; + * address: { + * city: string; + * street: string; + * postalCode: number; + * }; + * } + * + * const partialPerson: DeepPartial = {}; // This is now valid + * + * // You can assign partially filled objects + * const anotherPerson: DeepPartial = { + * name: 'John', + * address: { + * city: 'San Francisco', + * // street and postal code are optional + * } + * } + * ``` + */ export type DeepPartial = TObject extends object ? { [TKey in keyof TObject]?: DeepPartial; @@ -66,11 +166,22 @@ export type DeepPartial = TObject extends object : TObject; /** - * Replace the keys of an object with the keys of another object + * Creates a new type by replacing properties of {@link TObject} with properties from {@link TNew}. + * + * This is particularly useful to fine-tune the shape of an object type by altering some of its properties while keeping the rest intact. + * + * @template TObject - A type that extends `object`. This should be the original type that you intend to transform. + * @template TNew - A partial type of {@link TObject} where keys are replaced with new types. * * @example - * type X = Replace<{ foo: number, bar: number}, { foo: string }> - * // { foo: string, bar: number } + * + * ```typescript + * type Original = { foo: number, bar: number}; // Declare original type + * type Transformation = { foo: string }; // Declare keys to replace from original type + * + * type Result = Replace; + * // The transformed type now becomes { foo: string, bar: number } + * ``` */ export type Replace< TObject extends object, diff --git a/packages/core/types/src/utils/string.ts b/packages/core/types/src/utils/string.ts index 0faa09d54b..6edee1e3df 100644 --- a/packages/core/types/src/utils/string.ts +++ b/packages/core/types/src/utils/string.ts @@ -2,48 +2,140 @@ import type { Extends } from './expression'; /** * Alias for any literal type (useful for template string parameters) + * + * @see {@link Split} + * @see {@link Suffix} + * @see {@link Prefix} + * @see {@link StartsWith} + * @see {@link EndsWith} */ export type Literal = string | number | bigint | boolean; /** - * Used to check if a string includes a given literal - */ -export type Includes = `${string}${T}${string}`; - -/** - * Used to make sure the given string is not empty + * Ensures that a string is not empty. + * + * @template T - The type that extends a string. + * + * @example + * // T is assigned a string type + * type A = NonEmpty; + * // A can be any string except the empty string + * let a: A = "hello"; // Valid + * a = ""; // Error: Type '""' is not assignable to type 'NonEmpty' + * + * @example + * // T is assigned a string literal type + * type B = NonEmpty<"hello">; + * // B can only be "hello" + * let b: B = "hello"; // Valid + * b = ""; // Error: Type '""' is not assignable to type 'NonEmpty<"hello">' */ export type NonEmpty = T extends '' ? never : T; /** - * Split the given string into a tuple using the given `TSeparator` literal + * Splits a given string ({@link TValue}) around occurrences of a given separator ({@link TSeparator}). + * + * The resulting type is an array where each item is a part of the original string that falls between two instances of the separator. + * + * If the string does not contain the separator, the array will contain just the original string. + * + * If the string is empty, the result is an empty array. + * + * @template TValue - The string to split. Must extend `string`. + * @template TSeparator - The character(s) used to determine in which locations the string should be split. Must be a `string`. + * + * @example + * + * ```typescript + * type Example = Split<'a.b.c.d', '.'>; // Output will be: ['a', 'b', 'c', 'd'] + * ``` + * In the above example, the string 'a.b.c.d' is split around occurrences of the '.' separator. + * + * @example + * + * ```typescript + * type ExampleUnion = Split<'a.b-c', '.' | '-'>; // Output will be: ['a', 'b-c'] | ['a.b', 'c'] + * ``` + * The split operation will distribute the union members and create two possible return type for the union */ -export type Split< - TValue extends string, - TSeparator extends Literal -> = TValue extends `${infer TLeft}${TSeparator}${infer TRight}` - ? [TLeft, ...Split] - : TValue extends '' - ? [] - : [TValue]; +export type Split = { + [TSep in TSeparator]: TValue extends `${infer TLeft}${TSep}${infer TRight}` + ? [TLeft, ...Split] + : TValue extends '' + ? [] + : [TValue]; +}[TSeparator]; /** - * Add a literal suffix (`TSuffix`) at the end of the given string + * The `Suffix` type appends a literal suffix ({@link TSuffix}) to the end of a provided string ({@link TValue}). + * + * @template TValue - The string to add the suffix to. + * @template TSuffix - It extends the {@link Literal} type, and represents the suffix to append. + * + * @example + * ```typescript + * // A type that appends '.com' to a string + * type DomainName = Suffix; + * const myComDomain: DomainName = 'example.com'; // This is valid + * const myNetDomain: DomainName = 'example.net'; // This is not valid + * + * // A variant using `number` as literal + * type SuffixedNumber = Suffix; + * const mySuffixedNumber: SuffixedNumber = 'example1'; // Also valid + * ``` */ export type Suffix = `${TValue}${TSuffix}`; /** - * Add a literal prefix (`TPrefix`) at the beginning of the given string + * Prepend a literal prefix ({@link TPrefix}) to the start of a provided string ({@link TValue}). + * + * @template TValue - The string to add the prefix to. + * @template TPrefix - It extends the {@link Literal} type, and represents the prefix to prepend. + * + * @example + * ```typescript + * // A type that prepends 'Hello ' to a string + * type Greeting = Suffix; + * const greeting: Greeting = 'Hello Bob'; // This is valid + * const farewell: Greeting = 'Bye Bob'; // This is not valid + * ``` */ export type Prefix = `${TPrefix}${TValue}`; /** * Creates an indexed object where every key is a string and every value is `T` + * + * @template T - Value type of the dictionary + * + * @example + * // Dictionary where each key is a string and is bound to a number type value. + * const numDict: Dict = { + * 'a': 1, + * 'b': 2, + * 'c': 3 + * }; */ export type Dict = { [key: string]: T }; /** - * Checks if a given string ends with the given literal + * Determines if a string, represented by {@link TValue}, ends with a specific literal ({@link TSuffix}). + * + * @template TValue - The string to check. + * @template TSuffix - A literal which may or may not be at the end of {@link TValue}. + * + * @remark + * To remember easily: `String.prototype.endsWith` method but at type level. + * + * @example + * ```typescript + * type Result = EndsWith<"HelloWorld", "World">; + * // Output: Constants.True + * ``` + * + * ```typescript + * type Result = EndsWith<"HelloWorld", "Hello">; + * // Output: Constants.False + * ``` */ export type EndsWith = Extends< TValue, @@ -51,7 +143,25 @@ export type EndsWith = Extends< >; /** - * Checks if a given string starts with the given literal + * Determines if a string, represented by {@link TValue}, starts with a specific literal ({@link TPrefix}). + * + * @template TValue - The string to check. + * @template TPrefix - A literal which may or may not be at the start of {@link TValue}. + * + * @remark + * To remember easily: `String.prototype.startsWith` method but at type level. + * + * @example + * ```typescript + * type IsHelloWorld = StartsWith<"Hello World", "Hello">; + * // Output: Constants.True + * ``` + * + * @example + * ```typescript + * type NotHelloWorld = StartsWith<"World Hello", "Hello">; + * // Output: Constants.False + * ``` */ export type StartsWith = Extends< TValue, diff --git a/packages/core/types/src/utils/tuple.ts b/packages/core/types/src/utils/tuple.ts index cdcd495239..7a0550ca6c 100644 --- a/packages/core/types/src/utils/tuple.ts +++ b/packages/core/types/src/utils/tuple.ts @@ -1,7 +1,17 @@ import type { Literal } from './string'; /** - * Aggregate the given tuple into a string, separated by the given `TSeparator` literal + * Transforms a tuple ({@link TCollection}) into a concatenated string, interlaced with a designated separator character ({@link TSeparator}). + * + * @template TCollection - Represents the array of elements to be joined. + * @template TSeparator - Represents the separator character used to join the elements of {@link TCollection}. + * + * @example + * ```typescript + * type R1 = Join<["John", "Doe", "Smith"], ",">; // type R1 = "John,Doe,Smith" + * type R2 = Join<[1, 2, 3, 4], "-">; // type R2 = "1-2-3-4" + * type R3 = Join<["Foo", "Bar", "Baz"], "," | "-">; // type R3 = "Foo,Bar,Baz" | "Foo-Bar-Baz" + * ``` */ export type Join = TCollection extends [ infer THead extends Literal,