Skip to content

Commit 93de7f7

Browse files
Gozalamikeal
andauthored
feat: CID.asCID and depracte CID.isCID (#23)
BREAKING CHANGE! * chore: remove is-class dependency * feat: implement CID.asCID method * chore: deprecate CID.isCID in favor of CID.asCID * fix: compatibility with js-cids * chore: address review comments Co-authored-by: Mikeal Rogers <mikeal.rogers@gmail.com>
1 parent 222ad1c commit 93de7f7

File tree

3 files changed

+138
-11
lines changed

3 files changed

+138
-11
lines changed

cid.js

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as bytes from 'multiformats/bytes.js'
2-
import withIs from 'class-is'
32

43
const readonly = (object, key, value) => {
54
Object.defineProperty(object, key, {
@@ -9,6 +8,36 @@ const readonly = (object, key, value) => {
98
})
109
}
1110

11+
// ESM does not support importing package.json where this version info
12+
// should come from. To workaround it version is copied here.
13+
const version = '0.0.0-dev'
14+
// Start throwing exceptions on major version bump
15+
const deprecate = (range, message) => {
16+
if (range.test(version)) {
17+
console.warn(message)
18+
/* c8 ignore next 3 */
19+
} else {
20+
throw new Error(message)
21+
}
22+
}
23+
24+
const IS_CID_DEPRECATION =
25+
`CID.isCID(v) is deprecated and will be removed in the next major release.
26+
Following code pattern:
27+
28+
if (CID.isCID(value)) {
29+
doSomethingWithCID(value)
30+
}
31+
32+
Is replaced with:
33+
34+
const cid = CID.asCID(value)
35+
if (cid) {
36+
// Make sure to use cid instead of value
37+
doSomethingWithCID(cid)
38+
}
39+
`
40+
1241
export default multiformats => {
1342
const { multibase, varint, multihash } = multiformats
1443
const parse = buff => {
@@ -22,6 +51,9 @@ export default multiformats => {
2251
...multihash
2352
])
2453
}
54+
55+
const cidSymbol = Symbol.for('@ipld/js-cid/CID')
56+
2557
class CID {
2658
constructor (cid, ...args) {
2759
Object.defineProperty(this, '_baseCache', {
@@ -30,7 +62,7 @@ export default multiformats => {
3062
enumerable: false
3163
})
3264
readonly(this, 'asCID', this)
33-
if (_CID.isCID(cid)) {
65+
if (cid != null && cid[cidSymbol] === true) {
3466
readonly(this, 'version', cid.version)
3567
readonly(this, 'multihash', bytes.coerce(cid.multihash))
3668
readonly(this, 'buffer', bytes.coerce(cid.buffer))
@@ -104,11 +136,11 @@ export default multiformats => {
104136
throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0')
105137
}
106138

107-
return new _CID(0, this.code, this.multihash)
139+
return new CID(0, this.code, this.multihash)
108140
}
109141

110142
toV1 () {
111-
return new _CID(1, this.code, this.multihash)
143+
return new CID(1, this.code, this.multihash)
112144
}
113145

114146
get toBaseEncodedString () {
@@ -146,11 +178,56 @@ export default multiformats => {
146178
this.version === other.version &&
147179
bytes.equals(this.multihash, other.multihash)
148180
}
181+
182+
get [Symbol.toStringTag] () {
183+
return 'CID'
184+
}
185+
186+
get [cidSymbol] () {
187+
return true
188+
}
189+
190+
/**
191+
* Takes any input `value` and returns a `CID` instance if it was
192+
* a `CID` otherwise returns `null`. If `value` is instanceof `CID`
193+
* it will return value back. If `value` is not instance of this CID
194+
* class, but is compatible CID it will return new instance of this
195+
* `CID` class. Otherwise returs null.
196+
*
197+
* This allows two different incompatible versions of CID library to
198+
* co-exist and interop as long as binary interface is compatible.
199+
* @param {any} value
200+
* @returns {CID|null}
201+
*/
202+
static asCID (value) {
203+
// If value is instance of CID then we're all set.
204+
if (value instanceof CID) {
205+
return value
206+
// If value isn't instance of this CID class but `this.asCID === this` is
207+
// true it is CID instance coming from a different implemnetation (diff
208+
// version or duplicate). In that case we rebase it to this `CID`
209+
// implemnetation so caller is guaranteed to get instance with expected
210+
// API.
211+
} else if (value != null && value.asCID === value) {
212+
const { version, code, multihash } = value
213+
return new CID(version, code, multihash)
214+
// If value is a CID from older implementation that used to be tagged via
215+
// symbol we still rebase it to the this `CID` implementation by
216+
// delegating that to a constructor.
217+
} else if (value != null && value[cidSymbol] === true) {
218+
return new CID(value)
219+
// Otherwise value is not a CID (or an incompatible version of it) in
220+
// which case we return `null`.
221+
} else {
222+
return null
223+
}
224+
}
225+
226+
static isCID (value) {
227+
deprecate(/^0\.0/, IS_CID_DEPRECATION)
228+
return !!(value && value[cidSymbol])
229+
}
149230
}
150231

151-
const _CID = withIs(CID, {
152-
className: 'CID',
153-
symbolName: '@ipld/js-cid/CID'
154-
})
155-
return _CID
232+
return CID
156233
}

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@
100100
"base-x": "^3.0.8",
101101
"buffer": "^5.6.0",
102102
"cids": "^0.8.3",
103-
"class-is": "^1.1.0",
104103
"varint": "^5.0.0"
105104
},
106105
"directories": {
@@ -114,4 +113,4 @@
114113
"url": "https://github.com/multiformats/js-multiformats/issues"
115114
},
116115
"homepage": "https://github.com/multiformats/js-multiformats#readme"
117-
}
116+
}

test/test-cid.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,57 @@ describe('CID', () => {
332332
const cid = new CID(1, 112, hash)
333333
assert.ok(OLDCID.isCID(cid))
334334
})
335+
336+
test('asCID', () => {
337+
class IncompatibleCID {
338+
constructor (version, code, multihash) {
339+
this.version = version
340+
this.code = code
341+
this.multihash = multihash
342+
this.asCID = this
343+
}
344+
345+
get [Symbol.for('@ipld/js-cid/CID')] () {
346+
return true
347+
}
348+
}
349+
350+
const version = 1
351+
const code = 112
352+
const multihash = hash
353+
354+
const incompatibleCID = new IncompatibleCID(version, code, multihash)
355+
assert.ok(CID.isCID(incompatibleCID))
356+
assert.strictEqual(incompatibleCID.toString(), '[object Object]')
357+
assert.strictEqual(typeof incompatibleCID.toV0, 'undefined')
358+
359+
const cid1 = CID.asCID(incompatibleCID)
360+
assert.ok(cid1 instanceof CID)
361+
assert.strictEqual(cid1.code, code)
362+
assert.strictEqual(cid1.version, version)
363+
assert.strictEqual(cid1.multihash, multihash)
364+
365+
const cid2 = CID.asCID({ version, code, multihash })
366+
assert.strictEqual(cid2, null)
367+
368+
const duckCID = { version, code, multihash }
369+
duckCID.asCID = duckCID
370+
const cid3 = CID.asCID(duckCID)
371+
assert.ok(cid3 instanceof CID)
372+
assert.strictEqual(cid3.code, code)
373+
assert.strictEqual(cid3.version, version)
374+
assert.strictEqual(cid3.multihash, multihash)
375+
376+
const cid4 = CID.asCID(cid3)
377+
assert.strictEqual(cid3, cid4)
378+
379+
const cid5 = CID.asCID(new OLDCID(1, 'raw', Buffer.from(hash)))
380+
assert.ok(cid5 instanceof CID)
381+
assert.strictEqual(cid5.version, 1)
382+
same(cid5.multihash, hash)
383+
assert.strictEqual(cid5.code, 85)
384+
})
385+
335386
test('new CID from old CID', () => {
336387
const cid = new CID(new OLDCID(1, 'raw', Buffer.from(hash)))
337388
same(cid.version, 1)

0 commit comments

Comments
 (0)