#Narrowing Mapped Type

6 messages · Page 1 of 1 (latest)

thin musk
#

Bit of an advanced type that I'm working on at the moment that involves template literals and some mapped types. My ultimate goal is to dynamically produce output functions with names based on the combination of two objects.

Example in link below.

While I'm able to output the object with the keys that I want, the values are a union of all possible values within FunctionMap[keyof FunctionMap]. I've tried using Function directly, but that doesn't seem to work like I expect it to.

Some background: I've tried a few ways to get what I want, including using conditional types and overloads, but I feel like this might be the best option for DX + readability for what I'm using it for.

Glad to answer any other questions you might have.

wanton eagleBOT
#
mjlawson_92849#0

Preview:```ts
const functionMap = {
findById: "hi",
create: "hello",
} as const

const modifierMap = {
"": () => {},
forProcessing: () => {},
}

type Functions = "" | "forProcessing"

type MakeOutput<
ModifierMap extends Record<string, unknown>,
FunctionMap extends Record<string, unknown>

= {
[Modifier in keyof ModifierMap]: keyof ModifierMap extends string
? {
[Function in keyof FunctionM
...```

thin musk
#

I've made a bit of progress:

const functionMap = {
  findById: (input: string) => 'foo',
  create: (input: CreateObject) => 'foo'
} as const;

const modifierMap = {
  '': () => 'bar',
  forProcessing: () => 'bar'
};

type Practice<Noun extends string, Type> = {
  [Property in keyof Type as `${string & Property}${Capitalize<Noun>}`] : Type[Property]
}

type MakeOutput<FunctionMap, ModifierMap> = {
  [Modifier in keyof ModifierMap]: Practice<string & Modifier, FunctionMap>
}

declare const x: MakeOutput<typeof functionMap, typeof modifierMap>;
// This gives me x.forProcessing.findByIdForProcessing and x[''].create

The problem here, is when I try to index MakeOutput by keyof ModifierMap, it seems to intersect the results. Or at least, it doesn't do what I expect it to, which would be to return findByIdForProcessing as well as findById and create.

thin musk
#

Yet more progress, but also at a loss for what to try next:

const functionMap = {
  findById: (input: string) => 'foo',
  create: (input: CreateObject) => 'foo'
} as const;

const modifierMap = {
  '': (): CreateObject => obj,
  forProcessing: (): CreateObject => obj
};

type MakeOutput<
  FunctionMap,
  ModifierMap,
  Function extends keyof FunctionMap & string = keyof FunctionMap & string,
  Modifier extends keyof ModifierMap & string = keyof ModifierMap & string
  > = {
  [key in `${Function}${Capitalize<Modifier>}`]: Function
}

declare const x: MakeOutput<typeof functionMap, typeof modifierMap>;
x.findByIdForProcessing
// ^? (property) findByIdForProcessing: "findById" | "create"
// This should just by "findById"
thin musk
#

Okay, looks like what I wasn't realizing, is when I was using [keyof FunctionMap] to index the type, it was creating a union when I wanted an intersection. Adding a helper type seems to have fixed it. See the below TS Playground Link for the final product.

wanton eagleBOT
#
mjlawson_92849#0

Preview:```ts
const functionMap = {
findById: (input: string) => "foo",
create: (input: string) => "foo",
} as const

const modifierMap = {
"": () => {},
forProcessing: () => {},
}

type GetKeys<U> = U extends Record<infer K, any>
? K
: never

type UnionToIntersection<U extends object> = {
[K i
...```