├── .gitignore ├── Makefile ├── README.md ├── example └── simple.js ├── index.js ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw* 2 | node_modules 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @node_modules/.bin/tape test.js 4 | 5 | .PHONY: test 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # enstore 2 | 3 | In-memory persistence for streams. Enables you to replay streams, even if they're not finished yet. 4 | 5 | *Need real persistence? Check out [level-store](https://github.com/juliangruber/level-store) for a fast and flexible 6 | streaming storage engine based on LevelDB.* 7 | 8 | [![downloads](https://img.shields.io/npm/dm/enstore.svg)](https://www.npmjs.org/package/enstore) 9 | 10 | [![browser support](https://ci.testling.com/juliangruber/enstore.png)](https://ci.testling.com/juliangruber/enstore) 11 | 12 | ## Usage 13 | 14 | ```js 15 | var enstore = require('enstore'); 16 | 17 | // create a new store 18 | var store = enstore(); 19 | 20 | // store a someStream in it 21 | someStream.pipe(store.createWriteStream()); 22 | 23 | // pipe everything someStream emitted to someWhereElse 24 | // doesn't matter if someStream already finished 25 | store.createReadStream().pipe(someWhereElse); 26 | ``` 27 | 28 | ## Example: Cache for browserify 29 | 30 | This basically can be done for any streaming resource, like `fs.createReadStream()` or `request()`, that you 31 | want to cache in memory. 32 | 33 | ```js 34 | var http = require('http'); 35 | var browserify = require('browserify'); 36 | var enstore = require('enstore'); 37 | 38 | // fill the cache 39 | var cache = enstore(); 40 | browserify('app.js').bundle().pipe(cache.createWriteStream()); 41 | 42 | http.createServer(function (req, res) { 43 | if (req.url == '/bundle.js') { 44 | // stream the bundle to the client 45 | res.writeHead(200, { 'Content-Type' : 'application/javascript' }); 46 | store.createReadStream().pipe(res); 47 | } 48 | }); 49 | ``` 50 | 51 | To recreate / flush the cache just overwrite the `cache` variable with a new `enstore` instance. 52 | 53 | ## API 54 | 55 | ### enstore() 56 | 57 | Returns a new store. 58 | 59 | ### enstore#createWriteStream(opts) 60 | 61 | Writable stream that stores written data in the internal store. `opts` will be passed to the `Writable()` constructor. 62 | 63 | ### enstore#createReadStream() 64 | 65 | Readable stream that emits both what is already stored and what comes in over 66 | `createWriteStream()` until `end` is emitted. `opts` will be passed to the `Readable()` constructor. 67 | 68 | ## Installation 69 | 70 | With [npm](http://npmjs.org) do 71 | 72 | ```bash 73 | $ npm install enstore 74 | ``` 75 | 76 | For the client, bundle with [browserify](https://github.com/substack/node-browserify). 77 | 78 | ## License 79 | 80 | (MIT) 81 | -------------------------------------------------------------------------------- /example/simple.js: -------------------------------------------------------------------------------- 1 | var enstore = require('..'); 2 | var through = require('through'); 3 | 4 | var store = enstore(); 5 | read('initial'); 6 | 7 | var src = through(); 8 | src.pipe(store.createWriteStream()); 9 | 10 | var i = 0; 11 | (function write () { 12 | src.write('hey ' + i++); 13 | 14 | if (i == 2) { 15 | read('streaming'); 16 | } 17 | 18 | if (i == 3) { 19 | src.end(); 20 | return read('after'); 21 | } 22 | 23 | setTimeout(write, 500); 24 | })(); 25 | 26 | function read (name) { 27 | console.log('[' + name + '] reading now'); 28 | store.createReadStream().pipe(through(function (chunk) { 29 | console.log('[' + name + '] ' + chunk); 30 | })); 31 | } 32 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var inherits = require('util').inherits; 3 | var timestamp = require('monotonic-timestamp'); 4 | var Writable = require('stream').Writable; 5 | var Readable = require('stream').Readable; 6 | 7 | module.exports = Store; 8 | inherits(Store, EventEmitter); 9 | 10 | function Store(){ 11 | if (!(this instanceof Store)) return new Store(); 12 | EventEmitter.call(this); 13 | this.store = []; 14 | this.ended = false; 15 | } 16 | 17 | Store.prototype.createWriteStream = function(opts){ 18 | var self = this; 19 | var w = Writable(opts); 20 | w._write = function(chunk, _, done){ 21 | chunk = { 22 | ts : timestamp(), 23 | chunk : chunk 24 | } 25 | self.store.push(chunk); 26 | self.emit('chunk', chunk); 27 | done(); 28 | }; 29 | w.on('finish', function(){ 30 | self.ended = true; 31 | self.emit('end'); 32 | }); 33 | return w; 34 | }; 35 | 36 | Store.prototype.createReadStream = function(opts){ 37 | var self = this; 38 | var idx = 0; 39 | var r = Readable(opts); 40 | r._read = function(n){ 41 | if (self.store[idx]) return r.push(self.store[idx++].chunk); 42 | if (self.ended) return r.push(null); 43 | 44 | var onchunk = function(chunk){ 45 | self.removeListener('end', onend); 46 | idx++; 47 | r.push(chunk.chunk); 48 | } 49 | self.once('chunk', onchunk); 50 | 51 | var onend = function(){ 52 | self.removeListener('chunk', onchunk); 53 | r.push(null); 54 | }; 55 | self.once('end', onend); 56 | }; 57 | return r; 58 | }; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enstore", 3 | "description": "In-memory persistence for streams", 4 | "version": "1.0.1", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/juliangruber/enstore.git" 8 | }, 9 | "homepage": "https://github.com/juliangruber/enstore", 10 | "main": "index.js", 11 | "scripts": { 12 | "test": "tape test/*.js" 13 | }, 14 | "dependencies": { 15 | "monotonic-timestamp": "0.0.9" 16 | }, 17 | "devDependencies": { 18 | "concat-stream": "^1.4.6", 19 | "tape": "~4.6.2" 20 | }, 21 | "keywords": [ 22 | "stream", 23 | "store", 24 | "save", 25 | "persistence", 26 | "memory" 27 | ], 28 | "author": { 29 | "name": "Julian Gruber", 30 | "email": "mail@juliangruber.com", 31 | "url": "http://juliangruber.com" 32 | }, 33 | "license": "MIT", 34 | "testling": { 35 | "files": "test/*.js", 36 | "browsers": [ 37 | "ie/6..latest", 38 | "chrome/20..latest", 39 | "firefox/10..latest", 40 | "safari/5.0.5..latest", 41 | "opera/11.0..latest", 42 | "iphone/6", 43 | "ipad/6" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var enstore = require('./'); 2 | var through = require('through'); 3 | var test = require('tape'); 4 | var PassThrough = require('stream').PassThrough; 5 | var concat = require('concat-stream'); 6 | 7 | test('enstore', function(t){ 8 | t.plan(3); 9 | 10 | var store = enstore(); 11 | 12 | store.createReadStream().pipe(concat(function(res){ 13 | t.equal(res.toString(), 'foobarbaz', 'before'); 14 | })); 15 | 16 | setTimeout(function(){ 17 | var w = store.createWriteStream(); 18 | var p = PassThrough(); 19 | p.pipe(w); 20 | p.push('foo'); 21 | p.push('bar'); 22 | 23 | store.createReadStream().pipe(concat(function(res){ 24 | t.equal(res.toString(), 'foobarbaz', 'in between'); 25 | })); 26 | 27 | setTimeout(function(){ 28 | p.push('baz'); 29 | p.push(null); 30 | 31 | store.createReadStream().pipe(concat(function(res){ 32 | t.equal(res.toString(), 'foobarbaz', 'after'); 33 | })); 34 | }); 35 | }); 36 | }); 37 | 38 | --------------------------------------------------------------------------------