Collection of functions featuring specific data types

I'm currently exploring the idea of composing functions in a way that allows me to specify names, input types, and return types, and then access them from a central function. However, I've encountered an issue where I lose typing information when implementing this method.

Is there a way to create a system like this without relying on hardcoded value types?

import * as fs from 'fs';
import { promisify } from 'util';
import * as lodash from 'lodash';

const libs = []

export enum Types {
    number,
    numbers,
    string,
    buffer,
}

export enum Actions {
    add,
    subtract,
    readFile,
}

libs.push({
    action: Actions.add,
    from: Types.numbers,
    get: Types.number,
    fn: (...n: number[]): number => n.reduce((a, b) => a + b, 0),
})

libs.push({
    action: Actions.subtract,
    from: Types.numbers,
    get: Types.number,
    fn: (...n: number[]): number => n.reduce((a, b) => a - b, 0),
})

libs.push({
    action: Actions.readFile,
    from: Types.string,
    get: Types.string,
    fn: async (s:string): Promise<string> => promisify(fs.readFile)(s, 'UTF8'),
})

libs.push({
    action: Actions.readFile,
    from: Types.string,
    get: Types.buffer,
    fn: async (s:string): Promise<Buffer> => promisify(fs.readFile)(s),
})

const library = (a: Actions, from: Types, get: Types, lib) => {
    const found = lodash.find(lib, fn => {
        return (
        lodash.isEqual(fn.from, from) &&
        lodash.isEqual(fn.get, get)
        );
    });
    if (!found) throw new Error('no conversion');
    return found.fn;
}

const { readFile } = Actions;
const { string: s } = Types;

const x = library(readFile, s, s, libs)

x('./tres.ts').then(console.log)

Is there a way to preserve the typing information of x?

Answer №1

To maintain the type of items in the array, it is important to store it in libs. One way to achieve this is by using an additional function that can determine the type for libs based on the actual elements in the array, including literal types where Actions and Types are utilized.

Having this information enables us to define the library function to retrieve the specific function type from libs that shares the same action, get, and from as the provided types:

import * as fs from 'fs';
import { promisify } from 'util';
import * as lodash from 'lodash';


export enum Types {
    number,
    numbers,
    string,
    buffer,
}

export enum Actions {
    add,
    subtract,
    readFile,
}

function makeLib<T extends Array<{action : A, from: F, get: G, fn: (...a: any[])=> any}>, A extends Actions, F extends Types, G extends Types>(...a:T){
    return a;
}
const libs = makeLib({
    action: Actions.add,
    from: Types.numbers,
    get: Types.number,
    fn: (...n: number[]): number => n.reduce((a, b) => a + b, 0),
}, {
    action: Actions.subtract,
    from: Types.numbers,
    get: Types.number,
    fn: (...n: number[]): number >> n.reduce((a, b) => a - b, 0),
}, {
    action: Actions.readFile,
    from: Types.string,
    get: Types.string,
    fn: async (s:string): Promise<string> => promisify(fs.readFile)(s, 'UTF8'),
}, {
    action: Actions.readFile,
    from: Types.string,
    get: Types.buffer,
    fn: async (s:string): Promise<Buffer> => promisify(fs.readFile)(s),
})

const library = <T extends Array<{action : Actions, from: Types, get: Types, fn: (...a: any[])=> any}>, A extends Actions, F extends Types, G extends Types>(a: A, from: F, get: G, lib: T) => {
    const found = lodash.find(lib, fn => {
        return (
        lodash.isEqual(fn.from, from) && 
        lodash.isEqual(fn.get, get)
        );
    });
    if (!found) throw new Error('no conversion');
    return found.fn as Extract<T[number], {action : A, from: F, get: G }>['fn'];
}

const { readFile } = Actions;
const { string: s } = Types;

const x = library(readFile, s, s, libs) // x is (s: string) => Promise<string

x('./tres.ts').then(console.log)

const x2 = library(Actions.subtract, Types.string, Types.string, libs) // never
const x3 = library(Actions.subtract, Types.numbers, Types.number, libs) //  (...n: number[]) => number

You have the option to utilize strings instead of enums:

function makeLib<T extends Array<{action : V, from: V, get: V, fn: (...a: any[])=> any}>, V extends string>(...a:T){
    return a;
}
const libs = makeLib({
    action: "add",
    from: "numbers",
    get: "number",
    fn: (...n: number[]): number => n.reduce((a, b) => a + b, 0),
}, {
    action: "subtract",
    from: "numbers",
    get: "number",
    fn: (...n: number[]): number | null => n.reduce((a, b) => a - b, 0),
}, {
    action: "readFile",
    from: "string",
    get: "string",
    fn: async (s:string): Promise<string> => promisify(fs.readFile)(s, 'UTF8'),
}, {
    action: "readFile",
    from: "string",
    get: "buffer",
    fn: async (s:string): Promise<Buffer> => promisify(fs.readFile)(s),
})

const library = <T extends Array<{action : string, from: string, get: string, fn: (...a: any[])=> any}>, 
    A extends T[number]['action'], F extends T[number]['from'], G extends T[number]['get']>(a: A, from: F, get: G, lib: T) => {
    const found = lodash.find(lib, fn => {
        return (
        lodash.isEqual(fn.from, from) && 
        lodash.isEqual(fn.get, get)
        );
    });
    if (!found) throw new Error('no conversion');
    return found.fn as Extract<T[number], {action : A, from: F, get: G }>['fn'];
}

const { readFile } = Actions;
const { string: s } = Types;

const x = library("readFile", "string", "string", libs) // x is (s: string) => Promise<string

x('./tres.ts').then(console.log)

const x2 = library("subtract", "string", "string", libs) // never
const x3 = library("subtract", "numbers", "number", libs) //  (...n: number[]) => number

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

- "Is it possible to extract values from an optional variable?"

Is there a method to access individual variables from the data returned by the reload method? let reloadProps: ReloadProps | undefined; if (useClientSide() === true) { reloadProps = reload(props.eventId); } const { isTiketAdmin, jwt, user ...

Karma's connection was lost due to a lack of communication within 10000 milliseconds

The Karma test suite is encountering issues with the following message: Disconnected, because no message in 10000 ms. The tests are not running properly. "@angular/core": "7.1.3", "jasmine-core": "3.3.0", "karma-jasmine": "1.1.2", The failure seems t ...

In Angular, the data retrieved from the API will only appear on the page once it has been manually

Recently, I started working on an Angular project and encountered a problem with data display after each component routing. Initially, the data is not displayed until the page is reloaded. You can see the issue in the screenshots: [![After reload][2]][2]. ...

Check the @versionColumn value for validation prior to the entity save operation in TypeORM

I am currently in the process of saving data in a PostgreSQL database using TypeORM with NestJS integration. The data I am saving includes a version property, which is managed using TypeORM's @VersionColumn feature. This feature increments a number ea ...

How do you transfer byte[] data using a DTO in Java Spring?

I am currently facing an issue with my server-side application. The problem arises when attempting to convert a Blob to an Excel file on the front-end, specifically when the byte[] is sent within a DTO. When sending a POST request from the back-end (sprin ...

Creating Typescript packages that allow users to import the dist folder by using the package name

I am currently working on a TypeScript package that includes declarations to be imported and utilized by users. However, I have encountered an issue where upon publishing the package, it cannot be imported using the standard @scope/package-name format. I ...

"Attempting to verify a JSON Web Token using a promise that returns an object not compatible with the specified

Learning about Typescript has been quite a challenge for me, especially when it comes to using the correct syntax. I have implemented a promise to retrieve decoded content from jwt.verify - jsonwebtoken. It is functioning as intended and providing an obje ...

Generate a unique Object URL for the video source by utilizing the binary string obtained from the backend

I've been facing an issue with loading binary video data from my backend using fastAPI. When I curl the endpoint and save the file, it plays perfectly fine on my laptop. For the frontend, I'm using React+Typescript. I fetch the binary video data ...

"Optimize Your Data with PrimeNG's Table Filtering Feature

I'm currently working on implementing a filter table using PrimeNG, but I'm facing an issue with the JSON structure I receive, which has multiple nested levels. Here's an example: { "id": "123", "category": "nice", "place": { "ran ...

Adaptively linking to the property of a deeply nested object

Attempting to bind to a property of a nested object using [(ngModel)], but facing the challenge that the path to the property is dynamically set. Consider the following three classes: class A { p1: C constructor() { p1 = new C("A") } } class B { p2: ...

Angular Checkbox Click EventLearn how to handle click events on

How can I toggle the visibility of a form using ngIf when a click event is triggered by a checkbox? Below is the code for my column header and column values: <th><label class="btn btn-filter"> <input type="checkbox" ...

What is the correct method for retrieving a specific child element in React?

In my React project, I have created a component that consists of various subcomponents: import React, { FunctionComponent } from 'react'; import { FormControl, FormGroup, FormGroupProps, FormLabel, FormText, FormTextProps } from 'react-boots ...

What is the best way to showcase several images using Sweet Alert 2?

In the process of developing my Angular 2 application, I have incorporated sweet alert 2 into certain sections. I am looking to showcase multiple images (a minimum of two) at the same time in the pop-up. Does anyone have any suggestions on how to achieve ...

What steps can be taken to avoid an abundance of JS event handlers in React?

Issue A problem arises when an application needs to determine the inner size of the window. The recommended React pattern involves registering an event listener using a one-time effect hook. Despite appearing to add the event listener only once, multiple ...

Could you specify the type of useFormik used in formik forms?

For my react formik form, I have created multiple components and now I am looking for the right way to pass down the useFormik object to these components. What should be the correct type for formik? Main Form const formik = useFormik({ ... Subcomponent ...

Turning XSD into TypeScript code

Stumbling upon this tool called CXSD, I was intrigued. The documentation describes cxsd as a streaming XSD parser and XML parser generator designed for Node.js and TypeScript (optional but highly recommended). It seemed like the perfect solution for my ne ...

Having trouble finding the "make:migration" command in Adonis 5 - any suggestions?

After reviewing the introductory documentation for Adonis Js5, I attempted to create a new API server. However, when compiling the code using "node ace serve --watch" or "node ace build --watch", I kept receiving an error stating "make:migration command no ...

What are some methods to troubleshoot $injector type errors in TypeScript?

I'm currently facing an issue with my AngularJS code. Here is a snippet of what I have: this.$injector.get('$state').current.name !== 'login' But, it's giving me the following error message: error TS2339: Property 'c ...

Allow only specified tags in the react-html-parser white list

Recently, I've been working on adding a comments feature to my projects and have come across an interesting challenge with mentioning users. When creating a link to the user's profile and parsing it using React HTML parser, I realized that there ...

Is it possible to apply filters to individual columns in a dynamic mat table using Angular?

Does anyone know how to add a filter for each dynamic column in an Angular Material table? I've only found solutions for static headers, but my table headers are constantly changing. I'm looking for something similar to this example: https://i.st ...