UNPKG

nqm-minimongo

Version:

Client-side mongo database with server sync over http

218 lines (173 loc) 6.79 kB
# Utilities for db handling _ = require 'lodash' async = require 'async' bowser = require 'bowser' compileDocumentSelector = require('./selector').compileDocumentSelector compileSort = require('./selector').compileSort # Select appropriate local database, prefering IndexedDb, then WebSQLDb, then LocalStorageDb, then MemoryDb exports.autoselectLocalDb = (options, success, error) -> # Here due to browserify circularity quirks IndexedDb = require './IndexedDb' WebSQLDb = require './WebSQLDb' LocalStorageDb = require './LocalStorageDb' MemoryDb = require './MemoryDb' # Get browser capabilities browser = bowser.browser # Always use WebSQL in cordova if window.cordova console.log "Selecting WebSQLDb for Cordova" return new WebSQLDb options, success, error # Use WebSQL in Android, iOS, Chrome, Safari, Opera, Blackberry if browser.android or browser.ios or browser.chrome or browser.safari or browser.opera or browser.blackberry console.log "Selecting WebSQLDb for browser" return new WebSQLDb options, success, error # Use IndexedDb on Firefox >= 16 if browser.firefox and browser.version >= 16 console.log "Selecting IndexedDb for browser" return new IndexedDb options, success, error # Use Local Storage otherwise console.log "Selecting LocalStorageDb for fallback" return new LocalStorageDb(options, success, error) # Migrates a local database's pending upserts and removes from one database to another # Useful for upgrading from one type of database to another exports.migrateLocalDb = (fromDb, toDb, success, error) -> # Migrate collection using a HybridDb # Here due to browserify circularity quirks HybridDb = require './HybridDb' hybridDb = new HybridDb(fromDb, toDb) for name, col of fromDb.collections if toDb[name] hybridDb.addCollection(name) hybridDb.upload(success, error) # Processes a find with sorting and filtering and limiting exports.processFind = (items, selector, options) -> filtered = _.filter(_.values(items), compileDocumentSelector(selector)) # Handle geospatial operators filtered = processNearOperator(selector, filtered) filtered = processGeoIntersectsOperator(selector, filtered) if options and options.sort filtered.sort(compileSort(options.sort)) if options and options.limit filtered = _.first filtered, options.limit # Clone to prevent accidental updates, or apply fields if present if options and options.fields # For each item filtered = _.map filtered, (item) -> item = _.cloneDeep(item) newItem = {} if _.first(_.values(options.fields)) == 1 # Include fields for field in _.keys(options.fields).concat(["_id"]) path = field.split(".") # Determine if path exists obj = item for pathElem in path if obj obj = obj[pathElem] if not obj? continue # Go into path, creating as necessary from = item to = newItem for pathElem in _.initial(path) to[pathElem] = to[pathElem] or {} # Move inside to = to[pathElem] from = from[pathElem] # Copy value to[_.last(path)] = from[_.last(path)] return newItem else # Exclude fields for field in _.keys(options.fields).concat(["_id"]) path = field.split(".") # Go inside path obj = item for pathElem in _.initial(path) if obj obj = obj[pathElem] # If not there, don't exclude if not obj? continue delete obj[_.last(path)] return item else filtered = _.map filtered, (doc) -> _.cloneDeep(doc) return filtered # Creates a unique identifier string exports.createUid = -> 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, (c) -> r = Math.random()*16|0 v = if c == 'x' then r else (r&0x3|0x8) return v.toString(16) ) processNearOperator = (selector, list) -> for key, value of selector if value? and value['$near'] geo = value['$near']['$geometry'] if geo.type != 'Point' break list = _.filter list, (doc) -> return doc[key] and doc[key].type == 'Point' # Get distances distances = _.map list, (doc) -> return { doc: doc, distance: getDistanceFromLatLngInM( geo.coordinates[1], geo.coordinates[0], doc[key].coordinates[1], doc[key].coordinates[0]) } # Filter non-points distances = _.filter distances, (item) -> item.distance >= 0 # Sort by distance distances = _.sortBy distances, 'distance' # Filter by maxDistance if value['$near']['$maxDistance'] distances = _.filter distances, (item) -> item.distance <= value['$near']['$maxDistance'] # Limit to 100 distances = _.first distances, 100 # Extract docs list = _.pluck distances, 'doc' return list # Very simple polygon check. Assumes that is a square pointInPolygon = (point, polygon) -> # Check that first == last if not _.isEqual(_.first(polygon.coordinates[0]), _.last(polygon.coordinates[0])) throw new Error("First must equal last") # Check bounds if point.coordinates[0] < Math.min.apply(this, _.map(polygon.coordinates[0], (coord) -> coord[0])) return false if point.coordinates[1] < Math.min.apply(this, _.map(polygon.coordinates[0], (coord) -> coord[1])) return false if point.coordinates[0] > Math.max.apply(this, _.map(polygon.coordinates[0], (coord) -> coord[0])) return false if point.coordinates[1] > Math.max.apply(this, _.map(polygon.coordinates[0], (coord) -> coord[1])) return false return true # From http://www.movable-type.co.uk/scripts/latlong.html getDistanceFromLatLngInM = (lat1, lng1, lat2, lng2) -> R = 6371000 # Radius of the earth in m dLat = deg2rad(lat2 - lat1) # deg2rad below dLng = deg2rad(lng2 - lng1) a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2) c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) d = R * c # Distance in m return d deg2rad = (deg) -> deg * (Math.PI / 180) processGeoIntersectsOperator = (selector, list) -> for key, value of selector if value? and value['$geoIntersects'] geo = value['$geoIntersects']['$geometry'] if geo.type != 'Polygon' break # Check within for each list = _.filter list, (doc) -> # Reject non-points if not doc[key] or doc[key].type != 'Point' return false # Check polygon return pointInPolygon(doc[key], geo) return list