diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..ea565dd --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "ipfs" +} diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 0000000..624310c --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,27 @@ +on: + push: + branches: + - master + - main + - default + pull_request: + branches: + - '**' + +name: Typecheck +jobs: + check: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [12.x] + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: npm install + - name: Typecheck + uses: gozala/typescript-error-reporter-action@v1.0.4 diff --git a/package.json b/package.json index 7fd9020..148dd99 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "main": "src/index.js", "scripts": { "lint": "aegir lint", - "build": "aegir build", + "check": "tsc --noEmit --noErrorTruncation", + "build": "npm run build:js && npm run build:types", + "build:js": "aegir build", + "build:types": "tsc --emitDeclarationOnly --declarationDir dist", "test": "aegir test", "test:node": "aegir test --target node", "test:browser": "aegir test --target browser", @@ -15,7 +18,8 @@ "release-major": "aegir release --type major --docs", "coverage": "aegir coverage", "coverage-publish": "aegir coverage --provider coveralls", - "docs": "aegir docs" + "docs": "aegir docs", + "prepare": "npm run build:types" }, "pre-push": [ "lint", @@ -34,17 +38,24 @@ }, "homepage": "https://github.com/ipld/js-ipld-block#readme", "devDependencies": { - "aegir": "^25.0.0", - "uint8arrays": "^1.0.0" + "aegir": "^27.0.0", + "uint8arrays": "^1.0.0", + "typescript": "^4.0.3" }, "dependencies": { - "cids": "^1.0.0", - "class-is": "^1.1.0" + "cids": "^1.0.0" }, "engines": { "node": ">=6.0.0", "npm": ">=3.0.0" }, + "typesVersions": { + "*": { + "*": [ + "dist/*" + ] + } + }, "contributors": [ "David Dias ", "Volker Mische ", diff --git a/src/index.js b/src/index.js index 07e1cbd..5d10c5c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,10 @@ 'use strict' const CID = require('cids') -const withIs = require('class-is') + +const { version } = require('../package.json') +const blockSymbol = Symbol.for('@ipld/js-ipld-block/block') +const readonly = { writable: false, configurable: false, enumerable: true } /** * Represents an immutable block of data that is uniquely referenced with a cid. @@ -9,9 +12,8 @@ const withIs = require('class-is') * @example * const block = new Block(Uint8Array.from([0, 1, 2, 3]), new CID('...')) */ -module.exports = class Block { +class Block { /** - * @constructor * @param {Uint8Array} data - The data to be stored in the block as a Uint8Array. * @param {CID} cid - The cid of the data */ @@ -24,46 +26,77 @@ module.exports = class Block { throw new Error('second argument must be a CID') } - this._data = data - this._cid = cid + this.data = data + this.cid = cid + + Object.defineProperties(this, { + data: readonly, + cid: readonly + }) } /** * The data of this block. * + * @deprecated * @type {Uint8Array} */ - get data () { - return this._data - } - - set data (val) { - throw new Error('Tried to change an immutable block') + get _data () { + deprecateData() + return this.data } /** * The cid of the data this block represents. * + * @deprecated * @type {CID} */ - get cid () { - return this._cid + get _cid () { + deprecateCID() + return this.cid } - set cid (val) { - throw new Error('Tried to change an immutable block') + get [Symbol.toStringTag] () { + return 'Block' + } + + get [blockSymbol] () { + return true } - // eslint-disable-next-line valid-jsdoc /** * Check if the given value is a Block. + * + * @param {any} other * @returns {other is Block} */ - static isBlock (other) { // eslint-disable-line no-unused-vars - // implemented by class-is module + static isBlock (other) { + return Boolean(other && other[blockSymbol]) } } -// to trick the typings engine -// https://github.com/ipld/js-ipld-block/pull/55#discussion_r478845002 -module.exports = withIs(module.exports, { className: 'Block', symbolName: '@ipld/js-ipld-block/block' }) +/** + * @param {RegExp} range + * @param {string} message + * @returns {() => void} + */ +const deprecate = (range, message) => { + let warned = false + return () => { + if (range.test(version)) { + if (!warned) { + warned = true + // eslint-disable-next-line no-console + console.warn(message) + } + } else { + throw new Error(message) + } + } +} + +const deprecateCID = deprecate(/^0\.10/, 'block._cid is deprecated and will be removed in the next major release. Use block.cid instead') +const deprecateData = deprecate(/^0\.10/, 'block._data is deprecated and will be removed in the next major release. Use block.data instead') + +module.exports = Block diff --git a/test/index.spec.js b/test/index.spec.js index 15e0dee..dbdfd45 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -23,9 +23,18 @@ describe('block', () => { }) it('create', () => { - const b = new Block(uint8ArrayFromString('hello'), new CID('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n')) + const cid = new CID('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') + const data = uint8ArrayFromString('hello') + const b = new Block(data, cid) expect(Block.isBlock(b)).to.eql(true) + expect(b.toString()).to.eql('[object Block]') + expect(b.data).to.eql(data) + expect(b.cid).to.eql(cid) + expect(b._data).to.eql(data) + expect(b._data).to.eql(data) + expect(b._cid).to.eql(cid) + expect(b._cid).to.eql(cid) }) it('block stays immutable', () => { @@ -34,13 +43,13 @@ describe('block', () => { expect( () => { b.data = 'fail' } ).to.throw( - /immutable/ + /read.only/ ) expect( () => { b.cid = 'fail' } ).to.throw( - /immutable/ + /read.only/ ) }) }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4ce54b0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": false, + "noImplicitAny": true, + "noImplicitThis": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strictFunctionTypes": false, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "strictBindCallApply": true, + "strict": true, + "alwaysStrict": true, + "esModuleInterop": true, + "target": "ES2018", + "moduleResolution": "node", + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "skipLibCheck": true, + "stripInternal": true, + "resolveJsonModule": true, + "paths": { + "multiformats": [ + "src" + ] + }, + "baseUrl": "." + }, + "include": [ + "src" + ], + "exclude": [ + "vendor", + "node_modules" + ], + "compileOnSave": false +}