Creating a flexible TypeScript function handler that accepts optional arguments depending on the function's name

I am facing a challenge with defining the function type for running helper functions that prepare database queries. Some of these functions have arguments, while others do not.

TS Playground Link

type PreparedQuery = {
  query: string;
  params?: string[] | null
}

interface Queries {
  foo: {
    returns: {
      col1: string;
      col2: string;
      col3: number | null;
    }
    args: {
      arg1: string;
    }
  }
  bar: {
    returns: Database['public']['Tables']['table_b']
    args: null;
  }
}

const foo = ({arg1} : Queries['foo']['args']) : PreparedQuery => {
  //do stuff
  return {query: "SELECT ...........", params: ['xyz']}
}

const bar = () : PreparedQuery => {
  return {query: "SELECT ......"}
}

const QueryFunctions = {
  foo,
  bar
}

interface Database {
  public: {
    Tables: {
      table_a: {
        id: string;
        name: string;
      }
      table_b: {
        id: number;
        status: 0 | 1;
        create_time: Date;
        update_time: Date;
      }
    }
  }
}



class DBClient {
  pool: Pool // PGPool

  constructor() {
    this.pool = new Pool();
  }

  closePool() { this.pool.end() }


  async executeQuery({ query, params }: PreparedQuery) {
    const res = params ? await this.pool.query(query, params) : await this.pool.query(query);
    return res.rows
  }

  getQuery<Q extends keyof Queries>(queryName : Q, args : Queries[Q]['args']) : Queries[Q]['returns']
  getQuery<Q extends keyof Queries>(queryName : Q) : Queries[Q]['returns']
  async getQuery(queryName : keyof Queries, args? : Queries[typeof queryName]['args'] ){
      let f : PreparedQuery = args ?  QueryFunctions[queryName](args) : QueryFunctions[queryName]();
      return await this.executeQuery(f)
  }

}

The issue arises around the getQuery signature. The purpose of this function is to easily extend the number of functions and ensure TS autocomplete and safety by allowing only calling functions that exist and require the correct arguments (if any).

The problem I'm encountering is related to functions without arguments like bar. When calling getQuery('bar') without arguments, TS complains that QueryFunctions[queryName]() expects an argument, even though it shouldn't. On the other hand, when calling getQuery('foo') which should require an argument, there is no complaint from TS.

Simplification Update

new TS Playground Link

interface Queries {
  foo: {
    returns: {
      col1: string;
      col2: string;
      col3: number | null;
    }
    args: {
      arg1: string;
    }
  }
  bar: {
    returns: string
    args: null;
  }
}

const foo = ({ arg1 }: Queries['foo']['args']) => {
  return { query: "SELECT col1, col2, col3 from ", params: [arg1] }
}

const bar = () => {
  return { query: "SELECT * FROM table_b" }
}

const QueryFunctions = {foo,bar}



type QueryName = keyof Queries
type Args<T extends QueryName> = Queries[T]["args"] extends null ? [T] : [T, Queries[T]["args"]]

function query<T extends QueryName>(...args: Args<T>): Queries[T]["returns"] {
  
  const [fname, fargs] = args;
  const preparedQuery = fargs ? QueryFunctions[fname](fargs) : QueryFunctions[fname]() // this is causing a TS error but not a functional error
  console.log(preparedQuery)
  
  // Here you would return the real value, I cast to not get type error.
  return 123 as unknown as Queries[T]["returns"];
}

query("foo", { arg1: "123" })
query("foo") // correctly complaining about missing argument
query("bar")

I have simplified Felix's original question to emphasize the complexity of handling the invocation of helper functions with or without arguments. Specifically, foo necessitates an argument object, prompting a valid complaint from TypeScript when calling query("foo") without the necessary argument. Conversely, bar, lacking arguments altogether, does not prompt any errors when called with query("bar").

To resolve this dilemma, I attempted the following solution:

const [fname, fargs] = args;
const preparedQuery = fargs ? QueryFunctions[fname](fargs) : QueryFunctions[fname]()

While this method runs smoothly, TypeScript still raises an error, expecting an argument for QueryFunctions[fname](), despite the absence of arguments in bar. How can I address this TypeScript error effectively?

Answer №1

One potential solution to this issue could look something like the following:

type Args<T extends QueryName> = Queries[T]["args"] extends null ? [T] : [T, Queries[T]["args"]]

If you'd like to see a more detailed example, you can check out this Typescript Playground (a simplified version).

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

Discovering Typescript: Inferring the type of a union containing specific strings

I am working with a specific type called IPermissionAction which includes options like 'update', 'delete', 'create', and 'read'. type IPermissionAction = 'update' | 'delete' | 'create' | ...

What is the best way to troubleshoot the TypeScript error I am encountering in my JavaScript file?

Currently experiencing a TypeScript error within a JavaScript file https://i.sstatic.net/gBzWx.png The issue is within a folder containing only one JavaScript file, and there are no Node.js or package.json files present. I have disabled the TypeScript ex ...

In TypeScript, the first element of an array can be inferred based on the second element

Let's consider a scenario where we have a variable arr, which can be of type [number, 'number'] or [null, 'null']. Can we determine the type of arr[0] based on the value of arr[1]? The challenge here is that traditional function ov ...

Share edited collection with Observer

The challenge Imagine creating an Angular service that needs to expose an Observable<number[]> to consumers: numbers: Observable<number[]>; Our requirements are: Receive the latest value upon subscription Receive the entire array every tim ...

Warning: Potential spacing issues when dynamically adjusting Material UI Grid using Typescript

When working with Typescript, I encountered an error related to spacing values: TS2322: Type 'number' is not assignable to type 'boolean | 7 | 2 | 10 | 1 | 3 | 4 | 5 | 6 | 8 | "auto" | 9 | 11 | 12'. No lint errors found Version: typesc ...

Exploring the differences between importing all utilities as a whole using `import * as util from "./util"` and importing a specific function only with `import {someFunction

When comparing the two options of importing from a module, which is better: import * as util from "./Util" or import {someFunction} from "./Util"? ...

Error in TypeScript detected for an undefined value that was previously verified

I have developed a function that can add an item to an array or update an item at a specific index if provided. Utilizing TypeScript, I have encountered a peculiar behavior that is puzzling me. Here is the Playground Link. This simple TypeScript functio ...

Strange error message regarding ES6 promises that is difficult to interpret

Snippet getToken(authCode: string): Promise<Token> { return fetch(tokenUrl, { method: "POST" }).then(res => res.json()).then(json => { if (json["error"]) { return Promise.reject(json); } return new Token ...

Error: `target` property is not recognized on `htmlelement` type

I am attempting to retrieve the ID of a list item in a select menu but I am having trouble getting the value from it. The value should be a number. HTML File <div class="form-group mt-3"> <label class="form-label">Produc ...

Creating folders and writing data to text files in Angular 4/5 with TypeScript: A tutorial

Is it feasible to create a folder, text file, and write data into that file in Angular5 using Typescript for the purpose of logging errors? Your expertise on this matter would be greatly appreciated. Thank you in advance! ...

Encountering the "potential null object" TypeScript issue when utilizing template ref data in Vue

Currently, I am trying to make modifications to the CSS rules of an <h1> element with a reference ref="header". However, I have encountered a TypeScript error that is preventing me from doing so. const header = ref<HTMLElement | null> ...

Ways to determine if the keys of an object are present in an array, filtered by the array key

Working on an Angular 2 Ionic application and I'm wondering if there's a straightforward way to filter individuals by age in a specific array and then verify if any key in another object matches the name of a person in the array, returning a bool ...

Acquire Superheroes in Journey of Champions from a REST endpoint using Angular 2

Upon completing the Angular 2 Tour of heroes tutorial, I found myself pondering how to "retrieve the heroes" using a REST API. If my API is hosted at http://localhost:7000/heroes and returns a JSON list of "mock-heroes", what steps must I take to ensure a ...

There is an error appearing in my .ts code: [ts] The property 'name' is not found in type 'any[]'

While my coding is working fine and data is showing on the page, there seems to be an error occurring in the VSE editor. It is showing something like this: [ts] Property 'name' does not exist on type 'any[]'. This is a snippet of my ...

Exploring the functionality of the WHERE function in Firebase with Angular

Current Objective: Our main focus here is to allow users to post within their designated Organization Group. These posts should remain exclusively visible within the specific Organization Group they are posted in. To achieve this, I have attempted to impl ...

Tips for sending a file rather than a json object in nextjs

Is there a way to send a file from either route.ts or page.ts, regardless of its location in the file-system? Currently, I am using the following code in my back-end python + flask... @app.route("/thumbnail/<string:filename>") def get_file ...

The attribute "property" is not found in the specified type of "Request<ParamsDictionary>"

Struggling to enhance the Request interface in the express package with custom properties, I keep encountering this TypeScript error: TS2339: Property '' does not exist on type 'Request<ParamsDictionary>'. Any ideas on how to re ...

Steps for calculating the average of several columns within a table using Angular 10

Currently, I have a function that successfully calculates the sum of JSON data in all columns on my tables. However, my attempt to get the average of each column is resulting in NaN or infinity. What could be the issue here? Here is my current implementat ...

Having trouble resolving 'primeng/components/utils/ObjectUtils'?

I recently upgraded my project from Angular 4 to Angular 6 and everything was running smoothly on localhost. However, during the AOT-build process, I encountered the following error: ERROR in ./aot/app/home/accountant/customercost-form.component.ngfactory. ...

Exploring the Incorporation of String as a Component Identifier in React and TypeScript

My input component can render either a textarea component (from a library) or a regular input. Check out the code below: import React, { useEffect, useRef, useState } from 'react' import './AppInput.css' interface Props { placehold ...