-
Notifications
You must be signed in to change notification settings - Fork 53
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
Trim Link / CID interface and introduce something like LinkView interface #214
Comments
After looking at dag-json spec I'm realizing that
So basically |
For what it's worth, I just checked and found out that node Buffer's also turn into plain Uint8Array in new realms (I was hoping we might borrow a trick from node, but it doesn't seem to exist) |
I have been thinking about this more and I think it is worth calling out all the CID consumers to get down to what makes most sense for them. E.g. in absolute terms following representation of a CID does not seem ideal for any of the consumers (I'll go over them later) interface CID {
version: 0|1
code: number
multihash: MultihashDigest
bytes: Uint8Array
} Because:
Consider alternative, which captures all of the information yet make invalid CIDs impossible: type CID = CIDv0 | CIDv1
interface CIDv0 {
version: 0
multihash: MultihashDigest<0x12>
}
interface CIDv1<Code extends number = number, Alg extends number = number> {
version: 1
code: Code
multihash: MultihashDigest<Alg>
} ConsumersAs far as I can tell there are sever types of consumers of CIDs:
With above consumers in mind I think it might be a good idea to have optimal representation for Encoders and separate optimal representation for consumers. Incorporators would need to weight tradeoffs and choose to act as one or the other which would imply either importing static functions to operate on received CIDs or demand "CID Views" from it's consumers. I think this would also help with making code on the other side of wire bit more explicit, meaning it would have to act as encoder (well decoder actually, but encoder in our taxonomy) and not as consumer. With that insight it seems to me that for encoders we simply need // Using unboxed Uint8Array would be too error prone since any array starting with 0 or 18 could
// be mistaken for CID. Any other prefix is probably going to be just as error prone. To avoid this
// we simply box bytes as { CID: bytes }
interface CID<C extends number = number, Alg extend number = number, V = 0 | 1 = 0 | 1>
extends Phantom<{ version: V, code: C, MultihashDigest<Alg> }> {
// can be any other name as long as collision with arbitrary other objects is highly unlikely
// given using capital case names in JS is not idiomatic collision here seems unlikely.
CID: ByteView<CID<C, Alg, V>>
}
interface CIDView<C extends number = number, Alg extend number = number, V = 0 | 1 = 0 | 1>
extends CID<C, Alg, V> {
readonly version: V
readonly code: C
readonly multihash: MultihashDigest<Alg>
toBytes(): Uint8Array
toString(): string
toV1(): CIDView<C, Alg, 1>
equals(other:unknown): other is CID<C, Alg, V>
link(): CIDView<C, Alg, V>
[Symbol.toStringTag]: 'CID'
} In terms of implementation const digestCache = new WeakMap()
class CID {
constructor(bytes) {
this.CID = bytes
}
get version() {
return version(this)
}
get code() {
return code(this)
}
get multihash() {
return multihash(this)
}
equals(other) {
return equals(this, other)
}
toBytes() {
return toBytes(this)
}
toString() {
return toString(this)
}
}
export const code = ({ CID }) => varint.decode(CID, 1)
export const version = ({ CID }) => CID[0] === 18 ? 0 : 1
const multihash = (cid) => {
const digest = digestCache.get(cid.CID)
if (!digest) {
const offset = varint.encodingLength(version(cid)) + varint.encodingLength(code(cid))
const digest = Digest.decode(cid.CID.subarray(offset))
digestCache.set(cid.CID, digest)
return digest
}
return digest
}
export const equals = ({ CID }, other) => uint8arrays.equals(CID, other.CID)
export const decode = (bytes:Uint8Array) => {
switch (bytes[0]) {
case 18:
return decodeV0(bytes)
case 1:
return decodeV1(bytes)
default:
throw new Error(`Unsupported CID version ${bytes[0]}`)
}
}
export const { decode as fromBytes }
const decodeV0 = (bytes) => {
const byteOfffset = 1
const digest = Digest.decode(bytes, byteOffset)
digestCache.set(bytes, digest)
return new CID(bytes)
}
const decodeV1 = (bytes) => {
const byteOfffset = 1
const [code, size] = varint.decode(bytes, 1)
const digest = Digest.decode(bytes, byteOffset + size)
digestCache.set(bytes, digest)
return new CID(bytes)
)
export const toBytes = ({ CID }) => CID
export const toString = (cid) => {
switch (version(cid)) {
case 0:
return base58btc.encode(toBytes(cid)).slice(1)
case 1:
return base32.encode(toBytes(cid))
default:
throw new Error(`Unsupported CID version ${version(cid)}`)
}
}
export const parse = (source) => {
switch (source[0]) {
case 'Q':
return decode(base58.decode(`${base58btc.prefix}${source}`))
case base58btc.prefix: {
return decode(base58btc.decode(source))
case base32.prefix:
return decode(base32.decode(source))
default:
throw new Error(`To parse non base32 or base58btc encoded CIDs use CID.fromBytes(baseDecoder.decode("${source}"))`)
}
}
export { parse as fromString } |
I would like to address following comment from @RangerMauve
I think it's doable, yet I'm no longer sure it can be practical in JS due to the following:
So while I would really love to have a way to not box Uint8Array's I can't seem to find a reliable way to distinguish ones that represent CIDs from ones that do not. If anyone has suggestion on how to accomplish this, I would very much love to do the necessary work to make it happen. Otherwise some form of lightweight boxing is probably best we can do, in which case I agree that |
@achingbrain @Gozala do either of you want to move forward with more changes to |
Only changes to |
Given #213 #212 #208 and #211 I would like to propose trimming down
Link
interface to bare minimum where it acts like Model and introduce another interface e.g.LinkView
(can call whatever) that extendsLink
with bunch of methods:Note I have removed following entirely
js-multiformats/src/link/interface.ts
Lines 31 to 32 in 0ec751a
Idea here would be that
CID
class will implement both interfaces, but all the stuff onLinkView
will be implemented usingCID.prototype
which should ensure that value equality will hold.This also would imply that
isLink
would become something like this:The text was updated successfully, but these errors were encountered: