Aggregate data with a TypeScript type using a configuration array

For a while now, I've been working on getting these types to function properly. My main focus is on fixing the types rather than the implementation itself. The function in question takes and reduces over the config array using the fn property. Each item in the config should have the aggregated return type from the previous steps' fn function. The types seem to be correct, however, the autocomplete feature in my editor breaks when adding a new object to the array. TypeScript Server mistakenly identifies it as an array type. Is there a way to resolve this issue?

Here is the code:

// interfaces
interface Config<T extends object, R extends object> {
  fn: (state: T) => R
  bro?: boolean
}

interface OverloadRunner {
  <A extends object = object, B extends object = object>(
    configArray: [Config<A, B>],
  ): void
  <
    A extends object = object,
    B extends object = object,
    C extends object = object,
  >(
    configArray: [Config<A, B>, Config<A & B, C>],
  ): void

  <
    A extends object = object,
    B extends object = object,
    C extends object = object,
    D extends object = object,
  >(
    configArray: [Config<A, B>, Config<A & B, C>, Config<A & B & C, D>],
  ): void

  // extend for further items
}

// function
const overloadRunner: OverloadRunner = (steps: Config<any, any>[]) => {
  let accumulator = {}

  for (const step of steps) {
    const result = step.fn(accumulator)
    accumulator = {
      ...accumulator,
      ...result,
    }
  }
}

// Usage example
overloadRunner([
  {
    fn: (state) => {
      return { now: 'here' }
    },
    bro: false,
  },
  {
    fn: (state) => {
      return { next: 'step' } // state type correctly inferred as { now: string }
    },
    bro: false,
  },
  {
    // does not autocomplete here. TS Server thinks it's an array
  }
])

Screenshot showing incorrect autocomplete: vscode type autocomplete

I attempted to move the generic declarations to the entire interface instead of the overloads, but doing so compromised the piping aspect of the state argument.

interface OverloadRunner<
  A extends object = object,
  B extends object = object,
  C extends object = object,
  D extends object = object,
> {
  (configArray: [Config<A, B>]): void
  (configArray: [Config<A, B>, Config<A & B, C>]): void
  (configArray: [Config<A, B>, Config<A & B, C>, Config<A & B & C, D>]): void
}

I also experimented with some tuple utility types, but they too failed to fix the autocomplete issue.

type StepsArray<
  T extends any[],
  R extends object = RequestPropertiesAndHelpers,
> = T extends [infer First extends object, ...infer Rest]
  ? [Step<R, First>, ...StepsArray<Rest, R & First>]
  : []

Answer №1

In your specific scenario, you have the option to designate some elements in the configArray parameter as optional:

interface OverloadRunner {
    <
        A extends object = object,
        B extends object = object,
        C extends object = object,
        D extends object = object,
    >(
        configArray: [Config<A, B>, Config<A & B, C>?, Config<A & B & C, D>?],
    ): void    
}

This approach allows for successful execution even if the OverloadRunner is called with fewer elements than the maximum number specified. However, note that inferring the return type of omitted elements will default to object:

overloadRunner([
    { fn(x) { return { a: 1 } } },
    { fn(x) { return { b: x.a } } }
])
// overloadRunner<object, {a: number}, {b: number}, object>(⋯)

overloadRunner([
    { fn(x: { a: number }) { return { b: x.a.toFixed() } } },
])
// overloadRunner<{a: number}, {b: string}, object, object>(⋯)

overloadRunner([
    { fn(x: { a: number }) { return { b: x.a.toFixed() } } },
    { fn(x) { return { c: x.b.endsWith("2") } } },
    { fn(x) { return { d: x.c ? new Date() : undefined } } }
])
// overloadRunner<{a: number}, {b: string}, {c: boolean}, {d: Date | undefined}>(⋯)

With only a single call signature (not overloaded, making the term potentially obsolete), the IntelliSense experience should be simpler.


It's worth mentioning that while it's feasible to create a call signature supporting an indefinite number of elements in configArray, doing so might result in poor inference and IntelliSense due to contextual typing issues (as discussed in microsoft/TypeScript#47599). Given that you seem content with setting a reasonably modest cap on the maximum number of elements based on the question, this potential drawback shouldn't pose a problem.

Access Playground link for code

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

What is the methodology for obtaining the setter property type in TypeScript?

Describe a scenario where an object contains both getter and setter methods with different types. How can we determine the type of the setter function? Consider defining an object with getter and setter functions like the example below: type Foo = { g ...

What could be the reason behind lodash now classifying a mongoose object ID as empty when it previously did not?

Consider this scenario: const objId = new mongoose.Types.ObjectId('id goes here'); if (_.isEmpty(objId)) { throw new Error('an error is thrown here'); } I recently executed the code above and it got me thinking – is this a re ...

Is there a way to substitute the HOC with a single call and solely modify the prop?

One issue I've encountered in my project is the repetitive use of a Higher Order Component (HOC) for the header. Each time it's used, the props are set to determine whether header links should be displayed or not. My objective is to streamline th ...

Tips for obtaining the iframe #document with cheeriojs?

I've been struggling to scrape the anime videos page [jkanime], specifically with extracting the mp4 video formats embedded in an iframe #document. Despite trying to use cheerio for querying, I've only managed to retrieve src links from Facebook ...

Executing an individual .ts file within a Next.js application using ts-node for the purpose of testing

I'm attempting to execute a single ES module .ts file within a Next.js project using the default configuration for quick debugging: npx ts-node lib/my_module.ts However, I encounter the following error: Warning: To load an ES module, set "type&q ...

When utilizing the Page Object Model in Playwright with TypeScript, a Linting Error may occur, specifically a Parsing error related

Recently, I started using playwright and decided to implement the page object model using typescript. Everything was going smoothly until I ran a lint check. Unfortunately, the linting check failed in the Pull Request Check on GitHub. The error is occurri ...

In the past, my code would run smoothly without any issues, but now I am encountering a runtime error even though the code comp

Recently, I started learning TypeScript and encountered an issue while working with Classes. My code was functioning properly before but now it's displaying a runtime error. ...

How can we dynamically render a component in React using an object?

Hey everyone, I'm facing an issue. I would like to render a list that includes a title and an icon, and I want to do it dynamically using the map method. Here is the object from the backend API (there are more than 2 :D) // icons are Material UI Ic ...

The JSX component is unable to utilize the object

When working with Typescript in a react-three-fiber scene, I encountered an error that GroundLoadTextures cannot be used as a JSX component. My aim is to create a texture loader component that loads textures for use in other components. The issue arises f ...

Utilizing req.session in an Express application with Angular (written in TypeScript) when deploying the backend and frontend separately on Heroku

I'm currently facing an issue where I am unable to access req.session from my Express app in Angular. Both the backend and frontend are deployed separately on Heroku. I have already configured CORS to handle HTTP requests from Angular to my Express ap ...

What steps should I take to resolve the missing properties error stating '`ReactElement<any, any>` is lacking `isOpen` and `toggle` properties that are required by type `SidebarInterface`?

I've encountered an issue with two React components that appear to be configured similarly. The first component is a Navbar: type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { ...

What causes the typings for Partial Record to be undefined when using Object.values?

When I retrieve Object.values from a Partial Record, the values consist of a combination of what I anticipated and undefined. const example: Partial<Record<string, number>> = {} const values = Object.values(example) // The type for values is u ...

Tips for capturing an error generated by a child component's setter?

I've created an App component that contains a value passed to a Child component using the @Input decorator. app.component.html <app-child [myVariable]="myVariable"></app-child> app.component.ts @Component(...) export class AppC ...

Issue with implicitly assigning 'any' type to overloaded variadic generic function

We have some code snippets for you to review: export type actions = { abort: () => void; back: () => void; next: () => void; resume: () => void; }; class Sabar { public use<T1>(fn: (arg1: T1, ctx: object, actions: actions) =&g ...

What is the best way to update the value of a variable within a specific child component that is displayed using ngFor?

Hello there, I'm in need of some assistance with a coding issue. I have a parent component named "users-list" that displays a list of child components called "user" using *ngFor. Each component's class is dynamic and depends on various internal v ...

The functionality of translations within a TypeScript object is currently malfunctioning

I am facing a perplexing issue with my code. I am utilizing lingui for internationalization in my application. The translations are stored using the `t` macro in a TypeScript object, which can be found here: https://github.com/Flaburgan/disco2very/blob/mas ...

Testing Angular 2 components with material icons and images

Recently, I finished creating a unique component that showcases an image, material icons, and a custom directive known as ticker. This directive allows for scrolling text if it exceeds the width of the element. https://i.stack.imgur.com/GpDSr.png My next ...

Creating detailed documentation comments for TypeScript within Visual Studio

When using C# with ReSharper and StyleCop, I am able to automatically generate basic documentation comments for methods. This includes sections such as: /// <summary> /// The login. /// </summary> /// <param name="returnUrl" ...

Limit pasted content in an Angular contenteditable div

Is there a way to limit the input in a contenteditable div? I am developing my own WYSIWYG editor and want to prevent users from pasting content from external websites and copying styles. I want to achieve the same effect as if the content was pasted into ...

Using JSON to Map Routes in Angular 2

Service: export class ArticlesService { private _url = 'https://example.firebaseio.com/.json'; constructor(private _http: Http) { } getAllArticles(): Observable<any> { return this._http.get(this._url) .map((response: Re ...