-
Notifications
You must be signed in to change notification settings - Fork 28
Conversation
e37aec0
to
f2ab5b9
Compare
f2ab5b9
to
6a95834
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vasco-santos I did a pass here, but I think I have a more general feedback that is not pinned to any specific line:
aegir bulid
will generate types indist
so no.d.ts
files are necessary (I did pushed a commit here that removes them, but feel free to revert it if you disagree).- types in
dist
need to be mapped in package.json (I have created Feat/add types 2 #75 that does it). - type check / build is failing with this tree (I have created Feat/add types 2 #75 that addresses remaining issues)
- I think we need to distinguish between interfaces (
interface { ...}
) and classes that implement those. And users of those implementations should code against interfaces not implementation classes. That enables anyone to do a different implementation of the interface, ensure with type checker compatibility and use it as drop-in replacement. - I think
declare class
-es must go, if they are necessary they should be interfaces. - There are some incompatibilities regarding
MuxedStream
andDuplexIterableStream
s (I had to suppress some mismatches in Feat/add types 2 #75). I think we should resolve those. I don't have a complete enough understanding in this area to provide specific suggestions, but would love to have session so I can better understand it and also help resolve this. - There are bunch of places where instance fields are
null|T
but in code used asT
(I have suppressed those with@ts-ignore
in Feat/add types 2 #75, but I think we need a followup pull to resolve them)
Thanks for the reviews. On @Gozala feedback:
Yes, as mentioned on the PR desc, I kept them to test it with libp2p/js-libp2p#802
I added them to go around the type checker error of the type not being constructable. Anyway, this did not fix the issue, so I will remove them.
Yeah, I think they should all be MuxedStream, if you are talking about Pubsub. I will change them
I will have a look on these |
6f53c32
to
8fdc05d
Compare
30fe22d
to
0801fc3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add few nits, but looks good to me to go with or without them.
* @typedef {import('./message').Message} RPCMessage | ||
* @typedef {import('./signature-policy').SignaturePolicyType} SignaturePolicyType | ||
*/ | ||
|
||
/** | ||
* @typedef {Object} InMessage | ||
* @property {string} [from] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following code seems to suggest that from
isn't optional here
https://github.com/libp2p/js-libp2p-interfaces/pull/74/files#diff-2398b30d2245a825770bfde7b95fd0b65f3ce6435c3f8cd87a632f583a5b2addR66
If so can we turn it into non optional so type checker can report the error instead of leaking it into runtime ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is really dependent on the signature policy. Have a look into the validate
function. It will be undefined or should exist according to that policy being used.
The same for other places like the verifySignature
function, which validate
only calls if it has a signature. I will add an error there to avoid potential future usages of the function though
@@ -138,26 +140,26 @@ class PeerStreams extends EventEmitter { | |||
/** | |||
* Attach a raw outbound stream and setup a write stream | |||
* | |||
* @param {Stream} stream | |||
* @param {MuxedStream} stream | |||
* @returns {Promise<void>} | |||
*/ | |||
async attachOutboundStream (stream) { | |||
// If an outbound stream already exists, | |||
// gently close it | |||
const _prevStream = this.outboundStream |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case this line is no longer needed
const _prevStream = this.outboundStream |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need it for the check in the end of the function to only emit stream:outbound
if the connection is new. We should only emit it when the outbound stream is ready, so this should be in the end, which means we need that auxiliary constant
src/record/index.js
Outdated
@@ -7,25 +7,31 @@ const errcode = require('err-code') | |||
*/ | |||
class Record { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If thing does not need to be a class it's best to define it as interface / type because it could be substituted with other implementation or even just plain object that conforms the interface. Classes should really just be a one possible implementation of it. Current version implies that TS will error anywhere record instanceof Record
isn't true.
* Checks if the given value is a `MulticodecTopology` instance. | ||
* | ||
* @param {any} other | ||
* @returns {other is MulticodecTopology} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seeing the Either way it's just a suggestion and it's your decision to make. |
Ok if I take the current typedefs for transports and try to implement it like this: type TCPDial = { signal?: AbortSignal }
export class TCPTransport implements Transport<TCPDial> {
// ^^ Class 'TCPTransport' incorrectly implements interface 'Transport<TCPDial>'.
constructor(upgrader: Upgrader, ...others: any) {
return this
}
/**
* Dial a given multiaddr.
*/
dial(ma: Multiaddr, options?: TCPDial): Promise<Connection> {
throw 1
}
/**
* Create transport listeners.
*/
createListener(options: any, handler: (connection:Connection) => void): Listener {
throw 2
}
/**
* Takes a list of `Multiaddr`s and returns only valid addresses for the transport
*/
filter(multiaddrs: Multiaddr[]): Multiaddr[] {
return multiaddrs
}
} P.S.: It complains about the prototype field but if you remove prototype from interface it will still complain about Now consider what I was suggesting instead: type TCPDial = { signal?: AbortSignal }
export class TCPTransport implements Transport<TCPDial> {
constructor(_upgrader: Upgrader, ..._others: any) {
}
/**
* Dial a given multiaddr.
*/
dial(_ma: Multiaddr, _options?: TCPDial): Promise<Connection> {
throw 1
}
/**
* Create transport listeners.
*/
createListener(_options: any, _handler: (connection:Connection) => void): Listener {
throw 2
}
/**
* Takes a list of `Multiaddr`s and returns only valid addresses for the transport
*/
filter(multiaddrs: Multiaddr[]): Multiaddr[] {
return multiaddrs
}
}
export const dial = <T extends { signal: AbortSignal }>
(NetworkTransport: TransportFactory<T>, upgrader: Upgrader, ma:Multiaddr, options:T):Transport<T> => {
const transport = new NetworkTransport(upgrader)
transport.dial(ma, options)
return transport
} Unlike above example It allows implementation of the transport to claim that it implements the interface TransportFactory <DialOptions extends { signal?: AbortSignal }> {
new (upgrader: Upgrader, ...others: any): Transport<DialOptions>;
}
/**
* A libp2p transport is understood as something that offers a dial and listen interface to establish connections.
*/
export interface Transport <DialOptions extends { signal?: AbortSignal }> {
/**
* Dial a given multiaddr.
*/
dial(ma: Multiaddr, options?: DialOptions): Promise<Connection>;
/**
* Create transport listeners.
*/
createListener(options: any, handler: (connection:Connection) => void): Listener;
/**
* Takes a list of `Multiaddr`s and returns only valid addresses for the transport
*/
filter(multiaddrs: Multiaddr[]): Multiaddr[];
} |
src/transport/types.ts
Outdated
/** | ||
* Create transport listeners. | ||
*/ | ||
createListener(options: any, handler: (Connection) => void): Listener; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like options
here should be also made into generic rather than any.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can change it to unknown, but really not much more.
Taking a look at https://github.com/libp2p/js-libp2p-interfaces/tree/master/src/transport#create-a-listener there is no interface options for the listener. Each transport will have their own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that also the whole point of generics to define a generic transport interface so that specific ones can specify concrete types that correspond to them just like it was in the case of dial:
interface Tansport<DialOptions extends { signal: AbortSignal }, ListenerOption> {
dial(ma: Multiaddr, options?: DialOptions): Promise<Connection>;
createListener(options: ListenerOptions, handler: (Connection) => void): Listener;
filter(multiaddrs: Multiaddr[]): Multiaddr[];
}
class TCPTransport implements Transport<TCPDialOptions, TCPListenerOptions> {
dial(ma: Multiaddr, options?: DialOptions): Promise<Connection> {
// ...
}
createListener(options: TCPListenerOptions, handler: (connection: Connection) => void): Listener {
// ...
}
// ...
}
type TCPDialOptions = {
// ...
}
type TCPListenerOptions = //...
You could even go step further and make Transport
have a default type e.g.:
interface Transport <DialOptions extends AbortOptions = AbortOptions, ListenerOptions = void> {
}
// Will be a shortuct for implemnets TCPTransport<AbortOptions, void>
class TCPTransport implements Transport {
}
Ts check is just commented out in the config because it's fail if a repo doesn't have the tsconfig (looking at you @Gozala this could be better 🙏🏼) |
…factory interface and minor pubsub fixes
So, I did a new iteration on this with the latest feedback from @Gozala 👍 :
This should now be ready to go. |
Co-authored-by: Hugo Dias <hugomrdias@gmail.com>
There is |
Have you seen this comment #74 (comment) it contains links to the TS playground illustrating issues. IMO whole point of the interface is to be able to tell in the class implementation that it implementns some interface. Current version is not implementable (or more like it's not doing what I think assumbtion is about it). There is also a second link that illustrates how it could be split into two interfaces such that class can say it implements Transport and the other P.S.: |
Per libp2p/js-libp2p#802 feedback, I added two new minor commits to fix review issues |
This PR adds typedef improvements for the already available modules, as well as typescript files for the interfaces needed in libp2p core per libp2p/js-libp2p#802
On top of @Gozala 's #73 PR
Needs:
BREAKING CHANGE: Some types from connection, pubsub and topology are now fixed. Record is not a class anymore, but a typescript interface that should be implemented