#Type object returned from Array.reduce with literal type key/values?

8 messages · Page 1 of 1 (latest)

deep swan
#

Hello 👋

I'm struggling with a typing a function and I've got myself into a bit of a mess trying to figure it out.

Ignore the reproduction example, there's much more at play, but I've narrowed it down to the core functionality that I'm struggling to type.

JS example (Full TS attempt below):

const createLambdaParams = ({ genericParams, fnNames }) => {
  const lambdaParams = fnNames.reduce((acc, fnName) => {
    acc[`${fnName}Params`] = { ...genericParams, functionName: `${fnName}Lambda` };
      return acc;
    }, {});

  return lambdaParams;
}

The function works as I would expect:

createLambdaParams({genericParams, fnNames: ["fnOne", "fnTwo", "fnThree"]})

Returning an object that looks like this:

{
  "fnOneParams": {
    "region": "eu-central-1",
    "bucket": "my-bucket",
    "functionName": "fnOneLambda"
  },
  "fnTwoParams": {
    "region": "eu-central-1",
    "bucket": "my-bucket",
    "functionName": "fnTwoLambda"
  },
  "fnThreeParams": {
    "region": "eu-central-1",
    "bucket": "my-bucket",
    "functionName": "fnThreeLambda"
  }
}

There are a few things I'm trying to achieve, but I'm really struggling as far as typing the return keys.

  • Type completetion on the object being passed into the params
  • Type completion on the destructuring of the return value
  • Not casting the return type to the reduce object {}

Below is where I'm currently at with typing the function, along with a tsplayground link - It's in a bit of a mess right now through the trial and error - Any help would be appreciated!

#
type LambdaFn = "fnOne" | "fnTwo" | "fnThree"

interface GenericParams {
    region: string;
    bucket: string;
}

interface CreateLambdaParams<T> {
    genericParams: GenericParams;
    fnNames: T
}

type LambdaParams<T extends LambdaFn[]> = Record<keyof T, {
        genericParams: GenericParams;
        functionName: `${T}Lambda`;
    }>


const createLambdaParams = <T extends LambdaFn[]>({ genericParams, fnNames }: CreateLambdaParams<T>): LambdaParams<T> => {
    const lambdaParams = fnNames.reduce<LambdaParams<T>>((acc, fnName) => {
        acc[`${fnName}Params`] = { ...genericParams, functionName: `${fnName}Lambda` };
        return acc;
    }, {})

    return lambdaParams;
}

const genericParams: GenericParams = {
    region: "eu-central-1",
    bucket: "my-bucket"
}

const { fnOneParams, fnTwoParams, fnThreeParams } = createLambdaParams({genericParams, fnNames: ["fnOne", "fnTwo", "fnThree"]})
cobalt flameBOT
#
nickdevs#0

Preview:```ts
...
type LambdaFn = "fnOne" | "fnTwo" | "fnThree"

interface GenericParams {
region: string
bucket: string
}

interface CreateLambdaParams<T> {
genericParams: GenericParams
fnNames: T
}

type LambdaParams<T extends LambdaFn[]> = Record<
keyof T,
{
genericParams: GenericParams
functionName: ${T}Lambda
}

const createLambdaParams = <T extends LambdaFn[]>({
genericParams,
fnNames,
}: CreateLambdaParams<T>): LambdaParams<T> => {
const lambdaParams = fnNames.reduce<LambdaParams<T>>(
(acc, fnName) => {
acc[${fnName}Params] = {
...```

deep swan
#

Type object returned from Array.reduce with literal type key/values?

umbral knoll
#

In general this seems like the sort of things you might be able to get the external types to work for, but you're probably going to have to deal with some casting to make the implementation compile. If nothing else, this is true of reduce, but probably also some of the literal string stuff here isn't going to be 'understood' by the compiler.

#

But it should be possible to make it work externally; there's just a lot of smallish issues here.

#
  1. It'll simplify stuff if T extends LambdaFn and not T extends LambdaFn[] - you can 'unwrap' the array in places you need it with T[number] but I'm not sure it's going to work as well, so I'd change T and do fnNames: T[] instead.

  2. Record<keyof T, /*...*/> isn't what you want - especially if T is an array, keyof T is going to be the keys of an array (number and all the array methods). Record<T or (Record<T[number] when T is the array) is closer, but

  3. You actually want the keys here to be, e.g. fnOneParams not fnOne - to do this you need to do a mapping with a rename:

[K in T as `${K}Params`]: /* value*/
  1. On the value you're attaching genericParams as an property, but your implementation is spreading it, so:
GenericParams & { /* functionName */ }

not

{ genericParams: GenericParams, /* funtionName */ }
#

Here's a version that seems to work, though again there's casting in the implementation