diff --git a/.travis.yml b/.travis.yml index 6160d82..f3042df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,4 @@ node_js: - "4.0" - "0.12" - "0.11" - - "0.10" - "iojs" \ No newline at end of file diff --git a/index.js b/index.js index 0132dec..80225de 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ var level = require('level') var hypercore = require('hypercore') var createImportPipeline = require('./lib/import.js') +var FeedOperations = require('./lib/feedOperations.js') module.exports = Jawn @@ -14,3 +15,8 @@ function Jawn (opts) { Jawn.prototype.createImportPipeline = function (opts) { return createImportPipeline(this, opts) } + +Jawn.prototype.createAppendableFeed = function (feedId, data) { + var feed = new FeedOperations(this.core).appendableFeed(feedId) + return feed +} diff --git a/lib/feedOperations.js b/lib/feedOperations.js new file mode 100644 index 0000000..15f2631 --- /dev/null +++ b/lib/feedOperations.js @@ -0,0 +1,80 @@ +module.exports = FeedOperations + +function FeedOperations (core) { + this.core = core + this.replicate = copyOriginal + + function copyOriginal (refFeed, newFeed, resolve, reject) { + loop(null) + + function loop (err) { + if (err) { + console.log(err) + if (reject) { + return reject() + } + } + + refFeed.get(newFeed.blocks, function (err, block) { + if (err) { + console.log(err) + } + + if (!block) { + return resolve() + } + + newFeed.append(block, loop) + }) + } + } +} + +FeedOperations.prototype.appendableFeed = function (feedId) { + var refFeed = this.core.get(feedId) + var newFeed = this.core.add() + + newFeed.append_p = function (data) { + return new Promise(function (resolve, reject) { + newFeed.append(data, resolve) + }) + } + + newFeed.finalize_p = function () { + return new Promise(function (resolve, reject) { + newFeed.finalize(resolve) + }) + } + + var copyOriginal = this.replicate + + newFeed.initialize = function () { + return new Promise(function (resolve, reject) { + copyOriginal(refFeed, newFeed, resolve, reject) + }) + } + + return newFeed +} + +FeedOperations.prototype.replicateFeed = function (feedId, callback) { + var refFeed = this.core.get(feedId) + var newFeed = this.core.add() + + this.replicate(refFeed, newFeed, callback) + return newFeed +} + +// Copies data from reference feed, appends data to new feed and finalizes it +FeedOperations.prototype.append = function (feedId, data, callback) { + var refFeed = this.core.get(feedId) + var newFeed = this.core.add() + + this.replicate(refFeed, newFeed, function () { + newFeed.append(data, function () { + newFeed.finalize(callback) + }) + }) + + return newFeed +} diff --git a/test/feedOperations.js b/test/feedOperations.js new file mode 100644 index 0000000..7b94783 --- /dev/null +++ b/test/feedOperations.js @@ -0,0 +1,147 @@ +var test = require('tape') +var Jawn = require('../') +var memdb = require('memdb') +var FeedOps = require('../lib/feedOperations.js') + +test('Create appendable feed and copy data from reference feed', function (t) { + var jawn = freshJawn() + var feed = jawn.core.add() + + storeDataInFeed(feed, ['hello']) + .then(function () { + // Verify feed was finalized and has an id + console.log('Finalized with id ' + feed.id.toString('hex')) + // Create appendable feed + var feedOperations = new FeedOps(jawn.core) + var appfeed = feedOperations.appendableFeed(feed.id) + // Copy data from reference feed, finalize it, then verify the feed has the same number of blocks as the reference + appfeed.initialize().then(function () { + return appfeed.finalize_p() + }) + .then(function () { + t.same(appfeed.blocks, feed.blocks, 'testing') + t.end() + }) + }) +}) + +test('Create appendable feeed, copy data from reference feed and append to feed', function (t) { + var jawn = freshJawn() + var feed = jawn.core.add() + var expected = ['hello', 'there', 'goodbye'] + + storeDataInFeed(feed, ['hello', 'there']) + .then(function () { + console.log('Finalized with id ' + feed.id.toString('hex')) + + var appfeed = new FeedOps(jawn.core).appendableFeed(feed.id) + + // Copy data from original feed with initialize(), then append an additional block and finalize + appfeed.initialize().then(function () { + return appfeed.append_p('goodbye') + }) + .then(function () { + return appfeed.finalize_p() + }) + .then(function () { + // Verify the feed has the correct number of blocks, and the blocks match what was expected + t.same(appfeed.blocks, expected.length, 'Correct number of blocks') + + for (var i = 0; i < appfeed.blocks; i++) { + appfeed.get(i, function (err, block) { + if (err) { + console.log(err) + } + t.same(block.toString(), expected.shift(), 'Feed block match') + if (expected.length === 0) { + t.end() + } + }) + } + }) + }) +}) + +test('Create appendable feed without promises, copy data from reference feed', function (t) { + var jawn = freshJawn() + var feed = jawn.core.add() + + storeDataInFeed(feed, ['hello', 'there']) + .then(function () { + var appFeed = new FeedOps(jawn.core).replicateFeed(feed.id, finalizeCallback) + function finalizeCallback () { + appFeed.finalize(function () { + t.same(appFeed.blocks, feed.blocks, 'Correct number of blocks') + t.end() + }) + } + }) +}) + +test('Append data to feed without promises', function (t) { + var jawn = freshJawn() + var feed = jawn.core.add() + var expected = ['hello', 'there', 'goodbye'] + + storeDataInFeed(feed, ['hello', 'there']) + .then(function () { + var appFeed = new FeedOps(jawn.core).append(feed.id, ['goodbye'], testCallback) + function testCallback () { + t.same(appFeed.blocks, expected.length, 'Correct number of blocks in appended feed') + testFeedContents(appFeed, expected, t) + } + }) +}) + +function testFeedContents (feed, expected, t) { + var blockNumber = 0 + feed.get(0, loop) + + function loop (err, block) { + if (err) { + console.log(err) + } + + blockNumber += 1 + + if (block) { + t.same(block.toString(), expected.shift(), 'Feed block matches expected value') + } + + if (expected.length === 0) { + return t.end() + } + + feed.get(blockNumber, loop) + } +} + +// Create a feed, store data in it and finalize it. +function storeDataInFeed (feed, data) { + feed.pappend = function (data) { + return new Promise(function (resolve, reject) { + feed.append(data, resolve) + }) + } + + feed.pfinalize = function () { + return new Promise(function (resolve, reject) { + feed.finalize(resolve) + }) + } + + return new Promise(function (resolve, reject) { + feed.pappend(data).then(function () { + console.log('Appended') + return feed + }) + .then(function (feed) { + return feed.pfinalize() + }) + .then(resolve) + }) +} + +function freshJawn () { + return new Jawn({db: memdb()}) +} diff --git a/test/import.js b/test/import.js index 09a3c1b..4b16485 100644 --- a/test/import.js +++ b/test/import.js @@ -30,7 +30,7 @@ test('import json to jawn', function (t) { test('import csv to jawn', function (t) { var jawn = freshJawn() importFromFile(jawn, 'sample.csv', {'format': 'csv'}) - var importStream = importFromFile(jawn, 'sample.csv', {'format': 'csv'}, verify) + var importStream = importFromFile(jawn, 'sample.csv', {'format': 'csv'}) var expected = [ '{"Type of Experience":"Writing software in any programming language","Little/No Experience":"1","Some Experience":"5","Very Familiar":"4"}', '{"Type of Experience":"Frontend Web Development","Little/No Experience":"4","Some Experience":"3","Very Familiar":"3"}',