Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

Commit

Permalink
feat: add single item functions
Browse files Browse the repository at this point in the history
BREAKING CHANGE: put/get/remove functions are renamed

This commit introduces single item functions which are called `put()`/`get()`,`remove()`.

In order to put, get or remove multiple items you need to call
`putMany()`,`getMany()`/`removeMany()` now.
  • Loading branch information
vmx committed Mar 21, 2019
1 parent 43176ca commit 945fc61
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 147 deletions.
96 changes: 74 additions & 22 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,42 +127,94 @@ class IPLDResolver {
return extendIterator(generator())
}

/**
* Get a node by CID.
*
* @param {CID} cid - The CID of the IPLD Node that should be retrieved.
* @returns {Promise.<Object>} - Returns a Promise with the IPLD Node that correspond to the given `cid`.
*/
async get (cid) {
const block = await promisify(this.bs.get.bind(this.bs))(cid)
const format = await this._getFormat(block.cid.codec)
const node = await promisify(format.util.deserialize)(block.data)

return node
}

/**
* Get multiple nodes back from an array of CIDs.
*
* @param {Iterable.<CID>} cids - The CIDs of the IPLD Nodes that should be retrieved.
* @returns {Iterable.<Promise.<Object>>} - Returns an async iterator with the IPLD Nodes that correspond to the given `cids`.
*/
get (cids) {
getMany (cids) {
if (!typical.isIterable(cids) || typical.isString(cids) ||
Buffer.isBuffer(cids)) {
throw new Error('`cids` must be an iterable of CIDs')
}

const generator = async function * () {
for await (const cid of cids) {
const block = await promisify(this.bs.get.bind(this.bs))(cid)
const format = await this._getFormat(block.cid.codec)
const node = await promisify(format.util.deserialize)(block.data)
yield node
yield this.get(cid)
}
}.bind(this)

return extendIterator(generator())
}

/**
* Stores the given IPLD Node of a recognized IPLD Format.
*
* @param {Object} node - The deserialized IPLD node that should be inserted.
* @param {number} format - The multicodec of the format that IPLD Node should be encoded in.
* @param {Object} [userOptions] - Options is an object with the following properties.
* @param {number} [userOtions.hashAlg=hash algorithm of the given multicodec] - The hashing algorithm that is used to calculate the CID.
* @param {number} [userOptions.cidVersion=1] - The CID version to use.
* @param {boolean} [userOptions.onlyHash=false] - If true the serialized form of the IPLD Node will not be passed to the underlying block store.
* @returns {Promise.<CID>} - Returns the CID of the serialized IPLD Nodes.
*/
async put (node, format, userOptions) {
if (format === undefined) {
throw new Error('`put` requires a format')
}
if (typeof format !== 'number') {
throw new Error('`format` parameter must be number (multicodec)')
}

const formatImpl = await this._getFormat(format)
const defaultOptions = {
hashAlg: formatImpl.defaultHashAlg,
cidVersion: 1,
onlyHash: false
}
const options = mergeOptions(defaultOptions, userOptions)

const cidOptions = {
version: options.cidVersion,
hashAlg: options.hashAlg,
onlyHash: options.onlyHash
}
const cid = await promisify(formatImpl.util.cid)(node, cidOptions)

if (!options.onlyHash) {
await this._store(cid, node)
}

return cid
}

/**
* Stores the given IPLD Nodes of a recognized IPLD Format.
*
* @param {Iterable.<Object>} nodes - Deserialized IPLD nodes that should be inserted.
* @param {number} format - The multicodec of the format that IPLD Node should be encoded in.
* @param {Object} [userOptions] - Options are applied to any of the `nodes` and is an object with the following properties.
* @param {number} [userOtions.hashAlg=hash algorithm of the given multicodec] - The hashing algorithm that is used to calculate the CID.
* @param {number} [userOptions.cidVersion=1]`- The CID version to use.
* @param {number} [userOptions.cidVersion=1] - The CID version to use.
* @param {boolean} [userOptions.onlyHash=false] - If true the serialized form of the IPLD Node will not be passed to the underlying block store.
* @returns {Iterable.<Promise.<CID>>} - Returns an async iterator with the CIDs of the serialized IPLD Nodes.
*/
put (nodes, format, userOptions) {
putMany (nodes, format, userOptions) {
if (!typical.isIterable(nodes) || typical.isString(nodes) ||
Buffer.isBuffer(nodes)) {
throw new Error('`nodes` must be an iterable')
Expand Down Expand Up @@ -192,41 +244,41 @@ class IPLDResolver {
options = mergeOptions(defaultOptions, userOptions)
}

const cidOptions = {
version: options.cidVersion,
hashAlg: options.hashAlg,
onlyHash: options.onlyHash
}
const cid = await promisify(formatImpl.util.cid)(node, cidOptions)
if (!options.onlyHash) {
await this._store(cid, node)
}
yield cid
yield this.put(node, format, options)
}
}.bind(this)

return extendIterator(generator())
}

/**
* Remove an IPLD Node by the given CID.
*
* @param {CID} cid - The CID of the IPLD Node that should be removed.
* @return {Promise.<CID>} The CID of the removed IPLD Node.
*/
async remove (cid) {
return promisify(this.bs.delete.bind(this.bs))(cid)
}

/**
* Remove IPLD Nodes by the given CIDs.
*
* Throws an error if any of the Blocks can’t be removed. This operation is
* *not* atomic, some Blocks might have already been removed.
*
* @param {Iterable.<CID>} cids - The CIDs of the IPLD Nodes that should be removed
* @return {void}
* @param {Iterable.<CID>} cids - The CIDs of the IPLD Nodes that should be removed.
* @return {Iterable.<Promise.<CID>>} Returns an async iterator with the CIDs of the removed IPLD Nodes.
*/
remove (cids) {
removeMany (cids) {
if (!typical.isIterable(cids) || typical.isString(cids) ||
Buffer.isBuffer(cids)) {
throw new Error('`cids` must be an iterable of CIDs')
}

const generator = async function * () {
for await (const cid of cids) {
await promisify(this.bs.delete.bind(this.bs))(cid)
yield cid
yield this.remove(cid)
}
}.bind(this)

Expand Down
20 changes: 17 additions & 3 deletions test/basics.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,29 @@ module.exports = (repo) => {
const bs = new BlockService(repo)
const r = new IPLDResolver({ blockService: bs })
// choosing a format that is not supported
const result = r.put([null], multicodec.BLAKE2B_8)
await expect(r.put(null, multicodec.BLAKE2B_8)).to.be.rejectedWith(
'No resolver found for codec "blake2b-8"')
})

it('put - errors if no format is provided', async () => {
const bs = new BlockService(repo)
const r = new IPLDResolver({ blockService: bs })
await expect(r.put(null)).to.be.rejectedWith('`put` requires a format')
})

it('putMany - errors on unknown resolver', async () => {
const bs = new BlockService(repo)
const r = new IPLDResolver({ blockService: bs })
// choosing a format that is not supported
const result = r.putMany([null], multicodec.BLAKE2B_8)
await expect(result.next()).to.be.rejectedWith(
'No resolver found for codec "blake2b-8"')
})

it('put - errors if no format is provided', () => {
it('putMany - errors if no format is provided', () => {
const bs = new BlockService(repo)
const r = new IPLDResolver({ blockService: bs })
expect(() => r.put([null])).to.be.throw('`put` requires a format')
expect(() => r.putMany([null])).to.be.throw('`put` requires a format')
})

it('tree - errors on unknown resolver', async () => {
Expand Down
3 changes: 1 addition & 2 deletions test/format-support.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ module.exports = (repo) => {

data = { now: Date.now() }

const result = resolver.put([data], multicodec.DAG_CBOR)
cid = await result.last()
cid = await resolver.put(data, multicodec.DAG_CBOR)
})

describe('Dynamic format loading', () => {
Expand Down
19 changes: 9 additions & 10 deletions test/ipld-all.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ describe('IPLD Resolver for dag-cbor + dag-pb', () => {
{ node: nodePb, format: multicodec.DAG_PB, cidVersion: 0 },
{ node: nodeCbor, format: multicodec.DAG_CBOR, cidVersion: 1 }
], (nac, cb) => {
resolver.put([nac.node], nac.format, {
resolver.put(nac.node, nac.format, {
cidVersion: nac.cidVersion
}).first().then(
}).then(
() => cb(null),
(error) => cb(error)
)
Expand All @@ -84,12 +84,11 @@ describe('IPLD Resolver for dag-cbor + dag-pb', () => {
waterfall([
(cb) => dagPB.DAGNode.create(Buffer.from('Some data here'), cb),
(node, cb) => {
const result = resolver.put([node], multicodec.DAG_PB, {
resolver.put(node, multicodec.DAG_PB, {
onlyHash: true,
cidVersion: 1,
hashAlg: multicodec.SHA2_256
})
result.first().then(
}).then(
(cid) => cb(null, cid),
(error) => cb(error)
)
Expand All @@ -107,21 +106,21 @@ describe('IPLD Resolver for dag-cbor + dag-pb', () => {

describe('get', () => {
it('should return nodes correctly', async () => {
const result = resolver.get([cidCbor, cidPb])
const result = resolver.getMany([cidCbor, cidPb])
const [node1, node2] = await result.all()
expect(node1).to.eql(nodeCbor)
expect(node2).to.eql(nodePb)
})

it('should return nodes in input order', async () => {
const result = resolver.get([cidPb, cidCbor])
const result = resolver.getMany([cidPb, cidCbor])
const [node1, node2] = await result.all()
expect(node1).to.eql(nodePb)
expect(node2).to.eql(nodeCbor)
})

it('should return error on invalid CID', async () => {
const result = resolver.get([cidCbor, 'invalidcid'])
const result = resolver.getMany([cidCbor, 'invalidcid'])
// First node is valid
await result.next()
// Second one is not
Expand All @@ -131,15 +130,15 @@ describe('IPLD Resolver for dag-cbor + dag-pb', () => {
it('should return error on non-existent CID', async () => {
const nonExistentCid = new CID(
'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')
const result = resolver.get([cidCbor, nonExistentCid])
const result = resolver.getMany([cidCbor, nonExistentCid])
// First node is valid
await result.next()
// Second one is not
await expect(result.next()).to.be.rejectedWith('Not Found')
})

it('should return error on invalid input', () => {
expect(() => resolver.get('astring')).to.throw(
expect(() => resolver.getMany('astring')).to.throw(
'`cids` must be an iterable of CIDs')
})
})
Expand Down
27 changes: 9 additions & 18 deletions test/ipld-bitcoin.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ module.exports = (repo) => {

async function store () {
const nodes = [node1, node2, node3]
const result = resolver.put(nodes, multicodec.BITCOIN_BLOCK)
const result = resolver.putMany(nodes, multicodec.BITCOIN_BLOCK)
;[cid1, cid2, cid3] = await result.all()

done()
Expand All @@ -93,8 +93,7 @@ module.exports = (repo) => {

describe('public api', () => {
it('resolver.put with format', async () => {
const result = resolver.put([node1], multicodec.BITCOIN_BLOCK)
const cid = await result.first()
const cid = await resolver.put(node1, multicodec.BITCOIN_BLOCK)
expect(cid.version).to.equal(1)
expect(cid.codec).to.equal('bitcoin-block')
expect(cid.multihash).to.exist()
Expand All @@ -103,10 +102,9 @@ module.exports = (repo) => {
})

it('resolver.put with format + hashAlg', async () => {
const result = resolver.put([node1], multicodec.BITCOIN_BLOCK, {
const cid = await resolver.put(node1, multicodec.BITCOIN_BLOCK, {
hashAlg: multicodec.SHA3_512
})
const cid = await result.first()
expect(cid.version).to.equal(1)
expect(cid.codec).to.equal('bitcoin-block')
expect(cid.multihash).to.exist()
Expand Down Expand Up @@ -147,28 +145,21 @@ module.exports = (repo) => {
})

it('resolver.get round-trip', async () => {
const resultPut = resolver.put([node1], multicodec.BITCOIN_BLOCK)
const cid = await resultPut.first()
const resultGet = resolver.get([cid])
const node = await resultGet.first()
const cid = await resolver.put(node1, multicodec.BITCOIN_BLOCK)
const node = await resolver.get(cid)
expect(node).to.deep.equal(node1)
})

it('resolver.remove', async () => {
const resultPut = resolver.put([node1], multicodec.BITCOIN_BLOCK)
const cid = await resultPut.first()
const resultGet = resolver.get([cid])
const sameAsNode1 = await resultGet.first()
const cid = await resolver.put(node1, multicodec.BITCOIN_BLOCK)
const sameAsNode1 = await resolver.get(cid)
expect(sameAsNode1).to.deep.equal(node1)
return remove()

async function remove () {
const resultRemove = resolver.remove([cid])
// The items are deleted through iteration
await resultRemove.last()
await resolver.remove(cid)
// Verify that the item got really deleted
const resultGet = resolver.get([cid])
await expect(resultGet.next()).to.eventually.be.rejected()
await expect(resolver.get(cid)).to.eventually.be.rejected()
}
})
})
Expand Down
Loading

0 comments on commit 945fc61

Please sign in to comment.