Lorenzo Imperatrice
Lorenzo Imperatrice

Reputation: 997

What is the correct way to type this function in TypeScript?

EDIT:

I'm playing with typing because I would like to avoid returning Primise<any[]>, what I'm trying to achieve is to get the correct typing for every position in the returned list based on the list passed as a parameter.

EDIT 2:

The error that I was getting has been solved by @outoftouch, but unfortunately, the function typing return is like this:

const enabledResources: ({
  firstData: 'Some data',
  secondData: 'Some other data'
} | {
  thirdData: 'Some data',
  fourthData: 'Some other data'
} | undefined)[]

The desired result is instead:

const enabledResources: [
  { firstData: 'Some data', secondData: 'Some other data' } | undefined,
  { thirdData: 'Some data', fourthData: 'Some other data' } | undefined
]

As you can see every list position (in this case position 0 and 1) already have their typing inferred by the Resource list passed as param.


Original Question

I would like to have a function that gets as the first parameter a list of Resource that has a field named getResource that is a function that returns Promise<T>.

The function has to verify if the user is authorized to get that data and, if it is, return that data in the same position that was passed as a parameter, or otherwise returns undefinedin that list place.

An example of how I would like to use this function is:

const enabledResources = await getPageResources([
  {
    scope: 'some scope', // in User scopes
    getResource: Promise.resolve({ firstData: 'Some data', secondData: 'Some other data' }),
  },
  {
    scope: 'some other scope', // Not in user scopes
    getResource: Promise.resolve({ thirdData: 'Some data', fourthData: 'Some other data' }),
  },
]);

// This should log { firstData: 'Some data', secondData: 'Some other data' }
console.log(enabledResources[0]) 

// This should log undefined
console.log(enabledResources[1]) 

I've tried to achieve that result by writing this function but I got errors in typing:

export type Resource = {
  scope: Scope
  getResource: () => Promise<unknown>
}

export type Role = {
  name: string
  scopes: Scope[]
}

export async function getPageResources<T extends readonly Resource[]>(
  wantedResources: T,
  context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>
// I get error in the next line that says Type '"getResource"' cannot be used to index type 'T[P]'
): Promise<{ -readonly [P in keyof T]: Awaited<ReturnType<T[P]['getResource']>> | undefined }> {
  const session = await getSession(context)
  const userRole = session?.role as Role | null

  const resourcesToWait = wantedResources.map((e) => {
    const isInUserScopes = userRole?.scopes.includes(e.scope)
    return isInUserScopes ? e.getResource() : undefined
  })

  const resources = await Promise.all(resourcesToWait)

  return resources;
}

I actually don't get why this is happening, in my mind T[P] should be the element in the list passed as a parameter and I should get the awaited returned type (or undefined), typed in the correct list position.

What am I doing wrong?

Upvotes: 0

Views: 98

Answers (1)

user19057469
user19057469

Reputation:

You've might of forgotten that P could be any key of the array. For example, if P was "indexOf", T[P] would be a function, and functions do not have a getResponse method.

You have to explicitly check if T[P] is a Resource first with extends:

): Promise<{
  -readonly [P in keyof T]: T[P] extends Resource
   ? Awaited<ReturnType<T[P]['getResource']>> | undefined
   : T[P];
}> {

Upvotes: 1

Related Questions