Can TypeScript support the implementation of versatile decorators that can be linked together based on their input and output types?

Within our integration processes, we have developed templated implementations in our codebase that align well with the "pipe and filter" pattern in my opinion.

These "components" can be structured in the following formats:

class Component1<In, Out, Xin, Xout>
class Component2<Xin, Xout, Yin, Yout>
class Component3<Yin, Yout> // only includes 2 parameters, but could potentially be <Yin, Yout, None, None> for a customized 'None' type

The concept is to enable these components to be "chained" together, allowing for a sequence like this:

const c1 = new Component1<A,B,C,D>(...) // potentially pass parameter types in the constructor? Other alternatives?
const c2 = new Component2<C,D,E,F>(...)
const c3 = new Component3<E,F, None, None>(...)

const chain = c1.andThen(c2).andThen(c3) // The "final" element in the chain will "always" be a component of type <X,Y, None, None>

chain.run() // Uncertain if this is necessary, but to clarify that something executes this chain

I'm struggling to find a "universal" method of constructing these components where the chaining can be "specified" at compile time to restrict which components can connect with others (i.e., the input/output types must align). Therefore, c1 can only be followed by c2 and not by c3 - and no components can be linked after c3.

Is this a feasible endeavor? Any approaches to achieve something similar?

(For those who are curious: aiming for a similar level of composability as Finagle offers in the Scala realm)

Answer №1

It seems like there might be some confusion in your use of generics, particularly in distinguishing between type parameter variables and the actual concrete types being inserted. Additionally, the use of terms like val and None might not align with TypeScript conventions. Nevertheless, the following snippet is a possible solution that compiles and could potentially achieve the desired behavior:

type NotNever<T, Y=T, N=never> = [T] extends [never] ? N : Y;

// Defining a base component class with generic type parameters
declare class BaseComponent<In, Out, Xin=never, Xout=never> {
  i: In;
  o: Out;
  xi: Xin;
  xo: Xout;

  andThen<Yin, Yout>(
    this: NotNever<Xin | Xout, this>,
    c: BaseComponent<Xin, Xout, Yin, Yout>
  ): BaseComponent<In, Out, Yin, Yout>;

  run(this: BaseComponent<In, Out, never, never>): void;
}

class Component1 extends BaseComponent<'In', 'Out', 'Xin', 'Xout'> { }
class Component2 extends BaseComponent<'Xin', 'Xout', 'Yin', 'Yout'> { }
class Component3 extends BaseComponent<'Yin', 'Yout'> { }

Here's how you can test it:

const c1 = new Component1();
const c2 = new Component2();
const c3 = new Component3();

c1.andThen(c1); // error
c1.andThen(c2); // okay
c1.andThen(c3); // error
c1.run(); // error

c2.andThen(c1); // error
c2.andThen(c2); // error
c2.andThen(c3); // okay
c2.run(); // error

c3.andThen(c1); // error
c3.andThen(c2); // error
c3.andThen(c3); // error
c3.run(); // okay

const chain = c1.andThen(c2).andThen(c3);
chain.run(); // okay

If this approach aligns with your requirements, then good luck with your implementation!


Alternatively, here's another method to achieve a similar outcome without using conditional types or polymorphic this:

// Defining base classes for chain components
declare class EndComponent<In, Out> {
  i: In;
  o: Out;
  run(): void;
}

declare class PipeComponent<In, Out, Xin, Xout> {
  i: In;
  o: Out;
  xi: Xin;
  xo: Xout;
  
  andThen<Yin, Yout>(
    c: PipeComponent<Xin, Xout, Yin, Yout>
  ): PipeComponent<In, Out, Yin, Yout>;
  
  andThen(c: EndComponent<Xin, Xout>): EndComponent<In, Out>;
}

class Component1 extends PipeComponent<'In', 'Out', 'Xin', 'Xout'> {}
class Component2 extends PipeComponent<'Xin', 'Xout', 'Yin', 'Yout'> {}
class Component3 extends EndComponent<'Yin', 'Yout'> {}

The functionality should remain consistent with the previous approach. Best of luck with your project!

Answer №2

Here is the code snippet I have:

class Component<T, U> {
    constructor(private t: T, private u: U) {}
    andThen<V>(component: Component<U, V>): Component<U, V> {
        // implementation of andThen
        return component;
    }
    static run<T>(component: Component<T, null>) {
        // implementation of run
    }
}

type A = 'a'; const a: A = 'a';
type B = 'b'; const b: B = 'b';
type C = 'c'; const c: C = 'c';

const c1 = new Component<A, B>(a, b);
const c2 = new Component<B, C>(b, c);
const c3 = new Component<C, null>(c, null);

c2.andThen(c1); // TypeScript error: A is not assignable to B
Component.run(c1.andThen(c2)); // TypeScript error: Component<B,C> not assignable to Component<B,null>
Component.run(c1.andThen(c2).andThen(c3));

I have simplified the code by using <T,U> instead of <Xin, Xout, Yin, Yout>, but it can be easily adjusted.

The type of the chain works as expected. However, at runtime,

Component<...,X>.andThen(Component<Y,...>)
is recognized as invalid (first TypeScript error).

After some refactoring, I realized that it is not the chain itself (i.e., Component) that triggers the .run method - I couldn't figure out a compile-time way to detect if .run was called by a Component<..., null> (i.e., the last component in a chain).

Instead, I moved the run method as a static method of the Component class, and it only accepts a last component as input. The usage is demonstrated in the last two lines.

Lastly, the class is designed to be very generic and polymorphic, allowing for chaining of multiple components!

(new Component<'a', 'b'>('a', 'b'))
.andThen(new Component<'b', 'c'>('b', 'c'))
.andThen(new Component<'c', 'd'>('c', 'd'))
.andThen(new Component<'d', 'e'>('d', 'e'))
.andThen(new Component<'e', 'f'>('e', 'f'))
.andThen(new Component<'f', 'g'>('f', 'g'))

I hope this meets your requirements.

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

Leveraging ng-repeat within ng-view

I am facing an issue with using ng-repeat inside ng-view as the data is not being displayed. I have come across suggestions on forums to use a factory, but I am hesitant to utilize a service because my $scope data relies on $routeParams for querying. var ...

splitting xmlhttp.responsetext using loops

Currently, I have a JavaScript function that utilizes xmlhttp.responsetext to fetch data from MySQL. This data is then used to populate form fields in a dropdown menu, which has been functioning perfectly so far. However, the issue arises when I attempt to ...

Convert all key types into arrays of that key type using a TypeScript utility type

My interface (type) is currently defined as: interface User { name: string, id: string, age: number, town: string } I have a function now that will search for Users based on specific fields. I prefer not to manually declare an additi ...

Ensure the correct file extension is chosen when selecting a file for the 'file_field' and display any error messages using Ruby on Rails

I am currently using Ruby on Rails 3 and Prototype, and I need to be able to check the file extension when a file is selected with file_field. I only want to allow files with extensions of .doc or .pdf, any other extensions should display an error. In my ...

Exploring the power of Google Charts in conjunction with PHP arrays

I have three PHP arrays with key-value pairs: $needles = ["Needle1", "Needle2", "Needle3", "Needle4", "Needle5"]; $uph1 = ["Needle1" => 3, "Needle3" => 5, "Needle4" => 7]; $uph2 = ["Needle1" => 4, "Needle2" => 2, "Needle3" => 4]; ...

Generating a USA map with DataMaps in d3jsonData

I'm trying to create a basic US map using the DataMaps package and d3 library. Here's what I have attempted so far: <!DOCTYPE html> <html> <head> <title> TEST </title> <script src="https://d3js.org/d3.v5.js"> ...

a solution to the focus/blur issue in Firefox's browser bug

I have created the following script to validate if a value entered in an input field already exists in the database. $('#nome_field').blur(function(){ var field_name=$('#nome_field').val(); $.ajax({ url: " ...

Creating route in Node.js using the forEach method

While attempting to create routes for each ID using a forEach loop, I encountered a problem where the second route would not run and the application would continue loading until a timeout is reached. I have checked all the expected values and everything se ...

Refreshing a Vue JS component

As a beginner in VueJs, I am facing an issue with reloading a component and table when a refresh button is clicked. I have tried using the forceUpdate method and changing keys, but it has not been successful so far. If anyone has any suggestions on how to ...

There seems to be an issue in Angular as it is unable to retrieve /

I'm encountering an issue with my simple application where I am receiving the error message "Cannot GET /." Also, in the console, I see this error: TypeError: Cannot read property 'checked' of null at Object.SL_BBL_locer. I'm unsure ab ...

Arranging an array of JSON objects without keys in a specific order

Here is an array containing multiple objects without any keys: const result = [ ["Joe", "1", "2.22%", "$3,000.00"], ["Tom", "1", "2.22%", "$4,650.00"], ["Ryan", "4", "4.44%", "$18,925.00"], ["Jordan", "2", "4.44%", "$3,300.00"], ["Fred", "0" ...

Tips for verifying a login modal on an asp.net webforms using jQuery?

I am currently working on an asp.net webpage that utilizes a modal bootstrap for user login. Upon clicking the Login button, it should authenticate the user and initiate a server-side method called "ExportToZip" to download a zip file. My issue lies in ens ...

React is failing to display JSX elements within a map function

Currently, I am attempting to run a loop and then display JSX content: While the console.log displays the correct data, the return statement is not rendering the HTML content. const StaticTable = (props) => { const [data, setData] = useState({}); ...

Tips for indicating request parameters in Drive.Comments.list

I have successfully retrieved the default 20 comments using the code below by specifying a single fileId parameter. However, I am interested in pulling back one hundred comments or paginating to the next set of 20 out of curiosity. In my getComments funct ...

The error message "Property '...' is not found on the type 'ServerContextJSONValue'" pops up whenever I try to utilize the useContext() function

After creating a Provider and defining the type, I encountered a TypeScript error when using it with useContext(): "Property '...' does not exist on type 'ServerContextJSONValue'. I'm not sure what I am missing. Can anyone help me ...

Load a partial view in MVC using Ajax with a complex data structure

Within my main view, I have a section that loads a partial view containing data. Here is the code snippet that is executed upon initial loading: <div id="customerdetailsDIV" class="well main-well clearfix"> @Html.Partial("_customer_details", Mod ...

When copying text from React-PDF Display, the values may appear altered or varied

This snippet of text was excerpted from a brief SHA provided at the git summit. Generated using React-pdf, this is a PDF document with some interesting quirks. Although the displayed text reads as 4903677, it changes to •G07THH when copied. The font ...

Positioning tooltip arrows in Highcharts

I'm attempting to modify the Highcharts tooltip for a stacked column chart in order to have the arrow on the tooltip point to the center of the bar. I understand that I can utilize the positioner callback to adjust the tooltip's position, but it ...

Identifying Elements Generated on-the-fly in JavaScript

Currently, I am tackling the challenge of creating a box that can expand and collapse using regular JavaScript (without relying on jQuery). My main roadblock lies in figuring out how to effectively detect dynamically added elements or classes to elements a ...

Open the URL in a new tab based on certain conditions

Currently, my page redirects if a specific checkbox is selected and the "submit" button is clicked: if request.Form("myCheckbox") = "on" then response.Redirect("/newPage.asp?txt="staff"") else response.Redirect("/thisPage.asp") end if I ...