Tips for creating a TypeScript function that can accept an array of concatenated modifiers with the correct data type

Can I modify data using a chain of function modifiers with correct typing in TypeScript? Is this achievable?

const addA = (data: {}) => {
  return {
    ...data,
    a: "test"
  }
}

const addB = (data: {}) => {
  return {
    ...data,
    b: "test"
  }
}

const updateA = (data: {a:string}) => {
  return {
    ...data,
    a: data.a + " test"
  }
}

const func = <T extends ((data: Record<string, any>) => Record<string, any>)[]>(modifiers: T): unknown => {
  return modifiers.reduce((acc, modifier) => {
    return modifier(acc)
  }, {})
}

console.log(func([addA])) // Success, should pass.
console.log(func([addA, addB])) // Success, should pass.
console.log(func([addA, addB, updateA])) // Error, should pass.
console.log(func([updateA])) // Error, should fail. Unexpected runtime undefined value.

Playground

Answer №1

Note: Your functions always propagate input to the output, yet your types do not explicitly indicate this behavior. While generics could be used to convey this, it would complicate the solution provided below. As long as the function output is guaranteed to contain at least what was passed into it, the following solution will function correctly.

By utilizing a type parameter to deduce the array of functions as a tuple, and then employing a recursive conditional type to construct a tuple defining the expected types of the functions, you can intersect the type parameter with this validation type. This inspection serves as a check — if all is well, it essentially has no effect; otherwise, an error arises from the intersection:

type FunctionArray = Array<(p: any) => any>;
type ValidateChain<T extends FunctionArray , Input = {}, Result extends any[] = []> = 
  T extends [(data: Input) => infer R, ...infer Tail extends FunctionArray] ? ValidateChain<Tail, Input & R, [...Result, T[0]]>:
  T extends [(...p: infer P) => infer R, ...infer Tail extends FunctionArray] ? ValidateChain<Tail, Input & R, [...Result, (data: Input) => R]>:
  Result

type MergeAll<T extends FunctionArray , Input = {}> = 
  T extends [(data: any) => infer R, ...infer Tail extends FunctionArray] ? MergeAll<Tail, R & Input>: Input

const func = <T extends [] | FunctionArray>(modifiers: T & ValidateChain<T>): MergeAll<T> => {
  return (modifiers as T).reduce((acc, modifier) => {
    return modifier(acc)
  }, {}) as MergeAll<T> 
}

let r1 = func([addA]) // Pass
let r2 = func([addA, addB]) // Pass
let r3 = func([addA, addB, updateA]) //Pass.
let r4 = func([addA, addB, updateC]) // Fails
let r5 = func([updateA]) // Fails

Playground Link

To delve deeper into conditional types, refer to the handbook

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

The subscribe method in Angular TS may be marked as deprecated, but worry not as it is still

I have developed a function that retrieves new data from a service file each time it is called. Here is how the function looks: onCarChange() { this.carService.getCarData(this.selectedCar).subscribe( async (response: CarData) => { if (response?.d ...

Preventing duplicate namespace declarations in TypeScript

Let's say I have a variety of objects with the following data structure: { namespace: 'first'|'second'|'third' } Now, I need to include another object with the same data structure, but its namespace cannot be assigned ...

Filter the output from a function that has the ability to produce a Promise returning a boolean value or a

I can't help but wonder if anyone has encountered this issue before. Prior to this, my EventHandler structure looked like: export interface EventHandler { name: string; canHandleEvent(event: EventEntity): boolean; handleEvent(event: EventEntity ...

Using sl-vue-tree with vue-cli3.1 on internet explorer 11

Hello, I am a Japanese individual and my proficiency in English is lacking, so please bear with me. Currently, I am using vue-cli3.1 and I am looking to incorporate the sl-vue-tree module into my project for compatibility with ie11. The documentation menti ...

Show a nested JSON object when the specific key is not recognized

I received the following data from my API: { "id": 82, "shortname": "testing2", "fullname": "test2", "address": "addrtest2", "telephone" ...

How can we effectively transfer a value from TypeScript/Angular?

I am new to TypeScript and Angular, and I'm struggling with assigning a value to a variable within a function and then using that value outside of the function. In regular JavaScript, I would declare the variable first, define its value in the functio ...

Ensuring TypeScript's strict null check on a field within an object that is part of an

When using TypeScript and checking for null on a nullable field inside an object array (where strictNullCheck is set to true), the compiler may still raise an error saying that 'Object is possibly undefined'. Here's an example: interface IA ...

Using TypeScript: Retrieve enum type value in type definition

I'm encountering an issue while attempting to define a specific type based on the value of an enum. enum E { A = "apple", B = "banana", C = "cherries" } // Defining the type EnumKey as 'A' | 'B ...

Implementing a boolean toggle method in Typescript for a class property

Hello there, fellow programmers! I am interested in changing the value of a class field using a method. I have a button in Angular that, when clicked, triggers the onSave() method: export class CourseComponent { isActive:boolean; onSave() { ...

Angular 4 scatter chart with multiple data points using Google charts

I'm currently developing a scatter graph in Angular 4 using ng2-google-charts from https://www.npmjs.com/package/ng2-google-charts It seems like this is essentially a wrapper for Google Chart Service. The graph looks great with a few values, but whe ...

Trigger a method within a component when there is a change in the Vuex state

I need to trigger a method inside a component whenever the vuex state changes in TypeScript and Vue.js. While I can access the vuex state value using getters in the template, I am unsure how to access the data within the component class. The vuex state is ...

update icon when a router link becomes active

<div class="menuItem mb-3" *ngFor="let menuItem of menuItems"> <a routerLink="{{menuItem.link}}" routerLinkActive="active"> <img src="{{menuItem.icon}}" alt="{{menuItem.name}}" /> <p class="text-center f-12">{{me ...

Testing in Jasmine: Verifying if ngModelChange triggers the function or not

While running unit tests for my Angular app, I encountered an issue with spying on a function that is called upon ngModelChange. I am testing the logic inside this function but my test fails to spy on whether it has been called or not! component.spec.js ...

Using class variance authority variants allows for the acceptance of a "null" value, although it is not recommended

My approach with cva is as follows: const checkboxOptions = cva('border ...', { variants: { size: { sm: 'h-4 w-4', md: 'h-5 w-5', lg: 'h-6 w-6', }, }, defaultVariants: ...

The feature of getDisplayMedia is not included in TypeScript 3.8

I am currently developing a .Net Core/Angular 8 application in Visual Studio. Within the Angular (ClientApp) section of my project, I have TypeScript 3.5.3 located in node_modules, which includes the following definition in lib.dom.d.ts: interface Navigat ...

Most effective method for initiating model class attributes

Is there a more efficient way to initialize model classes without explicitly defining each member as undefined? The original concept was to be able to simply use super(data); in extended classes. class Model { construct(data: any) { Object.ke ...

What strategies can be utilized to manage a sizable data set?

I'm currently tasked with downloading a large dataset from my company's database and analyzing it in Excel. To streamline this process, I am looking to automate it using ExcelOnline. I found a helpful guide at this link provided by Microsoft Powe ...

Is it possible to retrieve all mandatory attributes of a TypeScript object?

Is there a method or approach available that can retrieve all necessary properties from a TypeScript interface or an object? For instance, something along the lines of Object.getOwnPropertyDescriptors(myObject) or keyof T, but with the specific details o ...

Breaking down arrays in Typescript

Let's say I have an array like this: public taskListCustom: any=[ {title: 'Task 1', status: 'done'}, {title: 'Task 2', status: 'done'}, {title: 'Task 3', status: 'done'}, {title: 'Task ...

Storing an array of objects in local storage is not working in Angular 9

I've encountered an issue trying to save an array of JSON objects into local storage, and I'm puzzled as to why it's not functioning correctly. Despite utilizing localStorage.setItem('comparisons', JSON.stringify(setComparisons)), ...