Define the type of an object with string keys and function values - node.js

I have a node JS function with one parameter that is an object with keys and the values are functions that then resolve to the underlying values.
We have just switched over to TS and I don't know how to define the key:value types of a parameter and further I don't know how to define a function as the value type?
The TS function looks like this...
const myJSFunction = o => input => ...
Where o is the string:function object. And input is then passed into each of the function values of o.
So I was thinking of having some signature along the lines of...
// define the generic <R> function as <U => any>
// define the generic <T> as an object of { string : R }
const myTSFunction = (o: T) => (input: U) => ...
Or something? I'm clutching at straws here as I don't know Typescript well enough to know what is possible with generics.
Thanks

What about something like this :
// We define what the type o is
// key: string means "any key should ..."
interface Obj<T> {
[key: string]: (input: T) => void,
};
// We instantiate an object for the test
const o: Obj<string> = {
a: (input) => { },
b: (input) => { },
};
// We define the function to work with any type of value of obj
// and call it for the test
function myTSFunction<T>(obj: Obj<T>, val: T): void {
obj[0](val);
}

Grégory NEUT answer helped a lot but there were some other constraints that I discovered on the way (that JS had been hiding away). I'm using Lodash so my object was not just an object but a type that they had defined.
So I defined some new types...
type TransformerFunction<T> = (o: T) => any;
type TransformerObject<T> = Dictionary<TransformerFunction<T>>;
And then the function became like...
export const objectTransform = <T extends any>(o: TransformerObject<T>) => <U extends T>(json: U): Dictionary<any> => _.flow(
_.mapValues((f: TransformerFunction<T>) => f(json)),
_.omitBy(_.isUndefined),
_.omit('undefined'),
)(o);
This is how I have been transforming JSON in JS and now moving it over to TS and loving the generics.

Related

Typescript: get parameters type of a method in a composition class

Hello there fellow TS community. So here's a problem: imagine we have a composition interface like this:
type IWorker = {
serviceTask: IServiceTask,
serviceSomethingElse: IServiceColorPicker
}
type IServiceTask = {
doSomething: (arg1: number) => Promise<void>;
}
type IServiceSomethingElse = {
doSomething1: (params: Record<string, any>) => Promise<string>;
}
We also have implementation for these interfaces, but I just skipped them here. Lemme now if you need those!
Anyways so let's say we have a function that runs one of the methods in a nodejs worker thread:
const runInWorker<T extends keyof IWorker, K extends keyof IWorker[T], P extends ????>(parameters: {workerService: T, serviceMethod: K, methodParams: P}) => {
const worker: IWorker = new Worker();
worker[workerService][serviceMethod].call(this, methodParams)
...
}
This runInWorker function has three arguments:
Name of service we're about to run - workerService: serviceTask or serviceSomethingElse
Name of method of a service - serviceMethod: doSomething or doSomething1
Parameters to pass into that method. methodParams: number | Record<string, any>. I've tried the Parameters<IWorker[T][K]> generic but it doesn't resolve to parameters - says 'type IWorker[T][K] doesn not satisfy constraint (...args: any) => any'- which is Parameters generic's type argument constraints
So I have 2 questions:
How to get rid of "call doesn't exist on type IWorker[T][K]" TS error?
How do I infer argument types of my methods?
Thanks!
With question 1 I've expected methods to be callable so I've tried just calling them with
worker[workerService][serviceMethod] but of course it didn't help
With question 2 as I said I've tried to use Parameters generic but looks like it's no good with other generics like that - it just doesn't know those types yet
Typescript cannot see that all fields on IWorker needs to be methods. You can either have some constraint on the level of type signature, or you can make a kind of constraint by having conditional type. The latter can be implemented like this. Swap the Parameters type with
type GetParameters<T> = T extends (...args: any) => any ? Parameters<T> : never
Here you check whether T is actually a function. If you would declare a non-function field on IWorker, then the third parameter would have never type, that is a type for you can't have any value, that means you couldn't make a call to such a function that would typecheck.
Thus, by having this signature, you can make the calls:
function runInWorker2<T extends keyof IWorker, K extends keyof IWorker[T]>(parameters: {workerService: T, serviceMethod: K, methodParams: GetParameters<IWorker[T][K]>}) {
// implementation
}
const t = runInWorker2({workerService: "serviceTask", serviceMethod: "doSomething", methodParams: [3]})
const t2 = runInWorker2({workerService: "serviceSomethingElse", serviceMethod: "doSomething1", methodParams: [{x: "y"}]})

Typescript - how can i add a custom property to express. Request with two or more types?

I have a problem. I know how to add a custom property to Express.Request, but i dont know how to give that custom property two or more types. For example i have a.ts and b.ts. These file export a function as default. And i import them in c.ts. All those functions add the same property to Express.Request. But those functions, add different types of functions as value! Look at this example:
a.ts:
declare module "express-serve-static-core" {
interface Request {
store: (name: string) => number
}
}
const func1 = (req:Request, res:Response) => {
req.store = (name) => {
return setInStore(name)
}
}
export default func1;
b.ts:
declare module "express-serve-static-core" {
interface Request {
store: (key: number) => string
}
}
const func2 = (req:Request, res:Response) => {
req.store = (key) => {
return getFromStore(key);
}
}
export default func2;
but when i set the type in b.ts, i will get error from a.ts. it says that type (name: string) => number is not assignable to type (key: number) => string.
and i think that there is an other way instead of using conditional types.
so how can i change the type of store property to (name: string) => number if user used the func1 middleware and change the type of store to (key: number) => string if user used func2 thanks for help.
I am not express user. maybe you can config the type through generics too which is easier
anyway, what you want to do is not possible. however, you can go with one of these solutions:
1. unions
if the store functions supports both value, then it is not important which type in which function you are using... so you can use union
(key: string | number) => void
2. more specific function name
split the store function into 2 seperate functions like this
storeByName: (name: string) => void
storeById: (id: number) => void

How to implement a type safe, phantom types based builder in typescript?

The idea is to allow a call to the .build() method only upon having all the mandatory parameters filled. So the constructor should be taught to do some validation.
If I understand you correctly, you have some kind of builder class, which by default doesn't have all the required parameters. And that class has a method, which updates its state. Only when all required parameters are set, only then build method should be available.
So first of all, we have T type which partial (all properties are optional).
On each update, we should return a new instance with type T & Record<K, T[K]> - it means optional T + one non-optional property.
Finally, we can use conditional types to allow build only when T extends Required<T>.
So the final solution:
function createBuilder<T>(initialData: T) {
return {
update: <K extends keyof T>(key: K, value: T[K]) => {
return createBuilder<T & Record<K, T[K]>>({
...initialData,
[key]: value
})
},
build: (() => {
//
}) as T extends Required<T> ? () => boolean : undefined
}
}
const builder1 = createBuilder<Partial<{
key1: string,
key2: number
}>>({})
builder1.build()
// Cannot invoke an object which is possibly 'undefined'
const builder2 = builder1.update('key1', 'abc')
builder2.build()
// Cannot invoke an object which is possibly 'undefined'
const builder3 = builder2.update('key2', 10)
builder3.build()
// No error
Hovewer, there is no point having this logic. If you can statically update the object, you probably can set all properties in the constructor.

Typescript - Nested arrow function typing

I have this code for deferring the execution of a function
export type DeferredFunction<T> = () => T | PromiseLike<T>;
export class Deferrable<T> {
protected df: DeferredFunction<T>;
constructor(df: DeferredFunction<T>) {
this.df = df;
}
public async execute(): Promise<T> {
return this.df();
}
}
export const defer = <T>(df: DeferredFunction<T>): Deferrable<T> => new Deferrable<T>(df);
That works fine and I can run code like
await defer(() => someFunction('foo', 'bar')).execute();
but I what I want to do is type DeferredFunction in a way that I can specify the inner function's signature but I can't get it working. In generic cases the above works but when I want to limit the arguments such that they are specific to a certain type of function I don't have that kind of control.
For clarity, I want to be able to type the inner function's inputs like (as an example)
export type InnerDeferredFunction<T> = (a: string, b: number, c: SomeObjectType) => T | PromiseLike<T>
Any help would be greatly appreciated!
What "inner function" are you talking about? Is it someFunction? If so then the type of DeferredFunction<T> has no handle on it, since it's a function called by the implementation of DeferredFunction<T>. There's no way in TypeScript to specify "a function whose implementation must call a function of type (x: string, y: number, z: boolean) => string". Implementation details are not part of a function's call signature.
The only way I can imagine to begin to approach this would be for DeferredFunction<T> to accept as a parameter the inner function you want to call, along with the list of arguments to call it with. This might not be what you're looking for, but it's the closest that the type system can represent.
Something like this:
export type InnerDeferredFunction<T, A extends any[]> = (...args: A) => T | PromiseLike<T>;
export type ZeroArgDeferredFunction<T> = InnerDeferredFunction<T, []>
Here I'm keeping A generic but you can specify it to some hardcoded list of arguments. I've renamed your DeferredFunction to ZeroArgDeferredFunction to be explicit that it doesn't need arguments.
But now Deferrable needs to know about T and A:
export class Deferrable<T, A extends any[]> {
protected df: ZeroArgDeferredFunction<T>;
constructor(df: InnerDeferredFunction<T, A>, ...args: A) {
this.df = () => df(...args);
}
public async execute(): Promise<T> {
return this.df();
}
}
And you can see that you have to construct one by passing it the inner function and its arguments, and the ZeroArgDeferredFunction is built inside the constructor and is not passed in.
There are different ways to define defer(). It could be a thin wrapper around new Deferrable the way you had it, or you could imagine splitting it up so that the args come first:
export const defer = <A extends any[]>(...args: A) => <T>(
df: InnerDeferredFunction<T, A>): Deferrable<T, A> => new Deferrable<T, A>(df, ...args);
And then you can test it like this:
function someFunction(x: string, y: string) {
return (x + y).length;
}
function anotherFunction(x: number, y: number) {
return (x * y).toFixed()
}
const deferFooBar = defer('foo', 'bar');
await deferFooBar(someFunction).execute(); // okay
await deferFooBar(anotherFunction); // error! string is not assignable to number
Once you call deferFooBar('foo', 'bar'), the returned value will only accept functions that can be safely called with the arguments foo and 'bar'. That means someFunction will be accepted and anotherFunction will be rejected.
Okay, hope that helps; good luck!
Playground link to code

TypeScript warning => TS7017: Index signature of object type implicitly has any type

I am getting the following TypeScript warning -
Index signature of object type implicitly has any type
Here is the code that cases the warning:
Object.keys(events).forEach(function (k: string) {
const ev: ISumanEvent = events[k]; // warning is for this line!!
const toStr = String(ev);
assert(ev.explanation.length > 20, ' => (collapsed).');
if (toStr !== k) {
throw new Error(' => (collapsed).');
}
});
can anyone determine from this code block why the warning shows up? I cannot figure it out.
If it helps this is the definition for ISumanEvent:
interface ISumanEvent extends String {
explanation: string,
toString: TSumanToString
}
You could add an indexer property to your interface definition:
interface ISumanEvent extends String {
explanation: string,
toString: TSumanToString,
[key: string]: string|TSumanToString|ISumanEvent;
}
which will allow you to access it by index as you do: events[k];. Also with union indexer it's better to let the compiler infer the type instead of explicitly defining it:
const ev = events[k];

Resources