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 there a way to verify within the "if" statement without repeating the code?

Is there a way in javascript (or typescript) to prevent redundant object rewriting within an if statement condition? For example: if (A != null || A != B) { // do something here } // can it be done like this: if (A != null || != B) { // avoid repeating ...

Tips for sending back a response after a request (post | get) is made:

In the service, there is a variable that verifies if the user is authorized: get IsAuthorized():any{ return this.apiService.SendComand("GetFirstKassir"); } The SendCommand method is used to send requests (either as a post or get request): ApiServi ...

Interactive 3D model movable within designated area | R3F

I am currently facing a challenge in limiting the drag area of my 3D models to the floor within my FinalRoom.glb model. After converting it to tsx using gltfjsx, I obtained the following code: import * as THREE from "three"; import React, { useRe ...

Typescript error encountered when executing multiple API calls in a loop causing Internal Server Error

I'm relatively new to Typescript/Javascript and I am working on a function called setBias(). In this function, I want to set all indices of this.articles[i].result equal to the biased rating returned by the function getBiasedRating(this.articles[i].ur ...

Using Rxjs to dynamically map values from an array with forkJoin

Greetings! I have a collection of Boolean observables and would like to apply a logical AND operation. Currently, I am passing static values 'a' and 'b', but I am unsure of the number of elements in the totalKeys array. import { forkJoi ...

Are npm @types packages causing issues in Visual Studio?

Nowadays, TypeScript's type packages are typically found in node packages with the format @types/packagename. Strangely, Visual Studio, despite its support for npm packages, appears to be unable to locate them: https://i.sstatic.net/7tOK1.png The s ...

Managing the re-rendering in React

I am encountering a situation similar to the one found in the sandbox example. https://codesandbox.io/s/react-typescript-fs0em My goal is to have Table.tsx act as the base component, with the App component serving as a wrapper. The JSX is being returned ...

The concept of Nested TypeScript Map Value Type

Similar to Nested Typescript Map Type, this case involves nesting on the "value" side. Typescript Playground const mapObjectObject: Map<string, string | Map<string, string>> = new Map(Object.entries({ "a": "b", &quo ...

Issue with the close button on ngb-alert not functioning properly

As I develop my website, I have incorporated an ngb-alert component to display an alert message to users upon login. While the alert itself is functioning properly, I have encountered an issue with the close button being misaligned, and I am struggling to ...

What is the best way to merge three arrays of data into a single iterable array?

I am working on a function that fetches data from an API. In this process, I execute a total of 3 queries, each returning an array of data. My goal is to combine these 3 arrays into a single iterable array, where the indexes correspond to the original a ...

Error: The property 'process' cannot be read because it is not defined

Seeking help with a code issue Any advice on best practices would be greatly appreciated. Thank you! An error has occurred: TypeError: Cannot read property 'process' of undefined myComponent.ts ProcessInfo: any | false; showSaveItems = ...

Using Node.js and Typescript to bring in external modules from

Attempting to generate a random integer between 1 and 6 using the 'random' library. Here's what I have coded so far: import random from 'random' function rollDice(min:number, max:number) { return Math.floor(Math.random() * (ma ...

What is the significance of the colon before the params list in Typescript?

Consider the following code snippet: import React, { FC } from "react"; type GreetingProps = { name: string; } const Greeting:FC<GreetingProps> = ({ name }) => { // name is string! return <h1>Hello {name}</h1> }; Wha ...

The collaboration between an object literal declaration and an object instantiated through a constructor function

If I declare let bar: Bar; and set it to initialFooConfig;, is bar still considered as type Bar and an object, or does it become an object in literal notation? If the assignment can be done both ways (assuming initialFooConfig is not a constant), what set ...

How can one click the button within the expanded dropdown while hovering over the navigation item in Angular and Bootstrap?

Issue Description: Upon hovering over a navigation item, the dropdown container is displayed but it's not clickable. Desired Behavior: Hovering over a navigation item should display the dropdown container and allow clicking on its contents. Furthermo ...

How Typescript Omit/Pick erases Symbols in a unique way

Recently, I have delved into TypeScript and started working on developing some custom utilities for my personal projects. However, I encountered an issue with type mapping involving Pick/Omit/Exclude and other typing operations where fields with symbol key ...

The process of ordering awaits using an asynchronous method

async fetchAndStoreRecords(): Promise<Records[]> { this.subRecords = await this.afs.collection<Records>('records') .valueChanges() .subscribe((data: Records[]) => { console.log('log before data ...

Component remains populated even after values have been reset

My child component is structured as shown below, ChildComponent.html <div> <button type="button" data-toggle="dropdown" aria-haspopup="true" (click)="toggleDropdown()"> {{ selectedItemName }} <span></span> </but ...

Encountering unanticipated breakpoints in compiled .Next files while using Visual Studio Code

As a newcomer to NextJS, I have encountered an issue that is disrupting my workflow. I followed the instructions in https://nextjs.org/docs/advanced-features/debugging#using-the-debugger-in-visual-studio-code to set up my launch.json file. Although I am ...

React-hook-form does not display the input length in a custom React component

Introducing a custom Textarea component designed for reusability, this basic textarea includes a maxlength prop allowing users to set the maximum input length. It also displays the current input length in the format current input length/max length. While ...