minimongo-sync
Version:
Client-side mongo database with server sync over http
186 lines (150 loc) • 5.5 kB
text/coffeescript
_ = require 'lodash'
utils = require('./utils')
compileSort = require('./selector').compileSort
# Replicates data into a both a master and a replica db. Assumes both are identical at start
# and then only uses master for finds and does all changes to both
# Warning: removing a collection removes it from the underlying master and replica!
module.exports = class ReplicatingDb
constructor: (masterDb, replicaDb) ->
@collections = {}
@masterDb = masterDb
@replicaDb = replicaDb
addCollection: (name, success, error) ->
collection = new Collection(name, @masterDb[name], @replicaDb[name])
@[name] = collection
@collections[name] = collection
if success? then success()
removeCollection: (name, success, error) ->
delete @[name]
delete @collections[name]
if success? then success()
getCollectionNames: -> _.keys(@collections)
# Replicated collection.
class Collection
constructor: (name, masterCol, replicaCol) ->
@name = name
@masterCol = masterCol
@replicaCol = replicaCol
find: (selector, options) ->
return @masterCol.find(selector, options)
findOne: (selector, options, success, error) ->
return @masterCol.findOne(selector, options, success, error)
upsert: (docs, bases, success, error) ->
[items, success, error] = utils.regularizeUpsert(docs, bases, success, error)
# Upsert does to both
@masterCol.upsert(_.pluck(items, "doc"), _.pluck(items, "base"), () =>
@replicaCol.upsert(_.pluck(items, "doc"), _.pluck(items, "base"), (results) =>
success(docs)
, error)
, error)
remove: (id, success, error) ->
# Do to both
@masterCol.remove(id, () =>
@replicaCol.remove(id, success, error)
, error)
cache: (docs, selector, options, success, error) ->
# Calculate what has to be done for cache using the master database which is faster (usually MemoryDb)
# then do minimum to both databases
# Index docs
docsMap = _.indexBy(docs, "_id")
# Compile sort
if options.sort
sort = compileSort(options.sort)
# Perform query
@masterCol.find(selector, options).fetch (results) =>
resultsMap = _.indexBy(results, "_id")
# Determine if each result needs to be cached
toCache = []
for doc in docs
result = resultsMap[doc._id]
# Exclude any excluded _ids from being cached/uncached
if options and options.exclude and doc._id in options.exclude
continue
# If not present locally, cache it
if not result
toCache.push(doc)
continue
# If both have revisions (_rev) and new one is same or lower, do not cache
if doc._rev and result._rev and doc._rev <= result._rev
continue
# Only cache if different
if not _.isEqual(doc, result)
toCache.push(doc)
toUncache = []
for result in results
# If at limit
if options.limit and docs.length == options.limit
# If past end on sorted limited, ignore
if options.sort and sort(result, _.last(docs)) >= 0
continue
# If no sort, ignore
if not options.sort
continue
# Exclude any excluded _ids from being cached/uncached
if options and options.exclude and result._id in options.exclude
continue
# Determine which ones to uncache
if not docsMap[result._id]
toUncache.push(result._id)
# Cache ones needing caching
performCaches = (next) =>
if toCache.length > 0
@masterCol.cacheList(toCache, () =>
@replicaCol.cacheList(toCache, () =>
next()
, error)
, error)
else
next()
# Uncache list
performUncaches = (next) =>
if toUncache.length > 0
@masterCol.uncacheList(toUncache, () =>
@replicaCol.uncacheList(toUncache, () =>
next()
, error)
, error)
else
next()
performCaches(=>
performUncaches(=>
if success? then success()
return
)
)
, error
pendingUpserts: (success, error) ->
@masterCol.pendingUpserts(success, error)
pendingRemoves: (success, error) ->
@masterCol.pendingRemoves(success, error)
resolveUpserts: (upserts, success, error) ->
@masterCol.resolveUpserts(upserts, () =>
@replicaCol.resolveUpserts(upserts, success, error)
, error)
resolveRemove: (id, success, error) ->
@masterCol.resolveRemove(id, () =>
@replicaCol.resolveRemove(id, success, error)
, error)
# Add but do not overwrite or record as upsert
seed: (docs, success, error) ->
@masterCol.seed(docs, () =>
@replicaCol.seed(docs, success, error)
, error)
# Add but do not overwrite upserts or removes
cacheOne: (doc, success, error) ->
@masterCol.cacheOne(doc, () =>
@replicaCol.cacheOne(doc, success, error)
, error)
# Add but do not overwrite upserts or removes
cacheList: (docs, success, error) ->
@masterCol.cacheList(docs, () =>
@replicaCol.cacheList(docs, success, error)
, error)
uncache: (selector, success, error) ->
@masterCol.uncache(selector, () =>
@replicaCol.uncache(selector, success, error)
, error)
uncacheList: (ids, success, error) ->
@masterCol.uncacheList(ids, () =>
@replicaCol.uncacheList(ids, success, error)
, error)