0

I'm using typescript to do the type arrangement on validators. For example, required, noRequired are mutually exclusive logic. If required is used, then neither required nor noRequired needs to be used again. You can debug this by copying the following code and using the b object in the vs code.

type CoFormRuleClarity = {
  email(): any;
  id(): any;
};

type CoFormRuleSize = {
  min(num: number): any;
  max(num: number): any;
  between(min: number, max: number): any;
};

type CoFormRuleRequire = {
  required(): any;
  noRequired(): any;
};

type InvalideFunctionParams = "Please pass the function parameters";

type CoFormRuleWarpKeys<T, Raw> = {
  [Key in keyof T]: T[Key] extends (...args: infer P) => any
    ? (
        ...args: P
      ) => Raw extends [T, ...(infer Right)]
        ? CoFormRuleMerge<Right, Raw>
        : Raw extends [...(infer Left), T]
        ? CoFormRuleMerge<Left, Raw>
        : Raw extends [
            infer Left1,
            ...(infer Left2),
            T,
            ...(infer Right1),
            infer Right2
          ]
        // the principal part in there
        ? CoFormRuleMerge<[Left1, ...Left2, ...Right1, Right2], Raw>
        : InvalideFunctionParams
    : InvalideFunctionParams;
};

type CoFormRuleMerge<T, Raw> = T extends [infer Left, ...(infer Right)]
  ? CoFormRuleWarpKeys<Left, Raw> & CoFormRuleMerge<Right, Raw>
  : T extends [infer Left]
  ? CoFormRuleWarpKeys<Left, Raw>
  : T extends [infer Left, infer Right]
  ? CoFormRuleWarpKeys<Left, Raw> & CoFormRuleWarpKeys<Right, Raw>
  : {};

type CoFormRuleMergeCall<T extends unknown[]> = CoFormRuleMerge<T, T>;

const a: CoFormRuleMergeCall<[
  CoFormRuleRequire,
  CoFormRuleSize,
  CoFormRuleClarity
]> = {} as any;

// example 1
const b = a.between(1, 2);
// b. current: CoFormRuleRequire
// b. expect: CoFormRuleRequire & CoFormRuleClarity

// example 2
const c = a.between(1, 2).required();
// b. current: CoFormRuleSize & CoFormRuleClarity
// b. expect: CoFormRuleClarity

thank you very much for the solution and the optimization solution.

1 Answers1

0

Instead of manipulating them as tuples, you should think about using unions. When doing so, we should first get a type that can turn a union (A | B | C) into an intersection (A & B & C). Luckily, that is already covered by jcalz here:

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;

Then, we can write your CoFormRuleMergeCall type as the following:

type CoFormRuleMergeCall<T extends unknown[]> = CoFormRuleMerge<T[number]>;

It still takes a tuple (or even array), and passes the types of its elements to CoFormRuleMerge as a union with T[number]. For example, if T was [A, B, C], T[number] would be A | B | C (the same is true for (A | B | C)[]). You can think of T[number] as "unwrapping" the tuple/array into its element type.

Given this union, you can then take the keys of the intersection of this union, and map it to the correct function type:

type CoFormRuleMerge<T> = {
    [K in keyof UnionToIntersection<T>]: (...args: Parameters<Extract<T, Record<K, any>>[K]>) => CoFormRuleMerge<Exclude<T, Record<K, any>>>;
};

For the parameters, we extract the original member of T, and for the return type, we use CoFormRuleMerge on the remaining members of T by excluding the member with the current key.

This works for your provided code:

// example 1
const b = a.between(1, 2);
//    ^? CoFormRuleMerge<CoFormRuleClarity | CoFormRuleRequire>

// example 2
const c = a.between(1, 2).required();
//    ^? CoFormRuleMerge<CoFormRuleClarity>

However, once you have used up all the rules, you will get never:

const d = a.between(1, 2).required().email();
//    ^? CoFormRuleMerge<never>
//       same as `never`

Playground

kelsny
  • 23,009
  • 3
  • 19
  • 48