UNPKG

meshblu-core-datastore

Version:
200 lines (167 loc) 7.22 kB
_ = require 'lodash' async = require 'async' crypto = require 'crypto' stringify = require 'json-stable-stringify' class Datastore constructor: ({database,collection,@cache,@cacheAttributes,@useQueryCache,@ttl}) -> throw new Error('Datastore: requires database') unless database? throw new Error('Datastore: requires collection') unless collection? @ttl ?= 60 * 60 @db = database.collection collection @dbRecycle = database.collection "deleted-#{collection}" find: (query, projection, options, callback) => if _.isFunction options callback ?= options options = undefined if _.isFunction projection callback ?= projection projection = undefined return callback new Error("Datastore: requires query") if _.isEmpty query options ?= {} projection ?= {} unless _.isEmpty projection falsey = _.some _.values(projection), (value) => value == false unless falsey _.each @cacheAttributes, (attribute) => projection[attribute] = true projection._id = false @db.find query, projection, options, (error, data) => return callback error if error? @_updateCacheRecords {projection, data}, (error) => return callback error if error? callback null, data findAndUpdate: ({ query, update, projection }, callback) => return callback new Error("Datastore: requires query") if _.isEmpty query projection ?= {} unless _.isEmpty projection falsey = _.some _.values(projection), (value) => value == false unless falsey _.each @cacheAttributes, (attribute) => projection[attribute] = true projection._id = false sort = {'_id': 1} @db.findAndModify { query, sort, update, fields: projection }, (error, result) => return callback error if error? @_clearCacheRecord {query}, (error) => return callback error if error? callback null, result findOne: (query, projection, callback) => if _.isFunction projection callback ?= projection projection = undefined return callback new Error("Datastore: requires query") if _.isEmpty query projection ?= {} unless _.isEmpty projection falsey = _.some _.values(projection), (value) => value == false unless falsey _.each @cacheAttributes, (attribute) => projection[attribute] = true projection._id = false @_findCacheRecord {query, projection}, (error, data) => return callback error if error? return callback null, data if data? @db.findOne query, projection, (error, data) => return callback error if error? @_updateCacheRecord {query, projection, data}, (error) => return callback error if error? callback null, data findOneRecycled: (query, callback) => return callback new Error("Datastore: requires query") if _.isEmpty query @dbRecycle.findOne query, callback insert: (record, callback) => @db.insert record, (error) => callback error recycle: (query, callback) => return callback new Error("Datastore: requires query") if _.isEmpty query @db.find query, (error, records) => return callback error if error? async.parallel [ async.apply @remove, query async.apply @_recycleRecords, records ], callback remove: (query, callback) => return callback new Error("Datastore: requires query") if _.isEmpty query @db.remove query, (error) => return callback error if error? @_clearCacheRecord {query}, callback update: (query, data, callback) => return callback new Error("Datastore: requires query") if _.isEmpty query @db.update query, data, (error, result) => return callback error if error? return callback null, updated: false if result.nModified == 0 @_clearCacheRecord {query}, (error) => return callback error if error? return callback null, updated: true upsert: (query, data, callback) => return callback new Error("Datastore: requires query") if _.isEmpty query @db.update query, data, {upsert: true}, (error) => return callback error if error? @_clearCacheRecord {query}, callback _findCacheRecord: ({query, projection}, callback) => return callback() unless @cache? cacheKey = @_generateCacheKey {query} return callback() unless cacheKey? cacheField = @_generateCacheField {query, projection} @_getCacheRecord {cacheKey, cacheField}, callback _logError: (error) => return unless error? console.error 'Error: ', error.message _updateCacheRecord: ({query, projection, data}, callback) => return callback() unless @cache? cacheKey = @_generateCacheKey {query} return callback() unless cacheKey? cacheField = @_generateCacheField {query, projection} @_setCacheRecord {cacheKey, cacheField, data}, callback _updateCacheRecords: ({projection, data}, callback) => return callback() unless @cache? records = {} async.eachLimit data, 100, (record, done) => attributes = _.pick record, @cacheAttributes recordCacheKey = @_generateCacheKey {query: attributes} recordCacheField = @_generateCacheField {query: attributes, projection: projection} records[recordCacheKey] = recordCacheField if recordCacheKey? @_updateCacheRecord {query: attributes, projection: projection, data: record}, done , callback _clearCacheRecord: ({query}, callback) => return callback() unless @cache? cacheKey = @_generateCacheKey {query} return callback() unless cacheKey? @cache.del cacheKey, (error) => # ignore any redis return values callback error return # redis fix _generateCacheField: ({query, projection}) => cacheField = stringify(query) + stringify(projection || '') crypto.createHash('sha1').update(cacheField).digest('hex') _generateCacheKey: ({query}) => return unless @cacheAttributes? attributes = _.pick query, @cacheAttributes return unless _.size(_.keys(attributes)) == _.size @cacheAttributes cacheKey = stringify(attributes) crypto.createHash('sha1').update(cacheKey).digest('hex') _getCacheRecord: ({cacheKey, cacheField}, callback) => return callback() unless @cache? @cache.hget cacheKey, cacheField, (error, data) => return callback error if error? return callback() unless data? data = @_tryJSON data @_setExpireRecord { cacheKey }, (error) => return callback error if error? callback null, data return # redis fix _recycleRecords: (records, callback) => async.eachLimit records, 100, (record, next) => @dbRecycle.insert record, next , callback _setCacheRecord: ({cacheKey, cacheField, data}, callback) => return callback() unless @cache? @cache.hset cacheKey, cacheField, JSON.stringify(data), (error) => return callback error if error? @_setExpireRecord { cacheKey }, callback return # redis fix _setExpireRecord: ({cacheKey}, callback) => @cache.expire cacheKey, @ttl, (error) => callback error return # redis fix _tryJSON: (str) => try return JSON.parse str module.exports = Datastore