Merge remote-tracking branch 'origin/proto/lists'

This commit is contained in:
haad 2016-02-22 12:38:25 +02:00
commit 09a8df8113
35 changed files with 2678 additions and 927 deletions

5
.gitignore vendored
View File

@ -1,4 +1,7 @@
*sublime*
node_modules/
debug.log
WIP/
.vagrant/
.idea/
isolate*.log
dump.rdb

View File

@ -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
View 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
View 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
View 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();

View File

@ -1,3 +1,5 @@
machine:
node:
version: 4.2.2
services:
- redis

56
examples/benchmark.js Normal file
View 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;

View File

@ -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
View 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;

View File

@ -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
View 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;

View File

@ -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
View 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;

View File

@ -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"

View File

@ -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;

View File

@ -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
View 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;

View File

@ -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));
})
})
}
}

View File

@ -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
};

View File

@ -1,6 +1,6 @@
'use strict';
let ItemTypes = {
const ItemTypes = {
Message: "text",
Snippet: "snippet",
File: "file",

View 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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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();
});
});
});

View File

@ -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
View 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();
});
});
}));

View 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();
}));
});
});