├── .gitignore ├── .npmignore ├── ci └── Jenkinsfile ├── example.js ├── datastore.js ├── package.json ├── README.md ├── docs └── index.md ├── index.js └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /test 2 | /benchmark 3 | /ci 4 | /coverage 5 | /.nyc_output 6 | -------------------------------------------------------------------------------- /ci/Jenkinsfile: -------------------------------------------------------------------------------- 1 | // Warning: This file is automatically synced from https://github.com/ipfs/ci-sync so if you want to change it, please change it there and ask someone to sync all repositories. 2 | javascript() 3 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const IPFS = require('ipfs') 2 | const Graph = require('./') 3 | const ipfs = new IPFS() 4 | 5 | ipfs.on('start', async () => { 6 | const graph = new Graph(ipfs.dag) 7 | const a = { 8 | some: { 9 | thing: 'nested' 10 | } 11 | } 12 | const b = { 13 | lol: 1 14 | } 15 | 16 | console.log(JSON.stringify(a, null, 2)) 17 | 18 | await graph.set(a, 'some/thing/else', b) 19 | console.log(JSON.stringify(a, null, 2)) 20 | 21 | var x = await graph.get(a, 'some/thing/else') 22 | console.log(x) 23 | 24 | await graph.flush(a) 25 | console.log(JSON.stringify(a, null, 2)) 26 | ipfs.stop() 27 | }) 28 | -------------------------------------------------------------------------------- /datastore.js: -------------------------------------------------------------------------------- 1 | const CID = require('cids') 2 | const multihashes = require('multihashes') 3 | 4 | // this is a very simple abtraction for `ipfs.dag`. 5 | module.exports = class Store { 6 | constructor (dag) { 7 | this._dag = dag 8 | } 9 | 10 | put (val, options) { 11 | return this._dag.put(val, options).then(link => link.buffer) 12 | } 13 | 14 | get (link, node, dropOptions = false) { 15 | const cid = new CID(link) 16 | if (!dropOptions) { 17 | node.options = {} 18 | node.options.format = cid.codec 19 | node.options.hashAlg = multihashes.decode(cid.multihash).name 20 | } 21 | return this._dag.get(cid).then(node => node.value) 22 | } 23 | 24 | static isValidLink (link) { 25 | try { 26 | const cid = new CID(link) 27 | return CID.isCID(cid) 28 | } catch (e) { 29 | return false 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ipld-graph-builder", 3 | "version": "1.3.8", 4 | "description": "a merkle trie implemention that if focused on being generic and fast", 5 | "leadMaintainer": "mjbecze ", 6 | "main": "index.js", 7 | "scripts": { 8 | "coveralls": "npm run coverage && nyc report --reporter=text-lcov | coveralls", 9 | "coverage": "nyc npm test", 10 | "lint": "standard", 11 | "test": "node ./test/index.js", 12 | "build:docs": "documentation build ./index.js --github --sort-order source -f md > ./docs/index.md" 13 | }, 14 | "keywords": [ 15 | "merkle", 16 | "dag", 17 | "ipfs" 18 | ], 19 | "license": "MPL-2.0", 20 | "dependencies": { 21 | "assert": "^1.4.1", 22 | "cids": "^0.7.3", 23 | "lockmap": "^0.1.0", 24 | "multihashes": "^0.4.14" 25 | }, 26 | "devDependencies": { 27 | "coveralls": "^3.0.2", 28 | "documentation": "^8.0.1", 29 | "ipfs": "^0.32.3", 30 | "nyc": "^13.0.1", 31 | "standard": "^12.0.1", 32 | "tape": "^4.9.1" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git@github.com:ipld/js-ipld-graph-builder.git" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/ipld/js-ipld-graph-builder/issues" 40 | }, 41 | "homepage": "https://github.com/ipld/js-ipld-graph-builder" 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This project is no longer maintained or actively developed and has been archived. See https://ipld.io for information on recommended usage of IPLD in JavaScript. A rewrite of this library exists [here](https://jsr.io/@nullradix/ipld-cbor-graph-builder)** 2 | 3 | # SYNOPSIS 4 | [![NPM Package](https://img.shields.io/npm/v/ipld-graph-builder.svg?style=flat-square)](https://www.npmjs.org/package/ipld-graph-builder) 5 | [![Coverage Status](https://img.shields.io/coveralls/ipld/js-ipld-graph-builder.svg?style=flat-square)](https://coveralls.io/r/ipld/js-ipld-graph-builder) 6 | [![Greenkeeper badge](https://img.shields.io/badge/GreenKeeper-Enabled-brightgreen.svg?longCache=true&style=flat-square)](https://greenkeeper.io/) 7 | 8 | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 9 | 10 | This provides an efficent way to build and manipulate IPLD DAGs as JSON. This is accomplished by only producing merkle roots when `flush`ing the DAG. If any object has a "/" property, its value will be replaced with the merkle hash of that value when flushed. This allows you to build object anyway you like. 11 | 12 | # LEAD MAINTAINER 13 | 14 | [wanderer](https://github.com/wanderer) 15 | 16 | # INSTALL 17 | `npm install ipld-graph-builder` 18 | 19 | # USAGE 20 | 21 | ```javascript 22 | const IPFS = require('ipfs') 23 | const Graph = require('ipld-graph-builder') 24 | const ipfs = new IPFS() 25 | 26 | ipfs.on('start', () => { 27 | const graph = new Graph(ipfs.dag) 28 | const a = { 29 | some: { 30 | thing: 'nested' 31 | } 32 | } 33 | const b = { 34 | lol: 1 35 | } 36 | 37 | graph.set(a, 'some/thing/else', b).then(result => { 38 | // set "patches" together two objects 39 | console.log(JSON.stringify(result)) 40 | > { 41 | > "some": { 42 | > "thing": { 43 | > "else": { 44 | > "/": { 45 | > "lol": 1 46 | > } 47 | > } 48 | > } 49 | > } 50 | >} 51 | 52 | 53 | // flush replaces the links with merkle links, resulting in a single root hash 54 | graph.flush(result).then((result) => { 55 | console.log(result) 56 | > { '/': 'zdpuAqnGt7k49xSfawetvZXSLm4b1vvkSMnDrk4NFqnCCnW5V' } 57 | 58 | // taverse paths through merkle links given a starting vertex 59 | graph.get(result, 'some/thing/else').then(result2 => { 60 | console.log(result2) 61 | > { lol: 1 } 62 | }) 63 | }) 64 | }) 65 | }) 66 | ``` 67 | Additonally you can define the encoding of each link by adding the follow `options` property to un-merklized links. `options` will be used as the options argument for [`DAG.put`](https://github.com/ipfs/interface-ipfs-core/tree/master/API/dag#dagput). For Example: 68 | ``` 69 | { 70 | 'my-link': { 71 | '/': { 72 | 'some': 'stuff here' 73 | }, 74 | 'options': { 75 | format: 'dag-cbor', 76 | hashAlg: 'sha2-256' 77 | } 78 | } 79 | } 80 | ``` 81 | 82 | # API 83 | ['./docs/'](./docs/index.md) 84 | 85 | # TESTS 86 | `npm run tests` 87 | 88 | # LICENSE 89 | [MPL-2.0](https://tldrlegal.com/license/mozilla-public-license-2.0-(mpl-2)) 90 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Table of Contents 4 | 5 | - [constructor][1] 6 | - [Parameters][2] 7 | - [findUnsavedLeafNodes][3] 8 | - [Parameters][4] 9 | - [set][5] 10 | - [Parameters][6] 11 | - [get][7] 12 | - [Parameters][8] 13 | - [tree][9] 14 | - [Parameters][10] 15 | - [flush][11] 16 | - [Parameters][12] 17 | 18 | ## constructor 19 | 20 | [index.js:24-31][13] 21 | 22 | ### Parameters 23 | 24 | - `dag` 25 | - `ipfsDag` **[Object][14]** an instance of [ipfs.dag][15] 26 | 27 | ## findUnsavedLeafNodes 28 | 29 | [index.js:54-67][16] 30 | 31 | given a node on the graph this returns all the leaf node that have not yet been saved 32 | 33 | ### Parameters 34 | 35 | - `node` **[Object][14]** 36 | 37 | Returns **[Array][17]** 38 | 39 | ## set 40 | 41 | [index.js:77-103][18] 42 | 43 | sets a value on a root object given its path 44 | 45 | ### Parameters 46 | 47 | - `node` **[Object][14]** 48 | - `path` **[String][19]** 49 | - `value` **any** 50 | - `noLink` **[boolean][20]** if true, value is added as a plain object instead of a link 51 | 52 | Returns **[Promise][21]** 53 | 54 | ## get 55 | 56 | [index.js:113-117][22] 57 | 58 | traverses an object's path and returns the resulting value in a Promise 59 | 60 | ### Parameters 61 | 62 | - `node` **[Object][14]** 63 | - `path` **[String][19]** 64 | - `dropOptions` **[boolean][20]** whether to add the encoding options of the 65 | nodes when loading from IPFS. Defaults to true 66 | 67 | Returns **[Promise][21]** 68 | 69 | ## tree 70 | 71 | [index.js:161-180][23] 72 | 73 | Resolves all the links in an object and does so recusivly for N `level` 74 | 75 | ### Parameters 76 | 77 | - `node` **[Object][14]** 78 | - `levels` **Integer** (optional, default `1`) 79 | - `dropOptions` **[boolean][20]** whether to add the encoding options of the 80 | nodes when loading from IPFS. Defaults to true 81 | 82 | Returns **[Promise][21]** 83 | 84 | ## flush 85 | 86 | [index.js:203-214][24] 87 | 88 | flush an object to ipfs returning the resulting CID in a promise 89 | 90 | ### Parameters 91 | 92 | - `node` **[Object][14]** 93 | - `opts` **[Object][14]** encoding options for [`dag.put`][25] (optional, default `{}`) 94 | - `opts.onHash` **[Function][26]** a callback that happens on each merklized node. It is given two arguments `hash` and `node` which is the node that was hashed 95 | 96 | Returns **[Promise][21]** 97 | 98 | [1]: #constructor 99 | 100 | [2]: #parameters 101 | 102 | [3]: #findunsavedleafnodes 103 | 104 | [4]: #parameters-1 105 | 106 | [5]: #set 107 | 108 | [6]: #parameters-2 109 | 110 | [7]: #get 111 | 112 | [8]: #parameters-3 113 | 114 | [9]: #tree 115 | 116 | [10]: #parameters-4 117 | 118 | [11]: #flush 119 | 120 | [12]: #parameters-5 121 | 122 | [13]: https://github.com/ipld/js-ipld-graph-builder/blob/d804c9c2d3c224248c4cc168d2710776df5c42f1/index.js#L24-L31 "Source code on GitHub" 123 | 124 | [14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object 125 | 126 | [15]: https://github.com/ipfs/interface-ipfs-core/tree/master/API/dag#dag-api 127 | 128 | [16]: https://github.com/ipld/js-ipld-graph-builder/blob/d804c9c2d3c224248c4cc168d2710776df5c42f1/index.js#L54-L67 "Source code on GitHub" 129 | 130 | [17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array 131 | 132 | [18]: https://github.com/ipld/js-ipld-graph-builder/blob/d804c9c2d3c224248c4cc168d2710776df5c42f1/index.js#L77-L103 "Source code on GitHub" 133 | 134 | [19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String 135 | 136 | [20]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean 137 | 138 | [21]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise 139 | 140 | [22]: https://github.com/ipld/js-ipld-graph-builder/blob/d804c9c2d3c224248c4cc168d2710776df5c42f1/index.js#L113-L117 "Source code on GitHub" 141 | 142 | [23]: https://github.com/ipld/js-ipld-graph-builder/blob/d804c9c2d3c224248c4cc168d2710776df5c42f1/index.js#L161-L180 "Source code on GitHub" 143 | 144 | [24]: https://github.com/ipld/js-ipld-graph-builder/blob/d804c9c2d3c224248c4cc168d2710776df5c42f1/index.js#L203-L214 "Source code on GitHub" 145 | 146 | [25]: https://github.com/ipfs/interface-ipfs-core/tree/master/API/dag#dagput 147 | 148 | [26]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function 149 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const LockMap = require('lockmap') 3 | const Store = require('./datastore.js') 4 | 5 | const DEFAULTS = { 6 | format: 'dag-cbor', 7 | hashAlg: 'sha2-256' 8 | } 9 | 10 | function isObject (obj) { 11 | return typeof obj === 'object' && obj !== null 12 | } 13 | 14 | function clearObject (myObject) { 15 | for (var member in myObject) { 16 | delete myObject[member] 17 | } 18 | } 19 | 20 | module.exports = class Graph { 21 | /** 22 | * @param {Object} ipfsDag an instance of [ipfs.dag](https://github.com/ipfs/interface-ipfs-core/tree/master/API/dag#dag-api) 23 | */ 24 | constructor (dag) { 25 | assert(dag, 'ipld-graph must have an instance of ipfs.dag') 26 | if (!(dag instanceof Store)) { 27 | dag = new Store(dag) 28 | } 29 | this._dag = dag 30 | this._loading = new LockMap() 31 | } 32 | 33 | // this loads a cid from the ipfs.dag 34 | async _loadCID (node, link, dropOptions) { 35 | // _loading is a map of cid's that we have already requested. We don't want 36 | // to rerequiest something that we already started loading 37 | const loadingOp = this._loading.get(link) 38 | if (loadingOp) { 39 | return loadingOp 40 | } else { 41 | const resolve = this._loading.lock(link) 42 | let value = await this._dag.get(link, node, dropOptions) 43 | node['/'] = value 44 | this._loading.delete(link) 45 | resolve() 46 | } 47 | } 48 | 49 | /** 50 | * given a node on the graph this returns all the leaf node that have not yet been saved 51 | * @param {Object} node 52 | * @return {Array} 53 | */ 54 | findUnsavedLeafNodes (node) { 55 | let links = [] 56 | for (const name in node) { 57 | const edge = node[name] 58 | if (isObject(edge)) { 59 | if (edge['/'] !== undefined && !this._dag.constructor.isValidLink(edge['/'])) { 60 | links.push(edge) 61 | } else { 62 | links = this.findUnsavedLeafNodes(edge).concat(links) 63 | } 64 | } 65 | } 66 | return links 67 | } 68 | 69 | /** 70 | * sets a value on a root object given its path 71 | * @param {Object} node 72 | * @param {String} path 73 | * @param {*} value 74 | * @param {boolean} noLink - if true, value is added as a plain object instead of a link 75 | * @return {Promise} 76 | */ 77 | async set (node, path, value, noLink) { 78 | path = formatPath(path) 79 | if (!noLink) { 80 | value = { 81 | '/': value 82 | } 83 | } 84 | const last = path.pop() 85 | let { 86 | value: foundVal, 87 | remainderPath: remainder, 88 | parent 89 | } = await this._get(node, path) 90 | 91 | // if the found value is a litaral attach an object to the parent object 92 | if (!isObject(foundVal)) { 93 | const pos = path.length - remainder.length - 1 94 | const name = path.slice(pos, pos + 1)[0] 95 | foundVal = parent[name] = {} 96 | } 97 | // extend the path for the left over path names 98 | for (const name of remainder) { 99 | foundVal = foundVal[name] = {} 100 | } 101 | foundVal[last] = value 102 | return node 103 | } 104 | 105 | /** 106 | * traverses an object's path and returns the resulting value in a Promise 107 | * @param {Object} node 108 | * @param {String} path 109 | * @param {boolean} dropOptions - whether to add the encoding options of the 110 | * nodes when loading from IPFS. Defaults to true 111 | * @return {Promise} 112 | */ 113 | async get (node, path, dropOptions) { 114 | path = formatPath(path) 115 | const { value } = await this._get(node, path, dropOptions) 116 | return value 117 | } 118 | 119 | async _get (node, path, dropOptions) { 120 | let parent = node 121 | path = path.slice(0) 122 | while (1) { 123 | const link = node['/'] 124 | // if there is a link, traverse throught it 125 | if (this._dag.constructor.isValidLink(link)) { 126 | await this._loadCID(node, link, dropOptions) 127 | } else { 128 | if (link !== undefined) { 129 | // link is a POJO 130 | node = link 131 | } 132 | // traverse through POJOs 133 | if (!path.length) { 134 | break 135 | } 136 | const name = path.shift() 137 | const edge = node[name] 138 | node = edge 139 | if (isObject(edge)) { 140 | parent = node 141 | } else { 142 | break 143 | } 144 | } 145 | } 146 | return { 147 | value: node, 148 | remainderPath: path, 149 | parent: parent 150 | } 151 | } 152 | 153 | /** 154 | * Resolves all the links in an object and does so recusivly for N `level` 155 | * @param {Object} node 156 | * @param {Integer} levels 157 | * @param {boolean} dropOptions - whether to add the encoding options of the 158 | * nodes when loading from IPFS. Defaults to true 159 | * @return {Promise} 160 | */ 161 | async tree (node, levels = 1, dropOptions) { 162 | const orignal = node 163 | if (node) { 164 | const link = node['/'] 165 | if (this._dag.constructor.isValidLink(link)) { 166 | await this._loadCID(node, link, dropOptions) 167 | node = node['/'] 168 | } 169 | if (levels && isObject(node)) { 170 | levels-- 171 | const promises = [] 172 | for (const name in node) { 173 | const edge = node[name] 174 | promises.push(this.tree(edge, levels, dropOptions)) 175 | } 176 | await Promise.all(promises) 177 | } 178 | } 179 | return orignal 180 | } 181 | 182 | _flush (node, opts) { 183 | const links = this.findUnsavedLeafNodes(node) 184 | const awaiting = links.map(link => this._flush(link, opts)) 185 | 186 | return Promise.all(awaiting).then(() => { 187 | const link = node['/'] 188 | const options = Object.assign({}, opts, node.options) 189 | delete node.options 190 | return this._dag.put(link, options).then(buffer => { 191 | node['/'] = buffer 192 | }) 193 | }) 194 | } 195 | 196 | /** 197 | * flush an object to ipfs returning the resulting CID in a promise 198 | * @param {Object} node 199 | * @param {Object} opts - encoding options for [`dag.put`](https://github.com/ipfs/interface-ipfs-core/tree/master/API/dag#dagput) 200 | * @param {Function} opts.onHash - a callback that happens on each merklized node. It is given two arguments `hash` and `node` which is the node that was hashed 201 | * @return {Promise} 202 | */ 203 | async flush (node, opts = {}) { 204 | if (!this._dag.constructor.isValidLink(node['/'])) { 205 | const mergedOptions = Object.assign({}, DEFAULTS, opts) 206 | if (!node['/']) { 207 | const oldRoot = Object.assign({}, node) 208 | clearObject(node) 209 | node['/'] = oldRoot 210 | } 211 | await this._flush(node, mergedOptions) 212 | } 213 | return node 214 | } 215 | } 216 | 217 | function formatPath (path) { 218 | if (!path.split) { 219 | path = path.toString() 220 | } 221 | return path.split('/') 222 | } 223 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape') 2 | const IPFS = require('ipfs') 3 | const CID = require('cids') 4 | const Graph = require('../') 5 | const Datastore = require('../datastore.js') 6 | 7 | const node = new IPFS({ 8 | start: false 9 | }) 10 | 11 | node.on('ready', () => { 12 | tape('testing graph builder', async t => { 13 | const graph = new Graph(new Datastore(node.dag)) 14 | const a = { 15 | some: { 16 | thing: 'nested' 17 | } 18 | } 19 | const b = { 20 | lol: 'test' 21 | } 22 | let expect = { 23 | some: { 24 | thing: { 25 | else: { 26 | here: { 27 | '/': { 28 | lol: 'test' 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | await graph.set(a, 'some/thing/else/here', b) 37 | t.deepEquals(a, expect, 'should set a value correctly') 38 | await graph.set(a, 'some', b) 39 | expect = { 40 | some: { 41 | '/': { 42 | 'lol': 'test' 43 | } 44 | } 45 | } 46 | t.deepEquals(a, expect, 'should set a value correctly') 47 | 48 | const some = await graph.get(expect, 'some') 49 | t.deepEquals(some, { 50 | 'lol': 'test' 51 | }, 'should traverse objects with links') 52 | 53 | const cid = await graph.flush(a) 54 | let result = await node.dag.get(new CID(cid['/']), 'some/lol') 55 | t.deepEquals(result.value, 'test', 'should flush to dag store') 56 | 57 | result = await node.dag.get(new CID(cid['/'])) 58 | 59 | let getResult = await graph.get(result.value, 'some/lol') 60 | t.deepEquals(getResult, 'test', 'should get a value correctly') 61 | 62 | getResult = await graph.get(expect, 'some/lol') 63 | t.deepEquals(getResult, 'test', 'should get a value correctly') 64 | 65 | t.end() 66 | }) 67 | 68 | tape('flushing multiple leaf values', async t => { 69 | const graph = new Graph(node.dag) 70 | const a = { 71 | '/': { 72 | thing: { 73 | two: { 74 | '/': { 75 | lol: 'test' 76 | } 77 | }, 78 | else: { 79 | '/': { 80 | lol: 'test' 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | const b = a['/'] 88 | const expectedA = { 89 | '/': Buffer.from('01711220207ebd54e4992fbd944fca95bb84825d0153e87caa61fb742ba9e98fbbeb2710', 'hex') 90 | } 91 | const expectedB = { 92 | thing: { 93 | two: { 94 | '/': Buffer.from('01711220db3e85891631bb4fa52af90bb7af455f4a6982fd28a5e7060ac485d3f6b4ca4c', 'hex') 95 | }, 96 | else: { 97 | '/': Buffer.from('01711220db3e85891631bb4fa52af90bb7af455f4a6982fd28a5e7060ac485d3f6b4ca4c', 'hex') 98 | } 99 | } 100 | } 101 | const expectedTee = { 102 | '/': { 103 | 'thing': { 104 | 'two': { 105 | '/': { 106 | 'lol': 'test' 107 | }, 108 | 'options': { 109 | 'format': 'dag-cbor', 110 | 'hashAlg': 'sha2-256' 111 | } 112 | }, 113 | 'else': { 114 | '/': { 115 | 'lol': 'test' 116 | }, 117 | 'options': { 118 | 'format': 'dag-cbor', 119 | 'hashAlg': 'sha2-256' 120 | } 121 | } 122 | } 123 | }, 124 | 'options': { 125 | 'format': 'dag-cbor', 126 | 'hashAlg': 'sha2-256' 127 | } 128 | } 129 | 130 | const expectedTeeNoOps = { 131 | '/': { 132 | 'thing': { 133 | 'two': { 134 | '/': { 135 | 'lol': 'test' 136 | } 137 | }, 138 | 'else': { 139 | '/': { 140 | 'lol': 'test' 141 | } 142 | } 143 | } 144 | } 145 | } 146 | 147 | await graph.flush(a) 148 | t.deepEquals(a, expectedA, 'should flush correctly') 149 | t.deepEquals(b, expectedB, 'should flush correctly') 150 | 151 | const copyA = Object.assign({}, a) 152 | 153 | await graph.tree(a) 154 | t.equals(a['/']['thing']['two']['/'].toString('hex'), '01711220db3e85891631bb4fa52af90bb7af455f4a6982fd28a5e7060ac485d3f6b4ca4c', 'should load one level') 155 | 156 | await graph.tree(a, Infinity) 157 | t.deepEquals(a, expectedTee) 158 | 159 | const val = await graph.get(copyA, 'thing/two/lol') 160 | t.equals(val, 'test', 'should find the corret value') 161 | 162 | await graph.flush(a) 163 | await graph.tree(a, Infinity, true) 164 | t.deepEquals(a, expectedTeeNoOps, 'should tree correctly with no ops') 165 | 166 | t.end() 167 | }) 168 | 169 | tape('testing setting leaf values', async t => { 170 | const graph = new Graph(node.dag) 171 | const a = { 172 | some: { 173 | thing: 'nested' 174 | } 175 | } 176 | const b = { 177 | lol: Buffer.from([0]) 178 | } 179 | let expect = { 180 | some: { 181 | thing: { 182 | else: { 183 | '/': { 184 | lol: Buffer.from([0]) 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | await graph.set(a, 'some/thing/else', b) 192 | t.deepEquals(a, expect, 'should set a value correctly') 193 | await graph.set(a, 'some', b) 194 | expect = { 195 | some: { 196 | '/': { 197 | 'lol': Buffer.from([0]) 198 | } 199 | } 200 | } 201 | t.deepEquals(a, expect, 'should set a value correctly') 202 | 203 | t.end() 204 | }) 205 | 206 | tape('testing setting leaf values as plain objects', async t => { 207 | const graph = new Graph(node.dag) 208 | const a = { 209 | some: { 210 | thing: 'nested' 211 | } 212 | } 213 | const b = { 214 | lol: Buffer.from([0]) 215 | } 216 | let expect = { 217 | some: { 218 | thing: { 219 | else: { 220 | lol: Buffer.from([0]) 221 | } 222 | } 223 | } 224 | } 225 | 226 | await graph.set(a, 'some/thing/else', b, true) 227 | t.deepEquals(a, expect, 'should set a value correctly') 228 | await graph.set(a, 'some', b, true) 229 | expect = { 230 | some: { 231 | 'lol': Buffer.from([0]) 232 | } 233 | } 234 | t.deepEquals(a, expect, 'should set a value correctly') 235 | 236 | t.end() 237 | }) 238 | 239 | tape('testing setting leaf values in a subtree as plain objects', async t => { 240 | const graph = new Graph(node.dag) 241 | const a = { 242 | some: { 243 | thing: 'nested' 244 | } 245 | } 246 | const b = { 247 | lol: { 248 | trololo: 'lololo' 249 | } 250 | } 251 | let expect = { 252 | some: { 253 | thing: { 254 | else: { 255 | '/': { 256 | lol: { 257 | trololo: 'lololo' 258 | } 259 | } 260 | } 261 | } 262 | } 263 | } 264 | 265 | await graph.set(a, 'some/thing/else', b) 266 | t.deepEquals(a, expect, 'should set a value correctly') 267 | const c = 'stc' 268 | await graph.set(a, 'some/thing/else/lol/rofl', c, true) 269 | expect = { 270 | some: { 271 | thing: { 272 | else: { 273 | '/': { 274 | lol: { 275 | trololo: 'lololo', 276 | rofl: 'stc' 277 | } 278 | } 279 | } 280 | } 281 | } 282 | } 283 | t.deepEquals(a, expect, 'should set a value correctly') 284 | 285 | t.end() 286 | }) 287 | 288 | tape('failure cases', async t => { 289 | const graph = new Graph(node.dag) 290 | const value = { 291 | '/': { 292 | id: { 293 | nonce: [0], 294 | parent: { 295 | '/': null 296 | } 297 | }, 298 | type: 'test', 299 | vm: { 300 | '/': '' 301 | } 302 | } 303 | } 304 | 305 | const expectedTree = { 306 | '/': { 307 | 'id': { 308 | 'nonce': [0], 309 | 'parent': { 310 | '/': null, 311 | 'options': { 312 | 'format': 'dag-cbor', 313 | 'hashAlg': 'sha2-256' 314 | } 315 | } 316 | }, 317 | 'vm': { 318 | '/': '', 319 | 'options': { 320 | 'format': 'dag-cbor', 321 | 'hashAlg': 'sha2-256' 322 | } 323 | }, 324 | 'type': 'test' 325 | }, 326 | 'options': { 327 | 'format': 'dag-cbor', 328 | 'hashAlg': 'sha2-256' 329 | } 330 | } 331 | const expected = { 332 | '/': Buffer.from('01711220ad50688a54e58cff28d2f727754a61d3349c66b1dbe2eafb344ec78edcae54fd', 'hex') 333 | } 334 | 335 | const expected2 = Object.assign({}, expected) 336 | 337 | await graph.flush(value) 338 | t.deepEquals(value, expected) 339 | 340 | const testGet = { 341 | '/': { 342 | nonce: [0], 343 | parent: { 344 | '/': null 345 | } 346 | } 347 | } 348 | 349 | let result = await graph.get(testGet, 'parent') 350 | t.equals(result, null) 351 | const r = await graph.tree(expected, Infinity) 352 | t.equals(r, expected, 'tree should also return the correct result') 353 | t.deepEquals(expected, expectedTree, 'tree should travers graph with null leafs') 354 | 355 | await graph.flush(expected) 356 | t.deepEquals(expected, expected2, 'should round trip') 357 | 358 | const singlePath = { 359 | '/': { 360 | parent: null 361 | } 362 | } 363 | 364 | result = await graph.get(singlePath, 'parent') 365 | t.equals(result, null, 'should get null value') 366 | 367 | try { 368 | const graph2 = new Graph(node.dag) 369 | const root = { 370 | '/': Buffer.from('01711220ad58688a54e58cff28d2f727754a61d3349c66b1dbe2eafb344ec78edcae54fd', 'hex') 371 | } 372 | await graph2.get(root, 'test/test/test') 373 | } catch (e) { 374 | t.end() 375 | } 376 | }) 377 | 378 | tape('testing sequentail consistances', async t => { 379 | const graph = new Graph(node.dag) 380 | let test = { 381 | some: { 382 | thing: { 383 | else: { 384 | '/': { 385 | lol: { 386 | test: 1 387 | } 388 | } 389 | } 390 | } 391 | } 392 | } 393 | 394 | await graph.flush(test) 395 | const a = graph.get(test, 'some/thing/else/lol') 396 | const b = graph.get(test, 'some/thing/else/lol') 397 | const r = await Promise.all([a, b]) 398 | t.equals(r[0], r[1]) 399 | 400 | t.end() 401 | }) 402 | 403 | tape('testing ints as keys', async t => { 404 | const graph = new Graph(node.dag) 405 | let test = ['test'] 406 | 407 | await graph.flush(test) 408 | const b = await graph.get(test, 0) 409 | t.equals(b, 'test') 410 | 411 | await graph.set(test, 1, 'test2') 412 | await graph.flush(test) 413 | const c = await graph.get(test, 1) 414 | 415 | t.equals(c, 'test2') 416 | 417 | t.end() 418 | }) 419 | 420 | tape('flushing the same root mutliple time should have the same result', async t => { 421 | const graph = new Graph(node.dag) 422 | let test = { '/': ['test'] } 423 | 424 | await graph.flush(test) 425 | const r1 = test['/'].toString('hex') 426 | 427 | await graph.flush(test) 428 | const r2 = test['/'].toString('hex') 429 | t.equals(r1, r2) 430 | 431 | t.end() 432 | }) 433 | }) 434 | --------------------------------------------------------------------------------