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

fix: integrate with aegir types cmd #159

Closed
wants to merge 19 commits into from
Closed

fix: integrate with aegir types cmd #159

wants to merge 19 commits into from

Conversation

hugomrdias
Copy link
Member

@hugomrdias hugomrdias commented Nov 24, 2020

This PR does the following: (review might be easier by commit)

  • chore: update readme to look like the other multiformats repos

  • fix: update package.json and tsconfig

  • fix: transform multiaddr into a class

    BREAKING CHANGE: index.js default export no longer is both callable and
    constructor. encapsulate and decapsulate only accept Multiaddr instances as
    input.

  • fix: remove class-is and fix types in index.js

  • fix: add some types to protocols

  • fix: add some types to convert

    BREAKING CHANGE: Convert.toString now always returns a string

  • fix: add some types to codec.js

  • fix: fix resolver exports

  • fix: remove old docs stuff

  • feat: use github ci

The breaking changes should be theoretically based on the current documentation and usage but we never know if people are doing something not documented.

ping this PR #168

.github/ISSUE_TEMPLATE/bug_report.md Show resolved Hide resolved
package.json Show resolved Hide resolved
src/codec.js Show resolved Hide resolved
src/index.js Show resolved Hide resolved
src/index.js Show resolved Hide resolved
src/index.js Show resolved Hide resolved
src/index.js Outdated Show resolved Hide resolved
src/protocols-table.js Outdated Show resolved Hide resolved
src/resolvers/dns.js Outdated Show resolved Hide resolved
/**
* Resolver for dnsaddr addresses.
*
* @param {Multiaddr} addr
* @param {any} addr
Copy link
Member

Choose a reason for hiding this comment

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

Why not a multiaddr?

Copy link
Member Author

Choose a reason for hiding this comment

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

theres a TODO above explaining we can't get the Multiaddr type because of this issue microsoft/TypeScript#41672

Copy link
Member

Choose a reason for hiding this comment

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

Ah right! This is not nice as we will need to update this again after, I might wait for this to be solved before getting libp2p PR updating this merged in.
But we can move forward with this module here.

Copy link
Member Author

@hugomrdias hugomrdias Nov 26, 2020

Choose a reason for hiding this comment

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

the only way to solve this now, is to do a bigger breaking change and default export the Multiaddr class directly. this would remove the ability to multiaddr('/ip..') and force new Multiaddr('')

should we do this ?

Copy link
Member Author

Choose a reason for hiding this comment

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

i can add a static method to the Multiaddr class called multiaddr and users would only need to change from const multiaddr = require('multiaddr) to const { multiaddr } = require('multiaddr)

Copy link
Member

Choose a reason for hiding this comment

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

I would like to have the constructor exported by default to be honest. But, I would wait on @jacobheun input before moving that way as this has a impact on a large number of modules.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah having the constructor exported directly makes a bunch of things easier and documentation looks a lot better.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm in favor of exporting the constructor, we should make sure we have clear migration docs for people. We should also add a static method for creation, this would make it easy to hot swap the two and allow us to be more picky about the constructor args. Since this is in the multiformats org it might be good to be consistent with the cid update effort, multiformats/js-multiformats#23, and call it asMultiaddr

Copy link
Member

@vasco-santos vasco-santos left a comment

Choose a reason for hiding this comment

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

One minor detail left, and this should be good to go

src/index.js Show resolved Hide resolved
@hugomrdias
Copy link
Member Author

@jacobheun and @vasco-santos can you please review, added the migration docs and some more small tweaks as for the asMultiaddr i would like to do that in another PR.

Copy link
Contributor

@Gozala Gozala left a comment

Choose a reason for hiding this comment

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

I made some small suggestions and some not so small. Given that this is breaking change, I think it would be good to use this opportunity to do more changes that otherwise would be a lot harder to justify:

  1. Constructor takes address in arbitrary representation, tries to figure out what is it and then turns it into Multiaddr if it can. I think a factory functions (static method) representation kind is lot more clear both for user and implementer (as illustrated inline) and avoids unnecessary work when types are known. If it is not unreasonable I would really love to use this opportunity to do such a change. We can still have generic Multiaddr.from when representation is unknown.
  2. Multiaddr has large number of utility methods, having equivalent static methods would be really nice to have because:
  • They come handy in places like addrs.map(Multiaddr.toOptions)
  • Moving multiaddrs across realms / threads discards prototype chains. Which means static methods continue to work, but not instance methods. Which in turn means encode / decode pipeline.

I realize above two are big asks & only bringing them so we can consider / discuss given the opportunity presented by breaking change.

- run: yarn
- run: yarn lint
- run: yarn build
- uses: gozala/typescript-error-reporter-action@v1.0.4
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
- uses: gozala/typescript-error-reporter-action@v1.0.4
- uses: gozala/typescript-error-reporter-action@v1.0.8


```js
// The Multiaddr class has a factory method to help migration
const { multiaddr } = require('multiaddr')
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would make more sense to have Multiaddr.parse(string) static and make constructor @private. That way this turns into

Suggested change
const { multiaddr } = require('multiaddr')
const { from: multiaddr } = require('multiaddr')

And if we really want to smooth out transition we could have deprecated alias multiaddr.

My argument for discouraging new Multiadddr is because has tendency to grow into new Multiaddr(any) over time.

My personal opinion is to reserve constructors for property initializations, all the computation and logic should go into dedicated functions.

Edit: Added more details in the contstructor

"test:browser": "aegir test -t browser",
"test:types": "npx tsc",
"test:types": "aegir ts -p check",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"test:types": "aegir ts -p check",
"test:types": "aegir ts -p check",
"prepare": "aegir ts -p types",

function tuplesToStringTuples (tuples) {
return tuples.map(tup => {
const proto = protoFromTuple(tup)
if (tup.length > 1) {
if (tup.length > 1 && tup[1]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Checking length is redundant

Suggested change
if (tup.length > 1 && tup[1]) {
if (tup[1]) {

* // <Multiaddr 047f000001060fa1 - /ip4/127.0.0.1/tcp/4001>
* ```
*/
constructor (addr) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use this breaking change as an opportunity to make it more type friendly and switch to more cleaner approach:

const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr')

class Multiaddr {
  /**
   * Parses string encoded multiaddr.
   * 
   * @param {string} addr
   * @returns {Multiaddr}
   */
  static parse(addr) {
    if (addr.length > 0 && addr.charAt(0) !== '/') {
      throw new Error(`multiaddr "${addr}" must start with a "/"`)
    }
    return new Multiaddr(codec.fromString(addr))
  }

  /**
   * Decodedes binary encoded multiaddr.
   * 
   * @param {Uint8Array} addr
   * @returns {Multiaddr}
   */
  static decode(addr) {
    return new Multiaddr(codec.fromBytes(addr))
  }

  static empty() {
    return Multiaddr.parse('')
  }

  /**
   * @typedef {Multiaddr|string|InstanceType<typeof String>|Uint8Array|null} ToMultiaddr
   * @param {ToMultiaddr} addr
   * @returns {Multiaddr}
   */
  static from(addr) {
    if (typeof addr === 'string') {
      return Multiaddr.parse(addr)
    } else if (addr instanceof Uint8Array) {
      return Multiaddr.decode(addr)
    } else if (Multiaddr.isMultiaddr(addr)) {
      return new Multiaddr(addr.bytes)
    } else if (addr === null) {
      return Multiaddr.empty()
    } else {
      throw new Error('addr must be a string, Buffer, or another Multiaddr')
    }
  }
  /**
   * @param {any} value
   * @returns {value is Multiaddr}
   */
  static isMultiaddr(value) {
    return Boolean(value && value[symbol])
  }
  /**
   * @private
   * @param {Uint8Array} bytes
   */
  constructor(bytes) {
    this.bytes = bytes
  }
}

Which has bunch of benifs:

  1. When you read Multiadddr.parse(input) it is clear what the input without any tooling or side trips.
  2. Constructor is dead simple
  3. Each code path is also absolutely clear.
  4. You still have .from to take any input when you don't know what you have.
  5. No added overhead on each instantiation when you know your types.

Copy link
Contributor

Choose a reason for hiding this comment

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

For what it's worth that is where multiformats/cid ended up and feedback so far had been positive.

* ```
*/
constructor (addr) {
Object.defineProperty(this, symbol, { value: true })
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we turn that this into a getter please ?

class Multiaddr {
  get [symbol]() { 
    return true
  }
}

That is what we end up doing in other places.

opts.host = parsed[2]
opts.transport = parsed[3]
opts.port = parseInt(parsed[4])
return opts
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we do this instead, I find it a lot easier to follow (and engines can optimize better)

toOptions() {
  const [,family,host, transport, port] =  this.toString().split('/')
  return {
    famility: family === 'ip4' ? 'ipv4' : 'ipv6',
    host,
    transport,
    port
  }
}

* Returns if something is a Multiaddr that is a name
*
* @param {Multiaddr} addr
* @returns {boolean} isName
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @returns {boolean} isName
* @returns {boolean}

* - how to achieve it in the browser?
/**
* Static factory method
*
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would make sense to deprecate it to encourage transition.

Suggested change
*
*
* @deprecated

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