Flow types on objects - javascript

I've been looking at adding Flow to my javascript project.
In several cases, I do something like this, I have an object.
const myObject = {
x: 12,
y: "Hello World"
}
And I have a general function that does some mapping on the object, keeping the keys but replacing the values.
function enfunctionate(someObject) {
return _.mapValues(myObject, (value) => () => value)
}
I'd like the function to return the type {x: () => number, y: () => string} Is there a way to make this happen?
Of course, I could type it more generically as {[key: string]: any} but I'd lose a lot of the static typing I'd like to gain by using flow.
If I could replace my few cases that do this with code generation or macros that could work, but I don't see a good way to do that with flow.
Is there a way to solve this problem?

This is the best you can get at the moment, while it's safer than {[key: string]: any} there's still an any involved
function enfunctionate<O: Object>(someObject: O): { [key: $Keys<O>]: (...rest: Array<void>) => any } {
return _.mapValues(someObject, (value) => () => value)
}
const obj = enfunctionate(myObject)
obj.x() // ok
obj.unknown() // error
EDIT: starting from v.33 you can use $ObjMap
function enfunctionate<O>(o: O): $ObjMap<O, <V>(v : V) => () => V> {
return _.mapValues(o, (value) => () => value)
}

Flow is able to infer a lot of things. However, if you want to pin the types, then you have to create the types yourself. You need to create a type for the object you are passing to enfunctionate and then specify the type that is returned.
You can type the function with type alias. So, you can create a new explicit type for that function.
function enfunctionate<X, Y>(x: X): Y {
return _.mapValues(myObject, (value) => () => value)
}
As long as you know the type of X, then you know the type of Y. Type aliasing, generics, and unions should be able to give you flexibility you need.
However, I'm also thinking as long as the type of mapValues is specified well (hopefully by someone else) then you can get away with just specifying the type that enfunctionate takes and let Flow infer the return type of that function.

Related

Preserving generics in TypeScript mapped types

I have a class, whose instance methods are handlers, each of which represents an operation, taking a reference as the input and assign the output to the second parameter. A proxy object is generated by a third party library so that the handlers can be called directly.
type InputRef<T> = {
current: T,
};
type OutputRef<T> = {
current?: T,
};
class Original {
increment(input: InputRef<number>, output: OutputRef<number>) {
const { current: inValue } = input;
output.current = inValue + 1;
}
}
type Mapper<Fn> = Fn extends (input: InputRef<infer U>, output: OutputRef<infer V>) => unknown ? (input: U) => V : never;
type MyProxyGeneratedByThirdPartyJs = { [FnName in keyof Original]: Mapper<Original[FnName]> };
declare const proxy: MyProxyGeneratedByThirdPartyJs;
const result = proxy.increment(3); // 4
However, the mapper does not work when the handler involves generic types, e.g.,
class Original {
toBox<T>(input: InputRef<T>, output: OutputRef<{ boxed: T }>) {
const { current: inValue } = input;
output.current = { boxed: inValue };
}
}
Using the same way above, the type of proxy only involves unknown, and the generic information T is lost.
Ideally, I want proxy to be of type
{
toBox<T>(input: T): { boxed: T },
}
instead of
{
toBox(input: unknown): { boxed: unknown },
}
Is there any way achieving this?
This is not currently possible in TypeScript. In order to express what you're doing to generic functions at the type level, you'd need higher kinded types as requested in microsoft/TypeScript#1213, but these are not directly supported. The conditional type definition of Mapper<Fn> infers U as the input type and V as the output type, but there is no capacity for the compiler to see or represent any higher-order relationship between them when Fn is generic.
There is some support for transforming generic function types into other generic function types, but this only happens in very specific circumstances. The transformation needs to happen at least partially at the value level (there must be a function value which transforms one generic function type into another when called, not just the type of that function) so there will be JS code emitted. And the transformation only works for a single function at a time, so an object of functions cannot be mapped at once without losing the generics.
Still, to show that there is some ability to do this, here is how one might approach it:
function proxySingleFunction<U, V>(
f: (input: InputRef<U>, output: OutputRef<V>) => any
): (input: U) => V {
return function (input: U) {
const o: OutputRef<V> = {};
f({ current: input }, o);
const ret = o.current;
if (ret === undefined) throw new Error("OH NO");
return ret;
}
}
The type of proxySingleFunction() is
// function proxySingleFunction<U, V>(
// f: (input: InputRef<U>, output: OutputRef<V>) => any
// ): (input: U) => V
which looks similar to what you're doing with Mapper<Fn>. Then, if you call proxySingleFunction(), it will produce outputs of the relevant type:
const increment = proxySingleFunction(Original.prototype.increment);
// const increment: (input: number) => number
const toBox = proxySingleFunction(Original.prototype.toBox);
// const toBox: <T>(input: T) => { boxed: T; }
You can see that toBox is generic, as desired. Then you could pacakge these output functions in a single proxy object and use it:
const proxy = {
increment, toBox
}
console.log(proxy.increment(1).toFixed(1)) // "2.0"
console.log(proxy.toBox("a").boxed.toUpperCase()) // "A"
So that's great and it works. But it requires that you emit JavaScript for each method you want to transform. If you already have such a transformed object from a third party and just want to represent the typings, the closest you can get is to lie to the compiler via type assertions so that it thinks you're doing the transformations when you're actually not:
// pretend this function exists
declare const psf: <U, V>(
f: (input: InputRef<U>, output: OutputRef<V>) => any
) => (input: U) => V;
// pretend you're using it to create a proxy object
const myProxyType = (true as false) || {
increment: psf(Original.prototype.increment),
toBox: psf(Original.prototype.toBox)
}
// get the pretend type of that object
type MyProxyGeneratedByThirdPartyJs = typeof myProxyType;
/* type MyProxyGeneratedByThirdPartyJs = {
increment: (input: number) => number;
toBox: <T>(input: T) => { boxed: T; };
} */
I don't see this as a big win over just writing out these types manually in the first place, so I don't know that I'd recommend it. It's just the closest I can imagine getting to what you want with the language as it currently is.
Playground link to code

React Typescript useLocalStorage Hook

i wrote a custom hook to interact with the sessionStorage in React. Currently I don't like that I can just arbitrarily write any key-value pair in there. For testing and debugging purposes I would like to introduce a form of TypeSafety and I was thinking about maybe using a Union Type rather then the Generic.
I basically want to achieve two goals.
check if the key is a valid key that is allowed to be put into the sessionStorage
if the key is allowed make sure the type of the value is correct.
Does anyone have any idea how you would go about implementing these sort of checks in Typescript. Any help is appreciated.
export function useSessionStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.sessionStorage.getItem(key);// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.sessionStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue] as const;
}
In case someone stumbles across this question, I ended up finding how this is implemented in Typescript. You can achieve the desired effect by the use of the typeof operator on the generic.
basically you first define a interface or type:
type Allowed = {
item1: string
item2: boolean
item3: number
}
Then you can modify the function using the keyof operator
function useSessionStorage<T extends keyof Allowed>(key: T, initialValue: Allowed[T]){
{...}
}
the use of key of is better described here: https://www.typescriptlang.org/docs/handbook/advanced-types.html.
Hope this helps other people that might be struggling with this.

Is there anyway to communicate a type backwards through a chain from final return to parameter of a previous chain method? (TypeScript)

The below code works it just doesn't have as much type inference as I would wish (so in that regard at least it doesn't work completely as expected). Also, the above may be a weird and slightly dense title for the question, but basically I want two things that I'm not even sure if TypeScript can accomplish.
First, I want the final return type of the below chain to be a function whose return type we'll call T as what I'm expecting as the parameter to the first occurrence of modifyReturn in the given chain. As an example the type below of returnValue1 below should be T.
Secondly, I want a similar effect with the first call in the chain to modifyArgs in that I want its params to be the expected params of what we'll call the type FinalResolver which should be the function that is returned from resolve (which then ends up terminating the chain) to match the params/arguments of the function first in the chain for modifyArgs. For example the below a,b,c,d in the first modifyArgs should be basically of type Parameters<FinalResolver> which ends up being {}, {}, {db: any, response: any, request: any}, {}.
In other words (and much more briefly) first, I would like for the left hand side of the equal signs IResolver in the below code to determine the validity of the types of the first modifyArgs arguments, so a,b,c,d should be {}, {}, {db: any, response: any, request: any}, {} respectively. Second, I would like the return value of the resolve function to determine the type of the first modifyReturn's param.
This is how the type is used (really this shouldn't have to be changed [just here as an example usage]):
const temp: IResolver<{}, {}, {db: any, response: any, request: any}, {}, "baz"> = ResolveChain().modifyArgs((a,b,c,d /* should be (I'll use 'should be' to show that it's currently not working) {},{},{db: any, response: any, request: any}, {}, but is currently */) => {
return [1,2,3,4] as const;
}).modifyArgs(async (e,f,g,h /*is type (I'll use 'is type' to show it does work as intended) 1,2,3,4*/) => {
return [2,3,4,5] as const;
}).modifyReturn((returnValue1 /* should be 'foo' (T as explained above), but is currently any */) => {
return 'bar' as const;
}).modifyReturn((returnValue2 /* is type 'bar'*/) => {
return 'baz' as const;
}).resolve((foo, bar, baz, qux /* is type 2,3,4,5 */) => {
return 'foo'; // this return value is T
}); /* is type (root: {}, args: {}, context: {db: any, response: any, request: any}, {}) => 'baz' */
My utility types (this probably doesn't need to change either):
type IResolver<Root, Args, Context, Info, Return = any> = (root: Root, args: Args, context: Context, info: Info) => Return;
type If<Condition,TrueConditionType,FalseConditionType> = Condition extends true ? TrueConditionType : FalseConditionType;
The main type in question that I would like to fix to give me the two above properties is this one:
/** Allows one to chain and modify arguments and return values for a resolver. Arguments are modified before the resolver is called and return values are modified after. */
type ResolverChain<T, FinalResolver extends IResolver<any, any, any, any, T> = IResolver<any, any, any, any>, ArgsResolver extends IResolver<any, any, any, any> = IResolver<any, any, any, any>, IsFirst extends boolean = true> = FinalResolver extends IResolver<infer FinalRoot, infer FinalArgs, infer FinalContext, infer FinalInfo, infer FinalReturn> ? If<IsFirst, IResolver<FinalRoot, FinalArgs, FinalContext, FinalInfo>, ArgsResolver> extends IResolver<infer Root, infer Args, infer Context, infer Info, infer Return> ? {
/** This allows you to modify and change the type of the arguments being passed into the resolver. You can also throw an error if an argument is not
* provided or if given the arguments you can tell the User is not authorized.
*/
modifyArgs<NewRoot, NewArgs, NewContext, NewInfo>(resolveArgs: IResolver<Root, Args, Context, Info, readonly [NewRoot, NewArgs, NewContext, NewInfo] | Promise<readonly [NewRoot, NewArgs, NewContext, NewInfo]>>): ResolverChain<T, FinalResolver, IResolver<NewRoot, NewArgs, NewContext, NewInfo>, false>;
/** This allows you to change the return value of the resolve function after the resolver has been called (each subsequent call to modify return will be run in the order it is added).
* You can always throw to prevent the user from seeing something unauthorized or return null to prevent only the single field from being accessed.
*/
modifyReturn<NewReturn extends T>(resolveArgs: (resolverReturn: FinalReturn) => NewReturn): ResolverChain<T, IResolver<FinalRoot, FinalArgs, FinalContext, FinalInfo, NewReturn>, ArgsResolver, false>;
/** This will resolve the chain into a compatible resolver function. */
resolve(resolver: IResolver<Root, Args, Context, Info, T>): FinalResolver;
} : never : never;
Finally here's the implementation if you want to dig into how this function works better though it should most likely not need to be changed to answer the question:
Edited from this comment
/** */
function ResolveChain <T>(argResolvers: IResolver<any, any, any, any>[] = [], returnResolvers: ((arg:T) => T)[] = []): ResolverChain<T> {
return {
modifyArgs (fn) {
return ResolveChain([...argResolvers, fn], returnResolvers);
},
modifyReturn (fn) {
return ResolveChain(argResolvers, [...returnResolvers, fn]);
},
resolve (resolveFn) {
return async (...args) => {
for(const fn of argResolvers) {
args = await fn(...args);
}
let returnValue = await resolveFn(...args);
for(const fn of returnResolvers) {
returnValue = await fn(returnValue);
}
return returnValue;
};
}
}
}
If you noticed I tried and failed to fix the above two problems with the IsFirst generic used as a flag in combination with the If type (If<IsFirst, IResolver<FinalRoot, FinalArgs, FinalContext, FinalInfo>, ArgsResolver>) I made in order to set whether the function should get it's type from the previous chained resolver (ArgsResolver) or the FinalResolver. (I believe I will end up needing two flags and likely to Ifs if the flag idea ends up working)
Link edited to reflect this comment:
Playground Link
You and I had a long discussion in the comments about some of the hurdles and limitations of the current approach, but I'm happy to say that I have some types for you!
On the execution side of things, the applying of the resolvers, we are not able to have strict types. This is a limitation of the current implementation. We are storing arrays of resolvers and that doesn't allow us to enforce the necessary requirement that each element's return type becomes one of the input arguments for the next element.
But we can make major progress on the building side of things, where we add new resolvers and change our knowledge of the chain's types accordingly. You were inspired by a particular challenge problem to create a chainable type that updates the return type for its final get() call after each option() method call. My solution to that challenge is this:
interface Chainable<T = {}> {
option<K extends string, V>(key: K, value: V): Chainable<T & {[Key in K]: V}>
get(): T
}
With each call to option we infer generic values K and V based on the values of the function arguments. We return an object which still has the same interface Chainable but has an updated generic value reflecting the changes.
We will apply that same principle to your case but with more generics -- six of them! We have a variable Args that changes with each call to modifyArgs and a variable Value that changes with each call to modifyReturn. But when we finally resolve to a result by calling resolve() at the end of the chain, we are going to provide it with a value and args from the types that we started with, so we need to store those types. I have called them InitialValue and InitialArgs.
function ResolveChain <Context, Info, InitialValue, InitialArgs = {}, Value = InitialValue, Args = InitialArgs>(...
The values for the mutable generics, Value and Args, need to go last in the list so that we can make them optional and have them start with their initial values. The other four types are expected to be declared when you call the function to create a chain.
Were you to structure your code differently so that the arguments for the "final return" were passed in at the start rather than at the end, we could infer types rather than having to declare them explicitly.
We call this function and create a ResolverChain. I gave it the same types in the same order:
interface ResolverChain<Context, Info, InitialValue, InitialArgs = {}, Value = InitialValue, Args = InitialArgs> {
Our resolver chain interface needs to define three methods. Both modifyArgs and modifyReturn will take a function and return a new ResolverChain. I kept your definition for the function mostly the same. I've stated that the return type can be either a Promise or a value, which cleans things up a lot.
type Resolver<Value, Args, Context, Info, Return> = (
value: Value, args: Args, context: Context, info: Info
) => Return | Promise<Return>;
The function that we pass to modifyArgs takes the four standard arguments and returns a new type for args: NewArgs. The only generic that we need to infer here is the return type NewArgs. This function should act on the current args value Args, but because you call all of your argResolvers before any of your returnResolvers it receives InitialValue rather than Value.
The ResolverChain that we return has one of its generics changed. We replace Args with NewArgs and keep all the rest.
modifyArgs<NewArgs>(
resolveArgs: Resolver<InitialValue, Args, Context, Info, NewArgs>
): ResolverChain<Context, Info, InitialValue, InitialArgs, Value, NewArgs>;
modifyReturn does the same thing, but for Value. Our resolver return type gets inferred as NewValue and we return a chain with NewValue in place of Value.
There is one potential bug here which is a limitation of the current design. We know that all returnResolvers will be called with the final value of Args but when cannot possibly know what that type will be when we are still able to change it by calling modifyArgs. When creating your chain, you should make all calls to modifyArgs before modifyReturn in order to get the correct Args type.
How about we force the methods to be called in the proper order? When we return a chain from modifyReturn, we simply Omit the modifyArgs method. It is still present at runtime, but typescript doesn't know about it so we will get errors if we call modifyArgs after modifyReturn.
modifyReturn<NewValue>(
resolveArgs: Resolver<Value, Args, Context, Info, NewValue>
): Omit<ResolverChain<Context, Info, InitialValue, InitialArgs, NewValue, Args>, 'modifyArgs'>;
For the final resolve(), it doesn't make sense to me that we would pass it a function. If we want to mofify it, we can do that by calling modifyReturn right before. I wrote what makes sense to me, which is that we give it four values, the InitialValue and InitialArgs along with the immutable Context and Info, and expect it to return the current type of Value.
resolve: Resolver<InitialValue, InitialArgs, Context, Info, Value>;
The implementation cannot be perfectly typed, as I explained earlier. But here's the best we can do.
argResolvers needs any for Args and Return since these vary, while returnResolvers needs any for Value, Args, and Return.
(
argResolvers: Resolver<InitialValue, any, Context, Info, any>[] = [],
returnResolvers: Resolver<any, any, Context, Info, any>[] = []
)
Our resolve is now an aysnc method which returns a Promise that resolves to the final Value. Its args are four of the values from our generic, as explained previously.
async resolve (...args: [InitialValue, InitialArgs, Context, Info]): Promise<Value> {
async resolve (initialValue: InitialValue, initialArgs: InitialArgs, context: Context, info: Info): Promise<Value> {
For the values that we modify, those variables are going to start with their initial type. Typescript does not expect or allow that type to change, so in order to have the correct final type, we have to tell typescript to shut up. I told you the implementation was not ideal!
let args = initialArgs as unknown as Args;
let value = initialValue as unknown as Value;
Putting it all together we get:
type Resolver<Value, Args, Context, Info, Return> = (
value: Value, args: Args, context: Context, info: Info
) => Return | Promise<Return>;
interface ResolverChain<Context, Info, InitialValue, InitialArgs = {}, Value = InitialValue, Args = InitialArgs> {
modifyArgs<NewArgs>(
resolveArgs: Resolver<InitialValue, Args, Context, Info, NewArgs>
): ResolverChain<Context, Info, InitialValue, InitialArgs, Value, NewArgs>;
modifyReturn<NewValue>(
resolveArgs: Resolver<Value, Args, Context, Info, NewValue>
): Omit<ResolverChain<Context, Info, InitialValue, InitialArgs, NewValue, Args>, 'modifyArgs'>;
resolve: Resolver<InitialValue, InitialArgs, Context, Info, Value>;
};
function ResolveChain<Context, Info, InitialValue, InitialArgs = {}, Value = InitialValue, Args = InitialArgs>(
argResolvers: Resolver<InitialValue, any, Context, Info, any>[] = [],
returnResolvers: Resolver<any, any, Context, Info, any>[] = []
): ResolverChain<Context, Info, InitialValue, InitialArgs, Value, Args> {
return {
modifyArgs(fn) {
return ResolveChain([...argResolvers, fn], returnResolvers);
},
modifyReturn(fn) {
return ResolveChain(argResolvers, [...returnResolvers, fn]);
},
async resolve(initialValue: InitialValue, initialArgs: InitialArgs, context: Context, info: Info): Promise<Value> {
let args = initialArgs as unknown as Args;
for (const fn of argResolvers) {
args = await fn(initialValue, args, context, info);
}
let value = initialValue as unknown as Value;
for (const fn of returnResolvers) {
value = await fn(value, args, context, info);
}
return value;
}
}
}
Play around with the various callbacks on the Typescript Playground:
const temp2 = ResolveChain<{ db: any, response: any, request: any }, {}, 'baz', {initialArgument: number}>()
.modifyArgs((root, args) => ({
...args,
hello: "world",
}))
.modifyReturn((ret) => ({ key: ret }))
.modifyReturn((ret, args) => ({
...ret,
fromArgs: args.hello + args.initialArgument,
}))
.resolve('baz', {initialArgument: 5}, { db: "", response: "", request: "" }, {});
// resolves to { fromArgs: string; key: "baz"; }
note:
This might not be exactly what you intended. I didn't see your change from returnValue = await fn(...args) to returnValue = await fn(returnValue) until after I started writing this. I remain highly confused in the section on argResolvers whether args refers to the second of four arguments or to the array of all four, since you seem to be using it as both simultaneously. So modify this code as you need to.

Typescript Type for Dynamic Array.map Callback Function

In my Angular 8.2/Typescript 3.4.3 app, I need to generate an array.map callback dynamically, but the TS linter is barking about that function. Here is an example snippet I contrived to illustrate:
export type Role = [string, number];
export type BaseRole = [string, number[]];
const roles: BaseRole[] = [
[ 'hero', [100, 200, 300]],
[ 'villain', [300, 600, 900]],
[ 'minion', [20, 40, 60]]
];
function roleMapper(i: number): Function {
return ([role, strengths]) => [role, strengths[i]];
}
function getRolesAtLevel (level): Role[] {
return roles.map(roleMapper(level)); // <-- Linter warning occurs here
}
let myRoles = getRolesAtLevel(1);
Above I want to map an array of "BaseRoles" down to "Roles" based on user input. The linter complains about return roles.map(roleMapper(level)); (line 16) with the follow message:
Argument of type 'Function' is not assignable to parameter of type '(value: [string, number[]], index: number, array: [string, number[]][]) => [string, number]'.
I observe that the type '(value ... index ... array)' would be that of a map callback. The function I've provided is typed Function and supports that interface, so my questions are:
Must I be explicit about the interface of the function when using the Function type?
Is there another notation that I should have used to designate a "map" callback?
Thanks..
Yes, you should probably be more explicit about types. Function is too wide of a type to be useful for your purposes, since the compiler has no idea anymore what will come out of it. I'd suggest you write roleMapper() as follows:
function roleMapper(i: number) {
return ([role, strengths]: BaseRole): Role => [role, strengths[i]];
}
Here we are telling the compiler that the returned function takes a BaseRole and returns a Role. The compiler infers a return type equivalent to (br: BaseRole) => Role, which will then allow the rest of your code to function as you intend:
function getRolesAtLevel(level: number): Role[] {
return roles.map(roleMapper(level)); // okay
}
Okay, hope that helps; good luck!
Link to code

Flow: Object type incompatible with Array<mixed>

I don’t understand the flow error I’m currently getting. I have a Javascript object of objects (dataObject) that I want to convert to an array of objects, so I do so using Object.values(dataObject). Then, I iterate through each object in the array with the following:
const dataObjectArray = Object.values(dataObject);
return dataObjectArray((data: DataObject) => {
const { typeA, typeB } = data;
return {
TYPE_A: typeA,
TYPE_B: typeB,
};
});
But I get the following flowtype error:
I’m not sure how to match up the types. Currently my DataObject flow type is
type DataObject = {
typeA: string,
typeB: string,
};
Any help would be appreciated. Thanks!
The type definition for the Object.values function has no way to know that the argument passed to it is an object where the values are all the same type. You could just as easily be doing Object.values({foo: 4, bar: "str"}). The type definition is
(any) => Array<mixed>
meaning that you are doing .map on a value of type Array<mixed>.
That means if you want to use it as object, your method will not work. Assuming your "object of objects" is typed as
type DataObjects = {
[string]: DataObject,
}
You'd likely be better off doing
function values(objs: DataObjects): Array<DataObject> {
return Object.keys(objs).map(key => objs[key]);
}
If you prefer to use Object.values() (probably more efficient) and have typing right, you can use a helper function like this:
function objectToValues<A, B>(obj:{[key: A]: B} ): Array<B> {
return ((Object.values(obj): any): Array<B>)
}

Categories

Resources