In Flow, what does Missing an annotation on implicit `this` parameter of function. [missing-this-annot] mean? - flowtype

I have a Javascript file that looks like this:
// #flow
/**
* My database module.
*
* Interact with the database.
*/
'use strict';
module.exports = {
/** Mockable wrapper around mongoose. */
mongoose: function() /*:: : Object */ {
// $FlowExpectedError
return require('mongoose');
},
/** Mockable wrapper around env. */
env: function() /*:: : Object */ {
// $FlowExpectedError
return require('./env.js');
},
/** Initialize the connection to Mongoose. */
init: function(
callbackOK /*:: : () => number */,
callbackInitError /*:: : (x: string) => null */,
callbackError /*:: : (x: string) => null */) {
try {
// See https://mongoosejs.com/docs/connections.html.
this.mongoose().connect(this.uri(), {}).then(
() => callbackOK(),
err => callbackInitError(err),
);
this.mongoose().connection.on('error',
err => callbackError(err),
);
}
catch (err) {
callbackInitError(err);
}
},
/** Get the connection URI for the Mongoose database. */
uri: function() /*:: : string */ {
const user = String(this.env().required('MONGO_USER'));
const pass = String(this.env().required('MONGO_PASS'));
const host = String(this.env().required('MONGO_HOST'));
const port = String(this.env().required('MONGO_PORT'));
const db = String(this.env().required('MONGO_DB'));
return 'mongodb://' + user + ':' + pass + '#' + host + ':' + port + '/' + db + '?authSource=admin';
},
};
When I check it with Flow, it gives me this error:
Missing an annotation on implicit `this` parameter of function. [missing-this-annot]
v
24| init: function(
25| callbackOK /*:: : () => number */,
26| callbackInitError /*:: : (x: string) => null */,
27| callbackError /*:: : (x: string) => null */) {
-------------------------------------------^
How would I modify this code to fix this error?

Related

Mixing Either and TaskEither in a pipe in fp-ts

I have the following program that works fine when none of the functions is async.
interface Product {
count: number
pricePerItem: number
}
interface Tax {
tax: number
}
interface Delivery {
delivery: number
}
interface PTD { //ProductTaxDelivery
p: Product
t: number
d: number
}
function getProduct(): Either<Error, Product> {
return E.right({ count: 10, pricePerItem: 5 })
}
function getTax(p: Product): Either<Error, number> {
return E.right(p.pricePerItem * p.count * 0.085)
}
function getDelivery(p: Product): Either<Error, number> {
return E.right(p.count * 0.05)
//or maybe return E.left(Error('some error in delivery happened'))
}
function run(): Either<Error, PTD> {
return pipe(
E.Do,
E.bind('p', getProduct),
E.bind('tax', ({p}) => getTax(p)),
E.bind('delivery', ({p}) => getDelivery(p)),
E.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
E.fold(
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
}
)
)
}
main()
The question I'm having is if one of my functions, for example getDelivery() is async, then I'm not sure how to solve it.
Here's what I have tried:
function getDelivery(p: Product): TaskEither<Error, number> {
return TE.right(p.count * 0.05)
}
TE.bind('delivery', ({p}) => getDelivery(p)),
and many other variations, but all ended up in compiler errors.
The equivalent in imperative style is something like:
const getDelivery = async (p: Product) => {
return await something()
}
const run = async (): PTD => {
const product = getProduct()
const tax = getTax(product)
const delivery = await getDelivery(product)
return {
p: product, t: tax, d: delivery
}
}
What is the correct functional way (that I think involves both Either and TaskEither) using fp-ts?
Update: I also tried to replace Either with TaskEither, E with TE everywhere, but the problem is now a compiler error when I tried to fold in main(). Here's the code that replaces:
function getProduct(): TaskEither<Error, Product> {
return TE.right({ count: 10, pricePerItem: 5 })
}
function getTax(p: Product): TaskEither<Error, number> {
return TE.right(p.pricePerItem * p.count * 0.085)
}
function getDelivery(p: Product): TaskEither<Error, number> {
return TE.right(p.count * 0.05)
}
function run(): TaskEither<Error, PTD> {
return pipe(
TE.Do,
TE.bind('p', getProduct),
TE.bind('tax', ({ p }) => getTax(p)),
TE.bind('delivery', ({ p }) => getDelivery(p)),
TE.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
TE.fold(
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
//doNonFunctional()
}
)
)
}
main()
On line with (e) => {, the compiler error says:
error TS2345: Argument of type '(e: Error) => void' is not assignable to parameter of type '(e: Error) => Task<unknown>'.
Type 'void' is not assignable to type 'Task<unknown>'.
Update 2
OK, so I get the code to compile but no output when the program runs
const printError = (e: Error): T.Task<unknown> => {
console.log(`error: ${e}`)
return () => Promise.resolve()
}
const printPTD = (ptd: PTD): T.Task<unknown> => {
console.log(`ok ${ptd.p.count} ${ptd.p.pricePerItem} ${ptd.t} ${ptd.d}`)
return () => Promise.resolve()
}
function run(): TaskEither<Error, PTD> {
return pipe(
TE.Do,
TE.bind('p', getProduct),
TE.bind('tax', ({ p }) => getTax(p)),
TE.bind('delivery', ({ p }) => getDelivery(p)),
TE.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
TE.fold(
(e) => printError(e),
(ptd) => printPTD(ptd)
)
)
}
main()
The issue is when you create a Task in main with pipe, you are not actually running anything.
This is how Task is defined:
interface Task<A> {
(): Promise<A>
}
// same as type Task<A> = () => Promise<A>
Because Task is a thunk, you need to call it to actually execute the code.
async function main(): Promise<void> {
await pipe(
// ...
// vv note the call here
)()
}
main()
However, I would do it like this:
const main: T.Task<void> = pipe(/* ... */)
main()
Similarly, run doesn't need to be a function; it can be const run = pipe(/* ... */).
Also, there's a Console module that provides log functions that return an IO (a type for side-effectful actions).
Your code could be written as
import * as Console from 'fp-ts/Console'
import * as E from 'fp-ts/Either'
import * as T from 'fp-ts/Task'
import * as TE from 'fp-ts/TaskEither'
import {pipe} from 'fp-ts/function'
// <A>(a: A) => Task<void>
const taskLog = T.fromIOK(Console.log)
// You can still keep getProduct and getTask synchronous
function getProduct(): E.Either<Error, Product> { /* ... */ }
function getTax(p: Product): E.Either<Error, number> { /* ... */ }
function getDelivery(p: Product): TE.TaskEither<Error, number> { /* ... */ }
const run: TE.TaskEither<Error, PTD> = pipe(
TE.Do,
// See below for what TE.fromEither(K) does
TE.bind('p', TE.fromEitherK(getProduct)),
TE.bind('tax', ({p}) => TE.fromEither(getTax(p))),
TE.bind('delivery', ({p}) => getDelivery(p)),
TE.map(({p, tax, delivery}) => ({p, t: tax, d: delivery}))
)
const main: T.Task<void> = pipe(
run,
TE.fold(
e => taskLog(`error: ${e}`),
it => taskLog(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
)
)
main().catch(console.error)
TE.fromEither converts an Either into a TaskEither:
export declare const fromEither: NaturalTransformation22<'Either', 'TaskEither'>
// same as
export declare const fromEither: <E, A>(fa: Either<E, A>) => TaskEither<E, A>
TE.fromEitherK is the same as fromEither but for functions:
export declare const fromEitherK: <E, A extends readonly unknown[], B>(f: (...a: A) => Either<E, B>) => (...a: A) => TaskEither<E, B>
You can probably guess by now what T.fromIOK (used for taskLog) does:
export declare const fromIOK: <A, B>(f: (...a: A) => IO<B>) => (...a: A) => Task<B>
Here's a CodeSandbox with the full code.

Sending data to an imported module in React Native

I have a module called Chat.js that imports Fire.js in order to send data (message comes into Chat.js, and Fire.js handles storage).
I have a recipient's user ID that is only currently available in Chat.js, but it is important to get to Fire.js in order to store appropriately.
I removed some info for brevity, this is my current Chat.js:
import Fire from './Fire';
class Chat extends React.Component<Props> {
state = {
messages: [],
};
get user() {
return {
name: this.props.navigation.state.params.name,
_id: Fire.shared.uid,
};
}
render() {
return (
<GiftedChat
messages={this.state.messages}
onSend={Fire.shared.send}
user={this.user}
/>
);
}
componentDidMount() {
Fire.shared.on(message =>
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message),
}))
);
}
componentWillUnmount() {
Fire.shared.off();
}
}
export default Chat;
And this is my current Fire.js:
import firebase from 'react-native-firebase';
class Fire {
constructor() {
}
get ref() {
var recipient = 'recipientId'
return firebase.database().ref('messages/' + this.uid + '/' + recipient);
}
parse = snapshot => {
const { timestamp: numberStamp, text, user } = snapshot.val();
const { key: _id } = snapshot;
const timestamp = new Date(numberStamp);
const message = {
_id,
timestamp,
text,
user,
};
return message;
};
on = callback =>
this.ref
.limitToLast(20)
.on('child_added', snapshot => callback(this.parse(snapshot)));
// send the message to the Backend
send = messages => {
for (let i = 0; i < messages.length; i++) {
const { text, user } = messages[i];
const message = {
text,
user,
timestamp: this.timestamp,
};
this.append(message);
}
};
append = message => this.ref.push(message);
// close the connection to the Backend
off() {
this.ref.off();
}
}
Fire.shared = new Fire();
export default Fire;
I currently need to get the recipient ID, which is available in chat.js under
this.props.navigation.state.params.uid
Into the Fire.js lines:
get ref()
{
var recipient = 'recipientId'
I can't seem to get this uid into get ref()
Use getter and setters in Fire.js.
In Fire.js
setRecipient (id){
this.recipientId = id;
}
get getRecipientId () {
return this.recipientId;
}
And then call Fire.setRecipient(yourId) in Chat.js.

Window object in nativescript

I need to create a window object so that the external file, which is loaded inside iframe, can call nativescript function.
I, specifically need window object because the file I am loading can be any file that follows conformant (SCORM) related to LMS.
Edit :
The file I have loaded is a SCORM conformant file. They search for window.API object/window.parent.API and so on, for starting communication with the container in which they have been loaded. I can't alter that file.
Tell me if more details needed.
We are successfully handling SCORM content in our NativeScript application, but it does require a little bit of hackery to accomplish.
I authored a utility class that will inject into the main entry file (index) of the SCORM content an override for the window API.
Background:
In our app we decompress the zip courses to the devices: documents/courses/UUID (that's what identity is referencing)
You can change the path as needed for your implementation
Example usage of the utility class:
const documents = fs.knownFolders.documents();
const destination = fs.path.join(documents.path, 'courses', this.media.identity);
this._readAccessUrl = destination;
const src = `file://${destination}`;
if (fs.File.exists(destination)) {
SCORMUtils.readSCORMManifest(this.media.identity).then(fileName => {
this._src = `${src}/${fileName}`;
SCORMUtils.makeOfflineCompatible(fs.path.join(destination, fileName))
.then(() => {
this._loading = false;
});
this._loading = false;
});
}
Utility Class:
import { File, knownFolders } from 'tns-core-modules/file-system';
const SCORM_API = `
<script type="text/javascript">
(function () {
window.parent.API = (function() {
return {
LMSInitialize: function () {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSInitialize");
}
return "true";
},
LMSCommit: function () {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSCommit");
}
return "true";
},
LMSFinish: function () {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSFinish");
}
return "true";
},
LMSGetValue: function (key) {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSGetValue");
}
return "";
},
LMSSetValue: function (key, value) {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSSetValue");
}
return "true";
},
LMSGetLastError: function () {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSGetLastError");
}
return "0";
},
LMSGetErrorString: function (errorCode) {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSGetErrorString");
}
return "No error";
},
LMSGetDiagnostic: function (errorCode) {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSGetDiagnostic");
}
return "No error";
}
}
})();
})();
</script>
</head>
`;
export class SCORMUtils {
/**
* Converts a SCORM course to be opened offline
* #param entryFile The main entry point determined by imsmanifest.xml
*/
static makeOfflineCompatible(entryFile: string) {
return new Promise((resolve, reject) => {
// Rewrite the entry file first
this._rewriteFile(entryFile)
.then(() => {
this._discoverHTMLEntry(entryFile)
.then(() => {
resolve();
}, () => {
console.error('Unable to rewrite alternative HTML entry');
reject();
});
}, () => {
console.error(`Unable to rewrite primary entry point: ${entryFile}`);
reject();
});
});
}
/**
* Digests a SCORM Manifest file to determine the main point of entry
* for the course viewer. Normally this is a index.html file.
*/
static readSCORMManifest(identity: string): Promise<string> {
return new Promise((resolve, reject) => {
const manifestFile = knownFolders.documents()
.getFolder('courses')
.getFolder(identity)
.getFile('imsmanifest.xml');
if (!File.exists(manifestFile.path)) {
alert({
title: 'Error',
message: 'Course is missing imsmanifest.xml file',
okButtonText: 'Ok'
});
return reject();
}
const data = manifestFile.readTextSync(() => {
alert({
title: 'Error',
message: 'Cannot open course.',
okButtonText: 'Ok'
});
return reject();
});
const matches = data.match(/type="webcontent"+.+?href="(.*?)"/);
if (matches === null || matches.length < 1) {
alert({
title: 'Error',
message: 'Invalid imsmanifest.xml file',
okButtonText: 'Ok'
});
}
else {
resolve(matches[1]);
}
});
}
/**
* Rewrites a file to be SCORM offline-compliant
* #param path The path of the file to re-write
*/
private static _rewriteFile(path: string) {
return new Promise((resolve, reject) => {
const entryFile = File.fromPath(path);
entryFile.readText()
.then(htmlText => {
this._injectOfflineAPI(htmlText)
.then(updatedHtml => {
entryFile.writeText(updatedHtml).then(() => {
resolve();
}, () => {
console.error(`Error writing to file: ${path}`);
reject();
});
});
}, () => {
console.error(`There was an entry reading the entry file at: ${path}`);
reject();
});
});
}
/**
* Attempts to find another SCORM entry point for re-write
* #param mainEntry The main entry point to branch from
*/
private static _discoverHTMLEntry(mainEntry: string): Promise<any> {
return new Promise((resolve, reject) => {
const entryFile = File.fromPath(mainEntry);
entryFile.readText()
.then(htmlText => {
let htmlEntry = htmlText.match(/{"type":"html5","url":"(.*?)"}/);
if (htmlEntry === null || htmlEntry.length < 1) {
// Check for Articulate
htmlEntry = htmlText.match(/location\.href\.replace\("index_lms", "(.*?)"/);
}
if (htmlEntry !== null && htmlEntry.length > 0) {
let fileName = htmlEntry[1];
if (fileName.indexOf('.html') === -1) {
fileName = `${fileName}.html`;
}
const directory = mainEntry.substr(0, mainEntry.lastIndexOf('/'));
const entryPoint = `${directory}/${fileName}`;
if (File.exists(entryPoint)) {
this._rewriteFile(entryPoint)
.then(() => {
resolve();
}, () => {
console.error('Error discovering main entry point.');
reject();
});
}
else {
console.error(`Cannot find alternative entry point: ${entryPoint}`);
reject();
}
}
else {
// This course does not have an alternative entry point
console.error('Course does not have an alternative entry, skipping...');
resolve();
}
}, () => {
reject();
});
});
}
/**
* Injects the extended SCORM API for offline-compatible viewing
* #param text The unmodified HTML source text
*/
private static _injectOfflineAPI(text: string): Promise<string> {
return new Promise((resolve, reject) => {
// Prevent multiple rewrites of the same file
if (this._isConverted(text)) {
return resolve(text);
}
// Finds the end of the head tag for script injection
const head = text.match(/<\/head>/gi);
if (head !== null && head.length > 0) {
resolve(text.replace(head.toString(), SCORM_API));
}
else {
console.error('Unable to parse incoming HTML for head tag.');
reject({
message: 'Unable to parse HTML'
});
}
});
}
/**
* Checks if the HTML has already been converted for offline-viewing
* #param text The incoming HTML source text
*/
private static _isConverted(text: string) {
const match = text.match(/window.parent.API/);
return match !== null && match.length > 0;
}
}

Discriminated/disjoint unions in Flow

I'm trying to make Flow happy.
It's not very, giving me this:
app/components/commands/types.js:117
117: { state: 'state-retries-timeout',
^^^^^^^^^^^^^^^^^^^^^^^ string literal `state-retries-timeout`. Expected string literal `state-initial`, got `state-retries-timeout` instead
60: { state: 'state-initial', touched: boolean }
But I don't see how I'm not following the documentation
Source code:
// #flow
/**
* A server-side validation message. Should contain a globally unique key
* and a message. The key can be used for internationalisation purposes.
*/
export type ValidationMessage =
{ key: string,
message: string }
export function validationMessage(key: string, message: string): ValidationMessage {
return { key, message }
}
/**
* The field is untouched in this user-session.
*/
export const StateInitial = 'state-initial';
/**
* The edit is not yet persisted anywhere.
*/
export const StatePending = 'state-pending'
/**
* The command is not yet committed on the server, but is committed locally.
*/
export const StatePendingSavedLocally = 'state-pending-saved-locally'
/**
* The command was successfully commited.
*/
export const StateSuccess = 'state-success'
/**
* The command was commited, but there are resulting warnings.
*/
export const StateSuccessWarnings = 'state-success-warnings'
/**
* The command or its data was invalid and the server returned 400 Bad Request;
* it may not be retried without changing it.
*/
export const StateRejected = 'state-rejected'
/**
* Despite numerous retries, the app failed to persist the change to the server.
*/
export const StateRetriesTimeout = 'state-retries-timeout'
export type Initial =
{ state: 'state-initial',
touched: boolean }
export type Pending =
{ state: 'state-pending',
touched: boolean }
export type PendingSavedLocally =
{ state: 'state-pending-saved-locally',
touched: boolean }
export type Success =
{ state: 'state-success',
touched: boolean }
export type SuccessWarnings =
{ state: 'state-success-warnings',
warnings: ValidationMessage[],
touched: boolean }
export type Rejected =
{ state: 'state-rejected',
errors: ValidationMessage[],
touched: boolean }
export type RetriesTimeout =
{ state: 'state-retries-timeout',
touched: boolean }
/**
* The discriminated union of all states we allow fields to be represented as.
*/
export type ValueChangeState =
| Initial
| Pending
| PendingSavedLocally
| Success
| SuccessWarnings
| Rejected
| RetriesTimeout
export const initial: ValueChangeState =
{ state: 'state-initial',
touched: false }
export const pending: ValueChangeState =
{ state: 'state-pending',
touched: true }
export const pendingSavedLocally: ValueChangeState =
{ state: 'state-pending-saved-locally',
touched: true }
export const success: ValueChangeState =
{ state: 'state-success',
touched: true }
export function successWarnings(warnings: ValidationMessage[], touched: boolean = true): ValueChangeState {
return {
state: 'state-success-warnings',
warnings,
touched
}
}
export function rejected(errors: ValidationMessage[], touched: boolean = true): ValueChangeState {
return {
state: 'state-rejected',
errors,
touched
}
}
export const retriesTimeout: ValueChangeState =
{ state: 'state-retries-timeout',
touched: true }
Sample usage
// #flow
/*eslint no-unused-expressions: 0 */
import { expect } from 'chai';
import { describe, it } from 'mocha';
import { initial, pending } from './types';
import { valueStateReducer } from './reducers';
import { successOf, rejectionOf, timeoutOf, successWarningsOf } from './actions';
describe('(Reducer) components/commands', function() {
describe('valueStateReducer', function() {
const noop = () => ({ type: 'NOOP' });
const testing = () => ({ type: 'commands/TESTING' });
const subject = valueStateReducer('commands/TESTING');
it('other makes no change', function() {
const res = subject(initial, noop());
expect(res).to.deep.eq(initial);
});
it('of action makes pending', function() {
const res = subject(initial, testing());
expect(res).to.deep.eq(pending);
});
});
});
The answer is that flow doesn't properly handle destructive assignment of nullable/undefined discriminated union/sum types when also providing a default value.
In this screenshot I default valueState to initial (one of the DU cases). This triggers the bug.
If I instead default further down, flow doesn't barf:

FLOW: function call (Function cannot be called on empty prototype object)

Can anybody help me with what is causing this Error?
Here is my code. I have changed it a few times to see if I could get the expected coverage.
const NO_QUERY = new Error('You need to enter a Query to hit this endpoint')
const axios = require('axios')
class thirdParty {
headers = {
Authorization: `secret ${this.secretKey}`
}
apiKey = ''
secretKey = ''
constructor (apiKey: string, secretKey: string) {
if (!secretKey) throw NO_SECRET_KEY
if (!apiKey) throw NO_API_KEY
this.apiKey = apiKey
this.secretKey = secretKey
this.headers = {
Authorization: `secret ${secretKey}`
}
this._request = axios.create({
headers: {
Authorization: `secret ${secretKey}`
}
})
}
productSearch (
{
query = ''
}: {
query: string
},
cb: (error: Error | null, response: ?Object) => void
): Promise.resolve<?{ [key: string]: string }> | Promise.reject<Error> {
if (!cb || typeof cb !== 'function') cb = (error: ?Error | null, response: ?Object) => {}
return new Promise((resolve:
Promise.resolve<?{ [key: string]: string }>, reject:
Promise.reject<Error>):
Promise.resolve<?{ [key: string]: string }> |
Promise.reject<Error> => {
if (!query) return cb(NO_QUERY)
return axios.get(`<URLENDPOINT>?apiKey=${
this.apiKey
}&query=${
query
}`, {
headers: this.headers
}).then(({ data = {} }: { data: { [key: string]: string } }) => {
// [flow] function call (Function cannot be called on empty prototype object)
resolve(data)
cb(null, data)
})
.catch((err: Error) => {
// [flow] function call (Function cannot be called on empty prototype object)
reject(err)
cb(err)
})
})
}
}
As Commented above I am getting the following error on both the reject and resolve Promise methods
[flow] function call (Function cannot be called on empty prototype object)
There must be something I am missing here that is giving me this error but there is not a very descriptive reason for hitting this error.
Any help will be appreciated

Resources