Flow: conditional function response type - javascript

In Flow type checker, how to define function response type based on argument object property. Here is an example:
function test(argument) {
if (argument.responseType === "string") {
return "Some string value"
}
return { some: { json: "object" } };
}
Is it possible to add Flow typings to this code?
I know that it is possible to write:
declare export function test(argument: { responseType: string}): string | { some: { json: string } };
But that's not enough. I don't want an Union Type response. It has to be exactly string or object based on the argument provided.

Flow allows function overloading by providing several definitions to the same function. Together with literal types next should work:
declare function test(argument: {responseType: 'string'}): string;
declare function test(argument: {}): {some: {json: string}};
Here is a Flow try example code

Related

Types with different parameters like function overload

I'm trying to write a type which acts as a function signature that allows to have to different set of parameters:
The first case, the function expects a predicate.
The second case, the function expects two parameters.
This is the example: Link to TS PlaGround. I also copy the code here just in case:
type T1<Tentity> = (predicate: ((entity: Tentity) => Tentity | void)) => void;
type T2<TEntity> = (propertyName: keyof TEntity, newValue: unknown) => void;
type TCommon<TEntity> = T1<TEntity> | T2<TEntity>;
interface IEntity {
name: string;
surname: boolean;
}
const entity : IEntity = { name: 'Foo', surname: true };
const method : TCommon<IEntity> = (param1, param2) => {
if (typeof(param1) === 'function') {
param1(entity);
}
else {
(entity[param1] as any)= param2;
}
}
method('name', 'Bar');
method((entity) => { entity.name = 'Bar'});
As you can see, I expect both calls of method to work correctly.
From what I think, TypeScript/IntelliSense should be smart enough to make this reasoning:
Since the first parameter is a function, then we have a T1, therefore I don't even need the second parameter.
Otherwise, the first parameter is a string, thus we have a T2, thus I expect a second parameter as well.
Unfortunately, this is not what's happening: it seems TypeScript cannot "believe" that param1 can also be a function:
I'm pretty sure this can be done because we have the function overload, but I've been able to use them only inside a class. But it's basically the same thing, expect in this case I'm defining the method as a global variable.
Use an interface to describe a set of call signatures:
interface TCommon<TEntity> {
(predicate: ((entity: TEntity) => TEntity | void)): void
(propertyName: keyof TEntity, newValue: unknown): void;
}
But we aren't quite done yet. The second parameter is optional in the sense that the first overload doesn't need a second parameter but the second overload does.
So we mark the second parameter as optional:
const method: TCommon<IEntity> = (param1, param2?: unknown) => {
And that's it.
Playground

Get typescript default/initial argument type in order to extend it

I'm working on some code that auto-generates type hints based on function calls (GraphQL Nexus stuff).
Then, in one of it's functions, it expects an anonymous type based on those auto-generated attributes:
export const Item = {
isTypeOf(data) { // data's type is an anonymous type, here it would be '{ name: string }'
...
}
definition(t) {
t.string('name')
}
}
However, this data argument may contain more variables than the ones defined by the function calls. In my case, I need to access a kind property, but I can't call the t._type_ function because it would have undesired side-effects.
I also can't just pass the type as { kind: string } since isTypeOf type expects it to have at least all defined properties on it's arguments.
I could just use { name: string, kind: string } in this example, but my actual code contains more complex objects, and I would lose all the benefits of the auto-generated typing stuff.
Is there any way for me to in-line extend an argument with anonymous type? I was thinking something like an initial or default keyword to get an argument's own type, and use it like this:
isTypeOf(data: initial & { kind: string })
isTypeOf(data: default & { kind: string })
Typescript does not care about additional keys in objects passed to function (unless created in the parameter list).
If you only want to accept objects with given property and not return it, use the definition of isTypeOf.
If you need to return the same type and possibly extend it use the definition of definition.
export const Item = {
isTypeOf(data: { kind: string }): boolean {
return data.kind == '...';
},
definition<T extends { string: (key: string): void }>(t): T & { defined: true } {
t.string('name');
(t as T & { defined: true }).defined = true;
return t;
},
};
const myType = {
kind: 'hello',
name: 'World',
string(key: string): {},
};
Item.isTypeOf(myType); // OK
Item.isTypeOf({ kind: 'hello', name: 'World' }); // Not OK
Item.definition(myType); // OK
Item.definition({ string(key: string): {} }); // OK

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>)
}

Typeof arguments object in TypeScript

I basically have this:
function foo(){
// literally pass the arguments object into the pragmatik.parse method
// the purpose of pragmatik.parse is to handle more complex variadic functions
const [a,b,c,d,e] = pragmatik.parse(arguments);
// now we have the correct arguments in the expected location, using array destructuring
}
so we have the pragmatik.parse method:
function parse(args){
// return parsed arguments
}
now I want to use TypeScript to define types, all I know is that arguments is an Object:
function parse(args: Object){
}
so my question is: does TypeScript give a definition or type for an arguments object in JS? Sorry this is a bit meta, but please bear with me, what I am asking about is sane.
My Webstorm IDE suggests that this might be IArguments, which is provided by: lib/es6/d.ts, which is somewhere out there. Maybe someone can verify this is correct, but I am fairly certain.
So the answer would be:
function parse(args: IArguments){
}
and the full signature would be:
function parse(args: IArguments) : Array<any> {
}
since the parse method returns a generic array
You can pick exact type of arguments with tsargs package from npm
Eg:
import { ArgsN } from 'tsargs';
function foo(a: boolean, b: number, c: string) {}
const argsABC: ArgsN<typeof foo> = [ true, 123, 'Hello' ];
In your case:
import { ArgsN } from 'tsargs';
function parse<T extends (...args: any[]) => any>(args: IArguments): ArgsN<T> {
// return parsed arguments
}
// ...
const args = parse<typeof foo>(arguments);
// args -> [ boolean, number, string ];

How can I define an object to have different fields using typescript?

I have the following function:
function getAdminParams(entity) {
params = {};
params = {
pk: "0006000",
param: "?pk=0006000",
table: "Content",
success: true
};
return params;
}
In another file I use this function like this:
params = getAdminParams(entity);
if (params.success) {
Is there a way that I could use intellisense so that the params object
shows as having a "success" parameter? I saw that Typescript has classes and interfaces but I am not sure how or if I can use these as a return type.
If you define params as an interface, then you can use a colon after the function parentheses to declare it as the return type of getAdminParams(). Like this:
interface IParams {
success: bool;
pk: string;
// etc...
}
function getAdminParams(entity): IParams {
params = {
pk: "0006000",
success: true
// etc...
};
return params; // If the properties assigned do not fulfill the IParams interface, this will be marked as an error.
}
params = getAdminParams(entity);
if (params. // Intellisense should offer success and pk and any other properties of IParams).
Within getAdminParams you could explicitly declare params as a new IParams, but type inference will set up the intellisense for you even if you don't, as long as the properties you assign to the params object fulfill the contract specified in the IParams interface.
Of course your return type could be a class, or a string, or any other type, and you would declare that using the function f(): returnType syntax in just the same way.
The language specification covers all of this in great detail, or there is a shorter introduction here: http://www.codeproject.com/Articles/469280/An-introduction-to-Type-Script or a similar SO question here: How to declare Return Types for Functions in TypeScript

Categories

Resources