mirror of
https://github.com/orbitdb/orbitdb.git
synced 2025-06-30 01:32:29 +00:00
Merge remote-tracking branch 'origin/proto/lists'
This commit is contained in:
commit
09a8df8113
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,4 +1,7 @@
|
||||
*sublime*
|
||||
node_modules/
|
||||
debug.log
|
||||
WIP/
|
||||
.vagrant/
|
||||
.idea/
|
||||
isolate*.log
|
||||
dump.rdb
|
80
README.md
80
README.md
@ -4,22 +4,34 @@
|
||||
|
||||
Distributed, peer-to-peer* Key-Value Store and Event Log on IPFS.
|
||||
|
||||
Requires `orbit-server` to connect to. Get from https://github.com/haadcode/orbit-server.
|
||||
|
||||
_* Currently requires a centralized server. This will change in the future as required p2p features land in IPFS_
|
||||
_* Currently requires a redis-server server for pubsub communication. This will change in the future as soon as IPFS provides pubsub_
|
||||
|
||||
## Features
|
||||
- Distributed kv-store and event log database
|
||||
- Stores all data in IPFS
|
||||
- Data encrypted on the wire and at rest
|
||||
- Per channel access rights
|
||||
|
||||
_Channel is similar to "table", "keyspace", "topic", "feed" or "collection" in other systems_
|
||||
|
||||
## Example
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
Key-Value store example:
|
||||
```
|
||||
node examples/keyvalue.js
|
||||
```
|
||||
|
||||
Event log example (run in separate shells):
|
||||
```
|
||||
node examples/reader.js
|
||||
node examples/writer.js
|
||||
```
|
||||
|
||||
## API
|
||||
_See Usage below_
|
||||
|
||||
connect(host, username, password)
|
||||
connect(host, port, username, password)
|
||||
|
||||
channel(name, password)
|
||||
|
||||
@ -40,41 +52,37 @@ _See Usage below_
|
||||
|
||||
.get(key) // Retrieve value
|
||||
|
||||
.remove({ key: <key or hash> }) // Remove entry
|
||||
.del({ key: <key or hash> }) // Remove entry
|
||||
|
||||
.setMode(modes) // Set channel modes, can be an object or an array of objects
|
||||
|
||||
// { mode: "+r", params: { password: password } } // Set read mode
|
||||
// { mode: "-r" } // Remove read-mode
|
||||
// { mode: "+w", params: { ops: [orbit.user.id] } } // Set write-mode, only users in ops can write
|
||||
// { mode: "-w" } // Remove write-mode
|
||||
|
||||
.info() // Returns channel's current head and modes
|
||||
|
||||
.delete() // Deletes the channel, all data will be "removed" (unassociated with the channel, actual data is not deleted)
|
||||
.delete() // Deletes the channel, all data will be "removed" (unassociated with the channel, actual data is not deleted from ipfs)
|
||||
|
||||
## Usage
|
||||
```javascript
|
||||
var async = require('asyncawait/async');
|
||||
var OrbitClient = require('./OrbitClient');
|
||||
const async = require('asyncawait/async');
|
||||
const OrbitClient = require('./OrbitClient');
|
||||
|
||||
var host = 'localhost:3006'; // orbit-server address
|
||||
// Redis
|
||||
const host = 'localhost';
|
||||
const port = 6379;
|
||||
|
||||
async(() => {
|
||||
// Connect
|
||||
const orbit = OrbitClient.connect(host, username, password);
|
||||
const orbit = OrbitClient.connect(host, port, username, password);
|
||||
|
||||
const channelName = 'hello-world';
|
||||
const db = orbit.channel(channelName);
|
||||
|
||||
/* Event Log */
|
||||
const hash = orbit.channel(channelName).add('hello'); // <ipfs-hash>
|
||||
orbit.channel(channelName).remove({ key: hash });
|
||||
const hash = db.add('hello'); // <ipfs-hash>
|
||||
|
||||
// Remove event
|
||||
db.remove(hash);
|
||||
|
||||
// Iterator options
|
||||
const options = { limit: -1 }; // fetch all messages
|
||||
|
||||
// Get events
|
||||
const iter = orbit.channel(channelName).iterator(options); // Symbol.iterator
|
||||
const iter = db.iterator(options); // Symbol.iterator
|
||||
const next = iter.next(); // { value: <item>, done: false|true}
|
||||
|
||||
// OR:
|
||||
@ -85,26 +93,18 @@ async(() => {
|
||||
// console.log(i.hash, i.item);
|
||||
|
||||
/* KV Store */
|
||||
orbit.channel(channelName).put("key1", "hello world");
|
||||
orbit.channel(channelName).get("key1"); // returns "hello world"
|
||||
orbit.channel(channelName).remove("key1");
|
||||
|
||||
/* Modes */
|
||||
const password = 'hello';
|
||||
let channelModes;
|
||||
channelModes = orbit.channel(channel).setMode({ mode: "+r", params: { password: password } }); // { modes: { r: { password: 'hello' } } }
|
||||
channelModes = orbit.channel(channel, password).setMode({ mode: "+w", params: { ops: [orbit.user.id] } }); // { modes: { ... } }
|
||||
channelModes = orbit.channel(channel, password).setMode({ mode: "-r" }); // { modes: { ... } }
|
||||
channelModes = orbit.channel(channel, '').setMode({ mode: "-w" }); // { modes: {} }
|
||||
db.put('key1', 'hello world');
|
||||
db.get('key1'); // returns "hello world"
|
||||
db.del('key1');
|
||||
|
||||
/* Delete channel */
|
||||
const result = orbit.channel(channelName, channelPwd).delete(); // true | false
|
||||
const result = db.delete(); // true | false
|
||||
})();
|
||||
```
|
||||
|
||||
### Development
|
||||
#### Run Tests
|
||||
**Note!** Requires Aerospike, see http://www.aerospike.com/docs/operations/install/
|
||||
**Note!** Requires a redis-server at `localhost:6379`
|
||||
|
||||
```
|
||||
npm test
|
||||
@ -116,8 +116,4 @@ mocha -w
|
||||
```
|
||||
|
||||
### TODO
|
||||
- Tests for remove(), put() and get()
|
||||
- pubsub communication (use redis to mock ipfs pubsub)
|
||||
- Local caching of messages
|
||||
- Possibility to fetch content separately from data structure
|
||||
- Use HTTPS instead of HTTP (channel password are sent in plaintext atm)
|
||||
- Caching
|
||||
|
198
WIP/MOCK.json
Normal file
198
WIP/MOCK.json
Normal file
@ -0,0 +1,198 @@
|
||||
// Data set grouped and sorted
|
||||
"A" : [
|
||||
{ "id": "A", "seq": 0, "ver": 0, "prev": null},
|
||||
{ "id": "A", "seq": 0, "ver": 1, "prev": "A0.0"},
|
||||
{ "id": "A", "seq": 0, "ver": 2, "prev": "A0.1"},
|
||||
{ "id": "A", "seq": 0, "ver": 3, "prev": "A0.2"},
|
||||
{ "id": "A", "seq": 0, "ver": 4, "prev": "A0.3"},
|
||||
{ "id": "A", "seq": 2, "ver": 0, "prev": ["A0.4", "B1.1"]}
|
||||
],
|
||||
"B" : [
|
||||
{ "id": "B", "seq": 1, "ver": 0, "prev": ["A0.4", "C0.2"]},
|
||||
{ "id": "B", "seq": 1, "ver": 1, "prev": "B1.0"}
|
||||
],
|
||||
"C" : [
|
||||
{ "id": "C", "seq": 0, "ver": 0, "prev": null},
|
||||
{ "id": "C", "seq": 0, "ver": 1, "prev": "C0.0"},
|
||||
{ "id": "C", "seq": 0, "ver": 2, "prev": "C0.1"},
|
||||
{ "id": "C", "seq": 3, "ver": 0, "prev": ["C0.2", "A2.0]"]}
|
||||
]
|
||||
|
||||
A B C
|
||||
|
|
||||
0.0
|
||||
|
|
||||
0.1
|
||||
|
|
||||
0.2 0.0
|
||||
| |
|
||||
0.3 0.1
|
||||
| |
|
||||
0.4 0.2
|
||||
| \ / |
|
||||
| 1.0 |
|
||||
| | |
|
||||
| 1.1 |
|
||||
| / |
|
||||
2.0 |
|
||||
\ |
|
||||
\ |
|
||||
\ |
|
||||
3.0
|
||||
|
||||
// expected order A
|
||||
{ "id": "A", "seq": 0, "ver": 0, "prev": null},
|
||||
{ "id": "A", "seq": 0, "ver": 1, "prev": "A0.0"},
|
||||
{ "id": "A", "seq": 0, "ver": 2, "prev": "A0.1"},
|
||||
{ "id": "A", "seq": 0, "ver": 3, "prev": "A0.2"},
|
||||
{ "id": "A", "seq": 0, "ver": 4, "prev": "A0.3"},
|
||||
{ "id": "C", "seq": 0, "ver": 0, "prev": null},
|
||||
{ "id": "C", "seq": 0, "ver": 1, "prev": "C0.0"},
|
||||
{ "id": "C", "seq": 0, "ver": 2, "prev": "C0.1"},
|
||||
{ "id": "B", "seq": 1, "ver": 0, "prev": ["A0.4", "C0.2"]},
|
||||
{ "id": "B", "seq": 1, "ver": 1, "prev": "B1.0"}
|
||||
{ "id": "A", "seq": 2, "ver": 0, "prev": ["A0.4", "B1.1"]}
|
||||
{ "id": "C", "seq": 3, "ver": 0, "prev": ["C0.2", "A2.0]"]}
|
||||
|
||||
"VersionClock": {
|
||||
"seq": 0,
|
||||
"ver": 0
|
||||
}
|
||||
|
||||
"Item": {
|
||||
"id": "",
|
||||
"VersionClock": "<VersionClock>",
|
||||
"prev": []
|
||||
}
|
||||
|
||||
"List": {
|
||||
"items": ["<Item>"]
|
||||
}
|
||||
|
||||
/*
|
||||
list.add(data) {
|
||||
this.ver ++;
|
||||
const heads = _findHeads();
|
||||
const i = new Item(id, this.seq, this.ver, heads)
|
||||
outgoing.push(data)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
list.join(other) {
|
||||
// increase seq on join, reset version
|
||||
if(other.first.seq >= this.seq)
|
||||
this.seq = other.first.seq + 1
|
||||
this.ver = 0
|
||||
|
||||
items = items.concat(outgoing.concat(other))
|
||||
items = items.sortBy("seq", "id", "ver")
|
||||
outgoing = []
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
nextHeads() {
|
||||
referenced = []
|
||||
heads = all.groupBy("id").map((items) => items[items.length - 1])
|
||||
cleaned = heads.reverse().filter((e) => !isReferencedInChain(referenced, e))
|
||||
return cleaned;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
isReferencedInChain(list, other) {
|
||||
const res = other.map((o) => {
|
||||
const ref = list.map((e) => !(e.id == o.id && e.seq == o.seq && e.ver == o.ver))
|
||||
if(!ref)
|
||||
list.push(ref)
|
||||
//return false
|
||||
|
||||
//list.concat(list.filter((e) => !(e.id == o.id && e.seq == o.seq && e.ver == o.ver)))
|
||||
if(o.prev)
|
||||
ref = isReferencedInChain(list, o.prev)
|
||||
|
||||
return ref
|
||||
})
|
||||
return res.anyEqual(true)
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
A B C
|
||||
0.0
|
||||
|
|
||||
0.0 0.1
|
||||
| |
|
||||
0.1 0.2
|
||||
\ / |
|
||||
1.0 |
|
||||
| |
|
||||
1.1 |
|
||||
/ |
|
||||
2.0 |
|
||||
\ |
|
||||
\ |
|
||||
\ |
|
||||
3.0
|
||||
|
||||
// Sequence, --> syncs to
|
||||
listA.add("mango") // { "id": "A", "seq": 0, "ver": 0, "prev": null}
|
||||
listA.add("banana") // { "id": "A", "seq": 0, "ver": 1, "prev": "A.0.0"}
|
||||
--> B
|
||||
|
||||
// A
|
||||
// { "id": "A", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "A", "seq": 0, "ver": 1, "prev": "A.0.0"}
|
||||
|
||||
listC.add("apple") // { "id": "C", "seq": 0, "ver": 0, "prev": null}
|
||||
listC.add("strawberry") // { "id": "C", "seq": 0, "ver": 1, "prev": "C.0.0"}
|
||||
listC.add("orange") // { "id": "C", "seq": 0, "ver": 2, "prev": "C.0.1"}
|
||||
--> A,B
|
||||
|
||||
// A
|
||||
// { "id": "A", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "A", "seq": 0, "ver": 1, "prev": "A.0.0"}
|
||||
// { "id": "C", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "C", "seq": 0, "ver": 1, "prev": "C.0.0"}
|
||||
// { "id": "C", "seq": 0, "ver": 2, "prev": "C.0.1"}
|
||||
|
||||
listB.add("pineapple") // { "id": "B", "seq": 1, "ver": 0, "prev": ["A.0.1", "C.0.2"]}
|
||||
listB.add("papaya") // { "id": "B", "seq": 1, "ver": 1, "prev": "B.1.0"}
|
||||
--> A
|
||||
|
||||
// A
|
||||
// { "id": "A", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "A", "seq": 0, "ver": 1, "prev": "A.0.0"}
|
||||
// { "id": "C", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "C", "seq": 0, "ver": 1, "prev": "C.0.0"}
|
||||
// { "id": "C", "seq": 0, "ver": 2, "prev": "C.0.1"}
|
||||
// { "id": "B", "seq": 1, "ver": 0, "prev": ["A.0.1", "C.0.2"]}
|
||||
// { "id": "B", "seq": 1, "ver": 1, "prev": "B.1.0"}
|
||||
|
||||
listA.add("kiwi") // { "id": "A", "seq": 2, "ver": 0, "prev": ["A.0.1", "B1.1", "C0.2"]}
|
||||
--> C
|
||||
|
||||
// A
|
||||
// { "id": "A", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "A", "seq": 0, "ver": 1, "prev": "A.0.0"}
|
||||
// { "id": "C", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "C", "seq": 0, "ver": 1, "prev": "C.0.0"}
|
||||
// { "id": "C", "seq": 0, "ver": 2, "prev": "C.0.1"}
|
||||
// { "id": "B", "seq": 1, "ver": 0, "prev": ["A.0.0", "C.0.2"]}
|
||||
// { "id": "B", "seq": 1, "ver": 1, "prev": "B.1.0"}
|
||||
// { "id": "A", "seq": 2, "ver": 0, "prev": ["A.0.1", "B1.1", "C0.2"]}
|
||||
|
||||
listC.add("blueberry") // { "id": "C", "seq": 3, "ver": 0, "prev": ["A.2.0", "C.0.2"]}
|
||||
--> A,B
|
||||
|
||||
// A
|
||||
// { "id": "A", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "A", "seq": 0, "ver": 1, "prev": "A.0.0"}
|
||||
// { "id": "C", "seq": 0, "ver": 0, "prev": null}
|
||||
// { "id": "C", "seq": 0, "ver": 1, "prev": "C.0.0"}
|
||||
// { "id": "C", "seq": 0, "ver": 2, "prev": "C.0.1"}
|
||||
// { "id": "B", "seq": 1, "ver": 0, "prev": ["A.0.0", "C.0.2"]}
|
||||
// { "id": "B", "seq": 1, "ver": 1, "prev": "B.1.0"}
|
||||
// { "id": "A", "seq": 2, "ver": 0, "prev": ["A.0.1", "B1.1", "C0.2"]}
|
||||
// { "id": "C", "seq": 3, "ver": 0, "prev": ["A.2.0", "C.0.2"]}
|
70
WIP/test1.js
Normal file
70
WIP/test1.js
Normal file
@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const Timer = require('./examples/Timer');
|
||||
const List = require('./src/list/List');
|
||||
|
||||
var run = () => {
|
||||
var redis = require("redis");
|
||||
this.client1 = redis.createClient({ host: "localhost", port: 6379 });
|
||||
this.client2 = redis.createClient({ host: "localhost", port: 6379 });
|
||||
var hash = "ccc"
|
||||
this.client1.subscribe(hash);
|
||||
this.client1.subscribe(hash);
|
||||
|
||||
|
||||
let listA = new List("A");
|
||||
let listB = new List("B");
|
||||
let listC = new List("C");
|
||||
|
||||
const handleMessage = (hash, event) => {
|
||||
const l = List.fromJson(JSON.parse(event));
|
||||
// console.log("LIST", l);
|
||||
|
||||
if(l.id === 'A') {
|
||||
listB.join(l);
|
||||
listC.join(l);
|
||||
} else if(l.id === 'B') {
|
||||
listA.join(l);
|
||||
listC.join(l);
|
||||
} else if(l.id === 'C') {
|
||||
listA.join(l);
|
||||
console.log("Items:", listA.items.length);
|
||||
// console.log(JSON.stringify(listA, null, 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.client1.on("message", handleMessage);
|
||||
this.client2.on("message", handleMessage);
|
||||
|
||||
let h = 0;
|
||||
setInterval(() => {
|
||||
listC.add("C--"+h);
|
||||
this.client2.publish(hash, JSON.stringify(listC.toJson()));
|
||||
h++;
|
||||
}, 1000);
|
||||
|
||||
let i = 0;
|
||||
setInterval(() => {
|
||||
let a = 0;
|
||||
for(let a = 0; a < 10; a ++) {
|
||||
listB.add("B--"+(i+a));
|
||||
}
|
||||
this.client2.publish(hash, JSON.stringify(listB.toJson()));
|
||||
i++;
|
||||
}, 20);
|
||||
|
||||
let k = 0;
|
||||
setInterval(() => {
|
||||
listA.add("A--"+k);
|
||||
k++;
|
||||
listA.add("A--"+k);
|
||||
k++;
|
||||
listA.add("A--"+k);
|
||||
k++;
|
||||
this.client2.publish(hash, JSON.stringify(listA.toJson()));
|
||||
}, 100);
|
||||
};
|
||||
|
||||
run();
|
92
WIP/test2.js
Normal file
92
WIP/test2.js
Normal file
@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const ipfsDaemon = require('orbit-common/lib/ipfs-daemon');
|
||||
const ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
const List = require('./src/list/OrbitList');
|
||||
const Timer = require('./examples/Timer');
|
||||
|
||||
const startIpfs = async (() => {
|
||||
return new Promise(async((resolve, reject) => {
|
||||
const ipfsd = await(ipfsDaemon());
|
||||
resolve(ipfsd.daemon);
|
||||
}));
|
||||
});
|
||||
|
||||
let ipfs;
|
||||
|
||||
var run = async(() => {
|
||||
ipfs = await(startIpfs());
|
||||
|
||||
var redis = require("redis");
|
||||
this.client1 = redis.createClient({ host: "localhost", port: 6379 });
|
||||
this.client2 = redis.createClient({ host: "localhost", port: 6379 });
|
||||
var hash = "ccc"
|
||||
this.client1.subscribe(hash);
|
||||
this.client1.subscribe(hash);
|
||||
|
||||
|
||||
let listA = new List("A", ipfs);
|
||||
let listB = new List("B", ipfs);
|
||||
let listC = new List("C", ipfs);
|
||||
|
||||
const handleMessage = async((hash, event) => {
|
||||
// const l = List.fromJson(JSON.parse(event));
|
||||
console.log(">", event);
|
||||
const l = await(List.fromIpfsHash(ipfs, event));
|
||||
// console.log("ITEMS RECEIVED", l.items.length);
|
||||
|
||||
if(l.id === 'A') {
|
||||
listB.join(l);
|
||||
listC.join(l);
|
||||
} else if(l.id === 'B') {
|
||||
// listA.join(l);
|
||||
// listC.join(l);
|
||||
} else if(l.id === 'C') {
|
||||
listB.join(l);
|
||||
var timer = new Timer('a');
|
||||
listC.join(listB);
|
||||
console.log("join took " + timer.stop(true) + " ms");
|
||||
console.log("Items:", listC.items.length);
|
||||
// console.log(listC.toString());
|
||||
}
|
||||
});
|
||||
|
||||
this.client1.on("message", handleMessage);
|
||||
this.client2.on("message", handleMessage);
|
||||
|
||||
let h = 0;
|
||||
setInterval(async(() => {
|
||||
listC.add("C--"+h);
|
||||
const list = await(listC.getIpfsHash());
|
||||
this.client2.publish(hash, list);
|
||||
h++;
|
||||
}), 1000);
|
||||
|
||||
let i = 0;
|
||||
setInterval(async(() => {
|
||||
let a = 0;
|
||||
// for(let a = 0; a < 10; a ++) {
|
||||
listB.add("B--"+(i+a));
|
||||
// }
|
||||
const list = await(listB.getIpfsHash());
|
||||
this.client2.publish(hash, list);
|
||||
i++;
|
||||
}), 50);
|
||||
|
||||
// let k = 0;
|
||||
// setInterval(async(() => {
|
||||
// listA.add("A--"+k);
|
||||
// k++;
|
||||
// listA.add("A--"+k);
|
||||
// k++;
|
||||
// listA.add("A--"+k);
|
||||
// k++;
|
||||
// this.client2.publish(hash, JSON.stringify(listA.toJson()));
|
||||
// }), 100);
|
||||
// });
|
||||
});
|
||||
|
||||
run();
|
@ -1,3 +1,5 @@
|
||||
machine:
|
||||
node:
|
||||
version: 4.2.2
|
||||
services:
|
||||
- redis
|
56
examples/benchmark.js
Normal file
56
examples/benchmark.js
Normal file
@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('asyncawait/async');
|
||||
var OrbitClient = require('../src/OrbitClient');
|
||||
var Timer = require('./Timer');
|
||||
|
||||
// Redis host
|
||||
var host = '178.62.229.175';
|
||||
var port = 6379;
|
||||
|
||||
var username = 'testrunner';
|
||||
var password = '';
|
||||
|
||||
let run = (async(() => {
|
||||
try {
|
||||
// Connect
|
||||
var orbit = OrbitClient.connect(host, port, username, password);
|
||||
|
||||
const id = process.argv[2] ? process.argv[2] : 'a';
|
||||
const channelName = 'c1';
|
||||
const db = orbit.channel(channelName);
|
||||
|
||||
// Metrics
|
||||
let totalQueries = 0;
|
||||
let seconds = 0;
|
||||
let queriesPerSecond = 0;
|
||||
let lastTenSeconds = 0;
|
||||
|
||||
// Metrics output
|
||||
setInterval(() => {
|
||||
seconds ++;
|
||||
|
||||
if(seconds % 10 === 0) {
|
||||
console.log(`--> Average of ${lastTenSeconds/10} q/s in the last 10 seconds`)
|
||||
lastTenSeconds = 0
|
||||
}
|
||||
|
||||
console.log(`${queriesPerSecond} queries per second, ${totalQueries} queries in ${seconds} seconds`)
|
||||
queriesPerSecond = 0;
|
||||
}, 1000);
|
||||
|
||||
while(true) {
|
||||
let g = db.add(id + totalQueries);
|
||||
totalQueries ++;
|
||||
lastTenSeconds ++;
|
||||
queriesPerSecond ++;
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
console.error("error:", e);
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}))();
|
||||
|
||||
module.exports = run;
|
@ -1,58 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('asyncawait/async');
|
||||
var OrbitClient = require('../src/OrbitClient');
|
||||
var Timer = require('./Timer');
|
||||
|
||||
var host = 'localhost:3006';
|
||||
var username = 'testrunner';
|
||||
var password = '';
|
||||
|
||||
let run = (async(() => {
|
||||
try {
|
||||
// Connect
|
||||
var orbit = OrbitClient.connect(host, username, password);
|
||||
|
||||
console.log("-------- EVENT log -------")
|
||||
const c1 = 'cache-test';
|
||||
orbit.channel(c1).delete();
|
||||
|
||||
var timer1 = new Timer(true);
|
||||
console.log("Writing...");
|
||||
for(let i = 0; i < 100; i ++) {
|
||||
orbit.channel(c1).add("hello " + i);
|
||||
}
|
||||
console.log("Write took", timer1.stop() + "ms");
|
||||
|
||||
var timer2 = new Timer(true);
|
||||
console.log("Reading 1st time...");
|
||||
var items = orbit.channel(c1).iterator({ limit: -1 }).collect();
|
||||
items = items.map((e) => {
|
||||
return { key: e.item.key, val: e.item.Payload };
|
||||
});
|
||||
console.log("Reading 1st time took", timer2.stop() + "ms");
|
||||
|
||||
var timer3 = new Timer(true);
|
||||
console.log("Reading 2nd time...");
|
||||
var items = orbit.channel(c1).iterator({ limit: -1 }).collect();
|
||||
items = items.map((e) => {
|
||||
return { key: e.item.key, val: e.item.Payload };
|
||||
});
|
||||
console.log("Reading 2nd time took", timer3.stop() + "ms");
|
||||
|
||||
var timer4 = new Timer(true);
|
||||
console.log("Reading 3rd time...");
|
||||
var items = orbit.channel(c1).iterator({ limit: -1 }).collect();
|
||||
items = items.map((e) => {
|
||||
return { key: e.item.key, val: e.item.Payload };
|
||||
});
|
||||
console.log("Reading 3rd time took", timer4.stop() + "ms");
|
||||
|
||||
} catch(e) {
|
||||
console.error("error:", e);
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}))();
|
||||
|
||||
module.exports = run;
|
46
examples/keyvalue.js
Normal file
46
examples/keyvalue.js
Normal file
@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const OrbitClient = require('../src/OrbitClient');
|
||||
const Timer = require('./Timer');
|
||||
|
||||
// Redis
|
||||
const host = 'localhost';
|
||||
const port = 6379;
|
||||
|
||||
const username = 'LambOfGod';
|
||||
const password = '';
|
||||
|
||||
let run = (async(() => {
|
||||
try {
|
||||
const orbit = OrbitClient.connect(host, port, username, password);
|
||||
const channel = 'testing123';
|
||||
const db = orbit.channel(channel);
|
||||
|
||||
let count = 1;
|
||||
|
||||
while(true) {
|
||||
const key = "username";
|
||||
let timer = new Timer(true);
|
||||
db.put(key, "Lamb Of God " + count);
|
||||
let v = db.get(key);
|
||||
|
||||
console.log("---------------------------------------------------")
|
||||
console.log("Key | Value")
|
||||
console.log("---------------------------------------------------")
|
||||
console.log(`${key} | ${v}`);
|
||||
console.log("---------------------------------------------------")
|
||||
console.log(`Query #${count} took ${timer.stop(true)} ms\n`);
|
||||
|
||||
count ++;
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
console.error("error:", e);
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}))();
|
||||
|
||||
module.exports = run;
|
@ -1,70 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('asyncawait/async');
|
||||
var OrbitClient = require('../src/OrbitClient');
|
||||
var Timer = require('./Timer');
|
||||
|
||||
var host = 'localhost:3006';
|
||||
var username = 'testrunner';
|
||||
var password = '';
|
||||
|
||||
let run = (async(() => {
|
||||
try {
|
||||
// Connect
|
||||
var orbit = OrbitClient.connect(host, username, password);
|
||||
|
||||
var timer = new Timer(true);
|
||||
|
||||
console.log("-------- KV store -------")
|
||||
var channel = 'keyspace1'
|
||||
// orbit.channel(channel, '').delete();
|
||||
orbit.channel(channel).put("key3", "this is the value you're looking for: " + new Date().getTime());
|
||||
var val = orbit.channel(channel).get("key3");
|
||||
console.log("key3:", val);
|
||||
|
||||
orbit.channel(channel).put("key4", "this will be deleted");
|
||||
var val2 = orbit.channel(channel).get("key4");
|
||||
console.log("key4:", val2);
|
||||
orbit.channel(channel).remove({ key: "key4" });
|
||||
val2 = orbit.channel(channel).get("key4");
|
||||
console.log("key4:", val2);
|
||||
|
||||
console.log("-------- EVENT log -------")
|
||||
const c1 = 'c1';
|
||||
orbit.channel(c1).delete();
|
||||
var hash1 = orbit.channel(c1).add("hello1");
|
||||
var hash2 = orbit.channel(c1).add("hello2");
|
||||
|
||||
var items = orbit.channel(c1).iterator({ limit: -1 }).collect();
|
||||
items = items.map((e) => {
|
||||
return { key: e.item.key, val: e.item.Payload };
|
||||
});
|
||||
console.log(JSON.stringify(items, null, 2));
|
||||
|
||||
console.log("--> remove", hash1);
|
||||
orbit.channel(c1).remove({ key: hash1 });
|
||||
|
||||
items = orbit.channel(c1).iterator({ limit: -1 }).collect();
|
||||
items = items.map((e) => {
|
||||
return { key: e.item.key, val: e.item.Payload };
|
||||
});
|
||||
console.log(JSON.stringify(items, null, 2));
|
||||
|
||||
// You can also get the event based on its hash
|
||||
var value = orbit.channel(c1).get(hash2);
|
||||
console.log("key:", hash2, "value:", value);
|
||||
|
||||
console.log("--> remove", hash2);
|
||||
orbit.channel(c1).remove({ key: hash2 });
|
||||
|
||||
items = orbit.channel(c1).iterator({ limit: -1 }).collect();
|
||||
console.log(JSON.stringify(items, null, 2));
|
||||
|
||||
} catch(e) {
|
||||
console.error("error:", e);
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}))();
|
||||
|
||||
module.exports = run;
|
54
examples/reader.js
Normal file
54
examples/reader.js
Normal file
@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const OrbitClient = require('../src/OrbitClient');
|
||||
const Timer = require('./Timer');
|
||||
|
||||
// Redis
|
||||
const host = 'localhost';
|
||||
const port = 6379;
|
||||
|
||||
const username = 'LambOfGod';
|
||||
const password = '';
|
||||
|
||||
let run = (async(() => {
|
||||
try {
|
||||
var orbit = OrbitClient.connect(host, port, username, password);
|
||||
const c1 = 'c1';
|
||||
const channel = orbit.channel(c1);
|
||||
|
||||
let count = 1;
|
||||
let id = 'Log: Query '
|
||||
let running = false;
|
||||
|
||||
setInterval(async(() => {
|
||||
if(!running) {
|
||||
running = true;
|
||||
|
||||
// let timer = new Timer(true);
|
||||
channel.add("Hello " + count);
|
||||
// console.log(`Query #${count} took ${timer.stop(true)} ms\n`);
|
||||
|
||||
const c = channel.iterator({ limit: -1 }).collect().length;
|
||||
let items = channel.iterator({ limit: 5 }).collect();
|
||||
console.log("---------------------------------------------------")
|
||||
console.log("Key | Value")
|
||||
console.log("---------------------------------------------------")
|
||||
console.log(items.map((e) => `${e.payload.key} | ${e.payload.value}`).join("\n"));
|
||||
console.log("---------------------------------------------------")
|
||||
console.log(`Found ${items.length} items from ${c}\n`);
|
||||
|
||||
running = false;
|
||||
count ++;
|
||||
}
|
||||
}), 500);
|
||||
|
||||
} catch(e) {
|
||||
console.error("error:", e);
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}))();
|
||||
|
||||
module.exports = run;
|
@ -1,62 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('asyncawait/async');
|
||||
var OrbitClient = require('../src/OrbitClient');
|
||||
var Timer = require('./Timer');
|
||||
|
||||
var host = 'localhost:3006';
|
||||
var username = 'testrunner';
|
||||
var password = '';
|
||||
|
||||
let run = (async(() => {
|
||||
try {
|
||||
var channel = 'hello-world-test1'
|
||||
|
||||
// Connect
|
||||
var orbit = OrbitClient.connect(host, username, password);
|
||||
|
||||
// Delete channel and its data
|
||||
var result = orbit.channel(channel, '').delete();
|
||||
|
||||
// Add the first message and delete it immediately
|
||||
// orbit.channel(channel, '').put("hello world!");
|
||||
// var e = orbit.channel(channel, '').iterator({ limit: -1 }).collect()[0].hash;
|
||||
// orbit.channel(channel, '').remove(e);
|
||||
orbit.channel(channel, '').put("key two", "hello world!!!");
|
||||
|
||||
var messages = 10;
|
||||
var i = 1;
|
||||
while(i <= messages) {
|
||||
var timer = new Timer(true);
|
||||
// Send a message
|
||||
// var head = orbit.channel(channel, '').send(JSON.stringify({ omg: "hello" }));
|
||||
var head = orbit.channel(channel, '').put("key one", "hello world " + i);
|
||||
console.log(i, head, timer.stop() + "ms");
|
||||
|
||||
if(i === 4) {
|
||||
console.log("remove", head);
|
||||
// orbit.channel(channel, '').remove(head);
|
||||
}
|
||||
|
||||
i ++;
|
||||
}
|
||||
|
||||
var items = orbit.channel(channel, '').iterator({ limit: -1 }).collect();
|
||||
// console.log(items);
|
||||
var e = orbit.channel(channel, '').iterator({ limit: -1 }).collect();
|
||||
orbit.channel(channel, '').remove({ key: "key one" });
|
||||
// orbit.channel(channel, '').remove(items[2].hash); // 97
|
||||
// orbit.channel(channel, '').remove(items[3].hash); // 96
|
||||
// orbit.channel(channel, '').remove(items[66].hash); // 34
|
||||
// orbit.channel(channel, '').remove(items[items.length - 10].hash); // 11
|
||||
// orbit.channel(channel, '').remove(items[items.length - 9].hash); // 10
|
||||
// orbit.channel(channel, '').remove(items[items.length - 8].hash); // 9
|
||||
|
||||
} catch(e) {
|
||||
console.error("error:", e);
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}))();
|
||||
|
||||
module.exports = run;
|
53
examples/writer.js
Normal file
53
examples/writer.js
Normal file
@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('asyncawait/async');
|
||||
var await = require('asyncawait/await');
|
||||
var OrbitClient = require('../src/OrbitClient');
|
||||
var Timer = require('./Timer');
|
||||
|
||||
// Redis
|
||||
var host = 'localhost';
|
||||
var port = 6379;
|
||||
|
||||
var username = process.argv[2] ? process.argv[2] : 'DankoJones';
|
||||
var password = '';
|
||||
|
||||
let run = (async(() => {
|
||||
try {
|
||||
var orbit = OrbitClient.connect(host, port, username, password);
|
||||
const c1 = 'c1';
|
||||
let channel;
|
||||
|
||||
let count = 1;
|
||||
let id = 'Log: Query '
|
||||
|
||||
setInterval(async(() => {
|
||||
if(channel) {
|
||||
channel.add(username + " " + count);
|
||||
count ++;
|
||||
}
|
||||
}), process.argv[3] ? process.argv[3] : 1000);
|
||||
|
||||
setInterval(async(() => {
|
||||
if(!channel) {
|
||||
channel = orbit.channel(c1);
|
||||
console.log("subscribed to pubsub topic '" + c1);
|
||||
setTimeout(() => {
|
||||
if(channel) {
|
||||
console.log("leave");
|
||||
channel.leave();
|
||||
channel = null;
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}), 5000);
|
||||
|
||||
|
||||
} catch(e) {
|
||||
console.error("error:", e);
|
||||
console.error(e.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}))();
|
||||
|
||||
module.exports = run;
|
@ -10,14 +10,12 @@
|
||||
"main": "src/OrbitClient.js",
|
||||
"dependencies": {
|
||||
"asyncawait": "^1.0.1",
|
||||
"bluebird": "^3.1.1",
|
||||
"bs58": "^3.0.0",
|
||||
"lodash": "^4.3.0",
|
||||
"orbit-common": "^0.1.0",
|
||||
"unirest": "^0.4.2"
|
||||
"redis": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^2.3.4",
|
||||
"orbit-server": "^0.1.2"
|
||||
"mocha": "^2.3.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
|
@ -1,99 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('asyncawait/async');
|
||||
var await = require('asyncawait/await');
|
||||
var ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
var Keystore = require('orbit-common/lib/Keystore');
|
||||
var Encryption = require('orbit-common/lib/Encryption');
|
||||
var HashCache = require('./HashCacheClient');
|
||||
var HashCacheItem = require('./HashCacheItem').EncryptedHashCacheItem;
|
||||
var HashCacheOps = require('./HashCacheOps');
|
||||
var MemoryCache = require('./MemoryCache');
|
||||
|
||||
const pubkey = Keystore.getKeys().publicKey;
|
||||
const privkey = Keystore.getKeys().privateKey;
|
||||
|
||||
const DefaultAmount = 1;
|
||||
|
||||
class Aggregator {
|
||||
static fetchRecursive(ipfs, hash, password, options, currentAmount, deleted) {
|
||||
const opts = {
|
||||
amount: options.amount ? options.amount : DefaultAmount,
|
||||
last: options.last ? options.last : null,
|
||||
key: options.key ? options.key : null
|
||||
};
|
||||
|
||||
let result = [];
|
||||
let handledItems = deleted ? deleted : [];
|
||||
|
||||
if(!currentAmount) currentAmount = 0;
|
||||
|
||||
const item = await (this._fetchOne(ipfs, hash, password));
|
||||
|
||||
if(item) {
|
||||
if((item.op === HashCacheOps.Put || item.op === HashCacheOps.Add) && !this._contains(handledItems, item.key)) {
|
||||
if(!opts.key || (opts.key && opts.key === item.key)) {
|
||||
result.push({ hash: hash, item: item });
|
||||
currentAmount ++;
|
||||
handledItems.push(item.target);
|
||||
}
|
||||
} else if(item.op === HashCacheOps.Delete) {
|
||||
handledItems.push(item.target);
|
||||
}
|
||||
|
||||
if(opts.key && item.key === opts.key)
|
||||
return result;
|
||||
|
||||
if(opts.last && hash === opts.last)
|
||||
return result;
|
||||
|
||||
if(!opts.last && opts.amount > -1 && currentAmount >= opts.amount)
|
||||
return result;
|
||||
|
||||
if(item.next) {
|
||||
const items = this.fetchRecursive(ipfs, item.next, password, opts, currentAmount, handledItems);
|
||||
result = result.concat(items);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static _fetchOne(ipfs, hash, password) {
|
||||
// 1. Try fetching from memory
|
||||
let data = MemoryCache.get(hash);
|
||||
// TODO: 2. Try fetching from local cache
|
||||
|
||||
// 3. Fetch from network
|
||||
if(!data)
|
||||
data = await (ipfsAPI.getObject(ipfs, hash));
|
||||
|
||||
// Cache the fetched item (encrypted)
|
||||
MemoryCache.put(hash, data);
|
||||
|
||||
// Decrypt the item
|
||||
let item = HashCacheItem.fromEncrypted(data, pubkey, privkey, password);
|
||||
|
||||
// TODO: add possibility to fetch content separately
|
||||
// fetch and decrypt content
|
||||
if(item.op === HashCacheOps.Add || item.op === HashCacheOps.Put) {
|
||||
let payload = MemoryCache.get(item.target);
|
||||
if(!payload)
|
||||
payload = await (ipfsAPI.getObject(ipfs, item.target));
|
||||
|
||||
MemoryCache.put(item.target, payload);
|
||||
|
||||
const contentEnc = JSON.parse(payload.Data)["content"];
|
||||
const contentDec = Encryption.decrypt(contentEnc, privkey, 'TODO: pubkey');
|
||||
item.Payload = contentDec;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
static _contains(src, e) {
|
||||
return src.filter((f) => f.toString() === e.toString()).length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Aggregator;
|
@ -1,65 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
var unirest = require('unirest')
|
||||
|
||||
class Request {
|
||||
constructor() {
|
||||
this.url = '';
|
||||
this.method = 'GET';
|
||||
this.headers = {};
|
||||
this.body = {};
|
||||
}
|
||||
|
||||
get(url) {
|
||||
return this._init('GET', url);
|
||||
}
|
||||
|
||||
post(url) {
|
||||
return this._init('POST', url);
|
||||
}
|
||||
|
||||
put(url) {
|
||||
return this._init('PUT', url);
|
||||
}
|
||||
|
||||
delete(url) {
|
||||
return this._init('DELETE', url);
|
||||
}
|
||||
|
||||
_init(method, url) {
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
this.body = {};
|
||||
this.headers = {};
|
||||
return this;
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this.headers[key] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
send(body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
end(callback) {
|
||||
if(!this.url.startsWith("http"))
|
||||
this.url = "http://" + this.url
|
||||
|
||||
unirest(this.method, this.url)
|
||||
.headers(this.headers)
|
||||
.type('application/json')
|
||||
.send(this.body)
|
||||
.end((res) => {
|
||||
if(res.error)
|
||||
callback(res.body ? res.body.message : "Connection refused", null);
|
||||
else
|
||||
callback(null, res.body);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new Request();
|
94
src/DataStore.js
Normal file
94
src/DataStore.js
Normal file
@ -0,0 +1,94 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const OrbitList = require('./list/OrbitList');
|
||||
const HashCacheOps = require('./HashCacheOps');
|
||||
|
||||
const DefaultAmount = 1;
|
||||
|
||||
class DataStore {
|
||||
constructor(id, ipfs) {
|
||||
this._ipfs = ipfs;
|
||||
this.list = new OrbitList(id, this._ipfs);
|
||||
}
|
||||
|
||||
add(hash) {
|
||||
return this.list.add(hash);
|
||||
}
|
||||
|
||||
join(other) {
|
||||
this.list.join(other);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.list.clear();
|
||||
}
|
||||
|
||||
get(options) {
|
||||
return this._fetchRecursive(options);
|
||||
}
|
||||
|
||||
_fetchOne(index) {
|
||||
const item = this.list.items[this.list.items.length - index - 1];
|
||||
if(item) {
|
||||
await(item.getPayload());
|
||||
const f = item.compact();
|
||||
return { hash: f.data, payload: f.Payload };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_fetchRecursive(options, currentAmount, deleted, res) {
|
||||
const opts = {
|
||||
amount: options && options.amount ? options.amount : DefaultAmount,
|
||||
first: options && options.first ? options.first : null,
|
||||
last: options && options.last ? options.last : null,
|
||||
key: options && options.key ? options.key : null
|
||||
};
|
||||
|
||||
let result = res ? res : [];
|
||||
let handledItems = deleted ? deleted : [];
|
||||
|
||||
if(!currentAmount) currentAmount = 0;
|
||||
|
||||
const item = this._fetchOne(currentAmount);
|
||||
|
||||
if(item && item.payload) {
|
||||
const wasHandled = _.includes(handledItems, item.payload.key);
|
||||
if((item.payload.op === HashCacheOps.Put || item.payload.op === HashCacheOps.Add) && !wasHandled) {
|
||||
if((!opts.key || (opts.key && opts.key === item.payload.key)) &&
|
||||
(!opts.first || (opts.first && (opts.first === item.payload.key && result.length === 0))
|
||||
|| (opts.first && (opts.first !== item.payload.key && result.length > 0))))
|
||||
{
|
||||
result.push(item);
|
||||
handledItems.push(item.payload.key);
|
||||
}
|
||||
} else if(item.payload.op === HashCacheOps.Delete) {
|
||||
handledItems.push(item.payload.key);
|
||||
}
|
||||
|
||||
currentAmount ++;
|
||||
|
||||
if(opts.key && item.payload.key === opts.key)
|
||||
return result;
|
||||
|
||||
if(opts.last && item.payload.key === opts.last)
|
||||
return result;
|
||||
|
||||
if(!opts.last && opts.amount > -1 && result.length >= opts.amount)
|
||||
return result;
|
||||
|
||||
if(currentAmount >= this.list.items.length)
|
||||
return result;
|
||||
|
||||
result = this._fetchRecursive(opts, currentAmount, handledItems, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DataStore;
|
@ -1,85 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
var request = require('./BetterRequest');
|
||||
|
||||
class HashCacheClient {
|
||||
constructor(host, credentials, info) {
|
||||
this.host = host
|
||||
this.credentials = credentials
|
||||
this.info = info;
|
||||
this.linkedList = this.linkedList.bind(this)
|
||||
}
|
||||
|
||||
linkedList(hash, password) {
|
||||
return {
|
||||
head: () => this._get(hash, password),
|
||||
add: (head) => this._add(hash, password, head),
|
||||
setMode: (mode) => this._setModes(hash, password, mode),
|
||||
delete: () => this._delete(hash, password)
|
||||
}
|
||||
}
|
||||
|
||||
_get(hash, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request
|
||||
.get(this.host + '/channel/' + hash)
|
||||
.set('Authorization', this.credentials)
|
||||
.send({ password: password })
|
||||
.end((err, res) => { this._resolveRequest(err, res, resolve, reject) });
|
||||
})
|
||||
}
|
||||
|
||||
_add(hash, password, head) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request
|
||||
.put(this.host + '/channel/' + hash + '/add')
|
||||
.set('Authorization', this.credentials)
|
||||
.send({ head: head, password: password })
|
||||
.end((err, res) => { this._resolveRequest(err, res, resolve, reject) });
|
||||
})
|
||||
}
|
||||
|
||||
_setModes(hash, password, modes) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request
|
||||
.post(this.host + '/channel/' + hash)
|
||||
.set('Authorization', this.credentials)
|
||||
.send({ modes: modes, password: password })
|
||||
.end((err, res) => { this._resolveRequest(err, res, resolve, reject) });
|
||||
})
|
||||
}
|
||||
|
||||
_delete(hash, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request
|
||||
.delete(this.host + '/channel/' + hash)
|
||||
.set('Authorization', this.credentials)
|
||||
.send({ password: password })
|
||||
.end((err, res) => { this._resolveRequest(err, res, resolve, reject) });
|
||||
})
|
||||
}
|
||||
|
||||
_resolveRequest(err, res, resolve, reject) {
|
||||
if(err)
|
||||
reject(res ? res : err.toString());
|
||||
else
|
||||
resolve(res ? res : {});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
connect: (host, username, password) => {
|
||||
var credentials = `Basic ${username}=${password}`;
|
||||
return new Promise((resolve, reject) => {
|
||||
request
|
||||
.post(host + '/register')
|
||||
.set('Authorization', credentials)
|
||||
.end((err, res) => {
|
||||
if(err)
|
||||
reject(res ? res.body.message : err.toString())
|
||||
else
|
||||
resolve(new HashCacheClient(host, credentials, res));
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -2,6 +2,15 @@
|
||||
|
||||
const Encryption = require('orbit-common/lib/Encryption');
|
||||
|
||||
class OrbitDBItem {
|
||||
constructor(operation, key, value, metaInfo) {
|
||||
this.op = operation;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.meta = metaInfo;
|
||||
}
|
||||
}
|
||||
|
||||
class HashCacheItem {
|
||||
constructor(operation, key, sequenceNumber, targetHash, metaInfo, next) {
|
||||
this.op = operation;
|
||||
@ -15,7 +24,11 @@ class HashCacheItem {
|
||||
|
||||
class EncryptedHashCacheItem extends HashCacheItem {
|
||||
constructor(operation, key, sequenceNumber, targetHash, metaInfo, next, publicKey, privateKey, salt) {
|
||||
if(key)
|
||||
key = Encryption.encrypt(key, privateKey, publicKey);
|
||||
|
||||
super(operation, key, sequenceNumber, targetHash, metaInfo, next);
|
||||
|
||||
try {
|
||||
this.pubkey = publicKey;
|
||||
this.target = Encryption.encrypt(targetHash, privateKey, publicKey);
|
||||
@ -42,12 +55,16 @@ class EncryptedHashCacheItem extends HashCacheItem {
|
||||
data.target = targetDec;
|
||||
data.meta = JSON.parse(metaDec);
|
||||
|
||||
if(data.key)
|
||||
data.key = Encryption.decrypt(data.key, privateKey, 'TODO: pubkey');
|
||||
|
||||
const item = new HashCacheItem(data.op, data.key, data.seq, data.target, data.meta, next, publicKey, privateKey, salt);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
OrbitDBItem: OrbitDBItem,
|
||||
HashCacheItem: HashCacheItem,
|
||||
EncryptedHashCacheItem: EncryptedHashCacheItem
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
let ItemTypes = {
|
||||
const ItemTypes = {
|
||||
Message: "text",
|
||||
Snippet: "snippet",
|
||||
File: "file",
|
||||
|
@ -1,15 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
let items = {};
|
||||
|
||||
class MemoryCache {
|
||||
static put(hash, item) {
|
||||
items[hash] = item;
|
||||
}
|
||||
|
||||
static get(hash) {
|
||||
return items[hash];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MemoryCache;
|
@ -1,9 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
class MetaInfo {
|
||||
constructor(type, size, ts) {
|
||||
constructor(type, size, from, ts) {
|
||||
this.type = type;
|
||||
this.size = size;
|
||||
this.from = from;
|
||||
this.ts = ts;
|
||||
}
|
||||
}
|
||||
|
@ -1,44 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('asyncawait/async');
|
||||
var await = require('asyncawait/await');
|
||||
var Keystore = require('orbit-common/lib/Keystore');
|
||||
var Encryption = require('orbit-common/lib/Encryption');
|
||||
var ipfsDaemon = require('orbit-common/lib/ipfs-daemon');
|
||||
var ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
var HashCache = require('./HashCacheClient');
|
||||
var HashCacheItem = require('./HashCacheItem').EncryptedHashCacheItem;
|
||||
var HashCacheOps = require('./HashCacheOps');
|
||||
var ItemTypes = require('./ItemTypes');
|
||||
var MetaInfo = require('./MetaInfo');
|
||||
var Post = require('./Post');
|
||||
var Aggregator = require('./Aggregator');
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const Keystore = require('orbit-common/lib/Keystore');
|
||||
const Encryption = require('orbit-common/lib/Encryption');
|
||||
const ipfsDaemon = require('orbit-common/lib/ipfs-daemon');
|
||||
const ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
const OrbitDBItem = require('./HashCacheItem').OrbitDBItem;
|
||||
const HashCacheOps = require('./HashCacheOps');
|
||||
const ItemTypes = require('./ItemTypes');
|
||||
const MetaInfo = require('./MetaInfo');
|
||||
const Post = require('./Post');
|
||||
const PubSub = require('./Pubsub');
|
||||
const List = require('./list/OrbitList');
|
||||
const DataStore = require('./DataStore');
|
||||
|
||||
var pubkey = Keystore.getKeys().publicKey;
|
||||
var privkey = Keystore.getKeys().privateKey;
|
||||
const pubkey = Keystore.getKeys().publicKey;
|
||||
const privkey = Keystore.getKeys().privateKey;
|
||||
|
||||
class OrbitClient {
|
||||
constructor(ipfs) {
|
||||
this.ipfs = ipfs;
|
||||
this.network = {};
|
||||
this._ipfs = ipfs;
|
||||
this.user = null;
|
||||
}
|
||||
|
||||
channel(hash, password) {
|
||||
if(password === undefined) password = '';
|
||||
|
||||
this._pubsub.subscribe(hash, password, async((hash, message) => {
|
||||
const other = await(List.fromIpfsHash(this._ipfs, message));
|
||||
if(other.id !== this.user.username)
|
||||
this._store.join(other);
|
||||
}));
|
||||
|
||||
return {
|
||||
info: (options) => this._info(hash, password),
|
||||
delete: () => this._deleteChannel(hash, password),
|
||||
iterator: (options) => this._iterator(hash, password, options),
|
||||
setMode: (mode) => this._setMode(hash, password, mode),
|
||||
add: (data) => this._add(hash, password, data),
|
||||
//TODO: tests
|
||||
remove: (options) => this._remove(hash, password, options),
|
||||
del: (key) => this._remove(hash, password, key),
|
||||
put: (key, data) => this._put(hash, password, key, data),
|
||||
get: (key, options) => {
|
||||
const items = this._iterator(hash, password, { key: key }).collect();
|
||||
return items[0] ? items[0].item.Payload : null;
|
||||
return items[0] ? items[0].payload.value : null;
|
||||
},
|
||||
//TODO: tests
|
||||
leave: () => this._pubsub.unsubscribe(hash)
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +65,6 @@ class OrbitClient {
|
||||
return item;
|
||||
},
|
||||
collect: () => messages
|
||||
// TODO: add first() and last() ?
|
||||
}
|
||||
|
||||
return iterator;
|
||||
@ -78,141 +84,91 @@ class OrbitClient {
|
||||
const reverse = options.reverse ? options.reverse : false;
|
||||
const key = options.key ? options.key : null;
|
||||
|
||||
let startFromHash;
|
||||
if(lt || lte) {
|
||||
startFromHash = lte ? lte : lt;
|
||||
} else {
|
||||
var channel = await (this.client.linkedList(channel, password).head());
|
||||
startFromHash = channel.head ? channel.head : null;
|
||||
}
|
||||
|
||||
if((gt || lt) && limit > -1) limit += 1;
|
||||
|
||||
if(startFromHash) {
|
||||
const opts = {
|
||||
amount: limit,
|
||||
first: lte ? lte : lt,
|
||||
last: gte ? gte : gt,
|
||||
key: key
|
||||
};
|
||||
|
||||
// Get messages
|
||||
messages = Aggregator.fetchRecursive(this.ipfs, startFromHash, password, opts);
|
||||
|
||||
// Slice the array
|
||||
let startIndex = 0;
|
||||
let endIndex = messages.length;
|
||||
if(limit < 0) {
|
||||
endIndex = messages.length - (gt ? 1 : 0);
|
||||
} else {
|
||||
startIndex = Math.max(0, messages.length - limit);
|
||||
endIndex = messages.length - ((gt || lt) ? 1 : 0);
|
||||
}
|
||||
messages = await(this._store.get(opts));
|
||||
|
||||
// Remove the first/last item if greater/lesser than is set
|
||||
let startIndex = lt ? 1 : 0;
|
||||
let endIndex = gt ? messages.length - 1 : messages.length;
|
||||
messages = messages.slice(startIndex, endIndex)
|
||||
}
|
||||
|
||||
if(reverse) messages.reverse();
|
||||
if(!reverse) messages.reverse();
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
_publish(data) {
|
||||
let post = new Post(data);
|
||||
post.encrypt(privkey, pubkey);
|
||||
return await (ipfsAPI.putObject(this.ipfs, JSON.stringify(post)));
|
||||
// post.encrypt(privkey, pubkey);
|
||||
return await (ipfsAPI.putObject(this._ipfs, JSON.stringify(post)));
|
||||
}
|
||||
|
||||
_createMessage(channel, password, operation, key, target) {
|
||||
// Get the current channel head and bump the sequence number
|
||||
let seq = 0;
|
||||
const currentHead = await(this.client.linkedList(channel, password).head())
|
||||
if(currentHead.head) {
|
||||
const headItem = await (ipfsAPI.getObject(this.ipfs, currentHead.head));
|
||||
seq = JSON.parse(headItem.Data)["seq"] + 1;
|
||||
}
|
||||
|
||||
// Create meta info
|
||||
_createMessage(channel, password, operation, key, value) {
|
||||
const size = -1;
|
||||
const metaInfo = new MetaInfo(ItemTypes.Message, size, new Date().getTime());
|
||||
|
||||
// Create the hash cache item
|
||||
const hcItem = new HashCacheItem(operation, key, seq, target, metaInfo, null, pubkey, privkey, password);
|
||||
|
||||
// Save the item to ipfs
|
||||
const data = await (ipfsAPI.putObject(this.ipfs, JSON.stringify(hcItem)));
|
||||
let newHead = { Hash: data.Hash };
|
||||
|
||||
// If this is not the first item in the channel, patch with the previous (ie. link as next)
|
||||
if(seq > 0)
|
||||
newHead = await (ipfsAPI.patchObject(this.ipfs, data.Hash, currentHead.head));
|
||||
|
||||
return newHead;
|
||||
const meta = new MetaInfo(ItemTypes.Message, size, this.user.username, new Date().getTime());
|
||||
const item = new OrbitDBItem(operation, key, value, meta);
|
||||
const data = await (ipfsAPI.putObject(this._ipfs, JSON.stringify(item)));
|
||||
return data.Hash;
|
||||
}
|
||||
|
||||
/* DB Operations */
|
||||
_add(channel, password, data) {
|
||||
const post = this._publish(data);
|
||||
const key = post.Hash;
|
||||
this._createOperation(channel, password, HashCacheOps.Add, key, post.Hash);
|
||||
return key;
|
||||
return await(this._createOperation(channel, password, HashCacheOps.Add, key, post.Hash, data));
|
||||
}
|
||||
|
||||
_put(channel, password, key, data) {
|
||||
const post = this._publish(data);
|
||||
return this._createOperation(channel, password, HashCacheOps.Put, key, post.Hash);
|
||||
return await(this._createOperation(channel, password, HashCacheOps.Put, key, post.Hash));
|
||||
}
|
||||
|
||||
_remove(channel, password, options) {
|
||||
const key = null;
|
||||
const target = options.key ? options.key : (options.hash ? options.hash : null);
|
||||
return this._createOperation(channel, password, HashCacheOps.Delete, key, target);
|
||||
_remove(channel, password, hash) {
|
||||
return await(this._createOperation(channel, password, HashCacheOps.Delete, hash, null));
|
||||
}
|
||||
|
||||
_createOperation(channel, password, operation, key, value) {
|
||||
const message = this._createMessage(channel, password, operation, key, value);
|
||||
await(this.client.linkedList(channel, password).add(message.Hash));
|
||||
return message.Hash;
|
||||
_createOperation(channel, password, operation, key, value, data) {
|
||||
const hash = this._createMessage(channel, password, operation, key, value);
|
||||
const res = await(this._store.add(hash));
|
||||
const listHash = await(this._store.list.getIpfsHash());
|
||||
await(this._pubsub.publish(channel, listHash));
|
||||
// return res;
|
||||
return key;
|
||||
}
|
||||
|
||||
_deleteChannel(channel, password) {
|
||||
await(this.client.linkedList(channel, password).delete());
|
||||
this._store.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
_setMode(channel, password, modes) {
|
||||
let m = [];
|
||||
if(typeof modes !== 'Array')
|
||||
m.push(modes);
|
||||
else
|
||||
m = modes;
|
||||
const res = await(this.client.linkedList(channel, password).setMode(m));
|
||||
return res.modes;
|
||||
}
|
||||
|
||||
_info(channel, password) {
|
||||
return await(this.client.linkedList(channel, password).head());
|
||||
}
|
||||
|
||||
_connect(host, username, password) {
|
||||
this.client = await(HashCache.connect(host, username, password));
|
||||
this.user = this.client.info.user;
|
||||
this.network = {
|
||||
id: this.client.info.networkId,
|
||||
name: this.client.info.name,
|
||||
config: this.client.info.config
|
||||
};
|
||||
_connect(host, port, username, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.user = { username: username, id: 'hello-todo' }
|
||||
this._pubsub = new PubSub(this._ipfs, host, port, username, password);
|
||||
this._store = new DataStore(username, this._ipfs);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class OrbitClientFactory {
|
||||
static connect(host, username, password, ipfs) {
|
||||
static connect(host, port, username, password, ipfs) {
|
||||
if(!ipfs) {
|
||||
let ipfsd = await(ipfsDaemon());
|
||||
ipfs = ipfsd.daemon;
|
||||
}
|
||||
|
||||
const client = new OrbitClient(ipfs);
|
||||
await(client._connect(host, username, password))
|
||||
await(client._connect(host, port, username, password))
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var Encryption = require('orbit-common/lib/Encryption');
|
||||
const Encryption = require('orbit-common/lib/Encryption');
|
||||
|
||||
class Post {
|
||||
constructor(content) {
|
||||
|
52
src/PubSub.js
Normal file
52
src/PubSub.js
Normal file
@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
const redis = require("redis");
|
||||
const List = require('./list/OrbitList');
|
||||
|
||||
class Pubsub2 {
|
||||
constructor(ipfs, host, port, username, password) {
|
||||
this.ipfs = ipfs;
|
||||
this._subscriptions = {};
|
||||
this.client1 = redis.createClient({ host: host, port: port });
|
||||
this.client2 = redis.createClient({ host: host, port: port });
|
||||
this.client1.on("message", this._handleMessage.bind(this));
|
||||
// this.client1.on('connect', () => console.log('redis connected'));
|
||||
// this.client1.on("subscribe", (channel, count) => console.log(`subscribed to ${channel}`));
|
||||
}
|
||||
|
||||
subscribe(hash, password, callback) {
|
||||
if(!this._subscriptions[hash] || this._subscriptions[hash].password !== password) {
|
||||
this._subscriptions[hash] = {
|
||||
password: password,
|
||||
head: null,
|
||||
callback: callback
|
||||
};
|
||||
this.client1.subscribe(hash);
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribe(hash) {
|
||||
delete this._subscriptions[hash];
|
||||
this.client1.unsubscribe();
|
||||
this.client2.unsubscribe();
|
||||
}
|
||||
|
||||
publish(hash, message) {
|
||||
this.client2.publish(hash, message);
|
||||
}
|
||||
|
||||
latest(hash) {
|
||||
return { head: this._subscriptions[hash] ? this._subscriptions[hash].head : null };
|
||||
}
|
||||
|
||||
_handleMessage(hash, message) {
|
||||
if(this._subscriptions[hash]) {
|
||||
this._subscriptions[hash].head = message;
|
||||
|
||||
if(this._subscriptions[hash].callback)
|
||||
this._subscriptions[hash].callback(hash, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Pubsub2;
|
83
src/list/List.js
Normal file
83
src/list/List.js
Normal file
@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const Node = require('./Node');
|
||||
|
||||
class List {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
this.seq = 0;
|
||||
this.ver = 0;
|
||||
this._items = [];
|
||||
this._currentBatch = [];
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this._items.concat(this._currentBatch);
|
||||
}
|
||||
|
||||
add(data) {
|
||||
const heads = this._findHeads(this.items);
|
||||
const node = new Node(this.id, this.seq, this.ver, data, heads);
|
||||
this._currentBatch.push(node);
|
||||
this.ver ++;
|
||||
}
|
||||
|
||||
join(other) {
|
||||
this.seq = (other.seq && other.seq > this.seq ? other.seq : this.seq) + 1;
|
||||
this.ver = 0;
|
||||
const current = _.differenceWith(this._currentBatch, this._items, this._equals);
|
||||
const others = _.differenceWith(other.items, this._items, this._equals);
|
||||
const final = _.unionWith(current, others, this._equals);
|
||||
this._items = this._items.concat(final);
|
||||
this._currentBatch = [];
|
||||
}
|
||||
|
||||
_findHeads(list) {
|
||||
const grouped = _.groupBy(list, 'id');
|
||||
const heads = Object.keys(grouped).map((g) => _.last(grouped[g]));
|
||||
const cleaned = heads.filter((e) => !this._isReferencedInChain(list, e));
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
_isReferencedInChain(all, item) {
|
||||
let isReferenced = _.findLast(all, (e) => this._references(e, item)) !== undefined;
|
||||
return isReferenced;
|
||||
}
|
||||
|
||||
_equals(a, b) {
|
||||
return a.id == b.id && a.seq == b.seq && a.ver == b.ver;
|
||||
}
|
||||
|
||||
_references(a, b) {
|
||||
for(let i = 0; i < a.next.length; i ++) {
|
||||
if(b.compactId === a.next[i])
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static fromJson(json) {
|
||||
let list = new List(json.id);
|
||||
list.seq = json.seq;
|
||||
list.ver = json.ver;
|
||||
list._items = _.uniqWith(json.items.map((f) => new Node(f.id, f.seq, f.ver, f.data, f.next)), _.isEqual);
|
||||
return list;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
id: this.id,
|
||||
seq: this.seq,
|
||||
ver: this.ver,
|
||||
items: this._currentBatch.map((f) => f.compact())
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
const items = this.items.map((f) => JSON.stringify(f.compact())).join("\n");
|
||||
return `id: ${this.id}, seq: ${this.seq}, ver: ${this.ver}, items:\n${items}`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = List;
|
21
src/list/Node.js
Normal file
21
src/list/Node.js
Normal file
@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
class Node {
|
||||
constructor(id, seq, ver, data, next) {
|
||||
this.id = id;
|
||||
this.seq = seq;
|
||||
this.ver = ver;
|
||||
this.data = data || null;
|
||||
this.next = next ? next.map((f) => f.compactId ? f.compactId : f) : [];
|
||||
}
|
||||
|
||||
get compactId() {
|
||||
return "" + this.id + "." + this.seq + "." + this.ver;
|
||||
}
|
||||
|
||||
compact() {
|
||||
return { id: this.id, seq: this.seq, ver: this.ver, data: this.data, next: this.next }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Node;
|
69
src/list/OrbitList.js
Normal file
69
src/list/OrbitList.js
Normal file
@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
const List = require('./List');
|
||||
const Node = require('./OrbitNode');
|
||||
|
||||
const MaxBatchSize = 200;
|
||||
|
||||
class OrbitList extends List {
|
||||
constructor(id, ipfs) {
|
||||
super(id);
|
||||
this._ipfs = ipfs;
|
||||
this.hash = null;
|
||||
}
|
||||
|
||||
add(data) {
|
||||
const heads = super._findHeads(this.items);
|
||||
const node = new Node(this._ipfs, this.id, this.seq, this.ver, data, heads);
|
||||
this._currentBatch.push(node);
|
||||
this.ver ++;
|
||||
|
||||
if(this.ver >= MaxBatchSize)
|
||||
this._commit();
|
||||
|
||||
return node.ipfsHash;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._items = [];
|
||||
this._currentBatch = [];
|
||||
}
|
||||
|
||||
getIpfsHash() {
|
||||
const list = await(ipfsAPI.putObject(this._ipfs, JSON.stringify(this.toJson())));
|
||||
return list.Hash;
|
||||
}
|
||||
|
||||
static fromIpfsHash(ipfs, hash) {
|
||||
const l = await(ipfsAPI.getObject(ipfs, hash));
|
||||
const list = OrbitList.fromJson(ipfs, JSON.parse(l.Data));
|
||||
return list;
|
||||
}
|
||||
|
||||
static fromJson(ipfs, json) {
|
||||
let list = new List(json.id);
|
||||
list.seq = json.seq;
|
||||
list.ver = json.ver;
|
||||
// list._items = _.uniqWith(json.items.map((f) => new Node(ipfs, f.id, f.seq, f.ver, f.data, f.next)), _.isEqual);
|
||||
list._items = json.items.map((f) => new Node(ipfs, f.id, f.seq, f.ver, f.data, f.next));
|
||||
return list;
|
||||
}
|
||||
|
||||
static get batchSize() {
|
||||
return MaxBatchSize;
|
||||
}
|
||||
|
||||
_commit() {
|
||||
const current = _.differenceWith(this._currentBatch, this._items, this._equals);
|
||||
this._items = this._items.concat(current);
|
||||
this._currentBatch = [];
|
||||
this.ver = 0;
|
||||
this.seq ++;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrbitList;
|
49
src/list/OrbitNode.js
Normal file
49
src/list/OrbitNode.js
Normal file
@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
const Node = require('./Node');
|
||||
|
||||
class OrbitNode extends Node {
|
||||
constructor(ipfs, id, seq, ver, data, next) {
|
||||
super(id, seq, ver, data, next);
|
||||
this.hash = null;
|
||||
this._ipfs = ipfs;
|
||||
}
|
||||
|
||||
get compactId() {
|
||||
if(!this.hash) {
|
||||
const t = this.compact();
|
||||
const r = await(ipfsAPI.putObject(this._ipfs, JSON.stringify(t)));
|
||||
this.hash = r.Hash;
|
||||
}
|
||||
return "" + this.id + "." + this.seq + "." + this.ver + "." + this.hash;
|
||||
}
|
||||
|
||||
get ipfsHash() {
|
||||
if(!this.hash) {
|
||||
const t = this.compact();
|
||||
const r = await(ipfsAPI.putObject(this._ipfs, JSON.stringify(t)));
|
||||
this.hash = r.Hash;
|
||||
}
|
||||
return this.hash;
|
||||
}
|
||||
|
||||
getPayload() {
|
||||
if(!this.Payload) {
|
||||
const payload = await(ipfsAPI.getObject(this._ipfs, this.data));
|
||||
this.Payload = JSON.parse(payload.Data);
|
||||
if(this.Payload.value) {
|
||||
const value = await(ipfsAPI.getObject(this._ipfs, this.Payload.value));
|
||||
this.Payload.value = JSON.parse(value.Data)["content"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compact() {
|
||||
return { id: this.id, seq: this.seq, ver: this.ver, data: this.data, next: this.next, Payload: this.Payload }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrbitNode;
|
61
test/list-node-tests.js
Normal file
61
test/list-node-tests.js
Normal file
@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const List = require('../src/list/List');
|
||||
const Node = require('../src/list/Node');
|
||||
|
||||
describe('Node', () => {
|
||||
describe('Constructor', () => {
|
||||
it('initializes member variables', (done) => {
|
||||
const node = new Node('A', 0, 0, 'hello', []);
|
||||
assert.equal(node.id, 'A');
|
||||
assert.equal(node.seq, 0);
|
||||
assert.equal(node.ver, 0);
|
||||
assert.equal(node.data, 'hello');
|
||||
assert.equal(node.next instanceof Array, true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('initializes member variables without specified data and next', (done) => {
|
||||
const node = new Node('A', 0, 0);
|
||||
assert.equal(node.id, 'A');
|
||||
assert.equal(node.seq, 0);
|
||||
assert.equal(node.ver, 0);
|
||||
assert.equal(node.data, null);
|
||||
assert.equal(node.next instanceof Array, true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('compactId', () => {
|
||||
it('presents the node as a string with id, sequence and version', (done) => {
|
||||
const node1 = new Node('A', 0, 0);
|
||||
const node2 = new Node('B', 123, 456);
|
||||
assert.equal(node1.compactId, 'A.0.0');
|
||||
assert.equal(node2.compactId, 'B.123.456');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('compact', () => {
|
||||
it('presents the node as a compacted object', (done) => {
|
||||
const node1 = new Node('A', 0, 0, 'hello');
|
||||
const node2 = new Node('B', 0, 0, 'hello', [node1]);
|
||||
const compacted1 = node1.compact();
|
||||
const compacted2 = node2.compact();
|
||||
|
||||
assert.notEqual(compacted1, null);
|
||||
assert.equal(compacted1.id, 'A');
|
||||
assert.equal(compacted1.seq, 0);
|
||||
assert.equal(compacted1.ver, 0);
|
||||
assert.equal(compacted1.data, 'hello');
|
||||
assert.equal(compacted1.next instanceof Array, true);
|
||||
assert.equal(compacted1.next.length, 0);
|
||||
|
||||
assert.equal(compacted2.id, 'B');
|
||||
assert.equal(compacted2.next.length, 1);
|
||||
assert.equal(compacted2.next[0], 'A.0.0');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
73
test/list-perf-tests.js
Normal file
73
test/list-perf-tests.js
Normal file
@ -0,0 +1,73 @@
|
||||
// 'use strict';
|
||||
|
||||
// const assert = require('assert');
|
||||
// const async = require('asyncawait/async');
|
||||
// const await = require('asyncawait/await');
|
||||
// const ipfsDaemon = require('orbit-common/lib/ipfs-daemon');
|
||||
// const ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
// const List = require('../src/list/List');
|
||||
// const OrbitList = require('../src/list/OrbitList');
|
||||
// const Timer = require('../examples/Timer');
|
||||
|
||||
|
||||
// describe('List - Performance Measurement', function() {
|
||||
// this.timeout(60000);
|
||||
|
||||
// it('add', (done) => {
|
||||
// let ms = 0;
|
||||
|
||||
// for(let t = 1000; t <= 5000; t += 1000) {
|
||||
// const list = new List('A');
|
||||
// let timer = new Timer(true);
|
||||
|
||||
// for(let i = 0; i < t; i ++) {
|
||||
// list.add("hello" + i);
|
||||
// }
|
||||
|
||||
// ms = timer.stop(true);
|
||||
// console.log(` > ${t} took ${ms} ms`)
|
||||
// }
|
||||
|
||||
// assert.equal(true, true);
|
||||
// done();
|
||||
// });
|
||||
|
||||
// });
|
||||
|
||||
// describe('OrbitList - Performance Measurement', function() {
|
||||
// const startIpfs = async (() => {
|
||||
// return new Promise(async((resolve, reject) => {
|
||||
// const ipfsd = await(ipfsDaemon());
|
||||
// resolve(ipfsd.daemon);
|
||||
// }));
|
||||
// });
|
||||
|
||||
// let ipfs;
|
||||
|
||||
// this.timeout(60000);
|
||||
|
||||
// before(async((done) => {
|
||||
// ipfs = await(startIpfs());
|
||||
// done();
|
||||
// }));
|
||||
|
||||
// it('add', async((done) => {
|
||||
// let ms = 0;
|
||||
|
||||
// for(let t = 100; t <= 1000; t += 300) {
|
||||
// const list = new OrbitList('A', ipfs);
|
||||
// let timer = new Timer(true);
|
||||
|
||||
// for(let i = 0; i < t; i ++) {
|
||||
// list.add("hello" + i);
|
||||
// }
|
||||
|
||||
// ms = timer.stop(true);
|
||||
// console.log(` > ${t} took ${ms} ms`)
|
||||
// }
|
||||
|
||||
// assert.equal(true, true);
|
||||
// done();
|
||||
// }));
|
||||
|
||||
// });
|
470
test/list-tests.js
Normal file
470
test/list-tests.js
Normal file
@ -0,0 +1,470 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
var assert = require('assert');
|
||||
var List = require('../src/list/List');
|
||||
|
||||
describe('List', () => {
|
||||
describe('Constructor', () => {
|
||||
it('initializes member variables', (done) => {
|
||||
const list = new List('A');
|
||||
assert.equal(list.id, 'A');
|
||||
assert.equal(list.seq, 0);
|
||||
assert.equal(list.ver, 0);
|
||||
assert.equal(list._items instanceof Array, true);
|
||||
assert.equal(list._currentBatch instanceof Array, true);
|
||||
assert.equal(list._items.length, 0);
|
||||
assert.equal(list._currentBatch.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromJson', () => {
|
||||
it('creates a list from parsed json', (done) => {
|
||||
const list = new List('A');
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
list.add("hello3")
|
||||
const str = JSON.stringify(list.toJson(), null, 2)
|
||||
const res = List.fromJson(JSON.parse(str));
|
||||
assert.equal(res.id, 'A');
|
||||
assert.equal(res.seq, 0);
|
||||
assert.equal(res.ver, 3);
|
||||
assert.equal(res.items.length, 3);
|
||||
assert.equal(res.items[0].compactId, 'A.0.0');
|
||||
assert.equal(res.items[1].compactId, 'A.0.1');
|
||||
assert.equal(res.items[2].compactId, 'A.0.2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toJson', () => {
|
||||
it('presents the list as json', (done) => {
|
||||
const list = new List('A');
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
list.add("hello3")
|
||||
const json = list.toJson();
|
||||
const expected = {
|
||||
id: 'A',
|
||||
seq: 0,
|
||||
ver: 3,
|
||||
items: [
|
||||
{ id: 'A', seq: 0, ver: 0, data: 'hello1', next: [] },
|
||||
{ id: 'A', seq: 0, ver: 1, data: 'hello2', next: ['A.0.0'] },
|
||||
{ id: 'A', seq: 0, ver: 2, data: 'hello3', next: ['A.0.1'] }
|
||||
]
|
||||
};
|
||||
assert.equal(_.isEqual(json, expected), true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toString', () => {
|
||||
it('presents the list as a string', (done) => {
|
||||
const list = new List('A');
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
list.add("hello3")
|
||||
const str = list.toString();
|
||||
const expected = `id: A, seq: 0, ver: 3, items:\n{"id":"A","seq":0,"ver":0,"data":"hello1","next":[]}\n{"id":"A","seq":0,"ver":1,"data":"hello2","next":["A.0.0"]}\n{"id":"A","seq":0,"ver":2,"data":"hello3","next":["A.0.1"]}`;
|
||||
assert.equal(str, expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('items', () => {
|
||||
it('returns items', (done) => {
|
||||
const list = new List('A');
|
||||
let items = list.items;
|
||||
assert.equal(list.items instanceof Array, true);
|
||||
assert.equal(list.items.length, 0);
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
assert.equal(list.items instanceof Array, true);
|
||||
assert.equal(list.items.length, 2);
|
||||
assert.equal(list.items[0].data, 'hello1');
|
||||
assert.equal(list.items[1].data, 'hello2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('add', () => {
|
||||
it('adds an item to an empty list', (done) => {
|
||||
const list = new List('A');
|
||||
list.add("hello1")
|
||||
const item = list.items[0];
|
||||
assert.equal(list.id, 'A');
|
||||
assert.equal(list.seq, 0);
|
||||
assert.equal(list.ver, 1);
|
||||
assert.equal(list.items.length, 1);
|
||||
assert.equal(list._currentBatch.length, 1);
|
||||
assert.equal(list._items.length, 0);
|
||||
assert.equal(item, list._currentBatch[0]);
|
||||
assert.equal(item.id, 'A');
|
||||
assert.equal(item.seq, 0);
|
||||
assert.equal(item.ver, 0);
|
||||
assert.equal(item.data, 'hello1');
|
||||
done();
|
||||
});
|
||||
|
||||
it('adds 100 items to a list', (done) => {
|
||||
const list = new List('A');
|
||||
|
||||
for(let i = 1; i < 101; i ++) {
|
||||
list.add("hello" + i);
|
||||
}
|
||||
|
||||
assert.equal(list.id, 'A');
|
||||
assert.equal(list.seq, 0);
|
||||
assert.equal(list.ver, 100);
|
||||
assert.equal(list.items.length, 100);
|
||||
assert.equal(list._currentBatch.length, 100);
|
||||
assert.equal(list._items.length, 0);
|
||||
|
||||
const item = list.items[list.items.length - 1];
|
||||
assert.equal(item, list._currentBatch[list._currentBatch.length - 1]);
|
||||
assert.equal(item.id, 'A');
|
||||
assert.equal(item.seq, 0);
|
||||
assert.equal(item.ver, 99);
|
||||
assert.equal(item.data, 'hello100');
|
||||
assert.equal(item.next, 'A.0.98');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('join', () => {
|
||||
it('increases the sequence and resets the version if other list has the same or higher sequence', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
|
||||
list2.seq = 7;
|
||||
list1.add("helloA1")
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 0);
|
||||
assert.equal(list1.ver, 1);
|
||||
|
||||
list2.add("helloB1")
|
||||
list1.join(list2);
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 8);
|
||||
assert.equal(list1.ver, 0);
|
||||
done();
|
||||
});
|
||||
|
||||
it('increases the sequence by one if other list has lower sequence', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
list1.seq = 4;
|
||||
list2.seq = 1;
|
||||
list2.add("helloB1")
|
||||
list1.join(list2);
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 5);
|
||||
assert.equal(list1.ver, 0);
|
||||
done();
|
||||
});
|
||||
|
||||
it('finds the next head when adding a new element', (done) => {
|
||||
const list1 = new List('A');
|
||||
list1.add("helloA1")
|
||||
list1.add("helloA2")
|
||||
list1.add("helloA3")
|
||||
|
||||
assert.equal(list1._currentBatch.length, 3);
|
||||
assert.equal(list1._currentBatch[2].next.length, 1);
|
||||
assert.equal(list1._currentBatch[2].next[0], 'A.0.1');
|
||||
done();
|
||||
});
|
||||
|
||||
it('finds the next heads (two) after a join', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
list1.add("helloA2")
|
||||
|
||||
assert.equal(list1._currentBatch.length, 1);
|
||||
assert.equal(list1._currentBatch[0].next.length, 2);
|
||||
assert.equal(list1._currentBatch[0].next[0], 'A.0.0');
|
||||
assert.equal(list1._currentBatch[0].next[1], 'B.0.1');
|
||||
done();
|
||||
});
|
||||
|
||||
it('finds the next head (one) after a join', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
|
||||
list1.add("helloA2")
|
||||
list1.add("helloA3")
|
||||
|
||||
assert.equal(list1._currentBatch.length, 2);
|
||||
assert.equal(list1._currentBatch[1].next.length, 1);
|
||||
assert.equal(list1._currentBatch[1].next[0], 'A.1.0');
|
||||
done();
|
||||
});
|
||||
|
||||
it('finds the next heads after two joins', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
list1.add("helloA1")
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
|
||||
list1.add("helloA3")
|
||||
|
||||
list1.join(list2);
|
||||
|
||||
list1.add("helloA4")
|
||||
list1.add("helloA5")
|
||||
|
||||
const lastItem = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.items.length, 7);
|
||||
assert.equal(lastItem.next.length, 1);
|
||||
assert.equal(lastItem.next[0], 'A.2.0');
|
||||
done();
|
||||
});
|
||||
|
||||
it('finds the next heads after multiple joins', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
const list3 = new List('C');
|
||||
const list4 = new List('D');
|
||||
list1.add("helloA1")
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
|
||||
list3.add("helloC1")
|
||||
list4.add("helloD1")
|
||||
list1.join(list3);
|
||||
|
||||
list1.add("helloA3")
|
||||
list2.join(list1);
|
||||
list1.join(list2);
|
||||
list2.join(list4);
|
||||
|
||||
list4.add("helloD2")
|
||||
list4.add("helloD3")
|
||||
list1.add("helloA4")
|
||||
list1.join(list4);
|
||||
|
||||
list1.add("helloA5")
|
||||
|
||||
const lastItem = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.items.length, 11);
|
||||
assert.equal(lastItem.next.length, 2);
|
||||
assert.equal(lastItem.next[0], 'A.4.0');
|
||||
assert.equal(lastItem.next[1], 'D.0.2');
|
||||
done();
|
||||
});
|
||||
|
||||
it('joins list of one item with list of two items', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
|
||||
const lastItem = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 1);
|
||||
assert.equal(list1.ver, 0);
|
||||
assert.equal(list1._currentBatch.length, 0);
|
||||
assert.equal(list1._items.length, 3);
|
||||
assert.equal(lastItem.id, 'B');
|
||||
assert.equal(lastItem.seq, 0);
|
||||
assert.equal(lastItem.ver, 1);
|
||||
assert.equal(lastItem.data, 'helloB2');
|
||||
done();
|
||||
});
|
||||
|
||||
it('joins lists two ways', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
list1.add("helloA1")
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
list2.join(list1);
|
||||
|
||||
const lastItem1 = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 1);
|
||||
assert.equal(list1.ver, 0);
|
||||
assert.equal(list1._currentBatch.length, 0);
|
||||
assert.equal(list1._items.length, 4);
|
||||
assert.equal(lastItem1.id, 'B');
|
||||
assert.equal(lastItem1.seq, 0);
|
||||
assert.equal(lastItem1.ver, 1);
|
||||
assert.equal(lastItem1.data, 'helloB2');
|
||||
|
||||
const lastItem2 = list2.items[list2.items.length - 1];
|
||||
|
||||
assert.equal(list2.id, 'B');
|
||||
assert.equal(list2.seq, 2);
|
||||
assert.equal(list2.ver, 0);
|
||||
assert.equal(list2._currentBatch.length, 0);
|
||||
assert.equal(list2._items.length, 4);
|
||||
assert.equal(lastItem2.id, 'A');
|
||||
assert.equal(lastItem2.seq, 0);
|
||||
assert.equal(lastItem2.ver, 1);
|
||||
assert.equal(lastItem2.data, 'helloA2');
|
||||
done();
|
||||
});
|
||||
|
||||
it('joins lists twice', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list2.join(list1);
|
||||
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB2")
|
||||
list2.join(list1);
|
||||
|
||||
const secondItem = list2.items[1];
|
||||
const lastItem = list2.items[list2.items.length - 1];
|
||||
|
||||
assert.equal(list2.id, 'B');
|
||||
assert.equal(list2.seq, 2);
|
||||
assert.equal(list2.ver, 0);
|
||||
assert.equal(list2._currentBatch.length, 0);
|
||||
assert.equal(list2._items.length, 4);
|
||||
assert.equal(secondItem.id, 'A');
|
||||
assert.equal(secondItem.seq, 0);
|
||||
assert.equal(secondItem.ver, 0);
|
||||
assert.equal(secondItem.data, 'helloA1');
|
||||
assert.equal(lastItem.id, 'A');
|
||||
assert.equal(lastItem.seq, 0);
|
||||
assert.equal(lastItem.ver, 1);
|
||||
assert.equal(lastItem.data, 'helloA2');
|
||||
done();
|
||||
});
|
||||
|
||||
it('joins 4 lists to one', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
const list3 = new List('C');
|
||||
const list4 = new List('D');
|
||||
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB2")
|
||||
list3.add("helloC1")
|
||||
list4.add("helloD1")
|
||||
list3.add("helloC2")
|
||||
list4.add("helloD2")
|
||||
list1.join(list2);
|
||||
list1.join(list3);
|
||||
list1.join(list4);
|
||||
|
||||
const secondItem = list1.items[1];
|
||||
const lastItem = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 3);
|
||||
assert.equal(list1.ver, 0);
|
||||
assert.equal(list1._currentBatch.length, 0);
|
||||
assert.equal(list1._items.length, 8);
|
||||
assert.equal(secondItem.id, 'A');
|
||||
assert.equal(secondItem.seq, 0);
|
||||
assert.equal(secondItem.ver, 1);
|
||||
assert.equal(secondItem.data, 'helloA2');
|
||||
assert.equal(lastItem.id, 'D');
|
||||
assert.equal(lastItem.seq, 0);
|
||||
assert.equal(lastItem.ver, 1);
|
||||
assert.equal(lastItem.data, 'helloD2');
|
||||
done();
|
||||
});
|
||||
|
||||
it('joins lists from 4 lists', (done) => {
|
||||
const list1 = new List('A');
|
||||
const list2 = new List('B');
|
||||
const list3 = new List('C');
|
||||
const list4 = new List('D');
|
||||
|
||||
list1.add("helloA1")
|
||||
list1.join(list2);
|
||||
list2.add("helloB1")
|
||||
list2.join(list1);
|
||||
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB2")
|
||||
list1.join(list3);
|
||||
list3.join(list1);
|
||||
|
||||
list3.add("helloC1")
|
||||
list4.add("helloD1")
|
||||
|
||||
list3.add("helloC2")
|
||||
list4.add("helloD2")
|
||||
|
||||
list1.join(list3);
|
||||
list1.join(list2);
|
||||
list4.join(list2);
|
||||
list4.join(list1);
|
||||
list4.join(list3);
|
||||
|
||||
list4.add("helloD3")
|
||||
list4.add("helloD4")
|
||||
|
||||
const secondItem = list4.items[1];
|
||||
const lastItem1 = list4._items[list4._items.length - 1];
|
||||
const lastItem2 = list4.items[list4.items.length - 1];
|
||||
|
||||
assert.equal(list4.id, 'D');
|
||||
assert.equal(list4.seq, 7);
|
||||
assert.equal(list4.ver, 2);
|
||||
assert.equal(list4._currentBatch.length, 2);
|
||||
assert.equal(list4._items.length, 8);
|
||||
assert.equal(secondItem.id, 'D');
|
||||
assert.equal(secondItem.seq, 0);
|
||||
assert.equal(secondItem.ver, 1);
|
||||
assert.equal(secondItem.data, 'helloD2');
|
||||
assert.equal(lastItem1.id, 'C');
|
||||
assert.equal(lastItem1.seq, 3);
|
||||
assert.equal(lastItem1.ver, 1);
|
||||
assert.equal(lastItem1.data, 'helloC2');
|
||||
assert.equal(lastItem2.id, 'D');
|
||||
assert.equal(lastItem2.seq, 7);
|
||||
assert.equal(lastItem2.ver, 1);
|
||||
assert.equal(lastItem2.data, 'helloD4');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('_findHeads', () => {
|
||||
it('TODO', (done) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('_isReferencedInChain', () => {
|
||||
it('TODO', (done) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -1,244 +1,174 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var assert = require('assert');
|
||||
var async = require('asyncawait/async');
|
||||
var await = require('asyncawait/await');
|
||||
var ipfsDaemon = require('orbit-common/lib/ipfs-daemon');
|
||||
var logger = require('orbit-common/lib/logger');
|
||||
var Server = require('orbit-server/src/server');
|
||||
var OrbitClient = require('../src/OrbitClient');
|
||||
|
||||
var serverConfig = {
|
||||
networkId: "orbitdb-test",
|
||||
networkName: "OrbitDB Test Network",
|
||||
salt: "hellothisisdog",
|
||||
userDataPath: "/tmp/orbitdb-tests",
|
||||
verifyMessages: true
|
||||
}
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const OrbitClient = require('../src/OrbitClient');
|
||||
|
||||
// Orbit
|
||||
const host = 'localhost';
|
||||
const port = 3006;
|
||||
const port = 6379;
|
||||
const username = 'testrunner';
|
||||
const password = '';
|
||||
|
||||
const startServer = async (() => {
|
||||
return new Promise(async((resolve, reject) => {
|
||||
logger.setLevel('ERROR');
|
||||
const ipfsd = await(ipfsDaemon());
|
||||
const server = Server(ipfsd.daemon, ipfsd.nodeInfo, serverConfig);
|
||||
server.app.listen(port, () => {
|
||||
resolve(server);
|
||||
}).on('error', (err) => {
|
||||
resolve(server);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('Orbit Client', () => {
|
||||
let server, orbit;
|
||||
|
||||
let head = '';
|
||||
let client, db;
|
||||
let items = [];
|
||||
|
||||
let channel = 'abcdefgh';
|
||||
|
||||
before(async((done) => {
|
||||
var initialize = () => new Promise(async((resolve, reject) => {
|
||||
orbit = OrbitClient.connect(`${host}:${port}`, username, password);
|
||||
orbit.channel(channel, '').delete();
|
||||
resolve();
|
||||
}));
|
||||
server = await(startServer());
|
||||
await(initialize());
|
||||
client = OrbitClient.connect(host, port, username, password);
|
||||
db = client.channel(channel);
|
||||
db.delete();
|
||||
done();
|
||||
}));
|
||||
|
||||
after(function(done) {
|
||||
var deleteChannel = () => new Promise(async((resolve, reject) => {
|
||||
if(orbit) orbit.channel(channel, '').delete();
|
||||
resolve();
|
||||
}));
|
||||
server.shutdown();
|
||||
server = null;
|
||||
deleteChannel().then(done);
|
||||
});
|
||||
|
||||
/* TESTS */
|
||||
describe('Connect', function() {
|
||||
it('connects to hash-cache-server', async((done) => {
|
||||
assert.notEqual(orbit, null);
|
||||
assert.notEqual(orbit.client, null);
|
||||
assert.equal(orbit.user.id, 'Qmf5A5RSTQmcfvigT3j29Fqh2fAHRANk5ooBYKdWsPtr8U');
|
||||
assert.equal(orbit.network.id, serverConfig.networkId);
|
||||
assert.equal(orbit.network.name, serverConfig.networkName);
|
||||
assert.notEqual(orbit.network.config.SupernodeRouting, null);
|
||||
assert.notEqual(orbit.network.config.Bootstrap.length, 0);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Info', function() {
|
||||
it('gets channel info on empty channel', async((done) => {
|
||||
var info = orbit.channel(channel, '').info();
|
||||
assert.notEqual(info, null);
|
||||
assert.equal(info.head, null);
|
||||
assert.notEqual(info.modes, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('gets channel info on an existing channel', async((done) => {
|
||||
var msg = orbit.channel(channel, '').add('hello');
|
||||
var info = orbit.channel(channel, '').info();
|
||||
assert.notEqual(info, null);
|
||||
assert.notEqual(info.head, null);
|
||||
assert.notEqual(info.modes, null);
|
||||
assert.equal(info.modes.r, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('gets channel info when channel has modes set', async((done) => {
|
||||
try {
|
||||
orbit.channel(channel).delete();
|
||||
var mode = {
|
||||
mode: "+r",
|
||||
params: {
|
||||
password: 'password'
|
||||
}
|
||||
};
|
||||
var res = orbit.channel(channel, '').setMode(mode)
|
||||
var info = orbit.channel(channel, 'password').info();
|
||||
assert.notEqual(info, null);
|
||||
assert.equal(info.head, null);
|
||||
assert.equal(JSON.stringify(info.modes), JSON.stringify(res));
|
||||
orbit.channel(channel, 'password').delete();
|
||||
} catch(e) {
|
||||
orbit.channel(channel, 'password').delete();
|
||||
assert.equal(e, null);
|
||||
}
|
||||
done();
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('Delete', function() {
|
||||
it('deletes a channel from the database', async((done) => {
|
||||
var result = orbit.channel(channel, '').delete();
|
||||
assert.equal(result, true);
|
||||
var iter = orbit.channel(channel, '').iterator();
|
||||
assert.equal(iter.next().value, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('deletes a channel with a password', async((done) => {
|
||||
done();
|
||||
}));
|
||||
|
||||
it('doesn\'t delete a channel when password is wrong', async((done) => {
|
||||
done();
|
||||
}));
|
||||
|
||||
it('doesn\'t delete a channel when user is not an op', async((done) => {
|
||||
done();
|
||||
after(async((done) => {
|
||||
if(db) db.delete();
|
||||
done();
|
||||
}));
|
||||
|
||||
/*
|
||||
describe('Info', function() { // }
|
||||
// };
|
||||
// var res = db.setMode(mode)
|
||||
// var info = orbit.channel(channel, 'password').info();
|
||||
// assert.notEqual(info, null);
|
||||
// assert.equal(info.head, null);
|
||||
// assert.equal(JSON.stringify(info.modes), JSON.stringify(res));
|
||||
// orbit.channel(channel, 'password').delete();
|
||||
// } catch(e) {
|
||||
// orbit.channel(channel, 'password').delete();
|
||||
// assert.equal(e, null);
|
||||
// }
|
||||
// done();
|
||||
// }));
|
||||
});
|
||||
*/
|
||||
|
||||
describe('Add events', function() {
|
||||
it('adds an item to an empty channel', async((done) => {
|
||||
try {
|
||||
orbit.channel(channel, '').delete();
|
||||
const head = orbit.channel(channel, '').add('hello');
|
||||
const head = db.add('hello');
|
||||
assert.notEqual(head, null);
|
||||
assert.equal(head.startsWith('Qm'), true);
|
||||
assert.equal(head.length, 46);
|
||||
} catch(e) {
|
||||
assert.equal(e, null);
|
||||
}
|
||||
done();
|
||||
}));
|
||||
|
||||
it('adds a new item to a channel with one item', async((done) => {
|
||||
try {
|
||||
const head = orbit.channel(channel, '').iterator().collect()[0];
|
||||
const second = orbit.channel(channel, '').add('hello');
|
||||
const head = db.iterator().collect()[0];
|
||||
const second = db.add('hello');
|
||||
assert.notEqual(second, null);
|
||||
assert.notEqual(second, head);
|
||||
assert.equal(second.startsWith('Qm'), true);
|
||||
assert.equal(second.length, 46);
|
||||
} catch(e) {
|
||||
assert.equal(e, null);
|
||||
}
|
||||
done();
|
||||
}));
|
||||
|
||||
it('adds five items', async((done) => {
|
||||
for(var i = 0; i < 5; i ++) {
|
||||
try {
|
||||
var s = orbit.channel(channel, '').add('hello');
|
||||
assert.notEqual(s, null);
|
||||
assert.equal(s.startsWith('Qm'), true);
|
||||
assert.equal(s.length, 46);
|
||||
} catch(e) {
|
||||
assert.equal(e, null);
|
||||
}
|
||||
for(let i = 0; i < 5; i ++) {
|
||||
let hash = db.add('hello');
|
||||
// console.log(hash)
|
||||
assert.notEqual(hash, null);
|
||||
assert.equal(hash.startsWith('Qm'), true);
|
||||
assert.equal(hash.length, 46);
|
||||
}
|
||||
done();
|
||||
}));
|
||||
|
||||
it('adds an item that is > 256 bytes', async((done) => {
|
||||
try {
|
||||
var msg = new Buffer(512);
|
||||
let msg = new Buffer(1024);
|
||||
msg.fill('a')
|
||||
var s = orbit.channel(channel, '').add(msg.toString());
|
||||
assert.notEqual(s, null);
|
||||
assert.equal(s.startsWith('Qm'), true);
|
||||
assert.equal(s.length, 46);
|
||||
} catch(e) {
|
||||
assert.equal(e, null);
|
||||
}
|
||||
const hash = db.add(msg.toString());
|
||||
assert.notEqual(hash, null);
|
||||
assert.equal(hash.startsWith('Qm'), true);
|
||||
assert.equal(hash.length, 46);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Delete events', function() {
|
||||
it('deletes an item when only one item in the database', async((done) => {
|
||||
db.delete();
|
||||
const head = db.add('hello1');
|
||||
let item = db.iterator().collect();
|
||||
const delop = db.del(head);
|
||||
const items = db.iterator().collect();
|
||||
assert.equal(delop.startsWith('Qm'), true);
|
||||
assert.equal(items.length, 0);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('deletes an item when two items in the database', async((done) => {
|
||||
db.add('hello1');
|
||||
const head = db.add('hello2');
|
||||
db.del(head);
|
||||
const items = db.iterator().collect();
|
||||
assert.equal(items.length, 1);
|
||||
assert.equal(items[0].hash.startsWith('Qm'), true);
|
||||
assert.equal(items[0].payload.op, 'ADD');
|
||||
assert.equal(items[0].payload.value, 'hello1');
|
||||
assert.notEqual(items[0].payload.meta, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('deletes an item between adds', async((done) => {
|
||||
const head = db.add('hello1');
|
||||
db.add('hello2');
|
||||
db.del(head);
|
||||
db.add('hello3');
|
||||
const items = db.iterator().collect();
|
||||
assert.equal(items.length, 1);
|
||||
assert.equal(items[0].hash.startsWith('Qm'), true);
|
||||
assert.equal(items[0].payload.op, 'ADD');
|
||||
assert.equal(items[0].payload.value, 'hello3');
|
||||
assert.notEqual(items[0].payload.meta, null);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Iterator', function() {
|
||||
var items = [];
|
||||
var itemCount = 5;
|
||||
let items = [];
|
||||
const itemCount = 5;
|
||||
|
||||
before(function(done) {
|
||||
var addMessages = () => new Promise(async((resolve, reject) => {
|
||||
var result = orbit.channel(channel, '').delete();
|
||||
var iter = orbit.channel(channel, '').iterator();
|
||||
for(var i = 0; i < itemCount; i ++) {
|
||||
var s = orbit.channel(channel, '').add('hello' + i);
|
||||
items.push(s);
|
||||
before(async((done) => {
|
||||
db.delete();
|
||||
for(let i = 0; i < itemCount; i ++) {
|
||||
const hash = db.add('hello' + i);
|
||||
items.push(hash);
|
||||
}
|
||||
resolve();
|
||||
done();
|
||||
}));
|
||||
addMessages().then(done);
|
||||
});
|
||||
|
||||
describe('Defaults', function() {
|
||||
it('returns an iterator', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator();
|
||||
var next = iter.next().value;
|
||||
const iter = db.iterator();
|
||||
const next = iter.next().value;
|
||||
assert.notEqual(iter, null);
|
||||
assert.notEqual(next, null);
|
||||
assert.notEqual(next.item, null);
|
||||
assert.notEqual(next.item.op, null);
|
||||
assert.equal(next.item.seq, 4);
|
||||
assert.notEqual(next.item.target, null);
|
||||
assert.notEqual(next.item.next, null);
|
||||
assert.notEqual(next.item.Payload, null);
|
||||
assert.equal(next.item.Payload, 'hello4');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns an item with the correct structure', async((done) => {
|
||||
const iter = db.iterator();
|
||||
const next = iter.next().value;
|
||||
|
||||
assert.notEqual(next, null);
|
||||
assert.notEqual(next.hash, null);
|
||||
assert.equal(next.hash.startsWith('Qm'), true);
|
||||
assert.notEqual(next.payload, null);
|
||||
assert.equal(next.payload.op, 'ADD');
|
||||
assert.equal(next.payload.key.startsWith('Qm'), true);
|
||||
assert.equal(next.payload.value, 'hello4');
|
||||
assert.notEqual(next.payload.meta, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('implements Iterator interface', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: -1 });
|
||||
var messages = [];
|
||||
const iter = db.iterator({ limit: -1 });
|
||||
let messages = [];
|
||||
|
||||
for(let i of iter)
|
||||
messages.push(i.hash);
|
||||
@ -248,36 +178,33 @@ describe('Orbit Client', () => {
|
||||
}));
|
||||
|
||||
it('returns 1 item as default', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator();
|
||||
var first = iter.next().value;
|
||||
var second = iter.next().value;
|
||||
assert.equal(first.item.key, items[items.length - 1]);
|
||||
const iter = db.iterator();
|
||||
const first = iter.next().value;
|
||||
const second = iter.next().value;
|
||||
assert.equal(first.payload.key, items[items.length - 1]);
|
||||
assert.equal(second, null);
|
||||
assert.equal(first.item.Payload, 'hello4');
|
||||
assert.equal(first.payload.value, 'hello4');
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Collect', function() {
|
||||
it('returns all items', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: -1 });
|
||||
var messages = iter.collect();
|
||||
const messages = db.iterator({ limit: -1 }).collect();
|
||||
assert.equal(messages.length, items.length);
|
||||
assert.equal(messages[messages.length - 1].item.Payload, 'hello0');
|
||||
assert.equal(messages[0].item.Payload, 'hello4');
|
||||
assert.equal(messages[0].payload.value, 'hello0');
|
||||
assert.equal(messages[messages.length - 1].payload.value, 'hello4');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 1 item', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator();
|
||||
var messages = iter.collect();
|
||||
const messages = db.iterator().collect();
|
||||
assert.equal(messages.length, 1);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 3 items', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: 3 });
|
||||
var messages = iter.collect();
|
||||
const messages = db.iterator({ limit: 3 }).collect();
|
||||
assert.equal(messages.length, 3);
|
||||
done();
|
||||
}));
|
||||
@ -285,222 +212,228 @@ describe('Orbit Client', () => {
|
||||
|
||||
describe('Options: limit', function() {
|
||||
it('returns 1 item when limit is 0', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: 0 });
|
||||
var first = iter.next().value;
|
||||
var second = iter.next().value;
|
||||
assert.equal(first.item.key, items[items.length - 1]);
|
||||
const iter = db.iterator({ limit: 0 });
|
||||
const first = iter.next().value;
|
||||
const second = iter.next().value;
|
||||
assert.equal(first.payload.key, items[items.length - 1]);
|
||||
assert.equal(second, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 1 item when limit is 1', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: 1 });
|
||||
var first = iter.next().value;
|
||||
var second = iter.next().value;
|
||||
assert.equal(first.item.key, items[items.length - 1]);
|
||||
const iter = db.iterator({ limit: 1 });
|
||||
const first = iter.next().value;
|
||||
const second = iter.next().value;
|
||||
assert.equal(first.payload.key, items[items.length - 1]);
|
||||
assert.equal(second, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 3 items', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: 3 });
|
||||
var first = iter.next().value;
|
||||
var second = iter.next().value;
|
||||
var third = iter.next().value;
|
||||
var fourth = iter.next().value;
|
||||
assert.equal(first.item.key, items[items.length - 1]);
|
||||
assert.equal(second.item.key, items[items.length - 2]);
|
||||
assert.equal(third.item.key, items[items.length - 3]);
|
||||
const iter = db.iterator({ limit: 3 });
|
||||
const first = iter.next().value;
|
||||
const second = iter.next().value;
|
||||
const third = iter.next().value;
|
||||
const fourth = iter.next().value;
|
||||
assert.equal(first.payload.key, items[items.length - 3]);
|
||||
assert.equal(second.payload.key, items[items.length - 2]);
|
||||
assert.equal(third.payload.key, items[items.length - 1]);
|
||||
assert.equal(fourth, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns all items', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: -1 });
|
||||
var messages = iter.collect().map((e) => e.item.key);
|
||||
const messages = db.iterator({ limit: -1 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
messages.reverse();
|
||||
assert.equal(messages.length, items.length);
|
||||
assert.equal(messages[0], items[items.length - 1]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns all items when limit is bigger than -1', async((done) => {
|
||||
const messages = db.iterator({ limit: -300 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, items.length);
|
||||
assert.equal(messages[0], items[0]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns all items when limit is bigger than -1', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: -300 });
|
||||
var messages = iter.collect().map((e) => e.item.key);
|
||||
|
||||
assert.equal(messages.length, items.length);
|
||||
assert.equal(messages[0], items[items.length - 1]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns all items when limit is bigger than number of items', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: 300 });
|
||||
var messages = iter.collect().map((e) => e.item.key);
|
||||
const messages = db.iterator({ limit: 300 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, items.length);
|
||||
assert.equal(messages[0], items[items.length - 1]);
|
||||
assert.equal(messages[0], items[0]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Options: reverse', function() {
|
||||
it('returns all items reversed', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: -1, reverse: true });
|
||||
var messages = iter.collect().map((e) => e.item.key);
|
||||
const messages = db.iterator({ limit: -1, reverse: true })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, items.length);
|
||||
assert.equal(messages[0], items[0]);
|
||||
assert.equal(messages[0], items[items.length - 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Options: ranges', function() {
|
||||
var all = [];
|
||||
var head;
|
||||
|
||||
before((done) => {
|
||||
var fetchAll = () => new Promise(async((resolve, reject) => {
|
||||
all = orbit.channel(channel, '').iterator({ limit: -1 }).collect();
|
||||
head = all[0];
|
||||
resolve();
|
||||
}));
|
||||
fetchAll().then(done);
|
||||
});
|
||||
|
||||
describe('gt & gte', function() {
|
||||
it('returns 0 items when gt is the head', async((done) => {
|
||||
var messages = orbit.channel(channel, '').iterator({ gt: head.hash }).collect();
|
||||
assert.equal(messages.length, 0);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 1 item when gte is the head', async((done) => {
|
||||
var iter2 = orbit.channel(channel, '').iterator({ gte: head.hash, limit: -1 });
|
||||
var messages = iter2.collect().map((e) => e.item.key);
|
||||
const messages = db.iterator({ gte: _.last(items), limit: -1 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, 1);
|
||||
assert.equal(messages[0], items[items.length -1]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 0 items when gt is the head', async((done) => {
|
||||
const messages = db.iterator({ gt: _.last(items) }).collect();
|
||||
assert.equal(messages.length, 0);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 2 item when gte is defined', async((done) => {
|
||||
var gte = all[1].hash;
|
||||
var iter = orbit.channel(channel, '').iterator({ gte: gte, limit: -1 });
|
||||
var messages = iter.collect().map((e) => e.hash);
|
||||
const gte = items[items.length - 2];
|
||||
const messages = db.iterator({ gte: gte, limit: -1 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, 2);
|
||||
assert.equal(messages[0], all[0].hash);
|
||||
assert.equal(messages[1], all[1].hash);
|
||||
assert.equal(messages[0], items[items.length - 2]);
|
||||
assert.equal(messages[1], items[items.length - 1]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns all items when gte is the root item', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ gte: all[all.length -1], limit: -1 });
|
||||
var messages = iter.collect().map((e) => e.item.key);
|
||||
const messages = db.iterator({ gte: items[0], limit: -1 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, itemCount);
|
||||
assert.equal(messages[0], items[items.length - 1]);
|
||||
assert.equal(messages[messages.length - 1], items[0]);
|
||||
assert.equal(messages.length, items.length);
|
||||
assert.equal(messages[0], items[0]);
|
||||
assert.equal(messages[messages.length - 1], items[items.length - 1]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns items when gt is the root item', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ gt: all[all.length - 1], limit: -1 });
|
||||
var messages = iter.collect().map((e) => e.item.key);
|
||||
const messages = db.iterator({ gt: items[0], limit: -1 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, itemCount - 1);
|
||||
assert.equal(messages[0], items[items.length - 1]);
|
||||
assert.equal(messages[3], items[1]);
|
||||
assert.equal(messages[0], items[1]);
|
||||
assert.equal(messages[3], items[items.length - 1]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns items when gt is defined', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ limit: -1});
|
||||
var messages = iter.collect().map((e) => e.hash);
|
||||
const messages = db.iterator({ limit: -1})
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
var gt = messages[2];
|
||||
var iter2 = orbit.channel(channel, '').iterator({ gt: gt, limit: 100 });
|
||||
var messages2 = iter2.collect().map((e) => e.hash);
|
||||
const gt = messages[2];
|
||||
|
||||
const messages2 = db.iterator({ gt: gt, limit: 100 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages2.length, 2);
|
||||
assert.equal(messages2[0], messages[0]);
|
||||
assert.equal(messages2[1], messages[1]);
|
||||
assert.equal(messages2[0], messages[messages.length - 2]);
|
||||
assert.equal(messages2[1], messages[messages.length - 1]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('lt & lte', function() {
|
||||
it('returns one item when lt is the head', async((done) => {
|
||||
var iter2 = orbit.channel(channel, '').iterator({ lt: head.hash });
|
||||
var messages = iter2.collect().map((e) => e.hash);
|
||||
it('returns one item after head when lt is the head', async((done) => {
|
||||
const messages = db.iterator({ lt: _.last(items) })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, 1);
|
||||
assert.equal(messages[0], head.hash);
|
||||
assert.equal(messages[0], items[items.length - 2]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns all items when lt is head and limit is -1', async((done) => {
|
||||
var iter2 = orbit.channel(channel, '').iterator({ lt: head.hash, limit: -1 });
|
||||
var messages = iter2.collect().map((e) => e.hash);
|
||||
const messages = db.iterator({ lt: _.last(items), limit: -1 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, itemCount);
|
||||
assert.equal(messages[0], head.hash);
|
||||
assert.equal(messages[4], all[all.length - 1].hash);
|
||||
assert.equal(messages.length, items.length - 1);
|
||||
assert.equal(messages[0], items[0]);
|
||||
assert.equal(messages[messages.length - 1], items[items.length - 2]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 3 items when lt is head and limit is 3', async((done) => {
|
||||
var iter2 = orbit.channel(channel, '').iterator({ lt: head.hash, limit: 3 });
|
||||
var messages = iter2.collect().map((e) => e.hash);
|
||||
const messages = db.iterator({ lt: _.last(items), limit: 3 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, 3);
|
||||
assert.equal(messages[0], head.hash);
|
||||
assert.equal(messages[2], all[2].hash);
|
||||
assert.equal(messages[0], items[items.length - 4]);
|
||||
assert.equal(messages[2], items[items.length - 2]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns null when lt is the root item', async((done) => {
|
||||
var messages = orbit.channel(channel, '').iterator({ lt: all[all.length - 1].hash }).collect();
|
||||
const messages = db.iterator({ lt: items[0] }).collect();
|
||||
assert.equal(messages.length, 0);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns one item when lte is the root item', async((done) => {
|
||||
var iter = orbit.channel(channel, '').iterator({ lte: all[all.length - 1].hash });
|
||||
var messages = iter.collect().map((e) => e.hash);
|
||||
const messages = db.iterator({ lte: items[0] })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, 1);
|
||||
assert.equal(messages[0], all[all.length - 1].hash);
|
||||
assert.equal(messages[0], items[0]);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns all items when lte is the head', async((done) => {
|
||||
var iter2 = orbit.channel(channel, '').iterator({ lte: head.hash, limit: -1 });
|
||||
var messages = iter2.collect().map((e) => e.hash);
|
||||
const messages = db.iterator({ lte: _.last(items), limit: -1 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, itemCount);
|
||||
assert.equal(messages[0], all[0].hash);
|
||||
assert.equal(messages[4], all[all.length - 1].hash);
|
||||
assert.equal(messages[0], items[0]);
|
||||
assert.equal(messages[4], _.last(items));
|
||||
done();
|
||||
}));
|
||||
|
||||
it('returns 3 items when lte is the head', async((done) => {
|
||||
var iter2 = orbit.channel(channel, '').iterator({ lte: head.hash, limit: 3 });
|
||||
var messages = iter2.collect().map((e) => e.hash);
|
||||
const messages = db.iterator({ lte: _.last(items), limit: 3 })
|
||||
.collect()
|
||||
.map((e) => e.payload.key);
|
||||
|
||||
assert.equal(messages.length, 3);
|
||||
assert.equal(messages[0], all[0].hash);
|
||||
assert.equal(messages[1], all[1].hash);
|
||||
assert.equal(messages[2], all[2].hash);
|
||||
assert.equal(messages[0], items[items.length - 3]);
|
||||
assert.equal(messages[1], items[items.length - 2]);
|
||||
assert.equal(messages[2], _.last(items));
|
||||
done();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
describe('Modes', function() {
|
||||
var password = 'hello';
|
||||
|
||||
@ -512,7 +445,7 @@ describe('Orbit Client', () => {
|
||||
password: password
|
||||
}
|
||||
};
|
||||
var modes = orbit.channel(channel, '').setMode(mode)
|
||||
var modes = db.setMode(mode)
|
||||
assert.notEqual(modes.r, null);
|
||||
assert.equal(modes.r.password, password);
|
||||
} catch(e) {
|
||||
@ -574,5 +507,107 @@ describe('Orbit Client', () => {
|
||||
}));
|
||||
|
||||
});
|
||||
*/
|
||||
|
||||
describe('Delete', function() {
|
||||
it('deletes a channel from the database', async((done) => {
|
||||
const result = db.delete();
|
||||
assert.equal(result, true);
|
||||
const iter = db.iterator();
|
||||
assert.equal(iter.next().value, null);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Key-Value Store', function() {
|
||||
it('put', async((done) => {
|
||||
db.put('key1', 'hello!');
|
||||
let all = db.iterator().collect();
|
||||
assert.equal(all.length, 1);
|
||||
assert.equal(all[0].hash.startsWith('Qm'), true);
|
||||
assert.equal(all[0].payload.key, 'key1');
|
||||
assert.equal(all[0].payload.op, 'PUT');
|
||||
assert.notEqual(all[0].payload.meta, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('get', async((done) => {
|
||||
db.put('key1', 'hello!');
|
||||
const value = db.get('key1');
|
||||
assert.equal(value, 'hello!');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('put updates a value', async((done) => {
|
||||
db.put('key1', 'hello!');
|
||||
db.put('key1', 'hello again');
|
||||
const value = db.get('key1');
|
||||
assert.equal(value, 'hello again');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('deletes a key', async((done) => {
|
||||
db.put('key1', 'hello!');
|
||||
db.del('key1');
|
||||
const value = db.get('key1');
|
||||
assert.equal(value, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('deletes a key after multiple updates', async((done) => {
|
||||
db.put('key1', 'hello1');
|
||||
db.put('key1', 'hello2');
|
||||
db.put('key1', 'hello3');
|
||||
db.del('key1');
|
||||
const value = db.get('key1');
|
||||
assert.equal(value, null);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('put - multiple keys', async((done) => {
|
||||
db.put('key1', 'hello1');
|
||||
db.put('key2', 'hello2');
|
||||
db.put('key3', 'hello3');
|
||||
const all = db.iterator().collect();
|
||||
assert.equal(all.length, 1);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('get - multiple keys', async((done) => {
|
||||
db.put('key1', 'hello1');
|
||||
db.put('key2', 'hello2');
|
||||
db.put('key3', 'hello3');
|
||||
const v1 = db.get('key1');
|
||||
const v2 = db.get('key2');
|
||||
const v3 = db.get('key3');
|
||||
assert.equal(v1, 'hello1');
|
||||
assert.equal(v2, 'hello2');
|
||||
assert.equal(v3, 'hello3');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('get - integer value', async((done) => {
|
||||
db.put('key1', 123);
|
||||
const v1 = db.get('key1');
|
||||
assert.equal(v1, 123);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('get - object value', async((done) => {
|
||||
const val = { one: 'first', two: 2 };
|
||||
db.put('key1', val);
|
||||
const v1 = db.get('key1');
|
||||
assert.equal(_.isEqual(v1, val), true);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('get - array value', async((done) => {
|
||||
const val = [1, 2, 3, 4, 5];
|
||||
db.put('key1', val);
|
||||
const v1 = db.get('key1');
|
||||
assert.equal(_.isEqual(v1, val), true);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
593
test/orbit-list-tests.js
Normal file
593
test/orbit-list-tests.js
Normal file
@ -0,0 +1,593 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const assert = require('assert');
|
||||
const ipfsDaemon = require('orbit-common/lib/ipfs-daemon');
|
||||
const ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
const List = require('../src/list/OrbitList');
|
||||
|
||||
const startIpfs = async (() => {
|
||||
return new Promise(async((resolve, reject) => {
|
||||
const ipfsd = await(ipfsDaemon());
|
||||
resolve(ipfsd.daemon);
|
||||
}));
|
||||
});
|
||||
|
||||
let ipfs;
|
||||
|
||||
describe('OrbitList', async(function() {
|
||||
this.timeout(5000);
|
||||
|
||||
before(async((done) => {
|
||||
ipfs = await(startIpfs());
|
||||
done();
|
||||
}));
|
||||
|
||||
describe('Constructor', async(() => {
|
||||
it('initializes member variables', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
assert.equal(list.id, 'A');
|
||||
assert.equal(list.seq, 0);
|
||||
assert.equal(list.ver, 0);
|
||||
assert.equal(list._items instanceof Array, true);
|
||||
assert.equal(list._currentBatch instanceof Array, true);
|
||||
assert.equal(list._items.length, 0);
|
||||
assert.equal(list._currentBatch.length, 0);
|
||||
assert.equal(list._ipfs, ipfs);
|
||||
assert.equal(list.hash, null);
|
||||
done();
|
||||
}));
|
||||
}));
|
||||
|
||||
describe('add', async(() => {
|
||||
it('saves the data to ipfs', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
const text = 'testing 1 2 3 4';
|
||||
list.add(text)
|
||||
const hash = await(list.getIpfsHash());
|
||||
assert.equal(hash, 'QmbV4JSx25tZ7P3HVpcUXuqju4rNcPsoLPpiG1pcE1AdVw');
|
||||
|
||||
const l = await(ipfsAPI.getObject(ipfs, hash));
|
||||
const list2 = List.fromJson(ipfs, JSON.parse(l.Data));
|
||||
assert.equal(list2.items[0].data, text);
|
||||
|
||||
done();
|
||||
}));
|
||||
|
||||
it('updates the data to ipfs', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
const text1 = 'testing 1 2 3';
|
||||
const text2 = 'testing 456';
|
||||
let hash;
|
||||
|
||||
list.add(text1)
|
||||
|
||||
hash = await(list.getIpfsHash());
|
||||
assert.equal(hash, 'QmcBjB93PsJGz2LrVy5e1Z8mtwH99B8yynsa5f4q3GanEe');
|
||||
|
||||
list.add(text2)
|
||||
hash = await(list.getIpfsHash());
|
||||
assert.equal(hash, 'Qmf358H1wjuX3Bbaag4SSEiujoruowVUNR5pLCNQs8vivP');
|
||||
|
||||
const l = await(ipfsAPI.getObject(ipfs, hash));
|
||||
const list2 = List.fromJson(ipfs, JSON.parse(l.Data));
|
||||
assert.equal(list2.items[0].data, text1);
|
||||
assert.equal(list2.items[1].data, text2);
|
||||
|
||||
done();
|
||||
}));
|
||||
}));
|
||||
|
||||
describe('getIpfsHash', async(() => {
|
||||
it('returns the list as ipfs hash', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
const hash = await(list.getIpfsHash());
|
||||
assert.equal(hash, 'QmVkddks6YBH88TqJf7nFHdyb9PjebPmJAxaRvWdu8ueoE');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('saves the list to ipfs', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
const hash = await(list.getIpfsHash());
|
||||
const l = await(ipfsAPI.getObject(ipfs, hash));
|
||||
assert.equal(l.toString(), ({ Links: [], Data: '{"id":"A","seq":0,"ver":0,"items":[]}' }).toString());
|
||||
done();
|
||||
}));
|
||||
}));
|
||||
|
||||
describe('fromJson', () => {
|
||||
it('creates a list from parsed json', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
list.add("hello3")
|
||||
const str = JSON.stringify(list.toJson(), null, 2)
|
||||
const res = List.fromJson(ipfs, JSON.parse(str));
|
||||
assert.equal(res.id, 'A');
|
||||
assert.equal(res.seq, 0);
|
||||
assert.equal(res.ver, 3);
|
||||
assert.equal(res.items.length, 3);
|
||||
assert.equal(res.items[0].compactId, 'A.0.0.QmZfdeMV77si491NPX83Q8eRYE9WNzVorHrfWJPrJ51brt');
|
||||
assert.equal(res.items[1].compactId, 'A.0.1.QmbbtEWe4qHLSjtW2HkPuszFW3zfBTXBdPrkXMdbePxqfK');
|
||||
assert.equal(res.items[2].compactId, 'A.0.2.QmT6wQwBZsH6b3jQVxmM5L7kqV39nr3F99yd5tN6nviQPe');
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('fromIpfsHash', () => {
|
||||
it('creates a list from ipfs hash', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
list.add("hello3")
|
||||
const hash = await(list.getIpfsHash());
|
||||
assert.equal(hash, 'QmThvyS6FUsHvT7oC2pGNMTAdhjUncNsVMbXAkUB72J8n1');
|
||||
const res = await(List.fromIpfsHash(ipfs, hash));
|
||||
assert.equal(res.id, 'A');
|
||||
assert.equal(res.seq, 0);
|
||||
assert.equal(res.ver, 3);
|
||||
assert.equal(res.items.length, 3);
|
||||
assert.equal(res.items[0].compactId, 'A.0.0.QmZfdeMV77si491NPX83Q8eRYE9WNzVorHrfWJPrJ51brt');
|
||||
assert.equal(res.items[1].compactId, 'A.0.1.QmbbtEWe4qHLSjtW2HkPuszFW3zfBTXBdPrkXMdbePxqfK');
|
||||
assert.equal(res.items[2].compactId, 'A.0.2.QmT6wQwBZsH6b3jQVxmM5L7kqV39nr3F99yd5tN6nviQPe');
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('toJson', async(() => {
|
||||
it('presents the list as json', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
list.add("hello3")
|
||||
const json = list.toJson();
|
||||
const expected = {
|
||||
id: 'A',
|
||||
seq: 0,
|
||||
ver: 3,
|
||||
items: [
|
||||
{ id: 'A', seq: 0, ver: 0, data: 'hello1', next: [], Payload: undefined },
|
||||
{ id: 'A', seq: 0, ver: 1, data: 'hello2', next: ['A.0.0.QmZfdeMV77si491NPX83Q8eRYE9WNzVorHrfWJPrJ51brt'], Payload: undefined },
|
||||
{ id: 'A', seq: 0, ver: 2, data: 'hello3', next: ['A.0.1.QmbbtEWe4qHLSjtW2HkPuszFW3zfBTXBdPrkXMdbePxqfK'], Payload: undefined }
|
||||
]
|
||||
};
|
||||
// console.log(JSON.stringify(json, null, 1))
|
||||
assert.equal(_.isEqual(json, expected), true);
|
||||
done();
|
||||
}));
|
||||
}));
|
||||
|
||||
describe('toString', () => {
|
||||
it('presents the list as a string', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
list.add("hello3")
|
||||
const str = list.toString();
|
||||
const expected = `id: A, seq: 0, ver: 3, items:\n{"id":"A","seq":0,"ver":0,"data":"hello1","next":[]}\n{"id":"A","seq":0,"ver":1,"data":"hello2","next":["A.0.0.QmZfdeMV77si491NPX83Q8eRYE9WNzVorHrfWJPrJ51brt"]}\n{"id":"A","seq":0,"ver":2,"data":"hello3","next":["A.0.1.QmbbtEWe4qHLSjtW2HkPuszFW3zfBTXBdPrkXMdbePxqfK"]}`;
|
||||
assert.equal(str, expected);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('items', () => {
|
||||
it('returns items', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
let items = list.items;
|
||||
assert.equal(list.items instanceof Array, true);
|
||||
assert.equal(list.items.length, 0);
|
||||
list.add("hello1")
|
||||
list.add("hello2")
|
||||
assert.equal(list.items instanceof Array, true);
|
||||
assert.equal(list.items.length, 2);
|
||||
assert.equal(list.items[0].data, 'hello1');
|
||||
assert.equal(list.items[1].data, 'hello2');
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('add', () => {
|
||||
it('adds an item to an empty list', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
list.add("hello1")
|
||||
const item = list.items[0];
|
||||
assert.equal(list.id, 'A');
|
||||
assert.equal(list.seq, 0);
|
||||
assert.equal(list.ver, 1);
|
||||
assert.equal(list.items.length, 1);
|
||||
assert.equal(list._currentBatch.length, 1);
|
||||
assert.equal(list._items.length, 0);
|
||||
assert.equal(item, list._currentBatch[0]);
|
||||
assert.equal(item.id, 'A');
|
||||
assert.equal(item.seq, 0);
|
||||
assert.equal(item.ver, 0);
|
||||
assert.equal(item.data, 'hello1');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('adds 100 items to a list', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
|
||||
for(let i = 1; i < 101; i ++) {
|
||||
list.add("hello" + i);
|
||||
}
|
||||
|
||||
assert.equal(list.id, 'A');
|
||||
assert.equal(list.seq, 0);
|
||||
assert.equal(list.ver, 100);
|
||||
assert.equal(list.items.length, 100);
|
||||
assert.equal(list._currentBatch.length, 100);
|
||||
assert.equal(list._items.length, 0);
|
||||
|
||||
const item = list.items[list.items.length - 1];
|
||||
assert.equal(item, list._currentBatch[list._currentBatch.length - 1]);
|
||||
assert.equal(item.id, 'A');
|
||||
assert.equal(item.seq, 0);
|
||||
assert.equal(item.ver, 99);
|
||||
assert.equal(item.data, 'hello100');
|
||||
assert.equal(item.next, 'A.0.98.QmPZ1Qmf52ko62xh9RDYcVGNMWx8ZCtfFNyrvqyE1UmhG1');
|
||||
|
||||
done();
|
||||
}));
|
||||
|
||||
it('commits a list after batch size was reached', async((done) => {
|
||||
const list = new List('A', ipfs);
|
||||
|
||||
for(let i = 1; i <= List.batchSize; i ++) {
|
||||
list.add("hello" + i);
|
||||
}
|
||||
|
||||
assert.equal(list.id, 'A');
|
||||
assert.equal(list.seq, 1);
|
||||
assert.equal(list.ver, 0);
|
||||
assert.equal(list.items.length, List.batchSize);
|
||||
assert.equal(list._currentBatch.length, 0);
|
||||
assert.equal(list._items.length, List.batchSize);
|
||||
|
||||
const item = list.items[list.items.length - 1];
|
||||
assert.equal(item.id, 'A');
|
||||
assert.equal(item.seq, 0);
|
||||
assert.equal(item.ver, List.batchSize - 1);
|
||||
assert.equal(item.data, 'hello' + List.batchSize);
|
||||
assert.equal(item.next, 'A.0.198.QmRKrcfkejCvxTxApZACjHpxzAKKGnCtFi2rD31CT7RkBS');
|
||||
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('join', () => {
|
||||
it('increases the sequence and resets the version if other list has the same or higher sequence', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
|
||||
list2.seq = 7;
|
||||
list1.add("helloA1")
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 0);
|
||||
assert.equal(list1.ver, 1);
|
||||
|
||||
list2.add("helloB1")
|
||||
list1.join(list2);
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 8);
|
||||
assert.equal(list1.ver, 0);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('increases the sequence by one if other list has lower sequence', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
list1.seq = 4;
|
||||
list2.seq = 1;
|
||||
list2.add("helloB1")
|
||||
list1.join(list2);
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 5);
|
||||
assert.equal(list1.ver, 0);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('finds the next head when adding a new element', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
list1.add("helloA1")
|
||||
list1.add("helloA2")
|
||||
list1.add("helloA3")
|
||||
|
||||
assert.equal(list1._currentBatch.length, 3);
|
||||
assert.equal(list1._currentBatch[2].next.length, 1);
|
||||
assert.equal(list1._currentBatch[2].next[0], 'A.0.1.QmW3cnX41CNSAEkZE23w4qMRcsAY8MEUtsCT4wZmRZfQ76');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('finds the next heads (two) after a join', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
list1.add("helloA2")
|
||||
|
||||
assert.equal(list1._currentBatch.length, 1);
|
||||
assert.equal(list1._currentBatch[0].next.length, 2);
|
||||
assert.equal(list1._currentBatch[0].next[0], 'A.0.0.QmaHqKY1GUJTKGF6KA3QLoDaD3TS7oa6wHGTAxY6sVLKD9');
|
||||
assert.equal(list1._currentBatch[0].next[1], 'B.0.1.QmbsBfrDfqtTbaPNzuF8KNR1jbK74LwMe4UM2G6DgN6zmQ');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('finds the next head (one) after a join', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
|
||||
list1.add("helloA2")
|
||||
list1.add("helloA3")
|
||||
|
||||
assert.equal(list1._currentBatch.length, 2);
|
||||
assert.equal(list1._currentBatch[1].next.length, 1);
|
||||
assert.equal(list1._currentBatch[1].next[0], 'A.1.0.QmPxBabxGovTzTphiwoiEDCRnTGYwqZ7M7jahVVctbaJdF');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('finds the next heads after two joins', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
list1.add("helloA1")
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
|
||||
list1.add("helloA3")
|
||||
|
||||
list1.join(list2);
|
||||
|
||||
list1.add("helloA4")
|
||||
list1.add("helloA5")
|
||||
|
||||
const lastItem = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.items.length, 7);
|
||||
assert.equal(lastItem.next.length, 1);
|
||||
assert.equal(lastItem.next[0], 'A.2.0.QmTpRBszPFnxtuKccYJ4YShQoeYm2caeFhmMVBfiY1u7Jc');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('finds the next heads after multiple joins', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
const list3 = new List('C', ipfs);
|
||||
const list4 = new List('D', ipfs);
|
||||
list1.add("helloA1")
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
|
||||
list3.add("helloC1")
|
||||
list4.add("helloD1")
|
||||
list1.join(list3);
|
||||
|
||||
list1.add("helloA3")
|
||||
list2.join(list1);
|
||||
list1.join(list2);
|
||||
list2.join(list4);
|
||||
|
||||
list4.add("helloD2")
|
||||
list4.add("helloD3")
|
||||
list1.add("helloA4")
|
||||
list1.join(list4);
|
||||
|
||||
list1.add("helloA5")
|
||||
|
||||
const lastItem = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.items.length, 11);
|
||||
assert.equal(lastItem.next.length, 2);
|
||||
assert.equal(lastItem.next[0], 'A.4.0.Qmb7oeViDbsKTDNo7HAueFn47z3pon2fVptXNdXhcAigFz');
|
||||
assert.equal(lastItem.next[1], 'D.0.2.QmajSkuVj64RLy8YGVPqkDb4V52FjqDsvbGhJsLmkQLxsL');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('joins list of one item with list of two items', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
|
||||
const lastItem = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 1);
|
||||
assert.equal(list1.ver, 0);
|
||||
assert.equal(list1._currentBatch.length, 0);
|
||||
assert.equal(list1._items.length, 3);
|
||||
assert.equal(lastItem.id, 'B');
|
||||
assert.equal(lastItem.seq, 0);
|
||||
assert.equal(lastItem.ver, 1);
|
||||
assert.equal(lastItem.data, 'helloB2');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('joins lists two ways', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
list1.add("helloA1")
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB1")
|
||||
list2.add("helloB2")
|
||||
list1.join(list2);
|
||||
list2.join(list1);
|
||||
|
||||
const lastItem1 = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 1);
|
||||
assert.equal(list1.ver, 0);
|
||||
assert.equal(list1._currentBatch.length, 0);
|
||||
assert.equal(list1._items.length, 4);
|
||||
assert.equal(lastItem1.id, 'B');
|
||||
assert.equal(lastItem1.seq, 0);
|
||||
assert.equal(lastItem1.ver, 1);
|
||||
assert.equal(lastItem1.data, 'helloB2');
|
||||
|
||||
const lastItem2 = list2.items[list2.items.length - 1];
|
||||
|
||||
assert.equal(list2.id, 'B');
|
||||
assert.equal(list2.seq, 2);
|
||||
assert.equal(list2.ver, 0);
|
||||
assert.equal(list2._currentBatch.length, 0);
|
||||
assert.equal(list2._items.length, 4);
|
||||
assert.equal(lastItem2.id, 'A');
|
||||
assert.equal(lastItem2.seq, 0);
|
||||
assert.equal(lastItem2.ver, 1);
|
||||
assert.equal(lastItem2.data, 'helloA2');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('joins lists twice', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list2.join(list1);
|
||||
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB2")
|
||||
list2.join(list1);
|
||||
|
||||
const secondItem = list2.items[1];
|
||||
const lastItem = list2.items[list2.items.length - 1];
|
||||
|
||||
assert.equal(list2.id, 'B');
|
||||
assert.equal(list2.seq, 2);
|
||||
assert.equal(list2.ver, 0);
|
||||
assert.equal(list2._currentBatch.length, 0);
|
||||
assert.equal(list2._items.length, 4);
|
||||
assert.equal(secondItem.id, 'A');
|
||||
assert.equal(secondItem.seq, 0);
|
||||
assert.equal(secondItem.ver, 0);
|
||||
assert.equal(secondItem.data, 'helloA1');
|
||||
assert.equal(lastItem.id, 'A');
|
||||
assert.equal(lastItem.seq, 0);
|
||||
assert.equal(lastItem.ver, 1);
|
||||
assert.equal(lastItem.data, 'helloA2');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('joins 4 lists to one', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
const list3 = new List('C', ipfs);
|
||||
const list4 = new List('D', ipfs);
|
||||
|
||||
list1.add("helloA1")
|
||||
list2.add("helloB1")
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB2")
|
||||
list3.add("helloC1")
|
||||
list4.add("helloD1")
|
||||
list3.add("helloC2")
|
||||
list4.add("helloD2")
|
||||
list1.join(list2);
|
||||
list1.join(list3);
|
||||
list1.join(list4);
|
||||
|
||||
const secondItem = list1.items[1];
|
||||
const lastItem = list1.items[list1.items.length - 1];
|
||||
|
||||
assert.equal(list1.id, 'A');
|
||||
assert.equal(list1.seq, 3);
|
||||
assert.equal(list1.ver, 0);
|
||||
assert.equal(list1._currentBatch.length, 0);
|
||||
assert.equal(list1._items.length, 8);
|
||||
assert.equal(secondItem.id, 'A');
|
||||
assert.equal(secondItem.seq, 0);
|
||||
assert.equal(secondItem.ver, 1);
|
||||
assert.equal(secondItem.data, 'helloA2');
|
||||
assert.equal(lastItem.id, 'D');
|
||||
assert.equal(lastItem.seq, 0);
|
||||
assert.equal(lastItem.ver, 1);
|
||||
assert.equal(lastItem.data, 'helloD2');
|
||||
done();
|
||||
}));
|
||||
|
||||
it('joins lists from 4 lists', async((done) => {
|
||||
const list1 = new List('A', ipfs);
|
||||
const list2 = new List('B', ipfs);
|
||||
const list3 = new List('C', ipfs);
|
||||
const list4 = new List('D', ipfs);
|
||||
|
||||
list1.add("helloA1")
|
||||
list1.join(list2);
|
||||
list2.add("helloB1")
|
||||
list2.join(list1);
|
||||
|
||||
list1.add("helloA2")
|
||||
list2.add("helloB2")
|
||||
list1.join(list3);
|
||||
list3.join(list1);
|
||||
|
||||
list3.add("helloC1")
|
||||
list4.add("helloD1")
|
||||
|
||||
list3.add("helloC2")
|
||||
list4.add("helloD2")
|
||||
|
||||
list1.join(list3);
|
||||
list1.join(list2);
|
||||
list4.join(list2);
|
||||
list4.join(list1);
|
||||
list4.join(list3);
|
||||
|
||||
list4.add("helloD3")
|
||||
list4.add("helloD4")
|
||||
|
||||
const secondItem = list4.items[1];
|
||||
const lastItem1 = list4._items[list4._items.length - 1];
|
||||
const lastItem2 = list4.items[list4.items.length - 1];
|
||||
|
||||
assert.equal(list4.id, 'D');
|
||||
assert.equal(list4.seq, 7);
|
||||
assert.equal(list4.ver, 2);
|
||||
assert.equal(list4._currentBatch.length, 2);
|
||||
assert.equal(list4._items.length, 8);
|
||||
assert.equal(secondItem.id, 'D');
|
||||
assert.equal(secondItem.seq, 0);
|
||||
assert.equal(secondItem.ver, 1);
|
||||
assert.equal(secondItem.data, 'helloD2');
|
||||
assert.equal(lastItem1.id, 'C');
|
||||
assert.equal(lastItem1.seq, 3);
|
||||
assert.equal(lastItem1.ver, 1);
|
||||
assert.equal(lastItem1.data, 'helloC2');
|
||||
assert.equal(lastItem2.id, 'D');
|
||||
assert.equal(lastItem2.seq, 7);
|
||||
assert.equal(lastItem2.ver, 1);
|
||||
assert.equal(lastItem2.data, 'helloD4');
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('_findHeads', () => {
|
||||
it('TODO', (done) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('_isReferencedInChain', () => {
|
||||
it('TODO', (done) => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}));
|
63
test/orbitlist-node-tests.js
Normal file
63
test/orbitlist-node-tests.js
Normal file
@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const async = require('asyncawait/async');
|
||||
const await = require('asyncawait/await');
|
||||
const assert = require('assert');
|
||||
const ipfsDaemon = require('orbit-common/lib/ipfs-daemon');
|
||||
const ipfsAPI = require('orbit-common/lib/ipfs-api-promised');
|
||||
const Node = require('../src/list/OrbitNode');
|
||||
|
||||
const startIpfs = async (() => {
|
||||
return new Promise(async((resolve, reject) => {
|
||||
const ipfsd = await(ipfsDaemon());
|
||||
resolve(ipfsd.daemon);
|
||||
}));
|
||||
});
|
||||
|
||||
let ipfs;
|
||||
|
||||
describe('OrbitNode', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
before(async((done) => {
|
||||
ipfs = await(startIpfs());
|
||||
done();
|
||||
}));
|
||||
|
||||
describe('Constructor', () => {
|
||||
it('initializes member variables', async((done) => {
|
||||
const node = new Node(ipfs, 'A', 0, 0);
|
||||
assert.equal(node.id, 'A');
|
||||
assert.equal(node.seq, 0);
|
||||
assert.equal(node.ver, 0);
|
||||
assert.equal(node.data, null);
|
||||
assert.equal(node.next instanceof Array, true);
|
||||
assert.equal(node.hash, null);
|
||||
assert.equal(node._ipfs, ipfs);
|
||||
done();
|
||||
}));
|
||||
|
||||
it('initializes member variables with data', async((done) => {
|
||||
const node = new Node(ipfs, 'A', 0, 0, 'QmTnaGEpw4totXN7rhv2jPMXKfL8s65PhhCKL5pwtJfRxn');
|
||||
assert.equal(node.id, 'A');
|
||||
assert.equal(node.seq, 0);
|
||||
assert.equal(node.ver, 0);
|
||||
assert.equal(node.data, 'QmTnaGEpw4totXN7rhv2jPMXKfL8s65PhhCKL5pwtJfRxn');
|
||||
assert.equal(node.next instanceof Array, true);
|
||||
assert.equal(node.hash, null);
|
||||
assert.equal(node._ipfs, ipfs);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('compactId', () => {
|
||||
it('presents the node as a string with id, sequence, version and hash', async((done) => {
|
||||
const node1 = new Node(ipfs, 'A', 0, 0, "QmTnaGEpw4totXN7rhv2jPMXKfL8s65PhhCKL5pwtJfRxn");
|
||||
const node2 = new Node(ipfs, 'B', 123, 456, "QmdcCucbM2rnHHaVhAmjMxWDY5cCDwtTtjhYuS5nBHThQq");
|
||||
assert.equal(node1.compactId, 'A.0.0.QmcfXxBTpZGmWnYVUiPTpW4Uaf9e1x34Qh9vthvuAjmhTb');
|
||||
assert.equal(node2.compactId, 'B.123.456.QmWCVngHttRQQhrmgr94GZzY5F57m3g6fDdDwK9mgHFRn2');
|
||||
done();
|
||||
}));
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user