├── config.json ├── .gitignore ├── renovate.json ├── test ├── config.json └── index.js ├── README.md ├── docker-compose.yml ├── Makefile ├── .travis.yml ├── package.json ├── LICENSE ├── mongo.js ├── index.js └── index.test.js /config.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | coverage -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "development": { 3 | "MONGO_URL": "mongodb://localhost:27017/sourced" 4 | } 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sourced-repo-mongo 2 | ================== 3 | 4 | mongo data store and repository for sourced-style event sourcing models 5 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | 3 | services: 4 | 5 | mongo: 6 | image: mongo 7 | ports: 8 | - 27017:27017 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DEBUG=sourced,sourced-repo-mongo 2 | 3 | test: 4 | docker-compose up -d 5 | $(MAKE) DEBUG= test-debug 6 | docker-compose down --remove-orphans 7 | 8 | test-debug: 9 | DEBUG=$(DEBUG) npm test 10 | DEBUG=$(DEBUG) npm run jest 11 | 12 | .PHONY: test 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '12' 4 | - '14' 5 | - '16' 6 | cache: npm 7 | notifications: 8 | email: false 9 | script: 10 | - make test 11 | after_success: 12 | - npm install -g codecov 13 | - codecov 14 | branches: 15 | except: 16 | - '/^v\d+\.\d+\.\d+$/' 17 | jobs: 18 | include: 19 | - stage: deploy 20 | if: branch == master && !fork 21 | node_js: node # pre-installed version 22 | script: 23 | - npm install -g semantic-release 24 | - semantic-release 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sourced-repo-mongo", 3 | "version": "0.0.0-development", 4 | "description": "mongo data store and repository for sourced-style event sourcing models", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "debug": "4.3.1", 11 | "lodash": "4.17.21", 12 | "mongodb": "3.6.9", 13 | "sourced": "2.0.8" 14 | }, 15 | "devDependencies": { 16 | "jest": "27.0.4", 17 | "mocha": "9.0.0", 18 | "should": "13.2.3" 19 | }, 20 | "scripts": { 21 | "test": "mocha test -R spec --exit", 22 | "jest": "jest --coverage --forceExit" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/mateodelnorte/sourced-repo-mongo.git" 27 | }, 28 | "keywords": [ 29 | "sourced", 30 | "eventsourcing", 31 | "eventsource", 32 | "event-source" 33 | ], 34 | "author": "mattwalters5@gmail.com", 35 | "license": "ISC", 36 | "bugs": { 37 | "url": "https://github.com/mateodelnorte/sourced-repo-mongo/issues" 38 | }, 39 | "homepage": "https://github.com/mateodelnorte/sourced-repo-mongo" 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Matt Walters 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /mongo.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var log = require('debug')('sourced-repo-mongo'); 3 | var MongoClient = require('mongodb').MongoClient; 4 | var Server = require('mongodb').Server; 5 | var url = require('url'); 6 | var util = require('util'); 7 | 8 | function Mongo () { 9 | this.client = null; 10 | this.db = null; 11 | EventEmitter.call(this); 12 | } 13 | 14 | util.inherits(Mongo, EventEmitter); 15 | 16 | Mongo.prototype.connect = function connect (mongoUrl, database, options = { 17 | useNewUrlParser: true, 18 | useUnifiedTopology: true 19 | }) { 20 | var self = this; 21 | return new Promise((resolve, reject) => { 22 | self.on('connected', (db) => { 23 | resolve(db) 24 | }) 25 | self.on('error', (err) => { 26 | reject(err) 27 | }) 28 | MongoClient.connect(mongoUrl, options, function (err, client) { 29 | if (err) { 30 | log('✗ MongoDB Connection Error. Please make sure MongoDB is running: ', err); 31 | self.emit('error', err); 32 | } 33 | var expanded = url.parse(mongoUrl); 34 | // replica set url does not include db, it is passed in separately 35 | var dbName = database || expanded.pathname.replace('/', ''); 36 | self.client = client; 37 | var db = client.db(dbName); 38 | self.db = db; 39 | log('initialized connection to mongo at %s', mongoUrl); 40 | self.emit('connected', db); 41 | }); 42 | }) 43 | }; 44 | 45 | Mongo.prototype.close = function (cb) { 46 | log('closing sourced mongo connection'); 47 | return this.client.close((err) => { 48 | log('closed sourced mongo connection'); 49 | cb(err) 50 | }); 51 | }; 52 | 53 | module.exports = new Mongo(); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require('events').EventEmitter; 2 | var log = require('debug')('sourced-repo-mongo'); 3 | var mongo = require('./mongo'); 4 | var util = require('util'); 5 | var _ = require('lodash'); 6 | 7 | function Repository (entityType, options) { 8 | options = options || {}; 9 | EventEmitter.call(this); 10 | if ( ! options.db && ! mongo.db) { 11 | throw new Error('mongo has not been initialized. you must call require(\'sourced-repo-mongo/mongo\').connect(config.MONGO_URL); before instantiating a Repository'); 12 | } 13 | var indices = _.union(options.indices, ['id']); 14 | var self = this; 15 | var db = options.db || mongo.db; 16 | self.entityType = entityType; 17 | self.indices = indices; 18 | self.snapshotFrequency = options.snapshotFrequency || 10; 19 | 20 | var snapshotCollectionName = util.format('%s.snapshots', entityType.name); 21 | var snapshots = db.collection(snapshotCollectionName); 22 | self.snapshots = snapshots; 23 | var eventCollectionName = util.format('%s.events', entityType.name); 24 | var events = db.collection(eventCollectionName); 25 | self.events = events; 26 | 27 | var error = function (err) { 28 | if (err) return self.emit('error', err); 29 | }; 30 | 31 | self.indices.forEach(function (index) { 32 | snapshots.createIndex(index, error); 33 | events.createIndex(index, error); 34 | }); 35 | events.createIndex({ id: 1, version: 1 }, error); 36 | snapshots.createIndex({ id: 1, version: 1 }, error); 37 | snapshots.createIndex('snapshotVersion', error); 38 | 39 | log('initialized %s entity store', self.entityType.name); 40 | 41 | self.emit('ready'); 42 | } 43 | 44 | util.inherits(Repository, EventEmitter); 45 | 46 | Repository.prototype.commit = function commit (entity, options, cb) { 47 | if (typeof options === 'function') { 48 | cb = options; 49 | options = {}; 50 | } 51 | 52 | var self = this; 53 | 54 | log('committing %s for id %s', this.entityType.name, entity.id); 55 | 56 | this._commitEvents(entity, function _afterCommitEvents (err) { 57 | if (err) return cb(err); 58 | 59 | self._commitSnapshots(entity, options, function _afterCommitSnapshots (err) { 60 | if (err) return cb(err); 61 | self._emitEvents(entity); 62 | return cb(); 63 | }); 64 | }); 65 | 66 | }; 67 | 68 | Repository.prototype.commitAll = function commit (entities, options, cb) { 69 | if (typeof options === 'function') { 70 | cb = options; 71 | options = {}; 72 | } 73 | 74 | var self = this; 75 | 76 | log('committing %s for id %j', this.entityType.name, _.map(entities, 'id')); 77 | 78 | this._commitAllEvents(entities, function _afterCommitEvents (err) { 79 | if (err) return cb(err); 80 | self._commitAllSnapshots(entities, options, function _afterCommitSnapshots (err) { 81 | if (err) return cb(err); 82 | entities.forEach(function (entity) { 83 | self._emitEvents(entity); 84 | }); 85 | return cb(); 86 | }); 87 | }); 88 | 89 | }; 90 | 91 | Repository.prototype.get = function get (id, cb) { 92 | return this._getByIndex('id', id, cb); 93 | }; 94 | 95 | Repository.prototype._getByIndex = function _getByIndex (index, value, cb) { 96 | var self = this; 97 | 98 | if (this.indices.indexOf(index) === -1) throw new Error('Cannot get sourced entity type [%s] by index [%s]', this.entityType, index); 99 | 100 | log('getting %s for %s %s', this.entityType.name, index, value); 101 | 102 | var criteria = { }; 103 | criteria[index] = value; 104 | 105 | this.snapshots 106 | .find(criteria) 107 | .sort({ version: -1 }) 108 | .limit(-1) 109 | .toArray(function (err, snapshots) { 110 | if (err) return cb(err); 111 | 112 | var snapshot = snapshots[0]; 113 | 114 | if (snapshot) criteria.version = { $gt: snapshot.version }; 115 | self.events.find(criteria) 116 | .sort({ version: 1 }) 117 | .toArray(function (err, events) { 118 | if (err) return cb(err); 119 | if (snapshot) delete snapshot._id; 120 | if ( ! snapshot && ! events.length) return cb(null, null); 121 | 122 | var id = (index === 'id') ? value : (snapshot) ? snapshot.id : events[0].id; 123 | 124 | var entity = self._deserialize(id, snapshot, events); 125 | return cb(null, entity); 126 | }); 127 | }); 128 | }; 129 | 130 | Repository.prototype.getAll = function getAll (ids, cb) { 131 | var self = this; 132 | 133 | if (typeof ids === 'function') { 134 | cb = ids; 135 | ids = null; 136 | return this.events.distinct('id', function (err, distinctEventIds) { 137 | self.getAll(distinctEventIds, cb); 138 | }); 139 | } 140 | 141 | log('getting %ss for ids %j', this.entityType.name, ids); 142 | 143 | if (ids.length === 0) return cb(null, []); 144 | 145 | this._getAllSnapshots(ids, function _afterGetAllSnapshots (err, snapshots) { 146 | if (err) return cb(err); 147 | self._getAllEvents(ids, snapshots, function (err, entities) { 148 | if (err) return cb(err); 149 | return cb(null, entities); 150 | }); 151 | }); 152 | }; 153 | 154 | Repository.prototype._commitEvents = function _commitEvents (entity, cb) { 155 | var self = this; 156 | 157 | if (entity.newEvents.length === 0) return cb(); 158 | 159 | if ( ! entity.id) return cb(new Error('Cannot commit an entity of type [%s] without an [id] property', this.entityType)); 160 | 161 | var events = entity.newEvents; 162 | events.forEach(function _applyIndices (event) { 163 | if (event && event._id) delete event._id; // mongo will blow up if we try to insert multiple _id keys 164 | self.indices.forEach(function (index) { 165 | event[index] = entity[index]; 166 | }); 167 | }); 168 | self.events.insertMany(events, function (err) { 169 | if (err) return cb(err); 170 | log('committed %s.events for id %s', self.entityType.name, entity.id); 171 | entity.newEvents = []; 172 | return cb(); 173 | }); 174 | 175 | }; 176 | 177 | Repository.prototype._commitAllEvents = function _commitEvents (entities, cb) { 178 | var self = this; 179 | 180 | var events = []; 181 | entities.forEach(function (entity) { 182 | if (entity.newEvents.length === 0) return; 183 | 184 | if ( ! entity.id) return cb(new Error('Cannot commit an entity of type [%s] without an [id] property', self.entityType)); 185 | 186 | var evnts = entity.newEvents; 187 | evnts.forEach(function _applyIndices (event) { 188 | if (event && event._id) delete event._id; // mongo will blow up if we try to insert multiple _id keys 189 | self.indices.forEach(function (index) { 190 | event[index] = entity[index]; 191 | }); 192 | }); 193 | Array.prototype.unshift.apply(events, evnts); 194 | }); 195 | 196 | if (events.length === 0) return cb(); 197 | 198 | self.events.insertMany(events, function (err) { 199 | if (err) return cb(err); 200 | log('committed %s.events for ids %j', self.entityType.name, _.map(entities, 'id')); 201 | entities.forEach(function (entity) { 202 | entity.newEvents = []; 203 | }); 204 | return cb(); 205 | }); 206 | 207 | }; 208 | 209 | Repository.prototype._commitSnapshots = function _commitSnapshots (entity, options, cb) { 210 | var self = this; 211 | 212 | if (options.forceSnapshot || entity.version >= entity.snapshotVersion + self.snapshotFrequency) { 213 | var snapshot = entity.snapshot(); 214 | if (snapshot && snapshot._id) delete snapshot._id; // mongo will blow up if we try to insert multiple _id keys 215 | self.snapshots.insertOne(snapshot, function (err) { 216 | if (err) return cb(err); 217 | log('committed %s.snapshot for id %s %j', self.entityType.name, entity.id, snapshot); 218 | return cb(null, entity); 219 | }); 220 | } else { 221 | return cb(null, entity); 222 | } 223 | 224 | }; 225 | 226 | Repository.prototype._commitAllSnapshots = function _commitAllSnapshots (entities, options, cb) { 227 | var self = this; 228 | 229 | var snapshots = []; 230 | entities.forEach(function (entity) { 231 | if (options.forceSnapshot || entity.version >= entity.snapshotVersion + self.snapshotFrequency) { 232 | var snapshot = entity.snapshot(); 233 | if (snapshot) { 234 | if (snapshot._id) delete snapshot._id; // mongo will blow up if we try to insert multiple _id keys) 235 | snapshots.push(snapshot); 236 | } 237 | } 238 | }); 239 | 240 | if (snapshots.length === 0) return cb(); 241 | 242 | self.snapshots.insertMany(snapshots, function (err) { 243 | if (err) return cb(err); 244 | log('committed %s.snapshot for ids %s %j', self.entityType.name, _.map(entities, 'id'), snapshots); 245 | return cb(null, entities); 246 | }); 247 | 248 | }; 249 | 250 | Repository.prototype._deserialize = function _deserialize (id, snapshot, events) { 251 | log('deserializing %s entity ', this.entityType.name); 252 | var entity = new this.entityType(snapshot, events); 253 | entity.id = id; 254 | return entity; 255 | }; 256 | 257 | Repository.prototype._emitEvents = function _emitEvents (entity) { 258 | var self = this; 259 | 260 | var eventsToEmit = entity.eventsToEmit; 261 | entity.eventsToEmit = []; 262 | eventsToEmit.forEach(function (eventToEmit) { 263 | var args = Array.prototype.slice.call(eventToEmit); 264 | self.entityType.prototype.emit.apply(entity, args); 265 | }); 266 | 267 | log('emitted local events for id %s', entity.id); 268 | 269 | }; 270 | 271 | Repository.prototype._getAllSnapshots = function _getAllSnapshots (ids, cb) { 272 | var self = this; 273 | 274 | var match = { $match: { id: { $in: ids } } }; 275 | var sort = { $sort: { snapshotVersion: 1 } }; 276 | var group = { $group: { _id: '$id', snapshotVersion: { $last: '$snapshotVersion' } } }; 277 | 278 | self.snapshots.aggregate([match, sort, group], function (err, cursor) { 279 | if (err) return cb(err); 280 | cursor.toArray(function (err, idVersionPairs) { 281 | if (err) return cb(err); 282 | var criteria = {}; 283 | if (idVersionPairs.length === 0) { 284 | return cb(null, []); 285 | } else if (idVersionPairs.length === 1) { 286 | criteria = { id: idVersionPairs[0]._id, snapshotVersion: idVersionPairs[0].snapshotVersion }; 287 | } else { 288 | criteria.$or = []; 289 | idVersionPairs.forEach(function (pair) { 290 | var cri = { id: pair._id, snapshotVersion: pair.snapshotVersion }; 291 | criteria.$or.push(cri); 292 | }); 293 | } 294 | self.snapshots 295 | .find(criteria) 296 | .toArray(function (err, snapshots) { 297 | if (err) cb(err); 298 | return cb(null, snapshots); 299 | }); 300 | }); 301 | }); 302 | }; 303 | 304 | Repository.prototype._getAllEvents = function _getAllEvents (ids, snapshots, cb) { 305 | var self = this; 306 | 307 | var criteria = { $or: [] }; 308 | ids.forEach(function (id) { 309 | var snapshot; 310 | if ( ! (snapshot = _.find(snapshots, function (snapshot) { 311 | return id === snapshot.id; 312 | }))) { 313 | criteria.$or.push({ id: id }); 314 | } else { 315 | criteria.$or.push({ id: snapshot.id, version: { $gt: snapshot.snapshotVersion } }); 316 | } 317 | }); 318 | 319 | self.events.find(criteria) 320 | .sort({ id: 1, version: 1 }) 321 | .toArray(function (err, events) { 322 | if (err) return cb(err); 323 | if ( ! snapshots.length && ! events.length) return cb(null, null); 324 | var results = []; 325 | ids.forEach(function (id) { 326 | var snapshot = _.find(snapshots, function (snapshot) { 327 | return snapshot.id === id; 328 | }); 329 | if (snapshot) delete snapshot._id; 330 | var evnts = _.filter(events, function (event) { 331 | return event.id === id; 332 | }); 333 | var entity = self._deserialize(id, snapshot, evnts); 334 | results.push(entity); 335 | }); 336 | return cb(null, results); 337 | }); 338 | 339 | }; 340 | 341 | module.exports.Repository = Repository; 342 | -------------------------------------------------------------------------------- /index.test.js: -------------------------------------------------------------------------------- 1 | const Entity = require('sourced').SourcedEntity; 2 | const log = require('debug')('sourced-repo-mongo'); 3 | const mongo = require('./mongo.js'); 4 | const sourcedRepoMongo = require('./index.js'); 5 | const Repository = sourcedRepoMongo.Repository; 6 | 7 | const should = require('should'); 8 | 9 | /* Market model/entity */ 10 | class Market extends Entity { 11 | constructor(snapshot, events) { 12 | super() 13 | this.orders = []; 14 | this.price = 0; 15 | 16 | this.rehydrate(snapshot, events) 17 | } 18 | 19 | init(param) { 20 | this.id = param.id; 21 | this.digest('init', param); 22 | this.emit('initialized', param, this); 23 | } 24 | 25 | createOrder(param) { 26 | this.orders.push(param); 27 | var total = 0; 28 | this.orders.forEach(function (order) { 29 | total += order.price; 30 | }); 31 | this.price = total / this.orders.length; 32 | this.digest('createOrder', param); 33 | this.emit('done', param, this); 34 | }; 35 | } 36 | 37 | /* end Market model/entity */ 38 | 39 | describe('Repository', function () { 40 | 41 | let repository; 42 | 43 | beforeEach(function (done) { 44 | log('connecting to mongo') 45 | mongo.once('connected', function (db) { 46 | db.collection('Market.events').drop(function () { 47 | db.collection('Market.snapshots').drop(function () { 48 | log('connected to mongo, creating repo') 49 | repository = new Repository(Market); 50 | done(); 51 | }); 52 | }); 53 | }); 54 | mongo.connect('mongodb://127.0.0.1:27017/sourced'); 55 | }); 56 | 57 | afterAll(function (done) { 58 | mongo.close(done); 59 | }); 60 | 61 | it('should initialize market entity and digest 12 events, setting version, snapshotVersion, and price', function (done) { 62 | 63 | var id = 'somecusip'; 64 | var mrkt = new Market(); 65 | 66 | mrkt.init({ id: id }); 67 | 68 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 69 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 70 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 71 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 72 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 73 | mrkt.createOrder({ side: 's', price: 95, quantity: 1000 }); 74 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 75 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 76 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 77 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 78 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 79 | 80 | expect(mrkt.version).toBe(12); 81 | expect(mrkt.snapshotVersion).toBe(0); 82 | expect(mrkt.price).toBe(92.27272727272727); 83 | 84 | repository.commit(mrkt, function (err) { 85 | if (err) throw err; 86 | 87 | repository.get(id, function (err, market) { 88 | if (err) throw err; 89 | 90 | expect(market.version).toBe(12); 91 | expect(market.snapshotVersion).toBe(12); 92 | expect(market.price).toBe(92.27272727272727); 93 | 94 | done(); 95 | 96 | }); 97 | }); 98 | }); 99 | 100 | it('should load deserialized market entity from snapshot, digest two events, and update version, snapshotVersion, and price', function (done) { 101 | 102 | var id = 'somecusip'; 103 | var mrkt = new Market(); 104 | 105 | mrkt.init({ id: id }); 106 | 107 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 108 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 109 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 110 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 111 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 112 | mrkt.createOrder({ side: 's', price: 95, quantity: 1000 }); 113 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 114 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 115 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 116 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 117 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 118 | 119 | expect(mrkt.version).toBe(12); 120 | expect(mrkt.snapshotVersion).toBe(0); 121 | expect(mrkt.price).toBe(92.27272727272727); 122 | 123 | repository.commit(mrkt, function (err) { 124 | if (err) throw err; 125 | 126 | repository.get(id, function (err, mrkt) { 127 | if (err) throw err; 128 | 129 | expect(mrkt.version).toBe(12); 130 | expect(mrkt.snapshotVersion).toBe(12); 131 | expect(mrkt.price).toBe(92.27272727272727); 132 | 133 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 134 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 135 | 136 | expect(mrkt.version).toBe(14); 137 | expect(mrkt.snapshotVersion).toBe(12); 138 | expect(mrkt.price).toBe(92); 139 | expect(mrkt.newEvents.length).toBe(2); 140 | 141 | repository.commit(mrkt, function (err) { 142 | if (err) throw err; 143 | 144 | repository.get(id, function (err, market) { 145 | if (err) throw err; 146 | 147 | expect(market.version).toBe(14); 148 | expect(market.snapshotVersion).toBe(12); 149 | expect(market.price).toBe(92); 150 | expect(market.newEvents.length).toBe(0); 151 | 152 | done(); 153 | 154 | }); 155 | 156 | }); 157 | }); 158 | }); 159 | 160 | }); 161 | 162 | it('should emit all enqueued eventsToEmit after only after committing', function (done) { 163 | 164 | var id = 'somecusip'; 165 | var mrkt = new Market(); 166 | 167 | mrkt.init({ id: id }); 168 | 169 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 170 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 171 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 172 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 173 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 174 | mrkt.createOrder({ side: 's', price: 95, quantity: 1000 }); 175 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 176 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 177 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 178 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 179 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 180 | 181 | expect(mrkt.version).toBe(12); 182 | expect(mrkt.snapshotVersion).toBe(0); 183 | expect(mrkt.price).toBe(92.27272727272727); 184 | 185 | repository.commit(mrkt, function (err) { 186 | if (err) throw err; 187 | 188 | 189 | repository.get(id, function (err, market) { 190 | if (err) throw err; 191 | 192 | market.on('myEventHappened', function (data, data2) { 193 | expect(market.eventsToEmit.length).toBe(0); 194 | expect(market.newEvents.length).toBe(0); 195 | expect(data.data).toBe('data'); 196 | expect(data2.data2).toBe('data2'); 197 | done(); 198 | }); 199 | 200 | market.enqueue('myEventHappened', { data: 'data' }, { data2: 'data2' }); 201 | 202 | repository.commit(market, function (err) { 203 | if (err) throw err; 204 | 205 | repository.get(id, function (err, market) { 206 | if (err) throw err; 207 | 208 | expect(market.version).toBe(12); 209 | expect(market.snapshotVersion).toBe(12); 210 | expect(market.price).toBe(92.27272727272727); 211 | expect(market.newEvents.length).toBe(0); 212 | 213 | }); 214 | 215 | }); 216 | }); 217 | }); 218 | 219 | }); 220 | 221 | it('should load multiple deserialized market entities from snapshot, and commit in bulk', function (done) { 222 | 223 | var id = 'somecusip2'; 224 | var mrkt = new Market(); 225 | 226 | var id2 = 'somecusip3'; 227 | var mrkt2 = new Market(); 228 | 229 | var id3 = 'somecusip4'; 230 | var mrkt3 = new Market(); 231 | 232 | var id4 = 'somecusip5'; 233 | var mrkt4 = new Market(); 234 | 235 | mrkt.init({ id: id }); 236 | mrkt2.init({ id: id2 }); 237 | mrkt3.init({ id: id3 }); 238 | mrkt4.init({ id: id4 }); 239 | 240 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1001 }); 241 | 242 | mrkt2.createOrder({ side: 'b', price: 90, quantity: 1002 }); 243 | mrkt2.createOrder({ side: 'b', price: 90, quantity: 1003 }); 244 | 245 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1004 }); 246 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1005 }); 247 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1006 }); 248 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1007 }); 249 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1008 }); 250 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1009 }); 251 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1010 }); 252 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1011 }); 253 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1012 }); 254 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1013 }); 255 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1014 }); 256 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1015 }); 257 | 258 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1016 }); 259 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1017 }); 260 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1018 }); 261 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1019 }); 262 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1020 }); 263 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1022 }); 264 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1023 }); 265 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1024 }); 266 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1025 }); 267 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1026 }); 268 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1027 }); 269 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1028 }); 270 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1029 }); 271 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1030 }); 272 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1031 }); 273 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1032 }); 274 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1033 }); 275 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1034 }); 276 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1035 }); 277 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1036 }); 278 | 279 | repository.commitAll([mrkt, mrkt2, mrkt3, mrkt4], function (err) { 280 | if (err) return done(err); 281 | 282 | repository.getAll([ id, id2, id3, id4 ], function (err, markets) { 283 | if (err) return done(err); 284 | 285 | var market = markets[0]; 286 | var market2 = markets[1]; 287 | var market3 = markets[2]; 288 | var market4 = markets[3]; 289 | 290 | expect(market.id).toBe(id); 291 | expect(market.version).toBe(2); 292 | expect(market.snapshotVersion).toBe(0); 293 | 294 | expect(market2.id).toBe(id2); 295 | expect(market2.version).toBe(3); 296 | expect(market2.snapshotVersion).toBe(0); 297 | 298 | expect(market3.id).toBe(id3); 299 | expect(market3.version).toBe(13); 300 | expect(market3.snapshotVersion).toBe(13); 301 | 302 | expect(market4.id).toBe(id4); 303 | expect(market4.version).toBe(21); 304 | expect(market4.snapshotVersion).toBe(21); 305 | 306 | done(); 307 | 308 | }); 309 | }); 310 | 311 | }); 312 | 313 | it('should load all entities when getAll called with callback only', function (done) { 314 | 315 | var id = 'somecusip6'; 316 | var mrkt = new Market(); 317 | 318 | var id2 = 'somecusip7'; 319 | var mrkt2 = new Market(); 320 | 321 | var id3 = 'somecusip8'; 322 | var mrkt3 = new Market(); 323 | 324 | var id4 = 'somecusip9'; 325 | var mrkt4 = new Market(); 326 | 327 | mrkt.init({ id: id }); 328 | mrkt2.init({ id: id2 }); 329 | mrkt3.init({ id: id3 }); 330 | mrkt4.init({ id: id4 }); 331 | 332 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1001 }); 333 | 334 | mrkt2.createOrder({ side: 'b', price: 90, quantity: 1002 }); 335 | mrkt2.createOrder({ side: 'b', price: 90, quantity: 1003 }); 336 | 337 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1004 }); 338 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1005 }); 339 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1006 }); 340 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1007 }); 341 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1008 }); 342 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1009 }); 343 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1010 }); 344 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1011 }); 345 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1012 }); 346 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1013 }); 347 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1014 }); 348 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1015 }); 349 | 350 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1016 }); 351 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1017 }); 352 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1018 }); 353 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1019 }); 354 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1020 }); 355 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1022 }); 356 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1023 }); 357 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1024 }); 358 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1025 }); 359 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1026 }); 360 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1027 }); 361 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1028 }); 362 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1029 }); 363 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1030 }); 364 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1031 }); 365 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1032 }); 366 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1033 }); 367 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1034 }); 368 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1035 }); 369 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1036 }); 370 | 371 | repository.commitAll([mrkt, mrkt2, mrkt3, mrkt4], function (err) { 372 | if (err) return done(err); 373 | 374 | repository.getAll(function (err, markets) { 375 | if (err) return done(err); 376 | 377 | var market = markets[0]; 378 | var market2 = markets[1]; 379 | var market3 = markets[2]; 380 | var market4 = markets[3]; 381 | 382 | expect(market.id).toBe(id); 383 | expect(market.version).toBe(2); 384 | expect(market.snapshotVersion).toBe(0); 385 | 386 | expect(market2.id).toBe(id2); 387 | expect(market2.version).toBe(3); 388 | expect(market2.snapshotVersion).toBe(0); 389 | 390 | expect(market3.id).toBe(id3); 391 | expect(market3.version).toBe(13); 392 | expect(market3.snapshotVersion).toBe(13); 393 | 394 | expect(market4.id).toBe(id4); 395 | expect(market4.version).toBe(21); 396 | expect(market4.snapshotVersion).toBe(21); 397 | 398 | done(); 399 | 400 | }); 401 | }); 402 | 403 | }); 404 | 405 | it('should take snapshot when forceSnapshot provided', function (done) { 406 | 407 | var id = 'somecusip6'; 408 | 409 | var mrkt = new Market(); 410 | 411 | mrkt.init({ id: id }); 412 | 413 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 414 | 415 | expect(mrkt.version).toBe(2); 416 | expect(mrkt.snapshotVersion).toBe(0); 417 | expect(mrkt.price).toBe(90); 418 | 419 | repository.commit(mrkt, { forceSnapshot: true }, function (err) { 420 | if (err) throw err; 421 | 422 | repository.get(id, function (err, market) { 423 | if (err) throw err; 424 | 425 | expect(market.version).toBe(2); 426 | expect(market.snapshotVersion).toBe(2); 427 | expect(market.price).toBe(90); 428 | 429 | done(); 430 | 431 | }); 432 | 433 | }); 434 | 435 | }); 436 | 437 | it('should return null when get called with id of nonexisting entity', function (done) { 438 | 439 | repository.get('fake', function (err, market) { 440 | if (err) throw err; 441 | 442 | should(market).eql(null); 443 | 444 | done(); 445 | 446 | }); 447 | 448 | }); 449 | 450 | it('should return null when getAll called with only ids of nonexisting entities', function (done) { 451 | 452 | repository.getAll(['fake'], function (err, market) { 453 | if (err) throw err; 454 | 455 | should(market).eql(null); 456 | 457 | done(); 458 | 459 | }); 460 | 461 | }); 462 | 463 | }); -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var Entity = require('sourced').Entity; 2 | var log = require('debug')('sourced-repo-mongo'); 3 | var mongo = require('../mongo'); 4 | var sourcedRepoMongo = require('../index'); 5 | var Repository = sourcedRepoMongo.Repository; 6 | var util = require('util'); 7 | var _ = require('lodash'); 8 | 9 | var should = require('should'); 10 | 11 | /* Market model/entity */ 12 | function Market () { 13 | this.orders = []; 14 | this.price = 0; 15 | Entity.apply(this, arguments); 16 | } 17 | 18 | util.inherits(Market, Entity); 19 | 20 | Market.prototype.init = function (param) { 21 | this.id = param.id; 22 | this.digest('init', param); 23 | this.emit('initialized', param, this); 24 | }; 25 | 26 | Market.prototype.createOrder = function (param) { 27 | this.orders.push(param); 28 | var total = 0; 29 | this.orders.forEach(function (order) { 30 | total += order.price; 31 | }); 32 | this.price = total / this.orders.length; 33 | this.digest('createOrder', param); 34 | this.emit('done', param, this); 35 | }; 36 | /* end Market model/entity */ 37 | 38 | describe('Repository', function () { 39 | 40 | var repository; 41 | 42 | beforeEach(function (done) { 43 | mongo.once('connected', function (db) { 44 | db.collection('Market.events').drop(function () { 45 | db.collection('Market.snapshots').drop(function () { 46 | repository = new Repository(Market); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | mongo.connect('mongodb://127.0.0.1:27017/sourced'); 52 | }); 53 | 54 | after(function (done) { 55 | mongo.close(done); 56 | }); 57 | 58 | it('should initialize market entity and digest 12 events, setting version, snapshotVersion, and price', function (done) { 59 | 60 | var id = 'somecusip'; 61 | var mrkt = new Market(); 62 | 63 | mrkt.init({ id: id }); 64 | 65 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 66 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 67 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 68 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 69 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 70 | mrkt.createOrder({ side: 's', price: 95, quantity: 1000 }); 71 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 72 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 73 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 74 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 75 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 76 | 77 | mrkt.should.have.property('version', 12); 78 | mrkt.should.have.property('snapshotVersion', 0); 79 | mrkt.should.have.property('price', 92.27272727272727); 80 | 81 | repository.commit(mrkt, function (err) { 82 | if (err) throw err; 83 | 84 | repository.get(id, function (err, market) { 85 | if (err) throw err; 86 | 87 | market.should.have.property('version', 12); 88 | market.should.have.property('snapshotVersion', 12); 89 | market.should.have.property('price', 92.27272727272727); 90 | 91 | done(); 92 | 93 | }); 94 | }); 95 | }); 96 | 97 | it('should load deserialized market entity from snapshot, digest two events, and update version, snapshotVersion, and price', function (done) { 98 | 99 | var id = 'somecusip'; 100 | var mrkt = new Market(); 101 | 102 | mrkt.init({ id: id }); 103 | 104 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 105 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 106 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 107 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 108 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 109 | mrkt.createOrder({ side: 's', price: 95, quantity: 1000 }); 110 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 111 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 112 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 113 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 114 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 115 | 116 | mrkt.should.have.property('version', 12); 117 | mrkt.should.have.property('snapshotVersion', 0); 118 | mrkt.should.have.property('price', 92.27272727272727); 119 | 120 | repository.commit(mrkt, function (err) { 121 | if (err) throw err; 122 | 123 | repository.get(id, function (err, mrkt) { 124 | if (err) throw err; 125 | 126 | mrkt.should.have.property('version', 12); 127 | mrkt.should.have.property('snapshotVersion', 12); 128 | mrkt.should.have.property('price', 92.27272727272727); 129 | 130 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 131 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 132 | 133 | mrkt.should.have.property('version', 14); 134 | mrkt.should.have.property('snapshotVersion', 12); 135 | mrkt.should.have.property('price', 92); 136 | mrkt.newEvents.should.have.property('length', 2); 137 | 138 | repository.commit(mrkt, function (err) { 139 | if (err) throw err; 140 | 141 | repository.get(id, function (err, market) { 142 | if (err) throw err; 143 | 144 | market.should.have.property('version', 14); 145 | market.should.have.property('snapshotVersion', 12); 146 | market.should.have.property('price', 92); 147 | market.newEvents.should.have.property('length', 0); 148 | 149 | done(); 150 | 151 | }); 152 | 153 | }); 154 | }); 155 | }); 156 | 157 | }); 158 | 159 | it('should emit all enqueued eventsToEmit after only after committing', function (done) { 160 | 161 | var id = 'somecusip'; 162 | var mrkt = new Market(); 163 | 164 | mrkt.init({ id: id }); 165 | 166 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 167 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 168 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 169 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 170 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 171 | mrkt.createOrder({ side: 's', price: 95, quantity: 1000 }); 172 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 173 | mrkt.createOrder({ side: 's', price: 91, quantity: 1000 }); 174 | mrkt.createOrder({ side: 'b', price: 92, quantity: 1000 }); 175 | mrkt.createOrder({ side: 's', price: 93, quantity: 1000 }); 176 | mrkt.createOrder({ side: 'b', price: 94, quantity: 1000 }); 177 | 178 | mrkt.should.have.property('version', 12); 179 | mrkt.should.have.property('snapshotVersion', 0); 180 | mrkt.should.have.property('price', 92.27272727272727); 181 | 182 | repository.commit(mrkt, function (err) { 183 | if (err) throw err; 184 | 185 | 186 | repository.get(id, function (err, market) { 187 | if (err) throw err; 188 | 189 | market.on('myEventHappened', function (data, data2) { 190 | market.eventsToEmit.should.have.property('length', 0); 191 | market.newEvents.should.have.property('length', 0); 192 | data.should.have.property('data', 'data'); 193 | data2.should.have.property('data2', 'data2'); 194 | done(); 195 | }); 196 | 197 | market.enqueue('myEventHappened', { data: 'data' }, { data2: 'data2' }); 198 | 199 | repository.commit(market, function (err) { 200 | if (err) throw err; 201 | 202 | repository.get(id, function (err, market) { 203 | if (err) throw err; 204 | 205 | market.should.have.property('version', 12); 206 | market.should.have.property('snapshotVersion', 12); 207 | market.should.have.property('price', 92.27272727272727); 208 | market.newEvents.should.have.property('length', 0); 209 | 210 | }); 211 | 212 | }); 213 | }); 214 | }); 215 | 216 | }); 217 | 218 | it('should load multiple deserialized market entities from snapshot, and commit in bulk', function (done) { 219 | 220 | var id = 'somecusip2'; 221 | var mrkt = new Market(); 222 | 223 | var id2 = 'somecusip3'; 224 | var mrkt2 = new Market(); 225 | 226 | var id3 = 'somecusip4'; 227 | var mrkt3 = new Market(); 228 | 229 | var id4 = 'somecusip5'; 230 | var mrkt4 = new Market(); 231 | 232 | mrkt.init({ id: id }); 233 | mrkt2.init({ id: id2 }); 234 | mrkt3.init({ id: id3 }); 235 | mrkt4.init({ id: id4 }); 236 | 237 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1001 }); 238 | 239 | mrkt2.createOrder({ side: 'b', price: 90, quantity: 1002 }); 240 | mrkt2.createOrder({ side: 'b', price: 90, quantity: 1003 }); 241 | 242 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1004 }); 243 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1005 }); 244 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1006 }); 245 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1007 }); 246 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1008 }); 247 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1009 }); 248 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1010 }); 249 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1011 }); 250 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1012 }); 251 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1013 }); 252 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1014 }); 253 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1015 }); 254 | 255 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1016 }); 256 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1017 }); 257 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1018 }); 258 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1019 }); 259 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1020 }); 260 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1022 }); 261 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1023 }); 262 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1024 }); 263 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1025 }); 264 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1026 }); 265 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1027 }); 266 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1028 }); 267 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1029 }); 268 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1030 }); 269 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1031 }); 270 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1032 }); 271 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1033 }); 272 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1034 }); 273 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1035 }); 274 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1036 }); 275 | 276 | repository.commitAll([mrkt, mrkt2, mrkt3, mrkt4], function (err) { 277 | if (err) return done(err); 278 | 279 | repository.getAll([ id, id2, id3, id4 ], function (err, markets) { 280 | if (err) return done(err); 281 | 282 | var market = markets[0]; 283 | var market2 = markets[1]; 284 | var market3 = markets[2]; 285 | var market4 = markets[3]; 286 | 287 | market.should.have.property('id', id); 288 | market.should.have.property('version', 2); 289 | market.should.have.property('snapshotVersion', 0); 290 | 291 | market2.should.have.property('id', id2); 292 | market2.should.have.property('version', 3); 293 | market2.should.have.property('snapshotVersion', 0); 294 | 295 | market3.should.have.property('id', id3); 296 | market3.should.have.property('version', 13); 297 | market3.should.have.property('snapshotVersion', 13); 298 | 299 | market4.should.have.property('id', id4); 300 | market4.should.have.property('version', 21); 301 | market4.should.have.property('snapshotVersion', 21); 302 | 303 | done(); 304 | 305 | }); 306 | }); 307 | 308 | }); 309 | 310 | it('should load all entities when getAll called with callback only', function (done) { 311 | 312 | var id = 'somecusip6'; 313 | var mrkt = new Market(); 314 | 315 | var id2 = 'somecusip7'; 316 | var mrkt2 = new Market(); 317 | 318 | var id3 = 'somecusip8'; 319 | var mrkt3 = new Market(); 320 | 321 | var id4 = 'somecusip9'; 322 | var mrkt4 = new Market(); 323 | 324 | mrkt.init({ id: id }); 325 | mrkt2.init({ id: id2 }); 326 | mrkt3.init({ id: id3 }); 327 | mrkt4.init({ id: id4 }); 328 | 329 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1001 }); 330 | 331 | mrkt2.createOrder({ side: 'b', price: 90, quantity: 1002 }); 332 | mrkt2.createOrder({ side: 'b', price: 90, quantity: 1003 }); 333 | 334 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1004 }); 335 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1005 }); 336 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1006 }); 337 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1007 }); 338 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1008 }); 339 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1009 }); 340 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1010 }); 341 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1011 }); 342 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1012 }); 343 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1013 }); 344 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1014 }); 345 | mrkt3.createOrder({ side: 'b', price: 90, quantity: 1015 }); 346 | 347 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1016 }); 348 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1017 }); 349 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1018 }); 350 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1019 }); 351 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1020 }); 352 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1022 }); 353 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1023 }); 354 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1024 }); 355 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1025 }); 356 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1026 }); 357 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1027 }); 358 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1028 }); 359 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1029 }); 360 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1030 }); 361 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1031 }); 362 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1032 }); 363 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1033 }); 364 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1034 }); 365 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1035 }); 366 | mrkt4.createOrder({ side: 'b', price: 90, quantity: 1036 }); 367 | 368 | repository.commitAll([mrkt, mrkt2, mrkt3, mrkt4], function (err) { 369 | if (err) return done(err); 370 | 371 | repository.getAll(function (err, markets) { 372 | if (err) return done(err); 373 | 374 | var market = markets[0]; 375 | var market2 = markets[1]; 376 | var market3 = markets[2]; 377 | var market4 = markets[3]; 378 | 379 | market.should.have.property('id', id); 380 | market.should.have.property('version', 2); 381 | market.should.have.property('snapshotVersion', 0); 382 | 383 | market2.should.have.property('id', id2); 384 | market2.should.have.property('version', 3); 385 | market2.should.have.property('snapshotVersion', 0); 386 | 387 | market3.should.have.property('id', id3); 388 | market3.should.have.property('version', 13); 389 | market3.should.have.property('snapshotVersion', 13); 390 | 391 | market4.should.have.property('id', id4); 392 | market4.should.have.property('version', 21); 393 | market4.should.have.property('snapshotVersion', 21); 394 | 395 | done(); 396 | 397 | }); 398 | }); 399 | 400 | }); 401 | 402 | it('should take snapshot when forceSnapshot provided', function (done) { 403 | 404 | var id = 'somecusip6'; 405 | 406 | var mrkt = new Market(); 407 | 408 | mrkt.init({ id: id }); 409 | 410 | mrkt.createOrder({ side: 'b', price: 90, quantity: 1000 }); 411 | 412 | mrkt.should.have.property('version', 2); 413 | mrkt.should.have.property('snapshotVersion', 0); 414 | mrkt.should.have.property('price', 90); 415 | 416 | repository.commit(mrkt, { forceSnapshot: true }, function (err) { 417 | if (err) throw err; 418 | 419 | repository.get(id, function (err, market) { 420 | if (err) throw err; 421 | 422 | market.should.have.property('version', 2); 423 | market.should.have.property('snapshotVersion', 2); 424 | market.should.have.property('price', 90); 425 | 426 | done(); 427 | 428 | }); 429 | 430 | }); 431 | 432 | }); 433 | 434 | it('should return null when get called with id of nonexisting entity', function (done) { 435 | 436 | repository.get('fake', function (err, market) { 437 | if (err) throw err; 438 | 439 | should(market).eql(null); 440 | 441 | done(); 442 | 443 | }); 444 | 445 | }); 446 | 447 | it('should return null when getAll called with only ids of nonexisting entities', function (done) { 448 | 449 | repository.getAll(['fake'], function (err, market) { 450 | if (err) throw err; 451 | 452 | should(market).eql(null); 453 | 454 | done(); 455 | 456 | }); 457 | 458 | }); 459 | 460 | }); --------------------------------------------------------------------------------