├── .gitignore ├── LICENCE ├── README.md ├── example └── simple.js ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.err -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Raynos. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # append-only 2 | 3 | Append only scuttlebutt structure 4 | 5 | ## Example 6 | 7 | Represent an append only data structure through scuttlebutt. 8 | 9 | You push new pieces of data onto the list. You can also remove 10 | a item from the list (which is actually an append only message). 11 | 12 | `append-only` generates a unique `__id` on your item for you. 13 | 14 | ```js 15 | var AppendOnly = require("append-only") 16 | , list1 = AppendOnly() 17 | , list2 = AppendOnly() 18 | 19 | list1.on("item", function (item) { 20 | if (item.more) { 21 | list1.remove(item.__id) 22 | } 23 | }) 24 | 25 | list1.push({ some: "data" }) 26 | list1.push({ more: "data" }) 27 | 28 | list2.on("item", function (item) { 29 | console.log("items", item) 30 | }) 31 | 32 | list2.on("remove", function (item) { 33 | console.log("item removed", item) 34 | }) 35 | 36 | setTimeout(function () { 37 | var array = list2.createArray() 38 | console.log("array", array) 39 | }, 500) 40 | 41 | var stream1 = list1.createStream() 42 | , stream2 = list2.createStream() 43 | 44 | stream1.pipe(stream2).pipe(stream1) 45 | ``` 46 | 47 | ## Installation 48 | 49 | `npm install append-only` 50 | 51 | ## Contributors 52 | 53 | - Raynos 54 | 55 | ## MIT Licenced 56 | -------------------------------------------------------------------------------- /example/simple.js: -------------------------------------------------------------------------------- 1 | var AppendOnly = require("..") 2 | , assert = require("assert") 3 | 4 | , list1 = AppendOnly() 5 | , list2 = AppendOnly() 6 | , counter = 0 7 | 8 | list1.type = "list1" 9 | list2.type = "list2" 10 | 11 | list1.on("item", function (item) { 12 | if (item.more) { 13 | list1.remove(item.__id) 14 | } 15 | }) 16 | 17 | list1.push({ some: "data" }) 18 | list1.push({ more: "data" }) 19 | 20 | list2.on("item", function (item) { 21 | counter++ 22 | console.log("items", item) 23 | 24 | if (item.some === "data") { 25 | var obj = { "hello": "world" } 26 | list1.push(obj) 27 | list1.remove(obj) 28 | } 29 | }) 30 | 31 | list2.on("remove", function (item) { 32 | counter++ 33 | console.log("item removed", item) 34 | }) 35 | 36 | setTimeout(function () { 37 | var array = list2.createArray() 38 | console.log("array", array) 39 | assert.equal(array[0].some, "data") 40 | assert.equal(array.length, 1) 41 | assert.equal(counter, 3) 42 | }, 500) 43 | 44 | var stream1 = list1.createStream() 45 | , stream2 = list2.createStream() 46 | 47 | stream1.pipe(stream2).pipe(stream1) 48 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Scuttlebutt = require("scuttlebutt") 2 | , filter = Scuttlebutt.filter 3 | , inherits = require("util").inherits 4 | 5 | inherits(AppendOnly, Scuttlebutt) 6 | 7 | var proto = AppendOnly.prototype 8 | 9 | proto.push = push 10 | proto.remove = remove 11 | proto.applyUpdate = applyUpdate 12 | proto.history = history 13 | proto.toJSON = proto.createArray = createArray 14 | 15 | module.exports = AppendOnly 16 | 17 | function AppendOnly(options) { 18 | if (! (this instanceof AppendOnly)) { 19 | return new AppendOnly(options) 20 | } 21 | 22 | Scuttlebutt.call(this, options) 23 | 24 | var store = this._store = [] 25 | this._hash = {} 26 | 27 | this.on("_remove", function (update, update2) { 28 | var index = store.indexOf(update) 29 | if (index !== -1) { 30 | store.splice(index, 1) 31 | } 32 | 33 | index = store.indexOf(update2) 34 | if (index !== -1) { 35 | store.splice(index, 1) 36 | } 37 | }) 38 | } 39 | 40 | function push(item) { 41 | this.localUpdate({ push: item }) 42 | } 43 | 44 | function remove(id) { 45 | this.localUpdate({ remove: id.__id ? id.__id : id }) 46 | } 47 | 48 | function toId (update) { 49 | var ts = update[1] 50 | , source = update[2] 51 | 52 | return source + ":" + ts 53 | } 54 | 55 | function applyUpdate(update) { 56 | var value = update[0] 57 | 58 | this._store.push(update) 59 | 60 | if (value.push) { 61 | var item = value.push 62 | , id = toId(update) 63 | 64 | Object.defineProperty(item, "__id", { 65 | value: id 66 | , configurable: true 67 | }) 68 | this._hash[id] = update 69 | this.emit("item", item) 70 | } else if (value.remove) { 71 | var id = value.remove 72 | , _update = this._hash[id] 73 | 74 | ;delete this._hash[id] 75 | 76 | this.emit("_remove", _update, update) 77 | this.emit("remove", _update[0].push) 78 | } 79 | return true 80 | } 81 | 82 | function history(sources) { 83 | return this._store.filter(function (update) { 84 | return filter(update, sources) 85 | }) 86 | } 87 | 88 | function createArray() { 89 | var hash = this._hash 90 | 91 | return Object.keys(hash).map(findKey, hash) 92 | } 93 | 94 | function findKey(key) { 95 | return this[key][0].push 96 | } 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "append-only", 3 | "version": "0.2.3", 4 | "description": "Append only scuttlebutt structure", 5 | "keywords": [], 6 | "author": "Raynos ", 7 | "repository": "git://github.com/Raynos/append-only.git", 8 | "main": "index", 9 | "homepage": "https://github.com/Raynos/append-only", 10 | "contributors": [ 11 | { 12 | "name": "Jake Verbaten" 13 | } 14 | ], 15 | "bugs": { 16 | "url": "https://github.com/Raynos/append-only/issues", 17 | "email": "raynos2@gmail.com" 18 | }, 19 | "dependencies": { 20 | "scuttlebutt": "~5.5" 21 | }, 22 | "devDependencies": { 23 | "tap": "~0.3.1", 24 | "tape": "0.0.5", 25 | "macgyver": "~1.9.2" 26 | }, 27 | "licenses": [ 28 | { 29 | "type": "MIT", 30 | "url": "http://github.com/Raynos/append-only/raw/master/LICENSE" 31 | } 32 | ], 33 | "scripts": { 34 | "test": "tap --stderr --tap ./test" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var mac = require('macgyver')().autoValidate() 2 | 3 | require('tape')('test', function (t) { 4 | 5 | var Appender = require('..') 6 | 7 | var a = new Appender() 8 | 9 | console.log(a) 10 | 11 | 12 | a.on('item', mac().times(2)) 13 | a.on('remove', mac().once()) 14 | a.on('_remove', mac().once()) 15 | a.on('_update', mac().times(3)) 16 | 17 | var m = {message: 'hello'} 18 | 19 | a.once('item', mac(function (_m) { 20 | t.equal(_m, m) 21 | }).once()) 22 | 23 | a.once('remove', mac(function (_m) { 24 | t.equal(_m, m) 25 | }).once()) 26 | 27 | a.push(m) 28 | a.push({message: 'hello2'}) 29 | a.remove(m) 30 | 31 | console.log(a._store) 32 | t.end() 33 | }) 34 | --------------------------------------------------------------------------------