diff --git a/cid.js b/cid.js index 0cd80664..fc60fa42 100644 --- a/cid.js +++ b/cid.js @@ -1,5 +1,4 @@ import * as bytes from 'multiformats/bytes.js' -import withIs from 'class-is' const readonly = (object, key, value) => { Object.defineProperty(object, key, { @@ -9,6 +8,36 @@ const readonly = (object, key, value) => { }) } +// ESM does not support importing package.json where this version info +// should come from. To workaround it version is copied here. +const version = '0.0.0-dev' +// Start throwing exceptions on major version bump +const deprecate = (range, message) => { + if (range.test(version)) { + console.warn(message) + /* c8 ignore next 3 */ + } else { + throw new Error(message) + } +} + +const IS_CID_DEPRECATION = +`CID.isCID(v) is deprecated and will be removed in the next major release. +Following code pattern: + +if (CID.isCID(value)) { + doSomethingWithCID(value) +} + +Is replaced with: + +const cid = CID.asCID(value) +if (cid) { + // Make sure to use cid instead of value + doSomethingWithCID(cid) +} +` + export default multiformats => { const { multibase, varint, multihash } = multiformats const parse = buff => { @@ -22,6 +51,9 @@ export default multiformats => { ...multihash ]) } + + const cidSymbol = Symbol.for('@ipld/js-cid/CID') + class CID { constructor (cid, ...args) { Object.defineProperty(this, '_baseCache', { @@ -30,7 +62,7 @@ export default multiformats => { enumerable: false }) readonly(this, 'asCID', this) - if (_CID.isCID(cid)) { + if (cid != null && cid[cidSymbol] === true) { readonly(this, 'version', cid.version) readonly(this, 'multihash', bytes.coerce(cid.multihash)) readonly(this, 'buffer', bytes.coerce(cid.buffer)) @@ -104,11 +136,11 @@ export default multiformats => { throw new Error('Cannot convert non sha2-256 multihash CID to CIDv0') } - return new _CID(0, this.code, this.multihash) + return new CID(0, this.code, this.multihash) } toV1 () { - return new _CID(1, this.code, this.multihash) + return new CID(1, this.code, this.multihash) } get toBaseEncodedString () { @@ -146,11 +178,56 @@ export default multiformats => { this.version === other.version && bytes.equals(this.multihash, other.multihash) } + + get [Symbol.toStringTag] () { + return 'CID' + } + + get [cidSymbol] () { + return true + } + + /** + * Takes any input `value` and returns a `CID` instance if it was + * a `CID` otherwise returns `null`. If `value` is instanceof `CID` + * it will return value back. If `value` is not instance of this CID + * class, but is compatible CID it will return new instance of this + * `CID` class. Otherwise returs null. + * + * This allows two different incompatible versions of CID library to + * co-exist and interop as long as binary interface is compatible. + * @param {any} value + * @returns {CID|null} + */ + static asCID (value) { + // If value is instance of CID then we're all set. + if (value instanceof CID) { + return value + // If value isn't instance of this CID class but `this.asCID === this` is + // true it is CID instance coming from a different implemnetation (diff + // version or duplicate). In that case we rebase it to this `CID` + // implemnetation so caller is guaranteed to get instance with expected + // API. + } else if (value != null && value.asCID === value) { + const { version, code, multihash } = value + return new CID(version, code, multihash) + // If value is a CID from older implementation that used to be tagged via + // symbol we still rebase it to the this `CID` implementation by + // delegating that to a constructor. + } else if (value != null && value[cidSymbol] === true) { + return new CID(value) + // Otherwise value is not a CID (or an incompatible version of it) in + // which case we return `null`. + } else { + return null + } + } + + static isCID (value) { + deprecate(/^0\.0/, IS_CID_DEPRECATION) + return !!(value && value[cidSymbol]) + } } - const _CID = withIs(CID, { - className: 'CID', - symbolName: '@ipld/js-cid/CID' - }) - return _CID + return CID } diff --git a/package.json b/package.json index deb6586e..1bef4ae8 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,6 @@ "base-x": "^3.0.8", "buffer": "^5.6.0", "cids": "^0.8.3", - "class-is": "^1.1.0", "varint": "^5.0.0" }, "directories": { @@ -114,4 +113,4 @@ "url": "https://github.com/multiformats/js-multiformats/issues" }, "homepage": "https://github.com/multiformats/js-multiformats#readme" -} +} \ No newline at end of file diff --git a/test/test-cid.js b/test/test-cid.js index f33bdaaa..13aa6970 100644 --- a/test/test-cid.js +++ b/test/test-cid.js @@ -332,6 +332,57 @@ describe('CID', () => { const cid = new CID(1, 112, hash) assert.ok(OLDCID.isCID(cid)) }) + + test('asCID', () => { + class IncompatibleCID { + constructor (version, code, multihash) { + this.version = version + this.code = code + this.multihash = multihash + this.asCID = this + } + + get [Symbol.for('@ipld/js-cid/CID')] () { + return true + } + } + + const version = 1 + const code = 112 + const multihash = hash + + const incompatibleCID = new IncompatibleCID(version, code, multihash) + assert.ok(CID.isCID(incompatibleCID)) + assert.strictEqual(incompatibleCID.toString(), '[object Object]') + assert.strictEqual(typeof incompatibleCID.toV0, 'undefined') + + const cid1 = CID.asCID(incompatibleCID) + assert.ok(cid1 instanceof CID) + assert.strictEqual(cid1.code, code) + assert.strictEqual(cid1.version, version) + assert.strictEqual(cid1.multihash, multihash) + + const cid2 = CID.asCID({ version, code, multihash }) + assert.strictEqual(cid2, null) + + const duckCID = { version, code, multihash } + duckCID.asCID = duckCID + const cid3 = CID.asCID(duckCID) + assert.ok(cid3 instanceof CID) + assert.strictEqual(cid3.code, code) + assert.strictEqual(cid3.version, version) + assert.strictEqual(cid3.multihash, multihash) + + const cid4 = CID.asCID(cid3) + assert.strictEqual(cid3, cid4) + + const cid5 = CID.asCID(new OLDCID(1, 'raw', Buffer.from(hash))) + assert.ok(cid5 instanceof CID) + assert.strictEqual(cid5.version, 1) + same(cid5.multihash, hash) + assert.strictEqual(cid5.code, 85) + }) + test('new CID from old CID', () => { const cid = new CID(new OLDCID(1, 'raw', Buffer.from(hash))) same(cid.version, 1)