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

Understanding DefinitelyTyped: Deciphering the explanation behind 'export = _;'

Having trouble integrating angular-material with an ng-metadata project and encountering some issues. Utilizing DefinitelyTyped for angular material, the initial lines are as follows: declare module 'angular-material' { var _: string; expo ...

Auth0 Angular - No routes found to match

I recently set up an angular application and integrated it with Auth0 by following two helpful tutorials: https://auth0.com/docs/quickstart/spa/angular2/01-login https://auth0.com/docs/quickstart/spa/angular2/02-calling-an-api Here is a brief overview o ...

Can we trust the accuracy of the official type definition for JSON.stringify?

Upon reviewing the official type definition for JSON.stringify, it appears that it states JSON.stringify always returns a string, even when passed undefined. interface JSON { stringify(value: any, /*...*/): undefined; } However, executing JSON.stringif ...

Ways to verify function arguments within an asynchronous function using Jest

I have a function that needs to be tested export const executeCommand = async ( command: string ): Promise<{ output: string; error: string }> => { let output = ""; let error = ""; const options: exec.ExecOptions = { ...

Guide on utilizing a module in TypeScript with array syntax

import http from "http"; import https from "https"; const protocol = (options.port === 443 ? "https" : "http"); const req = [protocol].request(options, (res) => { console.log(res.statusCode); }); error TS2339 ...

Setting the selected value of a radio button in an edit form: a step-by-step

I'm currently developing an edit form using ReactiveFormModule. My goal is to display data in the edit form with various input elements such as textboxes, dropdowns, radio buttons, and checkboxes. I've been successful in setting values for textbo ...

What's the best way in Angular 6 to set focus on an element that's being made content editable?

I am currently utilizing the contentEditable attribute in Angular 6 to allow for editing the content of elements within an ngFor loop. Is there a way to focus on a tag element when its contentEditable attribute is set to true? <div class="tag" *ngFor= ...

Unable to reinitialize the DataTable using Angular Datatable

I've been working on an Angular application that has a simple CRUD functionality. Initially, I tested my data with a static HTML table and everything was functioning as expected. However, I decided to implement a data table framework called Angular da ...

Does Angular 2/4 actually distinguish between cases?

I have a question about Angular and its case sensitivity. I encountered an issue with my code recently where using ngfor instead of ngFor caused it to not work properly. After fixing this, everything functioned as expected. Another discrepancy I noticed w ...

Utilizing the useRef hook in React to retrieve elements for seamless page transitions with react-scroll

I've been working on creating a single-page website with a customized navbar using React instead of native JavaScript, similar to the example I found in this reference. My current setup includes a NavBar.tsx and App.tsx. The NavBar.tsx file consists o ...

Error message in Angular 2 RC-4: "Type 'FormGroup' is not compatible with type 'typeof FormGroup'"

I'm currently facing an issue with Angular 2 forms. I have successfully implemented a few forms in my project, but when trying to add this one, I encounter an error from my Angular CLI: Type 'FormGroup' is not assignable to type 'typeo ...

Switch on ngbAccordion via TypeScript File

I need to implement a function in my component.ts file that will toggle the accordion using a button. Can you help me with the script for this? This is my HTML code: <button (click)="toggleAcc()" type="button" class="btn btn-pr ...

I am unable to utilize Local Storage within NextJS

type merchandiseProps = { merchandises: merchandiseType[]; cart?:string, collection?:string, fallbackData?: any }; const MerchandiseList: FC<merchandiseProps> = ({ merchandises }) => { const [cart, setCart] = useState<merchandiseType ...

Running two different wdio.config.js files consecutively

Is it possible to run two wdio.config.js files with different configurations, one after another? Here is how the first configuration file is defined in the code: const { join } = require('path'); require('@babel/register') exports.co ...

What is the best way to retrieve the most recent entry in a Firebase real-time database?

Utilizing Firebase's real-time database, I am updating values in a chart as they change. However, my struggle lies in retrieving only the most recent value added to the database. While browsing through limitToLast and 'child_added' do not w ...

Error occurred during the Uglify process: Unable to access the 'kind' property as it is undefined

I developed a project using TypeScript (version 3.9.3) and Node (version 10.16.3), but now I want to minify the code by converting it to JavaScript and running UglifyJS. However, after going through this process, the services that were functioning properly ...

How to Use a For Each Loop with Google Maps DrawingManager to Create Polygons?

My Ionic 4 Application using Angular 8 incorporates a google maps component where I need to draw and edit multiple polygons, eventually saving their vertices in a database. Hard coding some polygons is easy with getPath() or getPaths(), but I'm utiliz ...

Eliminate spacing in MaterialUi grids

As I work on a React project, I am faced with the task of displaying multiple cards with content on them. To achieve this layout, I have opted to use MaterialUi cards within Material UI grids. However, there seems to be an issue with excessive padding in t ...

When conducting tests, TypeScript raises an issue when comparing the values of array elements subsequent to performing a shift()

I am working with an array of strings, which was created by splitting a larger string using the `split` operation. Specifically, I am performing some tests on the first two elements of this array: var tArray = tLongString.split("_") if (tArray[0] == "local ...

Error encountered during the compilation of Angular2 Typescript due to difficulty in mapping a JSON response with underscores in the names

I recently started working with angular2 and I'm trying to map a JSON response to an array of custom objects. The issue I'm facing is that when I try to access a variable with an underscore in its name, I encounter a compile error. I followed the ...