nqm-minimongo
Version:
Client-side mongo database with server sync over http
205 lines (159 loc) • 6.12 kB
text/coffeescript
_ = require 'lodash'
createUid = require('./utils').createUid
processFind = require('./utils').processFind
compileSort = require('./selector').compileSort
module.exports = class LocalStorageDb
constructor: (options, success) ->
@collections = {}
if options and options.namespace and window.localStorage
@namespace = options.namespace
if success then success(this)
addCollection: (name, success, error) ->
# Set namespace for collection
namespace = @namespace+"."+name if @namespace
collection = new Collection(name, namespace)
@[name] = collection
@collections[name] = collection
if success? then success()
removeCollection: (name, success, error) ->
if @namespace and window.localStorage
keys = []
for i in [0...window.localStorage.length]
keys.push(window.localStorage.key(i))
for key in keys
if key.substring(0, @namespace.length + 1) == @namespace + "."
window.localStorage.removeItem(key)
delete @[name]
delete @collections[name]
if success? then success()
# Stores data in memory, optionally backed by local storage
class Collection
constructor: (name, namespace) ->
@name = name
@namespace = namespace
@items = {}
@upserts = {} # Pending upserts by _id. Still in items
@removes = {} # Pending removes by _id. No longer in items
# Read from local storage
if window.localStorage and namespace?
@loadStorage()
loadStorage: ->
# Read items from localStorage
@itemNamespace = @namespace + "_"
for i in [0...window.localStorage.length]
key = window.localStorage.key(i)
if key.substring(0, @itemNamespace.length) == @itemNamespace
item = JSON.parse(window.localStorage[key])
@items[item._id] = item
# Read upserts
upsertKeys = if window.localStorage[@namespace+"upserts"] then JSON.parse(window.localStorage[@namespace+"upserts"]) else []
for key in upsertKeys
@upserts[key] = @items[key]
# Read removes
removeItems = if window.localStorage[@namespace+"removes"] then JSON.parse(window.localStorage[@namespace+"removes"]) else []
@removes = _.object(_.pluck(removeItems, "_id"), removeItems)
find: (selector, options) ->
return fetch: (success, error) =>
@_findFetch(selector, options, success, error)
findOne: (selector, options, success, error) ->
if _.isFunction(options)
[options, success, error] = [{}, options, success]
@find(selector, options).fetch (results) ->
if success? then success(if results.length>0 then results[0] else null)
, error
_findFetch: (selector, options, success, error) ->
if success? then success(processFind(@items, selector, options))
upsert: (doc, success, error) ->
# Handle both single and multiple upsert
items = doc
if not _.isArray(items)
items = [items]
# Handle case of array
for item in items
if not item._id
item._id = createUid()
# Replace/add
@_putItem(item)
@_putUpsert(item)
if success then success(doc)
remove: (id, success, error) ->
if _.has(@items, id)
@_putRemove(@items[id])
@_deleteItem(id)
@_deleteUpsert(id)
else
@_putRemove({ _id: id })
if success? then success()
_putItem: (doc) ->
@items[doc._id] = doc
if @namespace
window.localStorage[@itemNamespace + doc._id] = JSON.stringify(doc)
_deleteItem: (id) ->
delete @items[id]
if @namespace
window.localStorage.removeItem(@itemNamespace + id)
_putUpsert: (doc) ->
@upserts[doc._id] = doc
if @namespace
window.localStorage[@namespace+"upserts"] = JSON.stringify(_.keys(@upserts))
_deleteUpsert: (id) ->
delete @upserts[id]
if @namespace
window.localStorage[@namespace+"upserts"] = JSON.stringify(_.keys(@upserts))
_putRemove: (doc) ->
@removes[doc._id] = doc
if @namespace
window.localStorage[@namespace+"removes"] = JSON.stringify(_.values(@removes))
_deleteRemove: (id) ->
delete @removes[id]
if @namespace
window.localStorage[@namespace+"removes"] = JSON.stringify(_.values(@removes))
cache: (docs, selector, options, success, error) ->
# Add all non-local that are not upserted or removed
for doc in docs
@cacheOne(doc)
docsMap = _.object(_.pluck(docs, "_id"), docs)
if options.sort
sort = compileSort(options.sort)
# Perform query, removing rows missing in docs from local db
@find(selector, options).fetch (results) =>
for result in results
if not docsMap[result._id] and not _.has(@upserts, result._id)
# If past end on sorted limited, ignore
if options.sort and options.limit and docs.length == options.limit
if sort(result, _.last(docs)) >= 0
continue
@_deleteItem(result._id)
if success? then success()
, error
pendingUpserts: (success) ->
success _.values(@upserts)
pendingRemoves: (success) ->
success _.pluck(@removes, "_id")
resolveUpsert: (doc, success) ->
# Handle both single and multiple upsert
items = doc
if not _.isArray(items)
items = [items]
for item in items
if @upserts[item._id]
# Only safely remove upsert if item is unchanged
if _.isEqual(item, @upserts[item._id])
@_deleteUpsert(item._id)
if success? then success()
resolveRemove: (id, success) ->
@_deleteRemove(id)
if success? then success()
# Add but do not overwrite or record as upsert
seed: (doc, success) ->
if not _.has(@items, doc._id) and not _.has(@removes, doc._id)
@_putItem(doc)
if success? then success()
# Add but do not overwrite upserts or removes
cacheOne: (doc, success) ->
if not _.has(@upserts, doc._id) and not _.has(@removes, doc._id)
existing = @items[doc._id]
# If _rev present, make sure that not overwritten by lower _rev
if not existing or not doc._rev or not existing._rev or doc._rev >= existing._rev
@_putItem(doc)
if success? then success()