Obtain a tuple of identical length from a function

I'm attempting to create a function that returns a tuple with the same length as the parameter tuple passed to it. Although I tried using generics, I am encountering an error when applying the spread operator on the result.

My goal is best illustrated through an example where I'm writing a helper function to validate and extract query parameters from an express request object.

Initially, here is an example without types:

const getQueryParams = (req: Request) => (keys) => {
  const values = keys.map(key => {
    const value = req.query[key];
    if (value !== undefined && value !== null && typeof value === 'string') {
      return value;
    }
    throw new Error(`Missing query param ${key}`);
  });
  return values;
}

To ensure that the returned values have the same length as the input tuples, I added some type helpers:

type ArrLength<T extends readonly any[]> = T['length'];
type SameLength<T extends readonly any[]> = readonly string[] & {length: ArrLength<T>};

const getQueryParams = (req: Request) => <T extends readonly string[]>(keys: T): SameLength<T> => {
  const values = keys.map(key => {
    const value = req.query[key];
    if (value !== undefined && value !== null && typeof value === 'string') {
      return value;
    }
    throw new Error(`Missing query param ${key}`);
  }) as SameLength<T>;
  return values;
}

It appears to be working partially. For instance, this snippet runs successfully:

let params = getQueryParams(req)(['token', 'user', 'count'] as const);
params = ['1'] as const; //ERROR! TypeScript identifies that parmas has a length of 3
params = ['1', '2', '3'] as const; //OK! TypeScript recognizes that parmas has a length of 3

However, when trying to call a function with the spread operator:

someApiMethod(...params)

I encounter an error stating,

Expected 3 arguments, but got 0 or more
.

Full playground link

If anyone knows how to fix my types or offer an alternative solution, I would greatly appreciate the assistance.

(Alternatively, if there is no resolution available, an explanation for why this issue persists along with a relevant issue in TypeScript's GitHub repository, if applicable, would suffice. I couldn't find any)

Answer №1

If you want to simplify things, try utilizing mapped tuple types like this:

type SameLength<T extends readonly any[]> = { [K in keyof T]: string };

This approach will give you a proper tuple type when provided with one:

type TestTuple = SameLength<[false, 1, "two", Date]>;
// type TestTuple = [string, string, string, string]

Keep in mind that you might need an additional assertion to convert the output of map() from string[] to SameLength<T>, illustrated here:

const getQueryParams = (req: Request) => <T extends readonly string[]>(keys: T): SameLength<T> => {
  const values = keys.map(key => {
    const value = req.query[key];
    if (value !== undefined && value !== null && typeof value === 'string') {
      return value;
    }
    throw new Error(`Missing query param ${key}`);
  }) as readonly string[] as SameLength<T>; // might require intermediate assertion
  return values;
}

Now that params is a legitimate tuple, you can easily spread it as needed:

const handler = async (req: Request, res: any) => {
  let params = getQueryParams(req)(['token', 'user', 'count'] as const);
  const r = await someApiMethod(...params); // okay
  res.json(r);
}

Link to Playground for code demonstration

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Mastering the Implementation of Timetable.js in Angular with TypeScript

I am currently working on integrating an amazing JavaScript plugin called Timetable.js into my Angular6 project. You can find the plugin here and its repository on Github here. While searching for a way to implement this plugin, I stumbled upon a helpful ...

Loading custom components dynamically in Angular with SVG: a how-to guide

Looking for a way to dynamically load SVG items with ease. The items needed are quite simple. Here's a basic template: <svg:rect [attr.x]="x" [attr.y]="y" width="10" height="10" /> Component Class Example: export class DraggableSvgItemCompon ...

What is the best way to line up a Material icon and header text side by side?

Currently, I am developing a web-page using Angular Material. In my <mat-table>, I decided to include a <mat-icon> next to the header text. However, upon adding the mat-icon, I noticed that the icon and text were not perfectly aligned. The icon ...

Ways to retrieve the value of the variable within the confines of this particular

In my code, I have private variables in the constructor and public variables in the class. To reference these variables and functions, I use the "this" keyword. However, when trying to access these variables inside a function, I am getting an "undefined" ...

The html-duration-picker is not being displayed in the proper format

I've been working on integrating an external library that allows for inputting document length. Specifically, I'm using the html-duration-picker library, but it seems like the input functionality is not quite right for durations. Could it be th ...

Create a debounce click directive for buttons in a TypeScript file

I'm facing an issue with implementing debounce click on a dynamically added button using TypeScript. I need help with the correct syntax to make it work. private _initActionsFooter(): void { this.actionsFooterService.add([ { ...

Tips on ensuring the callback function is properly called when it is passed as an argument to another function

I am facing a challenge with my typescript method that needs to call another method, on(), which requires a callback method. I want the myConnect() method to wait until the callback is executed. I believe this involves using a promise, but I'm struggl ...

Showing records from Firebase that are still within the date range

I'm currently developing an application that allows users to book appointments on specific dates. After booking, I want the user to only be able to view appointments that are scheduled for future dates. I've attempted to compare the date of each ...

In Angular, a white screen may suddenly appear if the scrolling speed is too fast

My experience has been primarily on Chrome. I've noticed that when I scroll for a long time, the data on the screen disappears briefly and then reappears after a few seconds. Is there a resolution for this problem? Thank you, ...

Angular's custom validator consistently returns a null value

I need help with validating the uniqueness of a username field in a form where an administrator can create a new user. I have implemented a uniqueUserNameValidator function for this purpose, but it always returns null. I suspect that the issue lies in the ...

What is the process for linking read-only methods to Redux object instances?

Let's say I have a "user" object stored in redux, with fields for first name and last name (interface User { firstName : string, lastName : string} if using typescript). After retrieving a user from redux, I want to obtain the full name of the user by ...

Steps for aligning the upper rectangular text in the center of the larger rectangular border

https://i.stack.imgur.com/7yr5V.png I was aware of a particular element in html that had text positioned in the upper left corner, but my knowledge didn't go beyond that. Should I be adjusting the translation on both the X and Y axes based on the par ...

What steps can be taken to resolve the issue of being unable to rename an element in Typescript?

Why does VS code editor sometimes prevent me from renaming my typescript symbol using the f2 key? I keep encountering the error message "This element cannot be renamed." https://i.stack.imgur.com/mmqu9.png In some of my other projects, I am able to renam ...

How can we ensure in ReactJS that one API call has been completed before making another one?

How can we ensure one API call is completed before making the next call in reactJS? In my componentDidMount function, I need to check the length of data. When the length is more than 4, I first want to save the data and then retrieve it. componentDidM ...

"Error in Visual Studio: Identical global identifier found in Typescript code

I'm in the process of setting up a visual studio solution using angular 2. Initially, I'm creating the basic program outlined in this tutorial: https://angular.io/docs/ts/latest/guide/setup.html These are the three TS files that have been genera ...

Tips on implementing mongoose 'Query<any>' types

I have been exploring ways to incorporate a cache layer into my TypeScript project. Recently, I came across an insightful article on Medium titled How to add a Redis cache layer to Mongoose in Node.js The author demonstrates adding a caching function to m ...

Error message "After the upgrade to Angular 15, the property 'selectedIndex' is not recognized in the type 'AppComponent'."

My Ionic 6 app with capacitor has been updated in the package.json file. These are the changes: "dependencies": { "@angular/common": "^15.1.0", "@angular/core": "^15.1.0", "@angular/forms": "^15.1.0", "@angular/platform-browser": "^15.1. ...

The observable HTTP map appears to be more of a representation rather than a concrete entity

I seem to be struggling with understanding Typescript I expected the returned observable to have a method getTitle(), but it seems to be missing. Instead, I am getting an object that resembles AttachableContentModel without that particular method. What is ...

Exploring the concept of inheritance and nested views within AngularJS

I've encountered a challenge while setting up nested views in AngularJS. Utilizing the ui-router library has been beneficial, but I'm facing issues with separate controllers for each view without proper inheritance between them. This results in h ...

Tips for fixing the error "Module cannot be found" when testing Typescript in a Github Action

Whenever I use the Github Actions Typescript template to create a new repo and then check it out locally, I face a recurring issue: While I can work on the source code in VS Code without any problems and follow the steps mentioned in the template's re ...