Create a pipe function that destructures properties and appends new ones in a loop

In my current project, I'm working with a class that utilizes a dictionary to store data. This dictionary can be filled by piping functions that follow a specific format: they take in an object (dictionary), destructure its properties as arguments, and then return a new object with additional properties (that can also be added to the dictionary).

I'm wondering if there is a way to define the type for these pipe functions so that only properties that have already been added to the dictionary can be destructured. Essentially, restricting the access to only existing properties.

type AugmentFun = (source: Record<string, any>) => Record<string, any>;

class Pipeable {
  constructor(private dict: Record<string, any>) {}

  static of = (dict: Record<string, any>) => new Pipeable(dict);
  static augment = (source: Pipeable, fun: AugmentFun) => {
    return Pipeable.of(Object.assign({}, source.dict, fun(source.dict)));
  };

  pipe = (...funs: AugmentFun[]) => funs.reduce(Pipeable.augment, this);
}

const p = new Pipeable({});

// This functionality works without any type errors
const res1 = p.pipe(
  () => ({ a: 1, b: 2 }),       // { a: 1, b: 2 }
  ({ a, b }) => ({ c: a + b }), // { a: 1, b: 2, c: 3}
  ({ c }) => ({ d: c ** 2 })    // { a: 1, b: 2, c: 3, d: 9}
);

// In contrast, this scenario should trigger type errors but it doesn't
const res2 = p.pipe(
  () => ({ a: 1, b: 2 }),       // { a: 1, b: 2 }
  ({ c }) => ({ d: c + 10 })    // Expected error: "c" does not exist
);

(the augment() method is pure, but could potentially mutate the original instance instead, although this aspect is not crucial)

Answer №1

Encountering this issue is quite common, yet disappointingly there is no direct support in TypeScript for typing pipelines with a variable number of dependent functions. The most acceptable solution, without resorting to hacks, involves using overloads.

class Pipeable {
  constructor(private dict: Record<string, any>) {}

  static of = (dict: Record<string, any>) => new Pipeable(dict);
  static augment = (source: Pipeable, fun: AugmentFun) => {
    return Pipeable.of(Object.assign({}, source.dict, fun(source.dict)));
  };

  pipe<A>(fn1: () => A): A
  pipe<A, B>(fn1: () => A, fn2: (arg: A) => B): B
  pipe<A, B, C>(fn1: () => A, fn2: (arg: A) => B, fn3: (arg: A & B) => C): C
  pipe<A, B, C, D>(
    fn1: () => A, 
    fn2: (arg: A) => B, 
    fn3: (arg: A & B) => C, 
    fn4: (arg: A & B & C) => D
  ): D
  pipe(...funs: AugmentFun[]){ 
    funs.reduce(Pipeable.augment, this) 
  }
}

To handle the functionality we desire, we must create overloads matching the number of operators we wish to accommodate. Due to the absence of better alternatives, similar patterns are used for typing .pipe in RxJS and other libraries that implement pipes.

const p = new Pipeable({});

const res1 = p.pipe(
  () => ({ a: 1, b: 2 }),             // { a: 1, b: 2 }
  ({ a, b }) => ({ c: a + b }),       // { a: 1, b: 2, c: 3}
  ({ a, b, c }) => ({ d: c ** 2 })    // { a: 1, b: 2, c: 3, d: 9}
);

const res2 = p.pipe(
  () => ({ a: 1, b: 2 }),     
  ({ c }) => ({ d: c + 10 })  
//   ~ Property 'c' does not exist on type '{ a: number; b: number; }'
);

Playground

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

Displaying multiple items horizontally using ngFor

I am having trouble organizing my data using the *ngFor loop. I would like to have three items displayed per row when using ngFor. I attempted to use indexing, but it only resulted in one item per row. <div class="container"> <div class="row"&g ...

Creating child class instances inside parent classes in TypeScript

Could you please review the following example: type Task = (input: number) => number; abstract class ParentClass { constructor(protected tasks: Task[] = []) {} protected abstract getDescendantClass<T extends this>(): new (tasks: Task[]) ...

Compilation of various Typescript files into a single, encapsulated JavaScript bundle

After researching countless similar inquiries on this topic, I have come to the realization that most of the answers available are outdated or rely on discontinued NPM packages. Additionally, many solutions are based on packages with unresolved bug reports ...

I'm looking for a way to modify the Turkish characters and spaces in the names of JSON data objects. I plan to do this using WebApi

I am facing an issue with fetching data through an API. The JSON data format contains Turkish characters and spaces, causing problems when trying to display the data in a datatable. I have attempted to use the replace and parse functions, but so far, I hav ...

Event callback type narrowing based on the specific event key

While exploring different approaches to create a type-safe event emitter, I came across a pattern where you start by defining your event names and their corresponding types in an interface, as shown below: interface UserEvents { nameChanged: string; ...

Simultaneously accessing multiple APIs

I am currently facing an issue with calling two API requests sequentially, which is causing unnecessary delays. Can someone please advise me on how to call both APIs simultaneously in order to save time? this.data = await this.processService.workflowAPI1(& ...

The issue arises when TypeScript declarations contain conflicting variables across multiple dependencies

My current project uses .NET Core and ReactJS. Recently, I updated some packages to incorporate a new component in a .tsx file. Specifically, the version of @material-ui/core was updated from "@material-ui/core": "^3.0.3" to "@material-ui/core": "^4.1.3" i ...

Achieving click detection on a link within an iframe in Angular 2

Is there a way to detect a click on a link within an iframe? <iframe *ngIf="currentFrameUrl" id="contentFrame" [src]="currentFrameUrl | safe"></iframe> Inside my iframe component, I have a simple code that listens to changes in observable var ...

What are the recommended TypeScript tsconfig configurations for running Node.js 10?

Can someone provide information on the necessary target/libs for enabling Node.js v10.x to utilize async/await without generators? I have found plenty of resources for node 8 but not as much for node 10. ...

Methods for populating an object with Interface type and returning it

This is my function that populates an object based on interface type: public _fillAddModel<T>(lessonId: number, studyPeriodId: number, confirmed: boolean = false): T { let data: T; data = { lesson: this.substitution.lessonNumber, ...

Learn the process of extracting an array of objects by utilizing an interface

Working with an array of objects containing a large amount of data can be challenging. Here's an example dataset with multiple key-value pairs: [{ "id": 1, "name":"name1", age: 11, "skl": {"name": & ...

Oops! Looks like Deno encountered an error (TS2339) because it couldn't find the property x

Exploring Oak and Deno for the first time has been an exciting journey. I came across a helpful guide at along with the official documentation. My aim is to use REST-API deno by utilizing the script provided in (server.ts). import { v4 } from 'https: ...

Restricting the data type of a parameter in a TypeScript function based on another parameter's value

interface INavigation { children: string[]; initial: string; } function navigation({ children, initial }: INavigation) { return null } I'm currently working on a function similar to the one above. My goal is to find a way to restrict the initi ...

refresh my graph on the user interface once the service responds with JSON data

After obtaining the object successfully from my API, I have already displayed my custom graph component with default values. However, I need to update the 'chart1Title' of the graph component with the value of 'title' from the object. ...

Tips for adjusting HighCharts layout with highcharts-vue integrations

I have a fairly simple component: <template> <div> <chart v-if="!loading" ref="priceGraph" constructor-type="stockChart" :options="chartData" ...

Easy Steps to Simplify Your Code for Variable Management

I currently have 6 tabs, each with their own object. Data is being received from the server and filtered based on the tab name. var a = {} // First Tab Object var b = {} // Second Tab Object var c = {} // Third Tab Object var d = {}// Fou ...

Issues arise when attempting to assign GraphQL types, generics, and NestJS Type<> as they are not interchangeable with Type<&

I've been attempting to use this definition: import { GraphQLScalarType } from 'graphql'; import { Type } from '@nestjs/common'; export function createFilterClass<T extends typeof GraphQLScalarType>( FilterType: Type&l ...

Exploring the New Features of NodeJS 13 and Typescript 3.8: A Guide to Importing esm Modules

I'm encountering difficulties with some imports in NodeJS related to the utilization of Typescript 3.8's advanced features such as private fields like #myPrivateField. I've been struggling to correctly import the "typescript" module into my ...

Tips for showcasing unique validation error messages

My form includes a text area for the user to input JSON Code. If the entered text is not valid JSON, an error message should be displayed but unfortunately, it's not working as expected. Below is my custom validator code: import { AbstractControl, V ...

Create type declarations using the Typescript compiler by running the command:

After generating my definition file ".d.ts" using tsc --declaration or setting declaration as true in the tsconfig.json, I noticed that the generated files are missing declare module "mymodule" {... } This appears to be causing issues with "tslint" which ...