Type error failing to be reported in Flow - flowtype

I have defined this type:
export type RemoveFromWishlistFailureAction = {
type: types.REMOVE_FROM_WISHLIST_FAILURE,
error: Error
};
Elsewhere I have this code:
const removeFailure: RemoveFromWishlistFailureAction = {
type: types.REMOVE_FROM_WISHLIST_FAILURE
};
I expect there to be a Flow error because this object is missing an error property. There is no error.
Is there something wrong in my code, or in my understanding? Or elsewhere?

Related

Flow property is missing in mixed passed - despite the type annotation

I have the following in my declarations file (included in my [libs]):
export type EtlFieldNoIdxT = {
name: Name,
purpose: Purpose,
}
export type EtlFieldT = {
idx: number,
...EtlFieldNoIdxT
}
And the following in my use of the types:
export const createEtlField = (
etlFields: { [Name]: EtlFieldT },
newField: EtlFieldNoIdxT,
) => {
if (etlFields === {}) {
throw new Error({
message: 'Cannot create a new etlField with an empty etlFields',
});
}
const field: EtlFieldT = {
idx: maxId(etlFields, 'idx') + 1,
...newField,
};
const subject: Name = Object.values(etlFields).find(
(f) => f.purpose === 'subject', // <<< f.purpose "missing in mixed" error
).name; // <<< .name "missing in mixed" error
return newEtlField(field, subject);
};
Despite having annotated the input, can flow not infer the type of what Object.values would thus return?
Thank you in advance for pointing out my misunderstanding.
- E
If you check the declaration for Object.values you'll find that it returns an array of mixed:
static values(object: $NotNullOrVoid): Array<mixed>;
A quick google search came back with
https://davidwalsh.name/flow-object-values
So to solve your issue, you wrap Object.values(...) with any, and then inside your find arg you can type it as EtlFieldT and finally refine your type back to EtlFieldT after find.
const subject: Name = ((Object.values(etlFields): any).find(
(f: EtlFieldT) => f.purpose === 'subject',
): EtlFieldT).name;
Though you should be aware that find has the possibility of returning undefined. So to be sound, you should run the find, and declare subject if the value exists.

Apollo Network (Bad Request) error not caught in apollo-link-error

Sending a mutation with a bad set of variables (on purpose in this case) results in errors being thrown in the console, and the apollo-link-error link not picking up the error. Additionally, the mutation loading state is stuck as 'loading' and not error object comes through.
Through a debugging session, I fount that the zen-observable global error handling picks up an error thrown in the error-link's "next" function, because 'result' is not defined
pasted the apollo-link-error code that has the observable wrapped in a try catch, but the catch at the bottom here is not the catch that gets hit when if (result.errors) throws a 'nullpointer' because result in undefined.
try {
sub = forward(operation).subscribe({
next: function (result) {
// result is undefined, throwing an error
if (result.errors) {
retriedResult = errorHandler({
graphQLErrors: result.errors,
response: result,
operation: operation,
forward: forward,
});
if (retriedResult) {
retriedSub = retriedResult.subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
});
return;
}
}
observer.next(result);
},
error: function (networkError) {
retriedResult = errorHandler({
operation: operation,
networkError: networkError,
graphQLErrors: networkError &&
networkError.result &&
networkError.result.errors,
forward: forward,
});
if (retriedResult) {
retriedSub = retriedResult.subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
});
return;
}
observer.error(networkError);
},
complete: function () {
if (!retriedResult) {
observer.complete.bind(observer)();
}
},
});
} // the error is NOT caught here
catch (e) {
errorHandler({ networkError: e, operation: operation, forward: forward });
observer.error(e);
}
```
Link definition:
```javascript
export const client = new ApolloClient({
link: ApolloLink.from([
onError((errors) => {
console.log('errors in link!', errors);
handleServerError(errors);
}),
new MeteorAccountsLink(),
new HttpLink({
uri: '/graphql',
}),
]),
cache: new InMemoryCache(),
});
Edit:
The request in the browser does show a response with an error object with the graphQlErro of shape {errors: [{..}]} which is strange that it's not coming into the 'result' in the link.
Edit 2:
It looks like Meteor is picking up the error thrown in the http link prior to the error posted above, which might be why "result" is undefined. Writing a custom link to polyfill the missing 'result' so the app will at least work.
It seems like an issue with Meteor swallowing the errors. fixed make making a polyfill link to at least not break the js in the mutation complete function and show a general error.
const stupidMeteorErorrPolyfill = new ApolloLink((operation, forward) => forward(operation).map(data => data || { errors: [{ message: '! Empty response in gql links, see graphql.js and network tab' }] }));

Functions for constructing Flow types?

Is there any way I can do something like this:
// #flow
function FailureActionType(name: string): Type {
return {type: name, error: string}
}
type SearchFailureAction = FailureActionType("SEARCH_FAILURE")
Obviously there's problems in the way the typing/assignments are written in the return statement, but it would work such that
type SearchFailureAction = { type: "SEARCH_FAILURE", error: string }
Is there any way to do that?
You want a generic.
type FailureActionType<T: string> = { type: T, error: string }
The <T> there says that this type is dependent on another type.
<T: string> means this dependent type must be a type of string.
{ type: T, error: string } means the resulting type must have the dependant type on the type key of the object.
You use it by passing in a value for T in <> like so:
type SearchFailureAction = FailureActionType<"SEARCH_FAILURE">
const action1: SearchFailureAction = { type: 'SEARCH_FAILURE', error: 'some error' }
const action2: SearchFailureAction = { type: 'BAD', error: 'some error' } // type error
flow.org/try Proof
Generics are pretty powerful. Read the docs for more.
https://flow.org/en/docs/types/generics/

Sinon stub - Cannot destructure property 'x' of 'undefined' or 'null'

I need to stub the following with Sinon :
const { clientId, dateString } = await parameterParser.prepareInputParameters(con, req);
I have tried using the following:
const retData = {
clientId: 872,
dateString: '1970-01-01',
};
sandbox.stub(parameterParser, 'prepareInputParameters').withArgs(con, req).returns(retData);
but I get the error:
TypeError: Cannot destructure property 'clientId' of 'undefined' or 'null'.
I have successfully stubbed the following elsewhere in my tests:
const { retData } = await sqlFileReader.read('./src/database/sql/getClientIdFromLoanId.sql', [`${req.query.loan_id}`], con, req.logger);
by using:
const retData = {
rowsCount: 1,
rows: [{ client_id: 872 }],
};
sandbox.stub(sqlFileReader, 'read').returns({ retData });
but I cannot get my head round how to stub const { clientId, dateString }
You need to be using resolves instead of returns for these stubs, since you are awaiting their return values, which should actually be Promises that resolve with your retData, and not the retData itself.
In sinon, resolves is a convenience for asynchronous methods. The following two lines are similar:
sinon.stub(foo, 'bar').returns(Promise.resolve('baz'));
sinon.stub(foo, 'bar').resolves('baz');
Your second sample may not be throwing an error, but if you log the value of retData after this line:
const { retData } = await sqlFileReader.read('./src/database/sql/getClientIdFromLoanId.sql', [`${req.query.loan_id}`], con, req.logger);
You'll notice that it is undefined. This is because await causes the result of the method call to be a Promise, which does not have a property called retData.
As for why your first sample is behaving differently, I'm not sure. I suspect that there's something else going on that isn't evident from your samples. Would you mind sharing more code?

Flow - reading a union type property doesn't work when caching the common variable check

In this example, Flow shows us a way to read properties depending on the type.
I have refactored it to look like this, and it works as expected:
type Success = { success: true, value: boolean };
type Failed = { success: false, error: string };
type Response = Success | Failed;
function handleResponse(response: Response) {
const value = response.success && response.value;
const error = !response.success && response.error;
}
However, when the common property is a string, it works when doing a === check, but it doesn't if you cache the check into a variable:
type Success2 = { success: 'success', value: boolean };
type Failed2 = { success: 'not_success', error: string };
type Response2 = Success | Failed;
function handleResponse(response: Response2) {
const isSuccess = response.success === 'success';
// const value = response.success === 'success' && response.value; // WORK
const value = isSuccess && response.value; // DOESN'T WORK
}
In other words, there must be a === check (literally) before reading the variable, can't have it in a variable.
Is this a known Flow limitation, or am I missing something?
Yep, this is expected behavior. The feature you are referring to here is type refinement. The implementation is based on the standard flow-control mechanisms of Javascript, meaning that saving the result of a test and using it later will discard the type information that Flow might otherwise be able to infer. All Flow knows in your non-working example is that isSuccess is a boolean, it has no idea that true there implies that response.value exists.

Resources