From f712112109f206aaf3432b6de8f19ebfb172173d Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 30 Sep 2016 16:43:49 +0100 Subject: [PATCH 1/7] chore: update deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e603238..b55fd1d 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "homepage": "https://github.com/ipfs/js-ipfs-block-service#readme", "devDependencies": { - "aegir": "^8.0.0", + "aegir": "^8.1.2", "buffer-loader": "0.0.1", "chai": "^3.5.0", "fs-pull-blob-store": "^0.3.0", @@ -60,4 +60,4 @@ "Richard Littauer ", "Stephen Whitmore " ] -} \ No newline at end of file +} From bf2287a35226a86efec8b0db070ad30a572d0c3e Mon Sep 17 00:00:00 2001 From: David Dias Date: Sun, 2 Oct 2016 08:05:56 +0100 Subject: [PATCH 2/7] feat: remove extension support, support new ipfs-block and ipfs-repo --- package.json | 2 +- src/index.js | 37 ++++++++-------- test/block-service-test.js | 90 +++++++++++++++++++------------------- 3 files changed, 64 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index b55fd1d..89305e4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "ipfs-block-service", "version": "0.5.0", "description": "JavaScript Implementation of BlockService", - "main": "lib/index.js", + "main": "src/index.js", "jsnext:main": "src/index.js", "scripts": { "lint": "aegir-lint", diff --git a/src/index.js b/src/index.js index 5ee06b9..ad01b03 100644 --- a/src/index.js +++ b/src/index.js @@ -31,7 +31,9 @@ module.exports = class BlockService { } pull( - pull.values([block]), + pull.values([ + block + ]), this.putStream(), pull.onEnd(callback) ) @@ -42,44 +44,41 @@ module.exports = class BlockService { return this._bitswap.putStream() } - return this._repo.blockstore.putStream() + return pull( + pull.map((block) => { + return { data: block.data, key: block.key() } + }), + this._repo.blockstore.putStream() + ) } - get (key, extension, callback) { - if (typeof extension === 'function') { - callback = extension - extension = undefined - } - + get (key, callback) { pull( - this.getStream(key, extension), + this.getStream(key), pull.collect((err, result) => { - if (err) return callback(err) + if (err) { + return callback(err) + } callback(null, result[0]) }) ) } - getStream (key, extension) { + getStream (key) { if (this.isOnline()) { return this._bitswap.getStream(key) } - return this._repo.blockstore.getStream(key, extension) + return this._repo.blockstore.getStream(key) } - delete (keys, extension, callback) { - if (typeof extension === 'function') { - callback = extension - extension = undefined - } - + delete (keys, callback) { if (!Array.isArray(keys)) { keys = [keys] } parallelLimit(keys.map((key) => (next) => { - this._repo.blockstore.delete(key, extension, next) + this._repo.blockstore.delete(key, next) }), 100, callback) } } diff --git a/test/block-service-test.js b/test/block-service-test.js index 1b3f2f9..01bde97 100644 --- a/test/block-service-test.js +++ b/test/block-service-test.js @@ -23,22 +23,11 @@ module.exports = (repo) => { series([ (cb) => bs.put(b, cb), - (cb) => bs.get(b.key, (err, block) => { - if (err) return cb(err) - expect(b).to.be.eql(block) - cb() - }) - ], done) - }) - - it('store and get a block, with custom extension', (done) => { - const b = new Block('A random data block 2', 'ext') - - series([ - (cb) => bs.put(b, cb), - (cb) => bs.get(b.key, 'ext', (err, block) => { - if (err) return cb(err) - expect(b).to.be.eql(block) + (cb) => bs.get(b.key(), (err, block) => { + if (err) { + return cb(err) + } + expect(b.key()).to.be.eql(block.key()) cb() }) ], done) @@ -46,7 +35,7 @@ module.exports = (repo) => { it('get a non existent block', (done) => { const b = new Block('Not stored') - bs.get(b.key, (err, block) => { + bs.get(b.key(), (err, block) => { expect(err).to.exist done() }) @@ -58,7 +47,11 @@ module.exports = (repo) => { const b3 = new Block('3') pull( - pull.values([b1, b2, b3]), + pull.values([ + b1, + b2, + b3 + ]), bs.putStream(), pull.collect((err, meta) => { expect(err).to.not.exist @@ -81,7 +74,11 @@ module.exports = (repo) => { const b3 = new Block('3') pull( - pull.values([b1, b2, b3]), + pull.values([ + b1, + b2, + b3 + ]), bs.putStream(), pull.onEnd((err) => { expect(err).to.not.exist @@ -91,13 +88,22 @@ module.exports = (repo) => { function getAndAssert () { pull( - pull.values([b1.key, b2.key, b3.key]), - pull.map((key) => bs.getStream(key)), + pull.values([ + b1.key(), + b2.key(), + b3.key() + ]), + pull.map((key) => { + return bs.getStream(key) + }), pull.flatten(), pull.collect((err, blocks) => { expect(err).to.not.exist + const bPutKeys = blocks.map((b) => { + return b.key() + }) - expect(blocks).to.be.eql([b1, b2, b3]) + expect(bPutKeys).to.be.eql([b1.key(), b2.key(), b3.key()]) done() }) ) @@ -108,9 +114,9 @@ module.exports = (repo) => { const b = new Block('Will not live that much') bs.put(b, (err) => { expect(err).to.not.exist - bs.delete(b.key, (err) => { + bs.delete(b.key(), (err) => { expect(err).to.not.exist - bs.get(b.key, (err, block) => { + bs.get(b.key(), (err, block) => { expect(err).to.exist done() }) @@ -125,23 +131,9 @@ module.exports = (repo) => { }) }) - it('delete a block, with custom extension', (done) => { - const b = new Block('Will not live that much', 'ext') - bs.put(b, (err) => { - expect(err).to.not.exist - bs.delete(b.key, 'ext', (err) => { - expect(err).to.not.exist - bs.get(b.key, 'ext', (err, block) => { - expect(err).to.exist - done() - }) - }) - }) - }) - it('delete a non existent block', (done) => { const b = new Block('I do not exist') - bs.delete(b.key, (err) => { + bs.delete(b.key(), (err) => { expect(err).to.not.exist done() }) @@ -152,7 +144,11 @@ module.exports = (repo) => { const b2 = new Block('2') const b3 = new Block('3') - bs.delete([b1, b2, b3], 'data', (err) => { + bs.delete([ + b1.key(), + b2.key(), + b3.key() + ], (err) => { expect(err).to.not.exist done() }) @@ -173,12 +169,16 @@ module.exports = (repo) => { pull( pull.values(blocks), - pull.map((b) => b.key), - pull.map((key) => bs.getStream(key)), + pull.map((block) => { + return block.key() + }), + pull.map((key) => { + return bs.getStream(key) + }), pull.flatten(), - pull.collect((err, res) => { + pull.collect((err, retrievedBlocks) => { expect(err).to.not.exist - expect(res).to.be.eql(blocks) + expect(retrievedBlocks.length).to.be.eql(blocks.length) done() }) ) @@ -195,7 +195,7 @@ module.exports = (repo) => { }) }) - describe('online', () => { + describe.skip('online', () => { beforeEach(() => { bs = new BlockService(repo) }) From 58b7c9874d8d96c0395041822619250269b8c5e1 Mon Sep 17 00:00:00 2001 From: David Dias Date: Sun, 2 Oct 2016 19:58:18 +0100 Subject: [PATCH 3/7] feat: cids on get and delete --- src/index.js | 20 +++++++++--------- test/block-service-test.js | 42 ++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/index.js b/src/index.js index ad01b03..a725f9c 100644 --- a/src/index.js +++ b/src/index.js @@ -52,9 +52,9 @@ module.exports = class BlockService { ) } - get (key, callback) { + get (cid, callback) { pull( - this.getStream(key), + this.getStream(cid), pull.collect((err, result) => { if (err) { return callback(err) @@ -64,21 +64,21 @@ module.exports = class BlockService { ) } - getStream (key) { + getStream (cid) { if (this.isOnline()) { - return this._bitswap.getStream(key) + return this._bitswap.getStream(cid) } - return this._repo.blockstore.getStream(key) + return this._repo.blockstore.getStream(cid.multihash) } - delete (keys, callback) { - if (!Array.isArray(keys)) { - keys = [keys] + delete (cids, callback) { + if (!Array.isArray(cids)) { + cids = [cids] } - parallelLimit(keys.map((key) => (next) => { - this._repo.blockstore.delete(key, next) + parallelLimit(cids.map((cid) => (next) => { + this._repo.blockstore.delete(cid.multihash, next) }), 100, callback) } } diff --git a/test/block-service-test.js b/test/block-service-test.js index 01bde97..b332acf 100644 --- a/test/block-service-test.js +++ b/test/block-service-test.js @@ -6,6 +6,7 @@ const Block = require('ipfs-block') const pull = require('pull-stream') const _ = require('lodash') const series = require('run-series') +const CID = require('cids') const BlockService = require('../src') @@ -20,10 +21,11 @@ module.exports = (repo) => { describe('offline', () => { it('store and get a block', (done) => { const b = new Block('A random data block') + const cid = new CID(b.key()) series([ (cb) => bs.put(b, cb), - (cb) => bs.get(b.key(), (err, block) => { + (cb) => bs.get(cid, (err, block) => { if (err) { return cb(err) } @@ -35,7 +37,9 @@ module.exports = (repo) => { it('get a non existent block', (done) => { const b = new Block('Not stored') - bs.get(b.key(), (err, block) => { + const cid = new CID(b.key()) + + bs.get(cid, (err, block) => { expect(err).to.exist done() }) @@ -61,13 +65,6 @@ module.exports = (repo) => { ) }) - it('get: bad invocation', (done) => { - bs.get(null, (err) => { - expect(err).to.be.an('error') - done() - }) - }) - it('get many blocks', (done) => { const b1 = new Block('1') const b2 = new Block('2') @@ -94,7 +91,8 @@ module.exports = (repo) => { b3.key() ]), pull.map((key) => { - return bs.getStream(key) + const cid = new CID(key) + return bs.getStream(cid) }), pull.flatten(), pull.collect((err, blocks) => { @@ -114,9 +112,10 @@ module.exports = (repo) => { const b = new Block('Will not live that much') bs.put(b, (err) => { expect(err).to.not.exist - bs.delete(b.key(), (err) => { + const cid = new CID(b.key()) + bs.delete(cid, (err) => { expect(err).to.not.exist - bs.get(b.key(), (err, block) => { + bs.get(cid, (err, block) => { expect(err).to.exist done() }) @@ -124,16 +123,10 @@ module.exports = (repo) => { }) }) - it('delete: bad invocation', (done) => { - bs.delete(null, (err) => { - expect(err).to.be.an('error') - done() - }) - }) - it('delete a non existent block', (done) => { const b = new Block('I do not exist') - bs.delete(b.key(), (err) => { + const cid = new CID(b.key()) + bs.delete(cid, (err) => { expect(err).to.not.exist done() }) @@ -145,9 +138,9 @@ module.exports = (repo) => { const b3 = new Block('3') bs.delete([ - b1.key(), - b2.key(), - b3.key() + new CID(b1.key()), + new CID(b2.key()), + new CID(b3.key()) ], (err) => { expect(err).to.not.exist done() @@ -173,7 +166,8 @@ module.exports = (repo) => { return block.key() }), pull.map((key) => { - return bs.getStream(key) + const cid = new CID(key) + return bs.getStream(cid) }), pull.flatten(), pull.collect((err, retrievedBlocks) => { From 9e9902973238aff265906795832658e197757e6c Mon Sep 17 00:00:00 2001 From: David Dias Date: Sun, 2 Oct 2016 20:13:07 +0100 Subject: [PATCH 4/7] feat: CID support on put and putStream --- src/index.js | 26 ++++++++++++++++++-------- test/block-service-test.js | 19 +++++++++++-------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/index.js b/src/index.js index a725f9c..28f9369 100644 --- a/src/index.js +++ b/src/index.js @@ -24,15 +24,19 @@ module.exports = class BlockService { return this._bitswap != null } - put (block, callback) { + // Note: we have to pass the CID, so that bitswap can then use it for + // the smart selectors. For now, passing the CID is used so that we know + // the right multihash, this means that now we have multiple hashes official + // support \o/ + put (blockAndCID, callback) { callback = callback || (() => {}) - if (!block) { - return callback(new Error('Missing block')) + if (!blockAndCID) { + return callback(new Error('Missing block and CID')) } pull( pull.values([ - block + blockAndCID ]), this.putStream(), pull.onEnd(callback) @@ -40,15 +44,21 @@ module.exports = class BlockService { } putStream () { + let ps if (this.isOnline()) { - return this._bitswap.putStream() + ps = this._bitswap.putStream() + } else { + ps = this._repo.blockstore.putStream() } return pull( - pull.map((block) => { - return { data: block.data, key: block.key() } + pull.map((blockAndCID) => { + return { + data: blockAndCID.block.data, + key: blockAndCID.cid.multihash + } }), - this._repo.blockstore.putStream() + ps ) } diff --git a/test/block-service-test.js b/test/block-service-test.js index b332acf..a53252d 100644 --- a/test/block-service-test.js +++ b/test/block-service-test.js @@ -24,7 +24,7 @@ module.exports = (repo) => { const cid = new CID(b.key()) series([ - (cb) => bs.put(b, cb), + (cb) => bs.put({ block: b, cid: cid }, cb), (cb) => bs.get(cid, (err, block) => { if (err) { return cb(err) @@ -52,9 +52,9 @@ module.exports = (repo) => { pull( pull.values([ - b1, - b2, - b3 + { block: b1, cid: new CID(b1.key()) }, + { block: b2, cid: new CID(b2.key()) }, + { block: b3, cid: new CID(b3.key()) } ]), bs.putStream(), pull.collect((err, meta) => { @@ -72,9 +72,9 @@ module.exports = (repo) => { pull( pull.values([ - b1, - b2, - b3 + { block: b1, cid: new CID(b1.key()) }, + { block: b2, cid: new CID(b2.key()) }, + { block: b3, cid: new CID(b3.key()) } ]), bs.putStream(), pull.onEnd((err) => { @@ -110,7 +110,7 @@ module.exports = (repo) => { it('delete a block', (done) => { const b = new Block('Will not live that much') - bs.put(b, (err) => { + bs.put({ block: b, cid: new CID(b.key()) }, (err) => { expect(err).to.not.exist const cid = new CID(b.key()) bs.delete(cid, (err) => { @@ -156,6 +156,9 @@ module.exports = (repo) => { pull( pull.values(blocks), + pull.map((block) => { + return { block: block, cid: new CID(block.key()) } + }), bs.putStream(), pull.onEnd((err) => { expect(err).to.not.exist From b35c7fa139a70f1ac292e10151483f261eb76826 Mon Sep 17 00:00:00 2001 From: David Dias Date: Sun, 16 Oct 2016 04:05:27 +0100 Subject: [PATCH 5/7] docs: update documentation --- API.md | 67 --------------------------------------------- README.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 73 deletions(-) delete mode 100644 API.md diff --git a/API.md b/API.md deleted file mode 100644 index 60a7939..0000000 --- a/API.md +++ /dev/null @@ -1,67 +0,0 @@ -# API - -```js -const BlockService = require('ipfs-block-service') -``` - -### `new BlockService(repo)` - -- `repo: Repo` - -Creates a new block service backed by [IPFS Repo][repo] `repo` for storage. - -### `goOnline(bitswap)` - -- `bitswap: Bitswap` - -Add a bitswap instance that communicates with the network to retreive blocks -that are not in the local store. - -If the node is online all requests for blocks first check locally and -afterwards ask the network for the blocks. - -### `goOffline()` - -Remove the bitswap instance and fall back to offline mode. - -### `isOnline()` - -Returns a `Boolean` indicating if the block service is online or not. - -### `put(block, callback)` - -- `block: Block` -- `callback: Function` - -Asynchronously adds a block instance to the underlying repo. - -### `putStream()` - -Returns a through pull-stream, which `Block`s can be written to, and -that emits the meta data about the written block. - -### `get(multihash [, extension], callback)` - -- `multihash: Multihash` -- `extension: String`, defaults to 'data' -- `callback: Function` - -Asynchronously returns the block whose content multihash matches `multihash`. - -### `getStream(multihash [, extension])` - -- `multihash: Multihash` -- `extension: String`, defaults to 'data' - -Returns a source pull-stream, which emits the requested block. - -### `delete(multihashes, [, extension], callback)` - -- `multihashes: Multihash|[]Multihash` -- `extension: String`, defaults to 'data'- `extension: String`, defaults to 'data' -- `callback: Function` - -Deletes all blocks referenced by multihashes. - -[multihash]: https://github.com/multiformats/js-multihash -[repo]: https://github.com/ipfs/specs/tree/master/repo diff --git a/README.md b/README.md index 98cff4a..6129403 100644 --- a/README.md +++ b/README.md @@ -72,14 +72,17 @@ var repo = new IPFSRepo('example', { stores: Store }) // create a block const block = new Block('hello world') console.log(block.data) -console.log(block.key) +console.log(block.key()) // create a service const bs = new BlockService(repo) // add the block, then retrieve it -bs.put(block, function (err) { - bs.get(block.key, function (err, b) { +bs.put({ + block: block, + cid: cid, +}, function (err) { + bs.get(cid, function (err, b) { console.log(block.data.toString() === b.data.toString()) }) }) @@ -118,10 +121,70 @@ the global namespace. ``` -You can find the [API documentation here](API.md) +# API -[ipfs]: https://ipfs.io -[bitswap]: https://github.com/ipfs/specs/tree/master/bitswap +```js +const BlockService = require('ipfs-block-service') +``` + +### `new BlockService(repo)` + +- `repo: Repo` + +Creates a new block service backed by [IPFS Repo][repo] `repo` for storage. + +### `goOnline(bitswap)` + +- `bitswap: Bitswap` + +Add a bitswap instance that communicates with the network to retreive blocks +that are not in the local store. + +If the node is online all requests for blocks first check locally and +afterwards ask the network for the blocks. + +### `goOffline()` + +Remove the bitswap instance and fall back to offline mode. + +### `isOnline()` + +Returns a `Boolean` indicating if the block service is online or not. + +### `put(blockAndCID, callback)` + +- `blockAndCID: { block: block, cid: cid }` +- `callback: Function` + +Asynchronously adds a block instance to the underlying repo. + +### `putStream()` + +Returns a through pull-stream, which `blockAndCID`s can be written to, and +that emits the meta data about the written block. + +### `get(cid [, extension], callback)` + +- `cid: CID` +- `extension: String`, defaults to 'data' +- `callback: Function` + +Asynchronously returns the block whose content multihash matches `multihash`. + +### `getStream(cid [, extension])` + +- `cid: CID` +- `extension: String`, defaults to 'data' + +Returns a source pull-stream, which emits the requested block. + +### `delete(cids, [, extension], callback)` + +- `cids: CID | []CID` +- `extension: String`, defaults to 'data' - `extension: String`, defaults to 'data' +- `callback: Function` + +Deletes all blocks referenced by multihashes. ## Contribute @@ -134,3 +197,9 @@ This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/c ## License [MIT](LICENSE) + +[ipfs]: https://ipfs.io +[bitswap]: https://github.com/ipfs/specs/tree/master/bitswap +[repo]: https://github.com/ipfs/specs/tree/master/repo + + From e92a11848bb560c9f76a69acdf8beb1ca90513b3 Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 21 Oct 2016 07:35:10 +0100 Subject: [PATCH 6/7] feat: bitswap support back again --- src/index.js | 4 +++- test/block-service-test.js | 29 +++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/index.js b/src/index.js index 28f9369..216121d 100644 --- a/src/index.js +++ b/src/index.js @@ -46,6 +46,8 @@ module.exports = class BlockService { putStream () { let ps if (this.isOnline()) { + // NOTE: This will have to change in order for bitswap + // to understand CID ps = this._bitswap.putStream() } else { ps = this._repo.blockstore.putStream() @@ -76,7 +78,7 @@ module.exports = class BlockService { getStream (cid) { if (this.isOnline()) { - return this._bitswap.getStream(cid) + return this._bitswap.getStream(cid.multihash) } return this._repo.blockstore.getStream(cid.multihash) diff --git a/test/block-service-test.js b/test/block-service-test.js index a53252d..0e54eb7 100644 --- a/test/block-service-test.js +++ b/test/block-service-test.js @@ -192,7 +192,7 @@ module.exports = (repo) => { }) }) - describe.skip('online', () => { + describe('online', () => { beforeEach(() => { bs = new BlockService(repo) }) @@ -203,16 +203,20 @@ module.exports = (repo) => { }) it('retrieves a block through bitswap', (done) => { + // returns a block with a value equal to its key const bitswap = { getStream (key) { - return pull.values([new Block(key)]) + return pull.values([ + new Block(key) + ]) } } + bs.goOnline(bitswap) - bs.get('secret', (err, res) => { + bs.get('secret', (err, block) => { expect(err).to.not.exist - expect(res).to.be.eql(new Block('secret')) + expect(block.data).to.be.eql(new Block('secret').data) done() }) }) @@ -224,11 +228,18 @@ module.exports = (repo) => { } } bs.goOnline(bitswap) - bs.put(new Block('secret sauce'), done) + + const block = new Block('secret sauce') + + bs.put({ + block: block, + cid: new CID(block.key('sha2-256')) + }, done) }) it('getStream through bitswap', (done) => { const b = new Block('secret sauce 1') + const cid = new CID(b.key('sha2-256')) const bitswap = { getStream () { @@ -237,11 +248,13 @@ module.exports = (repo) => { } bs.goOnline(bitswap) + pull( - bs.getStream(b.key), - pull.collect((err, res) => { + bs.getStream(cid), + pull.collect((err, blocks) => { expect(err).to.not.exist - expect(res).to.be.eql([b]) + expect(blocks[0].data).to.be.eql(b.data) + expect(blocks[0].key('sha2-256')).to.be.eql(cid.multihash) done() }) ) From 72876215b7515fc03339951660173276726937e8 Mon Sep 17 00:00:00 2001 From: David Dias Date: Sat, 22 Oct 2016 01:51:11 +0100 Subject: [PATCH 7/7] fix: bitswap mock test --- package.json | 7 ++++--- test/block-service-test.js | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 89305e4..b41bf47 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "ipfs-block-service", "version": "0.5.0", "description": "JavaScript Implementation of BlockService", - "main": "src/index.js", + "main": "lib/index.js", "jsnext:main": "src/index.js", "scripts": { "lint": "aegir-lint", @@ -42,8 +42,8 @@ "chai": "^3.5.0", "fs-pull-blob-store": "^0.3.0", "idb-pull-blob-store": "^0.5.1", - "ipfs-block": "^0.3.0", - "ipfs-repo": "^0.9.0", + "ipfs-block": "^0.4.0", + "ipfs-repo": "^0.10.0", "lodash": "^4.15.0", "ncp": "^2.0.0", "pre-commit": "^1.1.3", @@ -51,6 +51,7 @@ "run-series": "^1.1.4" }, "dependencies": { + "cids": "^0.2.0", "pull-stream": "^3.4.5", "run-parallel-limit": "^1.0.3" }, diff --git a/test/block-service-test.js b/test/block-service-test.js index 0e54eb7..7e877a4 100644 --- a/test/block-service-test.js +++ b/test/block-service-test.js @@ -207,14 +207,17 @@ module.exports = (repo) => { const bitswap = { getStream (key) { return pull.values([ - new Block(key) + new Block('secret') ]) } } bs.goOnline(bitswap) - bs.get('secret', (err, block) => { + const block = new Block('secret') + const cid = new CID(block.key('sha2-256')) + + bs.get(cid, (err, block) => { expect(err).to.not.exist expect(block.data).to.be.eql(new Block('secret').data) done()