Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert from Flow to Typescript #816

Merged
merged 7 commits into from
Dec 24, 2017
Merged

Conversation

FredKSchott
Copy link
Contributor

@FredKSchott FredKSchott commented Dec 4, 2017

TypeScript is very similar to Flow, in that both provide a flexible typing system for JavaScript. The syntax is almost identical, so it should feel perfectly comfortable already. But TypeScript is much more powerful, so with the same syntax you catch more errors and enforce clearer code. This is a good, fair comparison of the two, and here are some examples in rippled-lib itself:

Additionally it's growing faster, adding new features, and is already fairly popular in the larger npm ecosystem. Lodash, for ex, publishes TS type definitions directly in their package, which is how I was able to catch that error above automatically. Also, type checking is a part of your build/compile, so they can't get out of date with the code.

This allows us to add function typing information to the new, main request() interface. That way all consumers, even those using regular JavaScript with an IDE like VSCode, will get typing help when making API requests and handling API responses. See #812 for more information.

/cc @intelliot This touches a lot of code so please ask if anything is confusing / looks off / could use some documentation.

@@ -64,11 +60,11 @@
"docgen": "node --harmony scripts/build_docs.js",
"clean": "rm -rf dist/npm",
"typecheck": "flow check",
"compile": "babel -D --optional runtime -d dist/npm/ src/",
"watch": "babel -w -D --optional runtime -d dist/npm/ src/",
"compile": "mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/ && tsc",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typescript only handles typescript files, so we have to copy the schemas directory here ourselves. Normally you could just move this folder to a single place outside of the src directory and load them by relative path, but unfortunately that breaks because dist/npm & src/ are at different levels of nesting (../ vs ../../).

This doesn't feel too complex but if we ever need to add more here we could move this into a build script or gulp file.

getServerInfo,
getFee,
getLedgerVersion,
connect = connect
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding these as class properties makes them a part of the interface, vs. _.assign() which was appending them to the prototype in a way that confused TypeScript.

@@ -3,11 +3,15 @@ import * as _ from 'lodash'
import {RippleAPI} from './api'

class RippleAPIBroadcast extends RippleAPI {

// TODO: Should this default to 0, or null/undefined?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think 0 is good here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> 1 > undefined
false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@intelliot my concern is that 0 is a number but isn't a valid ledger number. It do mean that things like (ledger.ledgerVersion > this.ledgerVersion) will work by default, but it also means we have to document and explain that 0 = unset/unknown. It would be more explicit if ledgerVersion = undefined means it hasn't been looked up yet.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I'm fine with undefined, but make sure to update the if condition (ledger.ledgerVersion > this.ledgerVersion) too.

private _proxyAuthorization?: string
private _authorization?: string
private _trustedCertificates?: string[]
// TODO: options.key never used?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's used in _createWebSocket()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, can't believe I missed that

@intelliot
Copy link
Collaborator

Still looking at this. Hoping to try it out soon :)

Any potentially breaking changes?

@AGallouin
Copy link
Contributor

Any news on this ? I'm really looking forward to this !

@sublimator
Copy link
Contributor

They too busy celebrating? haha

image

Copy link
Collaborator

@intelliot intelliot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great start. I left comments on a few of the TODOs.

Also, ripple-lib can be used with either node or in the browser, so I think we need to make some changes to our webpack config for browser builds to work.

@@ -186,7 +212,8 @@ class Connection extends events.EventEmitter {

this._retry = 0
this._ws.on('error', error => {
if (process.browser && error && error.type === 'error') {
// TODO: "type" does not exist on official error type, safe to remove?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. Perhaps some versions of ws included it? To be safe, we could remove it in a later release and note it in the changelog.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, I'll just leave it as a TODO then

@@ -60,16 +60,19 @@ function getServerInfo(connection: Connection): Promise<GetServerInfoResponse> {
})
}

// TODO: This was originally annotated to return a number, but actually
// returned a toString'ed number. Should this actually be returning a number?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this should be a string. We generally use strings for all currency amounts.

Copy link
Contributor Author

@FredKSchott FredKSchott Dec 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good.

Separately: Looking at this more closely now, should this use BigNumber internally? Number could cause problems with baseFeeXRP being a float, right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be correct to use BigNumber here internally.

paths.alternatives = _.filter(paths.alternatives, alt =>
!!alt.source_amount &&
!!pathfind.source.amount &&
// TODO: Returns false when alt.source_amount is a string. Fix?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can alt.source_amount be a string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alt.source_amount is of type RippledAmount, which can be a string. https://ripple.com/build/rippled-apis/ seems to document a few places where source_amount is a string.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like source_amount is only a string when the currency is XRP, in which case there is no need for pathfinding. So I think it's fine to return false in that case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like source_amount is only a string when the currency is XRP, in which case there is no need for pathfinding.

You can send XRP and use paths to send the recipient USD ...

What you mean no need for path finding ?

@@ -23,12 +21,14 @@ function getServerInfo(): Promise<GetServerInfoResponse> {
return common.serverInfo.getServerInfo(this.connection)
}

function getFee(): Promise<number> {
// TODO: getFee() was originally annotated to return a number, but actually
// returned a toString'ed number. Should this actually be returning a number?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return a string.

@@ -21,7 +19,9 @@ function compareSigners(a, b) {
function combine(signedTransactions: Array<string>): Object {
validate.combine({signedTransactions})

const txs = _.map(signedTransactions, binary.decode)
// TODO: signedTransactions is an array of strings in the documentation, but
// tests and this code handle it as an array of objects. Fix!
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 🤔

@FredKSchott
Copy link
Contributor Author

FredKSchott commented Dec 16, 2017

@intelliot Great catch re: webpack. I'll get that fixed for this PR.

Thanks for the review! No behavior should be changing here, except for a few small bug fixes that were explicitly bugs. Anything that could have been relied on as behavior was left as-is with a TODO, so no breaking changes expected.

Excited to do some more work on this over the holidays. Three branches can go into PR all together once this gets merged:

  • istanbul update Istanbul is now called "nyc", works with typescript automatically
  • tslint like eslint but for typescript, rules ported over from the eslintrc config
  • beginning to flesh out new request api: so far added support for account_offers, book_offers, and account_info. Doesn't need to be merged into "develop" branch just yet, but an early review would be good to test out & make sure it's going in the right direction.

@FredKSchott
Copy link
Contributor Author

webpack & gulp updated for TS: FredKSchott/ripple-lib@typescript...FredKSchott:webpack

It's currently in a separate branch, but I can add it directly into this PR if you'd prefer.

@intelliot
Copy link
Collaborator

That looks great!

I attempted to create a PR and merge that branch to this PR, but github won't allow it since I don't have write access to your fork 😅

@FredKSchott
Copy link
Contributor Author

Haha np, I'll merge it in now

Update webpack, gulpfile to support typescript
Copy link
Collaborator

@intelliot intelliot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good. The remaining TODOs could be handled later. I'll do a bit more manual testing meanwhile.

@intelliot intelliot merged commit 8204f6c into XRPLF:develop Dec 24, 2017
@intelliot
Copy link
Collaborator

Squashed and merged! I don't think there will be any difficulty rebasing on the squash commit, but let me know if so.

@FredKSchott
Copy link
Contributor Author

FredKSchott commented Jan 2, 2018

@intelliot woo! 🎉 Thanks for your reviews

Just getting back & caught up from the holidays, I'll rebase those other branches and create the next batch of PRs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants