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