How to use union type as function parameter

I the example below I have created a union type (string or number). Then I have created a function signature which should accept as input a string or number. But now I cannot use this function definition

type AB = string | number

type X = (inp: AB) => void;

const x: X = (inp: number) => { console.log('ok')}; // <-- ERROR
const y: X  = (inp: string) => { console.log('ok')}; // <- ERROR

Because the constants x and y have errors

enter image description here

UPDATED DEMO

The type AB can change, more types can be added, so I would be nice if I could use it in defining type X and then create implementation for each type defined in AB. But how can I do this?


By using a generic type parameter with X, you won't even need to type the argument in the implementations:

TS Playground

type AB = string | number;
type X<T extends AB> = (inp: T) => void;

const x: X<number> = (inp) => {
  inp // number
};

const y: X<string> = (inp) => {
  inp // string
};

Edit: Response to new question:

Note that idiomatically-named string enums use PascalCase.

See comments in code:

TS Playground

// Base types:

/**
 * From [Distributive conditional types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types):
 *
 * > ...an instantiation of `T extends U ? X : Y` with
 * > the type argument `A | B | C` for `T` is resolved as
 * > `(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)`.
 *
 * For `T extends unknown ? ...`, see also:
 * https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#example-3
 */
type DistributeUnionToFirstParamInFn<
  U,
  FnReturnType = void,
> = U extends unknown ? ((_: U) => FnReturnType) : never;

type AB = string | number;
const enum Z { X = 'x', Y = 'y'};

// First example:

type MyObj = Record<Z, DistributeUnionToFirstParamInFn<AB>>;

const obj1: MyObj = {
  x: (inp: string) => { console.log('this is a string ', inp) },
  y: (inp: number) => { console.log('this is a number ', inp) },
};

type ParamInObj1X = Parameters<typeof obj1['x']>[0]; // string | number
type ParamInObj1Y = Parameters<typeof obj1['y']>[0]; // string | number

// So, not quite what you want. Compare with...
//Second example:

function validateObject <T extends Record<Z, DistributeUnionToFirstParamInFn<AB>>>(value: T): T {
  return value;
}

validateObject({x: (inp: string) => { console.log('this is a string ', inp) }}); /*
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Property 'y' is missing in type... Error (2345) */

validateObject({
  x: (inp: string) => { console.log('this is a string ', inp) },
  y: (inp: boolean) => { console.log('this is a number ', inp) }, /*
  ^
Type '(inp: boolean) => void' is not assignable to type '((_: string) => void) | ((_: number) => void)'... Error (2322) */
});

const obj2 = validateObject({
  x: (inp: string) => { console.log('this is a string ', inp) },
  y: (inp: number) => { console.log('this is a number ', inp) },
});

type ParamInObj2X = Parameters<typeof obj2['x']>[0]; // string
type ParamInObj2Y = Parameters<typeof obj2['y']>[0]; // number

// 👍