GithubHelp home page GithubHelp logo

mwater / minimongo Goto Github PK

View Code? Open in Web Editor NEW
1.2K 42.0 132.0 2.67 MB

Client-side in-memory mongodb backed by localstorage with server sync over http

License: GNU Lesser General Public License v3.0

HTML 0.16% JavaScript 32.12% TypeScript 67.72%

minimongo's Introduction

Minimongo

A client-side MongoDB implementation which supports basic queries, including some geospatial ones.

Uses code from Meteor.js minimongo package, reworked to support more geospatial queries and made npm+browserify friendly. It was forked in January 2014.

It is either IndexedDb backed (IndexedDb), WebSQL backed (WebSQLDb), Local storage backed (LocalStorageDb) or in memory only (MemoryDb).

Autoselection is possible with utils.autoselectLocalDb(options, success, error). success is called with the selected database.

sqlite plugin is also supported when available, activate sqlite plugin with option {storage: 'sqlite'} in utils.autoselectLocalDb

Usage

Minimongo is designed to be used with browserify or Webpack

// Require minimongo
var minimongo = require("minimongo");

var LocalDb = minimongo.MemoryDb;

// Create local db (in memory database with no backing)
db = new LocalDb();

// Add a collection to the database
db.addCollection("animals");

doc = { species: "dog", name: "Bingo" };

// Always use upsert for both inserts and modifies
db.animals.upsert(doc, function() {
	// Success:

	// Query dog (with no query options beyond a selector)
	db.animals.findOne({ species:"dog" }, {}, function(res) {
		console.log("Dog's name is: " + res.name);
	});
});

// Access collections via collection for Typescript
await db.collection["animals"].upsert(doc)

Upserting

db.collection["sometable"].upsert(docs, bases, success, error) can take either a single document or multiple documents (array) for the first and second parameter.

docs is the document(s) to upsert. If bases is present, it is the base version on which the update is based. It can be omitted to use the current cached value as the base, or put as null to force an overwrite (a true upsert, not a patch)

Promise interface is also available:

await db.collection["sometable"].upsert(docs, bases) can take either a single document or multiple documents (array) for the first and second parameter.

Removing

db.collection["sometable"].remove(docId, success, error) to remove a document.

Promise interface is also available:

db.collection["sometable"].remove(docId)

Finding

db.collection["sometable"].find(selector, options).fetch(success, error)

selector is a standard MongoDB selector, e.g. { x: 5, y: { $in: ['a', 'b'] } }

options are MongoDB find options: e.g. { limit: 10 }, { sort: ["x"] }

Promise interface is also available:

await db.collection["sometable"].find(selector, options).fetch()

Caching

A set of rows can be cached in a local database. Call

db.collection["sometable"].cache(docs, selector, options, success, error)

selector and options are the selector and options that were used to perform the find that produced docs. The local database will add/remove/update its local copy appropriately.

Seeding

A set of rows can be seeded in a local database. Seeding does not overwrite an existing row; it only makes sure that a row with that _id exists.

db.collection["sometable"].seed(docs, success, error)

Un-caching

Cached rows matching a selector can be removed with:

db.collection["sometable"].uncache(selector, success, error)

It will not affect upserted rows.

Resolving upserts

Upserts are stored in local databases in a special state to record that they are upserts, not cached rows. The base document on which the upsert is based is also stored. For example, if a row starts in cached state with { x:1 } and is upserted to { x: 2 }, both the upserted and the original state are stored. This allows the server to do 3-way merging and apply only the changes.

To resolve the upsert (for example once sent to central db), use resolveUpserts on collection

db.collection["sometable"].resolveUpserts(upserts, success, error) takes the list of upserts to resolve

resolveUpserts does not resolve an upsert if another upsert on the same row has taken place. Instead, the base value is updated (since the change has been accepted by the server) but the new upserted value is left alone.

Resolving removes

Removed rows are still stored locally until they are resolved. This is so they can be later sent to the server.

To resolve all removes, first get a list of all ids to be removed, then resolve them one by one:

const idsToRemove = await new Promise((resolve, reject) => collection.pendingRemoves(resolve, reject))

for (const id of idsToRemove) {
	await new Promise((resolve, reject) => collection.resolveRemove(id, resolve, reject))
}

ReplicatingDb

Keeps two local databases in sync. Finds go only to master.

IndexedDb

To make a database backed by IndexedDb:

// Require minimongo
var minimongo = require("minimongo");

var IndexedDb = minimongo.IndexedDb;

// Create IndexedDb
db = new IndexedDb({namespace: "mydb"}, function() {
	// Add a collection to the database
	db.addCollection("animals", function() {
		doc = { species: "dog", name: "Bingo" };

		// Always use upsert for both inserts and modifies
		db.animals.upsert(doc, function() {
			// Success:

			// Query dog (with no query options beyond a selector)
			db.animals.findOne({ species:"dog" }, {}, function(res) {
				console.log("Dog's name is: " + res.name);
			});
		});
	});
}, function() { alert("some error!"); });

Caching

Rows can be cached without creating a pending upsert. This is done automatically when HybridDb uploads to a remote database with the returned upserted rows. It is also done when a query is performed on HybridDb: the results are cached in the local db and the query is re-performed on the local database.

The field _rev, if present is used to prevent overwriting with older versions. This is the odd scenario where an updated version of a row is present, but an older query to the server is delayed in returning. To prevent this race condition from giving stale data, the _rev field is used.

HybridDb

Combines results from the local database with remote data. Multiple options can be specified at the collection level and then overriden at the find/findOne level:

interim: (default true) true to return interim results from the local database before the (slower) remote database has returned. If the remote database gives different results, the callback will be called a second time. This approach allows fast responses but with subsequent correction if the server has differing information.

cacheFind: (default true) true to cache the find results from the remote database in the local database

cacheFindOne: (default true) true to cache the findOne results from the remote database in the local database

shortcut: (default false) true to return findOne results if any matching result is found in the local database. Useful for documents that change rarely.

useLocalOnRemoteError: (default true) true to use local results if the remote find fails. Only applies if interim is false.

To keep a local database and a remote database in sync, create a HybridDb:

hybridDb = new HybridDb(localDb, remoteDb)

Be sure to add the same collections to all three databases (local, hybrid and remote).

Then query the hybridDb (find and findOne) to have it get results and correctly combine them with any pending local results. If you are not interested in caching results, add { cacheFind: false, cacheFindOne: false } to the options of find or findOne or to the addCollection options.

When upserts and removes are done on the HybridDb, they are queued up in the LocalDb until hybridDb.upload(success, error) is called.

upload will go through each collection and send any upserts or removes to the remoteDb. You must call this to have the results go to the server! Calling periodically (e.g every 5 seconds) is safe as long as you wait for one upload call to complete before calling again.

findOne will not return an interim null result, but will only return interim results when one is present.

RemoteDb

Uses AJAX-JSON calls to an API to query a real Mongo database. API is simple and contains only query, upsert, patch and remove commands.

If the client field is passed to the constructor, it is appended as a query parameters (e.g. ?client=1234) to each request made.

Example code:

remoteDb = new minimongo.RemoteDb("http://someserver.com/api/", "myclientid123")

This would create a remote db that would make the following call to the api for a find to collection abc:

GET http://someserver.com/api/abc?client=myclientid123

The client is optional and is a string that is passed in each call only to make authentication easier.

The API that RemoteDb should support four HTTP methods for each collection:

GET /<collection>

Performs a query, returning an array of results. GET query parameters are:

selector (optional) : JSON of query, in MongoDB format. e.g. {"a": 1} to find records with field a having value 1 fields (optional) : JSON object indicating which fields to return in MongoDB format. e.g. {"a": 1} to return only field a and _id sort (optional) : JSON of MongoDB sort field. e.g. ["a"] to sort ascending by a, or [["a","desc"]] to sort descending by a limit (optional) : Maximum records to return e.g. 100

Possible HTTP response codes:

200 : normal response 401 : client was invalid

POST /<collection>/find (optionally implemented)

Performs a query, returning an array of results. POST body parameters are:

selector (optional) : JSON of query, in MongoDB format. e.g. {"a": 1} to find records with field a having value 1 fields (optional) : JSON object indicating which fields to return in MongoDB format. e.g. {"a": 1} to return only field a and _id sort (optional) : JSON of MongoDB sort field. e.g. ["a"] to sort ascending by a, or [["a","desc"]] to sort descending by a limit (optional) : Maximum records to return e.g. 100

Possible HTTP response codes:

200 : normal response 401 : client was invalid

POST /<collection>

Performs a single upsert, returning the upserted row. POST value is the document to upsert. Possible HTTP response codes:

200 : document was upserted. Returns the upserted object (see notes below on merging) 400 : document did not pass validation 401 : client was invalid or not present 403 : permission denied to upsert 409 : another client was upserting same document. Try again. 410 : document was already removed and cannot be upserted

On 403 or 410, the change is automatically discarded in the HybridDb.

If array is POSTed, upsert each one and return array of upserted documents

PATCH /<collection>

Performs a patch, returning the upserted row. PATCH value is the following structure:

{
	doc: <the document in its new form. Can also be array of documents>
	base: <base document on which the changes were made. Can also be array of base documents, which match length of doc array>
}

For example, to change { x:1, y:1 } to set x to be 2, PATCH would send

{
	doc: { x:2, y: 1 }
	base: { x:1, y: 1 }
}

Possible HTTP response codes:

200 : document was upserted. Returns the upserted object 400 : document did not pass validation 401 : client was invalid or not present 403 : permission denied to upsert 409 : another client was upserting same document. Try again. 410 : document was already removed and cannot be upserted

On 403 or 410, the change is automatically discarded in the HybridDb.

DELETE /<collection>/<_id>

Note: the RemoteDb does not support remove({ filter }), but only removing a single document by _id!!

Removes to the local collection are converted into a series of _ids to be removed when sent to the server.

Removes a document. _id of the document to remove

200 : document was removed 401 : client was invalid or not present 403 : permission denied to remove 410 : document was already removed and cannot be removed again

On 403 or 410, the change is automatically discarded in the HybridDb.

Merging

Minimongo is designed to work with a server that performs three-way merging of documents that are being upserted by multiple users.

It can also be used with a simple server that just overwrites documents completely on upsert, just taking the doc value of PATCH, though this is not recommended.

Testing

To test, run testem in the main directory.

To test a RemoteDb implementation, use test/LiveRemoteDbTests.ts. Server must have a collection called scratch with fields as specified at top of tests file.

Quickfind

Finds can be very wasteful when the client has large collections already cached. The quickfind protocol shards the existing docs on the client by id and then sends a hash of them to the server, which just responds with the changed ones. See src/quickfind.ts for more details. It needs to be enabled and is off by default.

minimongo's People

Contributors

broncha avatar cncolder avatar dallasvogels avatar grassick avatar mbriau avatar notslang avatar perlun avatar pubkey avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

minimongo's Issues

[Request] Make minimongo AMD Compatible

Hello,

I would like to request to make minimongo compatible with AMD since this is a separate package from meteor so it is better to have it as AMD module.

Also it let me handle dependencies much more elegent. For example, currently my company going to implement minimongo, but just the LocalStorage DB so it is better to have each module an AMD and we will manually include each module in our gulp build.

Some of the dependencies for minimongo are already available in our source code, and some other are used only by minimongo module like IndexDB, so it could be better to separate them to minimize unused javascript.

Much better and much more elegant.

Thank you!

How update data locally??

I need update data locally, im using upsert function but the new data is saved in base and the old in doc...

        $rootScope.IndexedDb[database].findOne({_id: data['_id']}, {}, function (res) {
          if (res === null) {
            $rootScope.IndexedDb[database].upsert(data, function () { });
          }
          else {
            if (angular.equals(res, data) == false){
              $rootScope.IndexedDb[database].upsert(res, data, function (data_upserted) {
                console.log(data_upserted);
              });
            }
          }

});

Sorting the collection locally

My business requirements specifies that my mongo docs should be displayed sorted and paged in a table, where their sort order ignores their case. Unfortunately, sorting while ignoring case, is not supported using the current Mongo publication interface.

So currently, my publication must request the complete collection, unsorted, and I must sort the docs locally, then determine which docs I can display for the page.

I am doing that. I copy the whole unsorted minimongo collection into a reactive array (using observe), then sort, then display the correct portion of that instead, and it works, but:

  • its unfortunately that I am required to publish the complete collection in order to determine which subset of docs to display. I only display 20 docs per page.
  • its also unfortunate that I lose the nice blaze feature that a single doc update only causes the matching displayed doc to be refreshed. Currently, because I am sorting using a reactive array, any displayed doc change causes all my displayed docs to be redisplayed.

Am I missing something? Is there any way I can hook into mongo/minimongo to specify my more specific publication request, and is there any way I can write something that supports blaze's singledoc display optimization, but when using arrays of docs instead of a collection?

As my number of docs in my collection increases, this is becoming more and more critical.

Any suggestions would be handy. THX

IndexedDb can't see data written from previous session

This is in Chrome.

I have minimongo working great for reading/writing to a MemoryDb and writing to an IndexedDb. It can read from the IndexedDb while the current session is active. However, if I refresh the page, in the resources tab of the Chrome debugger I can see all of my data, but after the IndexedDb returns the handle to the database it has no collections. If I create a collection it still can't see the data that was previously written to the collection.

$(document).ready(function() {
new minimongo.IndexedDb({namespace : 'xxxxxx_data'}, function(success) {
indexedDb = success;
call_my_initialize_function_here
}, function(failure) {
});
});

Any thoughts about what I'm doing wrong? Thanks for a great library!

collection.remove() not removing from IndexedDB

Hi,

Sorry if this isn't a code issue but I'm a bit stuck.

I'm having an issue with removing doc(s) from a database backed by IndexedDB. I've noticed that minimongo is setting the 'state' property to 'removed' in the database, it's not actually being removed.

Thanks in advance!

Uganda debugging

Introduced in d3ddea6... not sure what it's for but it seems like it should have an issue associated with it.

remove jquery dependency

since remoteDB support customized httpclient, is it possible to remove the must dependency on jquery, and require jquery only when there's no customized httpclient?

hybriddb usage?

I need a way to keep in sync the local indexeddb and the remote mongodb. I think I could use the HybridDB to get this working but I can't understand how does it work...

Could be possible have an usage example of this function?

remove() does not support a query

Remove is defined as remove(id, success, error), where as mongo defines remove(<query>, {opts}) ... so a query would be nice.

Hint: for anyone trying remove({}), do a .removeCollection() then recreate it. or with a filter (remove with query): thecollection.find({filter as usual}).fetch((rows)=>rows.map((row)=>thecollection.remove(row._id)))

Find out why local dbs don't resolve differing upserts

Test is this:

    it "retains changed pending upserts", (done) ->
      @db.scratch.upsert { _id: 2, a: 'banana' }, =>
        @db.scratch.upsert { _id: 2, a: 'banana2' }, =>
          @db.scratch.resolveUpsert { _id: 2, a: 'banana' }, =>
            @db.scratch.pendingUpserts (results) =>
              assert.equal results.length, 1
              assert.equal results[0].a, 'banana2'
              done()

But HybridDb uploads as:

        @remoteCol.upsert(upsert, () =>
          @localCol.resolveUpsert upsert, =>
            uploadUpserts(_.rest(upserts), success, error)

which never exercises the code. Even if it did, why would a server change (probably from a merge) require retaining the upsert?

Promise version?

I just noticed when promisifying collection methods that the callback from functions such as "upsert" won't use the node.js callback standard of ( error, result ).

Is there a reason for this? How am supposed to handle errors?

IOS WKWebView Spec Change

It looks like they have moved webkitIndexedDB to webkitIDBDatabase. Assuming I am reading your code right. There seems to be a factory pattern too. Not sure the proper way to use the object set yet.

This is reproducible when using the latest Cordova with this plugin.

High Vulnerability - Prototype Polution update to Lodash 4.17.11

                 === npm audit security report ===                        
                                                                            
                                                                            
                             Manual Review                                  
         Some vulnerabilities require your attention to resolve             
                                                                            
      Visit https://go.npm.me/audit-guide for additional guidance           

High Prototype Pollution

Package lodash

Patched in >=4.17.11

Dependency of minimongo

Path minimongo > lodash

More info https://nodesecurity.io/advisories/782

Low Prototype Pollution

Package lodash

Patched in >=4.17.5

Dependency of minimongo

Path minimongo > lodash

More info https://nodesecurity.io/advisories/577

found 2 vulnerabilities (1 low, 1 high) in 72 scanned packages
2 vulnerabilities require manual review. See the full report for details.

Has anyone solved RemoteDB security problems?

I want to use Minimongo.

Exposing remoteURL and connectionID on the client seems to be a security problem.

So it's hard to use it as a security issue.

Has anyone solved RemoteDB security problems?

I want you to let me know if you have a good idea ...

Support for update operators

Do you currently support any update operators, such as $inc, $push, $mul, etc. and is there a plan to support them?

Windows Phone chooses WebSQL which is not supported

 "user_agent": "Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; 909) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537"

bowser: {\"name\":\"Windows Phone\",\"windowsphone\":true,\"msie\":true,\"version\":\"11.0\",\"webkit\":true,\"android\":true,\"mobile\":true,\"a\":true}

utils.autoselectLocalDb() requires an example

trying out how it works, I ended up with this:
var localDb; var useless_something = minimongo.utils.autoselectLocalDb({namespace: "myDb"}, function(localDb_init) { localDb = localDb_init; }, function() { alert("some error!"); });

in hybridDb, by default it fetch all fields from remoteDb and cache them. wish to change this behavior

in hybridDb code we see:

      # If caching, get all fields
      if options.cacheFind
        delete remoteOptions.fields

but sometimes we need to fetch a list from remoteDb, which has loooooooots of items but just includes _id and name fields. we need to cache it, so cacheFind option has to be true. But we don't need the other fields. actually the other fields might be very huge and it's impossible to fetch logs of items all at once.

@grassick would you please consider change this behavior, or at least give a new option to turn this off?

[Bug] Occur 'transaction' of null at addCollection or removeCollection on IndexedDb backend

Possibly unhandled TypeError: Cannot read property 'transaction' of null
  at [object Object].IDBStore.iterate (/Users/mizchi/proj/kobito-shell/shell/node_modules/minimongo/node_modules/idb-wrapper/idbstore.js:1033:38)
  at [object Object].IDBStore.query (/Users/mizchi/proj/kobito-shell/shell/node_modules/minimongo/node_modules/idb-wrapper/idbstore.js:1093:19)
  at IndexedDb.module.exports.IndexedDb.removeCollection (/Users/mizchi/proj/kobito-shell/shell/node_modules/minimongo/lib/IndexedDb.js:60:23)

This log occurs at atom-shell envrironment but also occur at Chrome.

I tried 2 patterns but both failed. (sorry for coffeescript)

case 1

db = new minimongo.IndexedDb('testdb')
db.addCollection('a')
db.removeCollection('a')
db.addCollection('a')
db.a.find({})

case 2

db = new minimongo.IndexedDb('testdb')
db.addCollection 'a', ->
  db.removeCollection 'a', ->
    db.addCollection 'a', ->
      db.a.find {}

I added setTimeout before find so it works

db = new minimongo.IndexedDb('testdb')
db.addCollection('a')
db.removeCollection('a')
db.addCollection('a')
setTimeout ->
  db.a.find({})
, 100

I doubt minimongo lost correct callback of IndexedDb ready.

replication MONGODB

Hello,
Today I have in the backend an application with mongoDB and I'm building my frontend in PWA, how can I use in frontend the minimongo with synchronization, for my MongoDb backend database?

Typings

Are there typings for minimongo ?

Build tasks are racing

I am getting module not found errors from Browserify after cloning a fresh repo for development.

The (temporary) fix is:
Tasks need to be run individually (coffee > copy > dist), the 'default' task would allow the dist task to begin running before coffee & copy have completed.

Does it support react native?

So the main problem in react native is to find the compatiable library to support offline+sync. Does it support react native?

Can this be MIT license?

I like the work on this, but the GPL license makes it harder to use with commercial projects. Meteor has an MIT license, which works much better for that.

Is there a reason this needs to be GPL or can it switch to Meteor's MIT with the new developers added on?

setting up hybrid db

hi,

I have a database on a server and I'm trying to recreate what meteor does; a local db on the browser that syncs bi-directionally with the one on the server.
how do I set this up? I looked at the source code yet I'm unsure what to pass to the url, client and httpClient parameters for the remoteDb constructor. could you provide example code for this scenario? I'm unable to infer this just from reading the docs.

thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.