-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Discussion: Improving typings #3413
Comments
Sounds good. Moving them somewhere central instead of having everything depend on 🚲 🏡 Put them in the |
I'll follow this pattern in implementing #3293 |
@achingbrain do you have
Where in |
I don't have a strong opinion TBH, |
i would prefer types but its just a nit, we are already using types.ts in other repos so for a big group of types a |
@hugomrdias @achingbrain I would like to discuss / reach a consensus in regards to function overloads that have certain issues. This change e1ec19d is an illustrative of the genera problem so I'll use that as an example. We have certain APIs that produce different output based on the input (in terms of types and shapes). Let's consider this example from const example = async (api:LS) => {
for await (const entry of api.ls()) {
entry.stat.pinned
// ^^^^ Property 'stat' does not exist on type 'Entry'
}
for await (const entry of api.ls({ stat: true })) {
entry.stat.pinned
// works fine because stat was passed
}
}
interface LS {
ls(options?:AbortOptions): AsyncIterable<Entry>
ls(options:{stat:true} & AbortOptions): AsyncIterable<EntryWithStat>
}
interface AbortOptions { signal?: AbortSignal }
interface Entry { /** ... not important */}
interface EntryWithStat extends Entry { stat: Stat }
interface Stat {
pinned: number
/* ... not important */
} As you can see with about interface definitions type checker can identify if Another alternative is to use less precise types, here is an example illustrating that const example = async (api:LS) => {
for await (const entry of api.ls()) {
entry.stat.pinned
// ^^^^ Object is possibly 'undefined'
}
for await (const entry of api.ls({ stat: true })) {
entry.stat.pinned
// ^^^ Object is possibly 'undefined'
}
for await (const entry of api.ls({ stat: true })) {
if (entry.stat) {
entry.stat.pinned
// works here
} else {
// what to do here though ?
}
}
}
interface LS {
ls(options?:{stat?:true} & AbortOptions): AsyncIterable<Entry>
}
interface AbortOptions { signal?: AbortSignal }
interface Entry { stat?: Stat /** ... not important */}
interface Stat {
pinned: number
/* ... not important */
} Doing that (which is mostly what we have today) makes API more cumbersome to use for others and ourselves as well. Furthermore it introduces a questions like what to do if Finally there is yet another approach, that is not overloading functions in first place as this example illustrates const example = async (api:LS) => {
for await (const entry of api.ls()) {
entry.stat.pinned
// ^^^^ Property 'stat' does not exist on type 'Entry'.(2339)
}
for await (const entry of api.lsWithStat()) {
entry.stat.pinned
// Stats are there
}
}
interface LS {
ls(options?:AbortOptions): AsyncIterable<Entry>
lsWithStat(options?:AbortOptions):AsyncIterable<EntryWithStat>
}
interface AbortOptions { signal?: AbortSignal }
interface Entry { /** ... not important */}
interface EntryWithStat extends Entry { stat: Stat }
interface Stat {
pinned: number
/* ... not important */
} I personally believe that last approach is best compromise because:
Tradeoff is however just like with ( What do you think the right compromise here is, or is would it vary from case by case basis ? If there is one we should probably encode it into aegir ts guide. |
an option shouldn't change the return type, in the past "changing the return type" was more ambiguous we didn't pay much attention to the structure of an object return but we would still apply this rule in other cases (ie. string or number) so we should evolve this rule to the current context and go with your 3rd option and make a new method. |
So I have tried factor out some of the types like
This also suggest that So I think we need a whole new package e.g. |
Today I bumped http-ipfs-client and was happy to find types. I love IPFS but the current types are not great, to put it mildly. I casted the Ipfs constructor to any. Hope the type situation improves to ensure a good developer experience around IPFS in the long term ❤️ |
Thanks for the feedback - could you please open issues with what you see vs what you expect to see? At the very least they can be used as datapoints to feed into this conversation, but also they will represent discrete items of work that you or others can open PRs to address. |
I've opened issues #3450 and #3451 I'm sorry for the negative tone but working with Ipfs JS this last two years with frequent breaking changes has been very frustrating for the lack of proper types. |
Addresses #3442 (comment) by refactoring some of the common types used by root APIs as per #3413 Co-authored-by: achingbrain <[email protected]>
Addresses ipfs/js-ipfs#3442 (comment) by refactoring some of the common types used by root APIs as per #3413 Co-authored-by: achingbrain <[email protected]>
js-ipfs is being deprecated in favor of Helia. You can follow the migration plan here #4336 and read the migration guide. This issue has been resolved in Helia! if this does not address your concern please let us know by reopening this issue before 2023-06-05! |
Constraints
Here is the set of constraints following proposal attemts to satisfy (please call
out if you find something is missing or doesn't belong)
ipfs-core
andipfs-http-client
implementsame base API.
and so could
ipfs-core
but there should be common subset between two.peer review
magic types
that are hard to understand.
(as much as possible)
Proposal
I propose to satisify all of the listed constraints by doing following
Create a designated directory for reusable type definitions (e.g.
src/interface
or whatever wins bikeshed contest and have component specificinterface definitions for
ipfs.pin
,ipfs.files
,ipfs.dag
, etc..Below is illustration of what
interface/pin.ts
could look like:Each IPFS component that can provide
@implements {API}
annotation that typechecker will ensure.
Below is illustration of how
ipfs-core
can use this on the samepin
component:
This will work regardless if we choose to do Incidental Complexity: Dependency injection is getting in the way #3391 or a slight variation on
keep the current style used everywhere in chore: make IPFS API static (remove api-manager) #3365. If we keep
the current style above will look as:
Where
./add-all.js
looks like:Note that all the types are imported from
interface/*
.And if we choose to reduce to remove dependency incejection (as per Incidental Complexity: Dependency injection is getting in the way #3391) things work just as well (better actually)
ipfs-http-client
will do more or less the same, with thedifference that
config
is whatever it is inipfs-http-client
:Here as well we can continue with our current approach of
dependency injections:
Or move to dependency injection free approach:
Additional notes
@typedef {import('ipfs-core/src/interface/pin')} Pin
would gain namespace like behavior which will reduce amount of imports in the examples.The text was updated successfully, but these errors were encountered: