diff --git a/.gitignore b/.gitignore index ea32c98..7153fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ node_modules/ debug.log WIP/ .vagrant/ +.idea/ +isolate*.log +dump.rdb \ No newline at end of file diff --git a/MOCK.json b/MOCK.json new file mode 100644 index 0000000..54922e3 --- /dev/null +++ b/MOCK.json @@ -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": "", + "prev": [] +} + +"List": { + "items": [""] +} + +/* + 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"]} diff --git a/examples/pubsubReader.js b/examples/pubsubReader.js index cde793f..ba7293d 100644 --- a/examples/pubsubReader.js +++ b/examples/pubsubReader.js @@ -30,7 +30,7 @@ let run = (async(() => { console.log("Query..."); let items = channel.iterator({ limit: 3 }).collect(); - console.log(`Found items ${items.length} items`); + console.log(`Found ${items.length} items`); var g = items.filter((e) => e.item.Payload.startsWith(id)) var prev = -1; diff --git a/package.json b/package.json index de9aad6..c303053 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "asyncawait": "^1.0.1", "bluebird": "^3.1.1", "bs58": "^3.0.0", + "lodash": "^4.3.0", "orbit-common": "^0.1.0", "redis": "^2.4.2", "unirest": "^0.4.2" diff --git a/src/OrbitClient.js b/src/OrbitClient.js index f84fa21..30f753a 100644 --- a/src/OrbitClient.js +++ b/src/OrbitClient.js @@ -144,14 +144,17 @@ class OrbitClient { // Create the hash cache item const hcItem = new HashCacheItem(operation, key, seq, target, metaInfo, null, pubkey, privkey, password); + console.log("1") // Save the item to ipfs const data = await (ipfsAPI.putObject(this.ipfs, JSON.stringify(hcItem))); + console.log("2", data.Hash, head) 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, head)); + console.log("3") return { hash: newHead, seq: seq }; } @@ -170,17 +173,19 @@ class OrbitClient { _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); + return await(this._createOperation(channel, password, HashCacheOps.Delete, key, target)); } _createOperation(channel, password, operation, key, value, data) { let message, res = false; while(!res) { + this.posting = true; console.log("posting...") message = this._createMessage(channel, password, operation, key, value); res = await(this._pubsub.publish(channel, message.hash, message.seq)); if(!res) console.log("retry", message.hash, message.seq) } + this.posting = false; console.log("posted") return message.hash.Hash; } diff --git a/src/PubSub.js b/src/PubSub.js index 37993ba..01fc892 100644 --- a/src/PubSub.js +++ b/src/PubSub.js @@ -65,8 +65,11 @@ class PubSub { return new Promise((resolve, reject) => { if(this.publishQueue.length === 0) { this.publishQueue.splice(0, 0, { hash: message.Hash, callback: resolve }); + console.log("...") this.client2.publish(hash, JSON.stringify({ hash: message.Hash, seq: seq })); + console.log("published") } else { + console.log("queue full!") resolve(false); } }); @@ -106,6 +109,7 @@ class PubSub { var isNewer = message.seq > this._subscriptions[hash].seq; if(isNewer) { console.log("NEW HEAD!") + this.publishQueue.pop(); this._updateSubscription(hash, message.head, message.seq); } } diff --git a/test/list-perf-tests.js b/test/list-perf-tests.js new file mode 100644 index 0000000..50431a8 --- /dev/null +++ b/test/list-perf-tests.js @@ -0,0 +1,34 @@ +// 'use strict'; + +// var assert = require('assert'); +// var async = require('asyncawait/async'); +// var await = require('asyncawait/await'); +// var List = require('../test1').List; +// var Node = require('../test1').Node; +// var Timer = require('../examples/Timer'); + +// describe('List - Performance Measurement', function() { +// this.timeout(60000); + +// it('add', (done) => { +// let ms = 0; + +// for(let t = 1000; t <= 10000; 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(ms < t, true); +// } + +// assert.equal(true, true); +// done(); +// }); + +// }); diff --git a/test/list-tests.js b/test/list-tests.js new file mode 100644 index 0000000..bdbe3b3 --- /dev/null +++ b/test/list-tests.js @@ -0,0 +1,458 @@ +'use strict'; + +var assert = require('assert'); +var async = require('asyncawait/async'); +var await = require('asyncawait/await'); +var List = require('../test1').List; +var Node = require('../test1').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 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(); + }); + }); +}); + +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('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('toJson', () => { + it('presents the list in a compacted json form', (done) => { + const list = new List('A'); + list.add("hello1") + list.add("hello2") + list.add("hello3") + // list.add("hello4") + // list.add("hello5") + let compacted = list.toJson(); + // console.log(compacted) + assert.equal(compacted.id, 'A'); + assert.equal(compacted.seq, 0); + assert.equal(compacted.ver, 3); + assert.equal(compacted.items.length, 3); + assert.equal(compacted.items[0].id, 'A'); + assert.equal(compacted.items[0].seq, 0); + assert.equal(compacted.items[0].ver, 0); + assert.equal(compacted.items[0].next.length, 0); + assert.equal(compacted.items[compacted.items.length - 1].id, 'A'); + assert.equal(compacted.items[compacted.items.length - 1].seq, 0); + assert.equal(compacted.items[compacted.items.length - 1].ver, 2); + assert.equal(compacted.items[compacted.items.length - 1].next.length, 1); + assert.equal(compacted.items[compacted.items.length - 1].next[0], 'A.0.1'); + assert.equal(compacted.items[compacted.items.length - 2].id, 'A'); + assert.equal(compacted.items[compacted.items.length - 2].seq, 0); + assert.equal(compacted.items[compacted.items.length - 2].ver, 1); + assert.equal(compacted.items[compacted.items.length - 2].next.length, 1); + assert.equal(compacted.items[compacted.items.length - 2].next[0], 'A.0.0'); + 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") + + // console.log(list1.toString()) + 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 after joining a list with another', (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 after a two-way 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('find sthe next heads after join two-way join', (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]; + // console.log(list1.toString()) + assert.equal(list1.items.length, 7); + assert.equal(lastItem.next.length, 1); + assert.equal(lastItem.next[0], 'A.2.0'); + 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); + + // console.log(list1) + // console.log(list2) + + 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(list1._items[list1._items.length - 1].id, 'B'); + assert.equal(list1._items[list1._items.length - 1].seq, 0); + assert.equal(list1._items[list1._items.length - 1].ver, 1); + assert.equal(list1._items[list1._items.length - 1].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); + + 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(list1._items[list1._items.length - 1].id, 'B'); + assert.equal(list1._items[list1._items.length - 1].seq, 0); + assert.equal(list1._items[list1._items.length - 1].ver, 1); + assert.equal(list1._items[list1._items.length - 1].data, 'helloB2'); + + 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(list2._items[list2._items.length - 1].id, 'A'); + assert.equal(list2._items[list2._items.length - 1].seq, 0); + assert.equal(list2._items[list2._items.length - 1].ver, 1); + assert.equal(list2._items[list2._items.length - 1].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); + + 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(list2._items[1].id, 'A'); + assert.equal(list2._items[1].seq, 0); + assert.equal(list2._items[1].ver, 0); + assert.equal(list2._items[1].data, 'helloA1'); + assert.equal(list2._items[list2._items.length - 1].id, 'A'); + assert.equal(list2._items[list2._items.length - 1].seq, 0); + assert.equal(list2._items[list2._items.length - 1].ver, 1); + assert.equal(list2._items[list2._items.length - 1].data, 'helloA2'); + done(); + }); + + it('joins 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") + list2.add("helloB1") + // list2.join(list1); + + list1.add("helloA2") + list2.add("helloB2") + // list2.join(list1); + + list3.add("helloC1") + list4.add("helloD1") + // list2.join(list1); + + list3.add("helloC2") + list4.add("helloD2") + list1.join(list2); + list1.join(list3); + list1.join(list4); + + assert.equal(list1.id, 'A'); + // assert.equal(list1.seq, 1); + // assert.equal(list1.ver, 1); + assert.equal(list1._currentBatch.length, 0); + assert.equal(list1._items.length, 8); + assert.equal(list1._items[1].id, 'A'); + assert.equal(list1._items[1].seq, 0); + assert.equal(list1._items[1].ver, 1); + assert.equal(list1._items[1].data, 'helloA2'); + assert.equal(list1._items[list1._items.length - 1].id, 'D'); + assert.equal(list1._items[list1._items.length - 1].seq, 0); + assert.equal(list1._items[list1._items.length - 1].ver, 1); + assert.equal(list1._items[list1._items.length - 1].data, 'helloD2'); + done(); + }); + + it('joins 4 lists sequentally', (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") + + // console.log(list4.toString()); + 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(list4._items[1].id, 'D'); + assert.equal(list4._items[1].seq, 0); + assert.equal(list4._items[1].ver, 1); + assert.equal(list4._items[1].data, 'helloD2'); + assert.equal(list4._items[list1._items.length - 1].id, 'A'); + assert.equal(list4._items[list1._items.length - 1].seq, 1); + assert.equal(list4._items[list1._items.length - 1].ver, 0); + assert.equal(list4._items[list1._items.length - 1].data, 'helloA2'); + assert.equal(list4.items[list4.items.length - 1].id, 'D'); + assert.equal(list4.items[list4.items.length - 1].seq, 7); + assert.equal(list4.items[list4.items.length - 1].ver, 1); + assert.equal(list4.items[list4.items.length - 1].data, 'helloD4'); + done(); + }); + + }); + + it('solves a graph', (done) => { + done(); + }); + +}); diff --git a/test/orbit-client-tests.js b/test/orbit-client-tests.js index 3b879d9..ae3d8df 100644 --- a/test/orbit-client-tests.js +++ b/test/orbit-client-tests.js @@ -1,580 +1,580 @@ -'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 -} - -// Orbit -const host = 'localhost'; -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 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()); - 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, 'hello'); - // 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(); - })); - }); - - 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'); - 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'); - 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); - } - } - done(); - })); - - it('adds an item that is > 256 bytes', async((done) => { - try { - var msg = new Buffer(512); - 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); - } - done(); - })); - }); - - - describe('Iterator', function() { - var items = []; - var 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); - } - resolve(); - })); - addMessages().then(done); - }); - - describe('Defaults', function() { - it('returns an iterator', async((done) => { - var iter = orbit.channel(channel, '').iterator(); - var 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('implements Iterator interface', async((done) => { - var iter = orbit.channel(channel, '').iterator({ limit: -1 }); - var messages = []; - - for(let i of iter) - messages.push(i.hash); - - assert.equal(messages.length, items.length); - done(); - })); - - 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]); - assert.equal(second, null); - assert.equal(first.item.Payload, 'hello4'); - done(); - })); - }); - - describe('Collect', function() { - it('returns all items', async((done) => { - var iter = orbit.channel(channel, '').iterator({ limit: -1 }); - var messages = iter.collect(); - assert.equal(messages.length, items.length); - assert.equal(messages[messages.length - 1].item.Payload, 'hello0'); - assert.equal(messages[0].item.Payload, 'hello4'); - done(); - })); - - it('returns 1 item', async((done) => { - var iter = orbit.channel(channel, '').iterator(); - var messages = iter.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(); - assert.equal(messages.length, 3); - done(); - })); - }); - - 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]); - 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]); - 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]); - 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); - - messages.reverse(); - 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); - - assert.equal(messages.length, items.length); - assert.equal(messages[0], items[items.length - 1]); - 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); - - assert.equal(messages.length, items.length); - assert.equal(messages[0], items[0]); - 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); - - assert.equal(messages.length, 1); - assert.equal(messages[0], items[items.length -1]); - 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); - - // console.log(messages, all) - assert.equal(messages.length, 2); - assert.equal(messages[0], all[0].hash); - assert.equal(messages[1], all[1].hash); - 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); - - assert.equal(messages.length, itemCount); - assert.equal(messages[0], items[items.length - 1]); - assert.equal(messages[messages.length - 1], items[0]); - 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); - - assert.equal(messages.length, itemCount - 1); - assert.equal(messages[0], items[items.length - 1]); - assert.equal(messages[3], items[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); - - var gt = messages[2]; - var iter2 = orbit.channel(channel, '').iterator({ gt: gt, limit: 100 }); - var messages2 = iter2.collect().map((e) => e.hash); - - assert.equal(messages2.length, 2); - assert.equal(messages2[0], messages[0]); - assert.equal(messages2[1], messages[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); - - assert.equal(messages.length, 1); - assert.equal(messages[0], head.hash); - 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); - - assert.equal(messages.length, itemCount); - assert.equal(messages[0], head.hash); - assert.equal(messages[4], all[all.length - 1].hash); - 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); - - assert.equal(messages.length, 3); - assert.equal(messages[0], head.hash); - assert.equal(messages[2], all[2].hash); - 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(); - 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); - - assert.equal(messages.length, 1); - assert.equal(messages[0], all[all.length - 1].hash); - 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); - - assert.equal(messages.length, itemCount); - assert.equal(messages[0], all[0].hash); - assert.equal(messages[4], all[all.length - 1].hash); - 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); - - 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); - done(); - })); - }); - }); - - }); - - -/* - describe('Modes', function() { - var password = 'hello'; - - it('sets read mode', async((done) => { - try { - var mode = { - mode: "+r", - params: { - password: password - } - }; - var modes = orbit.channel(channel, '').setMode(mode) - assert.notEqual(modes.r, null); - assert.equal(modes.r.password, password); - } catch(e) { - assert.equal(e, null); - } - done(); - })); - - it('can\'t read with wrong password', async((done) => { - try { - var modes = orbit.channel(channel, 'invalidpassword').iterator(); - assert.equal(true, false); - } catch(e) { - assert.equal(e, 'Unauthorized'); - } - done(); - })); - - it('sets write mode', async((done) => { - try { - var mode = { - mode: "+w", - params: { - ops: [orbit.user.id] - } - }; - var modes = orbit.channel(channel, password).setMode(mode); - assert.notEqual(modes.w, null); - assert.equal(modes.w.ops[0], orbit.user.id); - } catch(e) { - assert.equal(e, null); - } - done(); - })); - - it('can\'t write when user not an op', async((done) => { - // TODO - done(); - })); - - it('removes write mode', async((done) => { - try { - var modes = orbit.channel(channel, password).setMode({ mode: "-w" }); - assert.equal(modes.w, null); - } catch(e) { - assert.equal(e, null); - } - done(); - })); - - it('removes read mode', async((done) => { - try { - var modes = orbit.channel(channel, password).setMode({ mode: "-r" }); - assert.equal(modes.r, null); - } catch(e) { - assert.equal(e, null); - } - done(); - })); - - }); -*/ -}); +// '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 +// } + +// // Orbit +// const host = 'localhost'; +// 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 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()); +// 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, 'hello'); +// // 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(); +// })); +// }); + +// 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'); +// 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'); +// 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); +// } +// } +// done(); +// })); + +// it('adds an item that is > 256 bytes', async((done) => { +// try { +// var msg = new Buffer(512); +// 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); +// } +// done(); +// })); +// }); + + +// describe('Iterator', function() { +// var items = []; +// var 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); +// } +// resolve(); +// })); +// addMessages().then(done); +// }); + +// describe('Defaults', function() { +// it('returns an iterator', async((done) => { +// var iter = orbit.channel(channel, '').iterator(); +// var 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('implements Iterator interface', async((done) => { +// var iter = orbit.channel(channel, '').iterator({ limit: -1 }); +// var messages = []; + +// for(let i of iter) +// messages.push(i.hash); + +// assert.equal(messages.length, items.length); +// done(); +// })); + +// 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]); +// assert.equal(second, null); +// assert.equal(first.item.Payload, 'hello4'); +// done(); +// })); +// }); + +// describe('Collect', function() { +// it('returns all items', async((done) => { +// var iter = orbit.channel(channel, '').iterator({ limit: -1 }); +// var messages = iter.collect(); +// assert.equal(messages.length, items.length); +// assert.equal(messages[messages.length - 1].item.Payload, 'hello0'); +// assert.equal(messages[0].item.Payload, 'hello4'); +// done(); +// })); + +// it('returns 1 item', async((done) => { +// var iter = orbit.channel(channel, '').iterator(); +// var messages = iter.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(); +// assert.equal(messages.length, 3); +// done(); +// })); +// }); + +// 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]); +// 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]); +// 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]); +// 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); + +// messages.reverse(); +// 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); + +// assert.equal(messages.length, items.length); +// assert.equal(messages[0], items[items.length - 1]); +// 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); + +// assert.equal(messages.length, items.length); +// assert.equal(messages[0], items[0]); +// 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); + +// assert.equal(messages.length, 1); +// assert.equal(messages[0], items[items.length -1]); +// 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); + +// // console.log(messages, all) +// assert.equal(messages.length, 2); +// assert.equal(messages[0], all[0].hash); +// assert.equal(messages[1], all[1].hash); +// 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); + +// assert.equal(messages.length, itemCount); +// assert.equal(messages[0], items[items.length - 1]); +// assert.equal(messages[messages.length - 1], items[0]); +// 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); + +// assert.equal(messages.length, itemCount - 1); +// assert.equal(messages[0], items[items.length - 1]); +// assert.equal(messages[3], items[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); + +// var gt = messages[2]; +// var iter2 = orbit.channel(channel, '').iterator({ gt: gt, limit: 100 }); +// var messages2 = iter2.collect().map((e) => e.hash); + +// assert.equal(messages2.length, 2); +// assert.equal(messages2[0], messages[0]); +// assert.equal(messages2[1], messages[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); + +// assert.equal(messages.length, 1); +// assert.equal(messages[0], head.hash); +// 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); + +// assert.equal(messages.length, itemCount); +// assert.equal(messages[0], head.hash); +// assert.equal(messages[4], all[all.length - 1].hash); +// 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); + +// assert.equal(messages.length, 3); +// assert.equal(messages[0], head.hash); +// assert.equal(messages[2], all[2].hash); +// 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(); +// 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); + +// assert.equal(messages.length, 1); +// assert.equal(messages[0], all[all.length - 1].hash); +// 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); + +// assert.equal(messages.length, itemCount); +// assert.equal(messages[0], all[0].hash); +// assert.equal(messages[4], all[all.length - 1].hash); +// 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); + +// 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); +// done(); +// })); +// }); +// }); + +// }); + + +// /* +// describe('Modes', function() { +// var password = 'hello'; + +// it('sets read mode', async((done) => { +// try { +// var mode = { +// mode: "+r", +// params: { +// password: password +// } +// }; +// var modes = orbit.channel(channel, '').setMode(mode) +// assert.notEqual(modes.r, null); +// assert.equal(modes.r.password, password); +// } catch(e) { +// assert.equal(e, null); +// } +// done(); +// })); + +// it('can\'t read with wrong password', async((done) => { +// try { +// var modes = orbit.channel(channel, 'invalidpassword').iterator(); +// assert.equal(true, false); +// } catch(e) { +// assert.equal(e, 'Unauthorized'); +// } +// done(); +// })); + +// it('sets write mode', async((done) => { +// try { +// var mode = { +// mode: "+w", +// params: { +// ops: [orbit.user.id] +// } +// }; +// var modes = orbit.channel(channel, password).setMode(mode); +// assert.notEqual(modes.w, null); +// assert.equal(modes.w.ops[0], orbit.user.id); +// } catch(e) { +// assert.equal(e, null); +// } +// done(); +// })); + +// it('can\'t write when user not an op', async((done) => { +// // TODO +// done(); +// })); + +// it('removes write mode', async((done) => { +// try { +// var modes = orbit.channel(channel, password).setMode({ mode: "-w" }); +// assert.equal(modes.w, null); +// } catch(e) { +// assert.equal(e, null); +// } +// done(); +// })); + +// it('removes read mode', async((done) => { +// try { +// var modes = orbit.channel(channel, password).setMode({ mode: "-r" }); +// assert.equal(modes.r, null); +// } catch(e) { +// assert.equal(e, null); +// } +// done(); +// })); + +// }); +// */ +// }); diff --git a/test1.js b/test1.js new file mode 100644 index 0000000..5c6d04a --- /dev/null +++ b/test1.js @@ -0,0 +1,345 @@ +'use strict'; + +const _ = require('lodash'); +const Timer = require('./examples/Timer'); + +Array.prototype.allEqual = function(val) { + for(var i = 1; i < this.length; i++) { + if(this[i] !== val || this[i] !== this[0]) + return false; + } + return true; +} + +if (!Array.prototype.last){ + Array.prototype.last = function(){ + return this[this.length - 1]; + }; +} + +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 } + // return { id: this.id, seq: this.seq, ver: this.ver, data: this.data, next: this.next.map((f) => f.compactId) } + } + + // static parseCompact(t) { + // let v = t.split('.'); + // return { id: v[0], seq: parseInt(v[1]), ver: parseInt(v[2]) }; + // } + +} + +class List { + constructor(id) { + this.id = id; + this.seq = 0; + this.ver = 0; + this._items = []; + this._currentBatch = []; + this._referenced = []; + } + + static fromJson(json) { + let list = new List(json.id); + list.seq = json.seq; + list.ver = json.ver; + // list._items = json.items.map((f) => new Node(f.id, f.seq, f.ver, f.data, f.next)); + list._items = _.uniqWith(json.items.map((f) => { + // console.log(JSON.stringify(f.next, null, 2)); + return new Node(f.id, f.seq, f.ver, f.data, f.next); + }), _.isEqual); + return list; + } + + get items() { + return this._items.concat(this._currentBatch); + } + + 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} + ` + } + + add(data) { + // var ii = this.items.map((f) => f.compact()); + // console.log("--->", this.seq, this.ver) + const heads = this._findHeads(this.items);//.map((f) => f.compact()); + // const heads = this._findHeads(this.items) + // console.log("jjjj", this._referenced, "\n\n") + // console.log("jjjj", this.items, "\n\n") + const node = new Node(this.id, this.seq, this.ver, data, heads); + // console.log("aaaa", node) + this._currentBatch.push(node); + // this._referenced.push(node); + this.ver ++; + } + + join(other) { + let ms; + + if(other.seq && other.seq > this.seq) { + this.seq = other.seq + 1; + this.ver = 0; + } else { + this.seq = this.seq + 1; + this.ver = 0; + } + // if(other.items.last() && other.items.last().seq > this.seq) { + // this.seq = other.seq + 1; + // this.ver = 0; + // } + + // return items that are only in this._currentBatch + const current = _.differenceWith(this._currentBatch, this._items, this._equals); + // return items that are only in other.items + + let timer = new Timer(true); + const others = _.differenceWith(other.items, this._items, this._equals); + ms = timer.stop(true); + // return unique items from e and d + // const final = _.unionWith(others, this._currentBatch, this._equals); + const final = _.unionWith(current, others, this._equals); + // append new items to all items + this._items = this._items.concat(final); + + this._currentBatch = []; + if(ms > 20) { + console.log("OVER 20MS!!", other.items.length) + console.log(`join took ${timer.stop(true)} ms`); + } + } + + _findHeads(list) { + let timer = new Timer(true); + let ms; + const grouped = _.groupBy(list, 'id'); + // const heads = Object.keys(grouped).sort((a, b) => a === this.id ? -1 : (a < b ? 1 : 0)).map((g) => grouped[g].last()); + const heads = Object.keys(grouped).map((g) => grouped[g].last()); + // const heads = _.differenceWith(list, this._referenced); + // console.log("HEADS", JSON.stringify(heads)); + // const cleaned = heads.filter((e) => !this._isReferencedInChain(list, e, this._referenced, 0)); + const cleaned = heads.filter((e) => !this._isReferencedInChain2(list, e)); + // const cleaned = heads; + // console.log("CLEANED", JSON.stringify(cleaned), "\n"); + // this._referenced = []; + ms = timer.stop(true); + if(ms > 20) { + console.log("OVER 20MS!!") + console.log(`_findHeads took ${ms} ms`); + } + // console.log(`_findHeads took ${ms} ms`); + return cleaned; + } + + + _isReferencedInChain2(all, item) { + // console.log("item:", item); + let isReferenced = all.find((e) => this._references(e, item)) !== undefined; + if(!isReferenced) { + // console.log("check children", item.next) + let childHasRef = item.next.map((f) => { + const next = all.find((e) => this._equals(e, f)); + const res = next ? this._isReferencedInChain2(all, next) : false; + return _.find(res, (e) => e === true) !== undefined; + }); + isReferenced = _.find(childHasRef, (e) => e === true) !== undefined; + // console.log("children have ref", isReferenced) + } + + // console.log("red:", isReferenced); + return isReferenced; + } + + // _parse(t) { + // let v = t.split('.'); + // return { id: v[0], seq: parseInt(v[1]), ver: parseInt(v[2]) }; + // } + + _equals(a, b) { + return a.id == b.id && a.seq == b.seq && a.ver == b.ver; + } + + _references(a, b) { + // return _.includes(a.next, b.compactId); + // faster than _.includes() + for(let i = 0; i < a.next.length; i ++) { + if(b.compactId === a.next[i]) return true; + } + return false; + } + +/* + _isReferencedInChain(all, item, next2, refs, depth) { + console.log("...item:", item); + depth += 1; + let timer = new Timer(true); + let ms; + + if(!item) + return false; + + // let isReferenced = refs.find((e) => e.id == item.id && e.seq == item.seq && e.ver == item.ver) !== undefined; + let isReferenced = refs.find((e) => this._equals(e, item)) !== undefined; + + if(isReferenced) + console.log("ref", item) + + if(isReferenced) + return true; + + // if(item.id == 'C' && item.seq == 3 && item.ver == 1) { + // console.log("!!!!", refs,"\n", item) + // } + refs.splice(0, 0, item); + + var check = (o) => { + const next = all.find((e) => this._equals(e, this._parse(o))); + if(!next) + return false; + + return this._isReferencedInChain(all, item, next, refs, depth); + }; + + // refs.push(item); + + // console.log(item.next); + // let res = item.next.map(this._parse).map(check); + let res = item.next.map(check); + + if(depth > 5) { + console.log("WTF!!!", depth, item, refs, res); + } + + // refs.splice(0, 0, item); + + // if(item.id == 'A' && item.seq == 0 && item.ver == 1) { + // console.log("????", isReferenced, refs, "\n\n", item, "\n\n", res) + // } + + // if(item.id == 'A' && item.seq == 1 && item.ver == 2) { + // console.log("+++", res) + // } + + // const hasRef2 = _.find(res, (e) => e === true) !== undefined; + // const hasRef2 = res.allEqual(true) + + ms = timer.stop(true); + if(ms > 20) { + console.log("OVER 20MS!!", refs.length) + console.log(`_isReferencedInChain took ${ms} ms`); + } + let hasRef2 = _.find(res, (e) => e === true) !== undefined; + // console.log("...res:", hasRef2, res); + // if(hasRef2) + // refs.splice(0, 0, item); + + return hasRef2; + } +*/ +} + +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); + + // var l = List.fromJson(JSON.parse(event)); + if(l.id === 'A') { + listB.join(l); + listC.join(l); + // console.log(listB); + } else if(l.id === 'B') { + listA.join(l); + listC.join(l); + // console.log("ListA:", listA); + } else if(l.id === 'C') { + listA.join(l); + // console.log("LIST", event); + console.log("Items:", listA.items.length); + // console.log(JSON.stringify(listA, null, 1)); + } + + } + + this.client1.on("message", handleMessage); + this.client2.on("message", handleMessage); + + setInterval(() => { + // listC.join(listB); + // listC.join(listA); + }, 5000); + + 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 < 100; a ++) { + listB.add("B--"+(i+a)); + // } + this.client2.publish(hash, JSON.stringify(listB.toJson())); + i++; + }, 10); + + 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(); + +module.exports = { + Node: Node, + List: List +};