├── .editorconfig ├── .gitattributes ├── .github └── stale.yml ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── OVERVIEW.md ├── README.md ├── benchmark.html ├── benchmark ├── benchmark.js ├── benchmark_binary.js ├── benchmark_remove.js ├── benchmark_sorting.js ├── benchmark_web.html ├── benchmark_web.js ├── bindex-stress.js ├── destress.js ├── stress.js └── throttled-stress.js ├── bower.json ├── build ├── loki-indexed-adapter.min.js └── lokijs.min.js ├── demos └── desktop_app │ ├── demo.json │ ├── index.html │ ├── main.css │ └── package.json ├── docs-conf.json ├── docs ├── Collection.html ├── Collection.module_Collection_Transforms.html ├── DynamicView.html ├── Loki.html ├── LokiEventEmitter.html ├── LokiFsAdapter.html ├── LokiFsStructuredAdapter.html ├── LokiIndexedAdapter.html ├── LokiLocalStorageAdapter.html ├── LokiMemoryAdapter.html ├── LokiPartitioningAdapter.html ├── Resultset.html ├── fonts │ ├── OpenSans-Bold-webfont.eot │ ├── OpenSans-Bold-webfont.svg │ ├── OpenSans-Bold-webfont.woff │ ├── OpenSans-BoldItalic-webfont.eot │ ├── OpenSans-BoldItalic-webfont.svg │ ├── OpenSans-BoldItalic-webfont.woff │ ├── OpenSans-Italic-webfont.eot │ ├── OpenSans-Italic-webfont.svg │ ├── OpenSans-Italic-webfont.woff │ ├── OpenSans-Light-webfont.eot │ ├── OpenSans-Light-webfont.svg │ ├── OpenSans-Light-webfont.woff │ ├── OpenSans-LightItalic-webfont.eot │ ├── OpenSans-LightItalic-webfont.svg │ ├── OpenSans-LightItalic-webfont.woff │ ├── OpenSans-Regular-webfont.eot │ ├── OpenSans-Regular-webfont.svg │ └── OpenSans-Regular-webfont.woff ├── index.html ├── loki-fs-structured-adapter.js.html ├── loki-indexed-adapter.js.html ├── lokijs.js.html ├── scripts │ ├── linenumber.js │ └── prettify │ │ ├── Apache-License-2.0.txt │ │ ├── lang-css.js │ │ └── prettify.js ├── styles │ ├── jsdoc-default.css │ ├── prettify-jsdoc.css │ └── prettify-tomorrow.css ├── tutorial-Autoupdating Collections.html ├── tutorial-Changes API.html ├── tutorial-Collection Transforms.html ├── tutorial-Indexing and Query performance.html ├── tutorial-Loki Angular.html ├── tutorial-Persistence Adapters.html ├── tutorial-Query Examples.html └── tutorial-TEST.html ├── examples ├── example.js ├── examples.html ├── loki-continuum.js ├── quickstart-chaining.js ├── quickstart-core.js ├── quickstart-dynview.js ├── quickstart-transforms.js ├── quickstart1.js ├── quickstart2.js ├── quickstart3.js ├── quickstart4.js └── sandbox │ ├── LokiSandbox.htm │ ├── jquery-2.1.0.min.js │ ├── sandbox.min.css │ └── sandbox.min.js ├── karma.build.conf.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── presentations ├── lokijs.odp └── lokijs.pptx ├── spec ├── browser │ ├── README.md │ └── incrementalIdb.spec.js ├── generic │ ├── README.md │ ├── autoUpdate.spec.js │ ├── binaryidx.spec.js │ ├── changesApi.spec.js │ ├── cloning.spec.js │ ├── collection.spec.js │ ├── dirtyIds.spec.js │ ├── dynamicview.spec.js │ ├── eventEmitter.spec.js │ ├── helpers.spec.js │ ├── immutable.spec.js │ ├── joins.spec.js │ ├── kv.spec.js │ ├── ops.spec.js │ ├── persistence.spec.js │ ├── remove.spec.js │ ├── sortingIndexing.spec.js │ ├── stage.spec.js │ ├── stats.spec.js │ ├── test.spec.js │ ├── transforms.spec.js │ ├── typed.spec.js │ └── unique.spec.js ├── helpers │ └── assert-helpers.js ├── incrementalidb.html ├── node │ ├── README.md │ ├── cryptedFileAdapter.spec.js │ └── nodePersistence.spec.js └── support │ └── jasmine.json ├── src ├── aws-s3-sync-adapter.js ├── incremental-indexeddb-adapter.js ├── jquery-sync-adapter.js ├── loki-angular.js ├── loki-crypted-file-adapter.js ├── loki-fs-structured-adapter.js ├── loki-fs-sync-adapter.js ├── loki-incremental-adapter.js ├── loki-indexed-adapter.js ├── loki-nativescript-adapter.js └── lokijs.js ├── test.db └── tutorials ├── Autoupdating Collections.md ├── Changes API.md ├── Collection Transforms.md ├── Indexing and Query performance.md ├── Loki Angular.md ├── Persistence Adapters.md └── Query Examples.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | docs/* linguist-documentation 2 | jsdoc/* linguist-documentation 3 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | demos/desktop_app/bower_copmonents/ 3 | demos/desktop_app/log 4 | demos/desktop_app/demo.json 5 | examples/quickstart2.db 6 | examples/quickstart3.db 7 | examples/quickstart3.db.0 8 | examples/quickstart3.db.1 9 | examples/quickstart4.db 10 | examples/quickstart-dynview.db 11 | examples/quickstart-transforms.db 12 | coverage/ 13 | testCollections 14 | testCollections~ 15 | LokiContinuum.db 16 | sandbox.db 17 | sandbox.db.0 18 | sandbox.db.1 19 | sandbox.db.2 20 | .[iI]dea -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | benchmark/ 3 | demos/ 4 | examples/ 5 | jsdoc/ 6 | tutorials/ 7 | spec/ 8 | coverage/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.0" 4 | - "6" 5 | - "8" 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | * add IncrementalIndexedDBAdapter 4 | * make ensureIndex faster 5 | * LokiEventEmitter.emit faster 6 | * make batch inserts faster (add option to overrideAdaptiveIndices) 7 | 8 | ## 1.5.6 9 | * added support for indexes on nested properties (#718) 10 | * more robust environment detection (#717) 11 | * precompile $regex filters recursively (#710) 12 | * Corrected $aeq example in Query Examples (#706) 13 | * change 'shallow-recurse-objects' clone to support deep array clone (#692) 14 | * jsdoc fixes for compoundsort example (#691) 15 | 16 | ## 1.5.5 17 | * fixed issue where batch removes did not update unique index (#677) 18 | 19 | ## 1.5.4 20 | * fixes to checkIndex maintenance fn (#654, #661) 21 | * bulk remove optimizations (#663) 22 | * fix to meta when cloning (#666) 23 | * AWS S3 sync adapter added (#670) 24 | * fix for browser project: fs not found (#674) 25 | * added comparators to default export (#672) 26 | 27 | ## 1.5.3 28 | * simplesort can leverage binary index (via index intersect) when weakly filtered (#645) 29 | * the simplesort 'descending' param is now multipurposed (with bw-compat) see jsdocs 30 | * added new simplified js comparison ops for unindexed optimizations (commit bb011d) 31 | * added collection.checkIndex() and collection.checkIndexes() for diagnostics (#654,#47) 32 | 33 | ## 1.5.2 34 | * fixed error when transform steps have non-serializable parameters (#624) 35 | * addCollection will return existing collection when collection named already exists (#635) 36 | * batch updates when not cloning no longer risk invalidating adaptive binary indices (#639) 37 | * fixed autosave race condition when using async adapter (#643) 38 | * collection option added to disableMeta if not using changes api or ttl (#644) 39 | 40 | ## 1.5.1 41 | * added disableDeltaChangesApi (default:true) collection option to store only differences. (#582) 42 | * loki indexed adapter deleteDatabase method will now wait until complete before callback (#584) 43 | * fixed clone methods correctly assigning object prototypes (#586) 44 | * partitioning adapter and memory adapter no longer throw error when database doesn't exist (yet) 45 | * simplesort and compoundsort now support sorting on nested properties via dot-notation (#574) 46 | * added support for binary indices on nested properties (#574) 47 | * fixed jsdoc syntax error causing vscode to crash (#614) 48 | * fixed error when using partitioning adapter with nativescript adapter (#615) 49 | * added optional 'dataOptions' to eqJoin and map (chain/transform) (see #618) 50 | 51 | ## 1.5.0 52 | * refactored binary indices sorting order 53 | * date values in properties with binary index will be converted to epoch time 54 | * fix to LokiFsStructuredAdapter first time autoload error where file doesn't exist yet 55 | * fixed simplesort descending with binary indices and no filters 56 | * with 'clone' option collection 'insert' events no longer emit internal obj reference 57 | * fix to clone method 'shallow' 58 | * added 'removeMeta' option to chained data calls to shallow clone and remove $loki and meta 59 | * added quickstart examples for node and updated loki sandbox with online web quickstarts 60 | * fixes to async unit tests 61 | * npm release trimmed from 5 megs to 500k (no examples, jsdocs, tests) 62 | 63 | ## 1.4.3 64 | * added throttled saves and loads (enabled by default) to ensure no overlapping calls 65 | * unfiltered simplesorts can leverage binary index 66 | * collection.clear now clears indices correctly 67 | * fix to LokiPartitioning adapter resetting maxId across saves 68 | * meta set correctly on batch inserts 69 | * LokiMemoryAdapter now configurable to simulate async 70 | * fix changesApi not enabled after loading database 71 | 72 | ## 1.4.2 73 | * added 'adaptiveBinaryIndices' option to collections (default true) to avoid rebuilds on inserts/updates/removes 74 | * added higher performance LokiFsStructuredAdapter for node 75 | * added destructured serialization methods for partitioning and exporting 76 | * added 'addListener' method alias of 'on' method 77 | * LokiFsAdapter doesn't throw exception when file doesn't exist (yet) 78 | * fix for circular structure error when setting ttl 79 | * fix unit testing on windows 80 | * experimental implementation of an incremental adapter 81 | 82 | ## 1.4.1 83 | * minor fixes 84 | * updated jsdoc coverage 85 | 86 | ## 1.4.0 87 | * fixes to loki indexed adapter 88 | * added nativescript adapter 89 | * added $aeq, $contains, $containsAny, $ne 90 | * fix unique index update with new object 91 | * expose persistence adapters to module export 92 | * fix to loki-crypted-file-adapter 93 | 94 | ## 1.3.0 95 | 96 | * UniqueIndex and `by()` method 97 | * staging API 98 | * count() utility method 99 | * RethinkDB-style joins (eqJoin) 100 | * moved testing to Karma 101 | * moved builds and run to npm instead of gulp 102 | * added $containsAny operator 103 | * statistical functions: average, max, maxRecord, min, minRecord, median, mode 104 | * extract() to extract a flat array of values for one field in each record 105 | * extractNumerical() - same as extract() for numerical values 106 | * pre-insert and pre-update events 107 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | First of all thank you for contributing. 2 | 3 | ## Overview 4 | We try our best to adhere to the [airbnb](https://github.com/airbnb/javascript/blob/master/README.md) javascript style guidelines. 5 | 6 | If you spot non compliant code in our codebase do not hesitate to flag it or even submit a pull request. 7 | Running `npm run lint` can help. 8 | 9 | We do not ask that you agree with our guidelines but if you want to contribute you will have to respect them. 10 | 11 | ## Pull-requests 12 | 13 | LokiJS puts a strong emphasis on performance. Make sure to benchmark performance on your machine before and after you apply a change and ensure performance is unchanged (or unnoticeable), even better if it is improved. 14 | Verify your changes are non-breaking by running `npm test`, and if you're adding a feature make sure to add a test somewhere in the relevant file in `spec/` (or a brand new file if the test cannot be included in existing files). 15 | 16 | ## A few things we recommend 17 | 18 | Most of these are included in the airbnb style guide but we feel like highlighting them: 19 | 20 | * use shortcuts in if conditions, and always follow with a statements in curly brackets. Do not do: 21 | `if (something) return;` instead do `if (something) { return; }` 22 | * Name callbacks, when possible try to declare them as functions that can be cached to save memory as well making your code more readable. i.e. instead of 23 | ```javascript 24 | result.filter(function () { /* ... */ }); 25 | ``` 26 | try to do: 27 | ```javascript 28 | function doFilter(obj) { 29 | // ... 30 | } 31 | result.filter(doFilter); 32 | ``` 33 | 34 | ## A few things we will not accept 35 | 36 | * comma first 37 | * avoid-semicolon-at-all-cost stupidity 38 | * general hipster code 39 | * coffeescript/TypeScript 40 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 TechFort 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /OVERVIEW.md: -------------------------------------------------------------------------------- 1 | # LokiJS 2 | 3 | [LokiJS.org web site](http://lokijs.org) | 4 | [LokiJS GitHub page](https://github.com/techfort/LokiJS) | 5 | [Sandbox / Playground](https://rawgit.com/techfort/LokiJS/master/examples/sandbox/LokiSandbox.htm) 6 | 7 | ## Documentation Overview 8 | 9 | This is an early effort to provide a more accurate and up-to-date version of LokiJS documentation by using jsdoc. Since modifications arise from various contributors, this should allow distributed effort toward 10 | maintaining this documentation. 11 | 12 | ## Getting Started 13 | 14 | Creating a database : 15 | 16 | ```javascript 17 | var db = new loki('example.db'); 18 | ``` 19 | 20 | Add a collection : 21 | 22 | ```javascript 23 | var users = db.addCollection('users'); 24 | ``` 25 | 26 | Insert documents : 27 | 28 | ```javascript 29 | users.insert({ 30 | name: 'Odin', 31 | age: 50, 32 | address: 'Asgard' 33 | }); 34 | 35 | // alternatively, insert array of documents 36 | users.insert([{ name: 'Thor', age: 35}, { name: 'Loki', age: 30}]); 37 | ``` 38 | 39 | Simple find query : 40 | 41 | ```javascript 42 | var results = users.find({ age: {'$gte': 35} }); 43 | 44 | var odin = users.findOne({ name:'Odin' }); 45 | ``` 46 | 47 | Simple where query : 48 | 49 | ```javascript 50 | var results = users.where(function(obj) { 51 | return (obj.age >= 35); 52 | }); 53 | ``` 54 | 55 | Simple Chaining : 56 | 57 | ```javascript 58 | var results = users.chain().find({ age: {'$gte': 35} }).simplesort('name').data(); 59 | ``` 60 | 61 | Simple named transform : 62 | 63 | ```javascript 64 | users.addTransform('progeny', [ 65 | { 66 | type: 'find', 67 | value: { 68 | 'age': {'$lte': 40} 69 | } 70 | } 71 | ]); 72 | 73 | var results = users.chain('progeny').data(); 74 | ``` 75 | 76 | Simple Dynamic View : 77 | 78 | ```javascript 79 | var pview = users.addDynamicView('progeny'); 80 | 81 | pview.applyFind({ 82 | 'age': {'$lte': 40} 83 | }); 84 | 85 | pview.applySimpleSort('name'); 86 | 87 | var results = pview.data(); 88 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LokiJS 2 | 3 | The super fast in-memory javascript document oriented database. 4 | 5 | Enable offline-syncing to your SQL/NoSQL database servers with [SyncProxy](https://www.syncproxy.com) !! Code-free real time syncing, ideal for mobile, electron and web apps. 6 | 7 | [![Join the chat at https://gitter.im/techfort/LokiJS](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/techfort/LokiJS?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | ![alt CI-badge](https://travis-ci.org/techfort/LokiJS.svg?branch=master) 9 | [![npm version](https://badge.fury.io/js/lokijs.svg)](http://badge.fury.io/js/lokijs) 10 | [![alt packagequality](http://npm.packagequality.com/shield/lokijs.svg)](http://packagequality.com/#?package=lokijs) 11 | 12 | ## Overview 13 | 14 | LokiJS is a document oriented database written in javascript, published under MIT License. 15 | Its purpose is to store javascript objects as documents in a nosql fashion and retrieve them with a similar mechanism. 16 | Runs in node (including cordova/phonegap and node-webkit), [nativescript](http://www.nativescript.org) and the browser. 17 | LokiJS is ideal for the following scenarios: 18 | 19 | 1. client-side in-memory db is ideal (e.g., a session store) 20 | 2. performance critical applications 21 | 3. cordova/phonegap mobile apps where you can leverage the power of javascript and avoid interacting with native databases 22 | 4. data sets loaded into a browser page and synchronised at the end of the work session 23 | 5. node-webkit desktop apps 24 | 6. nativescript mobile apps that mix the power and ubiquity of javascript with native performance and ui 25 | 26 | LokiJS supports indexing and views and achieves high-performance through maintaining unique and binary indexes (indices) for data. 27 | 28 | ## Demo 29 | 30 | The following demos are available: 31 | - [Sandbox / Playground](https://rawgit.com/techfort/LokiJS/master/examples/sandbox/LokiSandbox.htm) 32 | - a node-webkit small demo in the folder demos/desktop_app. You can launch it by running `/path/to/nw demos/desktop_app/` 33 | 34 | ## Wiki 35 | 36 | Example usage can be found on the [wiki](https://github.com/techfort/LokiJS/wiki) 37 | 38 | ## Main Features 39 | 40 | 1. Fast performance NoSQL in-memory database, collections with unique index (1.1M ops/s) and binary-index (500k ops/s) 41 | 2. Runs in multiple environments (browser, node, nativescript) 42 | 3. Dynamic Views for fast access of data subsets 43 | 4. Built-in persistence adapters, and the ability to support user-defined ones 44 | 5. Changes API 45 | 6. Joins 46 | 47 | ## Current state 48 | 49 | LokiJS is at version 1.3 [Eostre]. 50 | 51 | As LokiJS is written in JavaScript it can be run on any environment supporting JavaScript such as browsers, node.js/node-webkit, nativescript mobile framework and hybrid mobile apps (such as phonegap/cordova). 52 | 53 | Made by [@techfort](http://twitter.com/tech_fort), with the precious help of Dave Easterday. 54 | 55 | _[Leave a tip](https://gratipay.com/techfort/) or give us a star if you find LokiJS useful!_ 56 | 57 | ## Installation 58 | 59 | For browser environments you simply need the lokijs.js file contained in src/ 60 | 61 | You can use bower to install lokijs with `bower install lokijs` 62 | 63 | For node and nativescript environments you can install through `npm install lokijs`. 64 | 65 | ## Roadmap 66 | 67 | * exactIndex 68 | * key-value datastore 69 | * MRU cache 70 | * MongoDB API compatibility 71 | * server standalone (tcp and http servers and clients) 72 | * replication and horizontal scaling 73 | 74 | ## Contact 75 | 76 | For help / enquiries contact joe.minichino@gmail.com 77 | 78 | ## Commercial Support 79 | 80 | For commercial support contact info.techfort@gmail.com 81 | 82 | ## License 83 | 84 | Copyright (c) 2015 TechFort 85 | 86 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 89 | 90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 91 | 92 | -------------------------------------------------------------------------------- /benchmark.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /benchmark/benchmark_remove.js: -------------------------------------------------------------------------------- 1 | var loki = require('../src/lokijs.js'); 2 | 3 | var DOCUMENT_COUNT = 70000; 4 | var QUERY_INDEX_RANGE = 5; 5 | 6 | var db; 7 | var coll; 8 | 9 | var start, end; 10 | var totalMS = 0; 11 | 12 | function createDatabase(indexed) { 13 | db = new loki('remove-bench.db'); 14 | if (indexed) { 15 | coll = db.addCollection('profile', { indices: ['a']}); 16 | } 17 | else { 18 | coll = db.addCollection('profile'); 19 | } 20 | 21 | for(idx=0;idx 2 | 3 | 4 | 5 | 6 | 7 | 8 |

Loki browser benchmark

9 | 10 |

Practical browser benchmark to guage performance across 11 | various browser environments. Chrome is by far the fastest browser 12 | environment for loki.

13 | 14 |

Close console (if visible) and press start. Results will display in page when done.

15 |
16 | 17 | 18 |
19 | 20 |
21 | 22 | 31 | 32 | -------------------------------------------------------------------------------- /benchmark/bindex-stress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility script for stress-testing binary indices. 3 | * 4 | * We will randomly insert/update/remove objects with binary indices 5 | * and verify integrity after predefined number of permutations. 6 | */ 7 | 8 | var crypto = require("crypto"); // for random string generation 9 | var loki = require('../src/lokijs.js'); 10 | 11 | var INITIAL_DOCUMENT_COUNT = 10000; 12 | var ITERATION_COUNT = 40000; // number of permutations to perform 13 | var INTEGER_RANGE = 1000; 14 | 15 | var db, coll; 16 | 17 | // less memory 'leaky' way to generate random strings (node specific) 18 | function genRandomString() { 19 | return crypto.randomBytes(50).toString('hex'); 20 | } 21 | 22 | function genRandomObject() { 23 | a = Math.floor(Math.random() * INTEGER_RANGE); 24 | 25 | return { "a": a, "b": genRandomString(), "c": 0 }; 26 | } 27 | 28 | // create database, collection, and seed initial documents 29 | function createDatabase() { 30 | var idx, a, b; 31 | 32 | db = new loki('sorting-bench.db'); 33 | 34 | coll = db.addCollection('profile', { indices: ['a', 'b', 'c'] }); 35 | 36 | for(idx=0;idx rss : " + formatBytes(pmu.rss) + " heapTotal : " + formatBytes(pmu.heapTotal) + " heapUsed : " + formatBytes(pmu.heapUsed)); 68 | } 69 | 70 | function dbLoaded() { 71 | end = process.hrtime(start); 72 | console.info("database loaded... time : %ds %dms", end[0], end[1]/1000000); 73 | var doccount =0, cidx; 74 | db.collections.forEach(function(coll) { 75 | doccount += coll.data.length; 76 | }) 77 | 78 | logMemoryUsage("After loading database : "); 79 | console.log('number of docs in items collection(s) : ' + doccount); 80 | 81 | // if you want to verify that only dirty collections are saved (and thus faster), uncomment line below 82 | //dirtyCollAndSaveDatabase(); 83 | } 84 | 85 | function dirtyCollAndSaveDatabase() { 86 | var start, end; 87 | 88 | start = process.hrtime(); 89 | 90 | // dirty up a collection and save to see if just that collection (along with db) gets written 91 | db.collections[0].insert({ a: 1, b : 2}); 92 | db.saveDatabase(function(err) { 93 | if (err === null) { 94 | console.log('finished saving database'); 95 | logMemoryUsage("after database save : "); 96 | end = process.hrtime(start); 97 | console.info("database save time : %ds %dms", end[0], end[1]/1000000); 98 | } 99 | else { 100 | console.log('error encountered saving database : ' + err); 101 | } 102 | }); 103 | } 104 | 105 | logMemoryUsage("Before loading database : "); 106 | reloadDatabase(); 107 | -------------------------------------------------------------------------------- /benchmark/stress.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // This script can be used to stress the ability of loki to save large databases. 3 | // I have found that within most javascript engines there seems to be memory 4 | // contraints and inefficiencies involved with using JSON.stringify. 5 | // 6 | // One way to limit memory overhead is to serialize smaller objects rather than 7 | // one large (single) JSON.stringify of the whole database. 8 | // 9 | // The LokiFsStructuredAdapter streams the database in and out as a series 10 | // of smaller, individual serializations. It also partitions the database, 11 | // storing each collection separately within its own file. This adapter is 12 | // the most memory efficient and fastest adapter if you are using node.js. 13 | // It accomplishes this by using io streams to save and load the database 14 | // to disk rather than saving the whole database as a single string. 15 | // 16 | // This stress can be used to analyse memory overhead for saving a loki database. 17 | // Both stress.js and destress.js need to be configured to use the same serialization 18 | // method and adapter. By default, this is configured to use the 19 | // loki-fs-structured-adapter which will stream output and input. 20 | 21 | // On my first review i saw no significant benefit to "fs-structured" format if you 22 | // are going to save in single file, but subsequent benchmarks show that saves 23 | // are actually faster. I wasn't expecting this and if i had to guess at why I would 24 | // guess that by not doing one huge JSON.stringify, but instead doing many smaller 25 | // ones, that this is faster than whatever string manipulation they do in a single 26 | // deep object scan. Since this serialization is done within db.saveDatabase() 27 | // it showed up on my disk io benchmark portion of this stress test. 28 | 29 | // The closer you get to running out of heap space, the less memory is left over 30 | // for io bufferring so saves are slower. A few hundred megs of free heap space 31 | // will keep db save io speeds from exponentially rising. 32 | 33 | var crypto = require("crypto"); // for random string generation 34 | var loki = require('../src/lokijs.js'); 35 | var lfsa = require('../src/loki-fs-structured-adapter.js'); 36 | 37 | // number of collections to create and populate 38 | var numCollections = 2; 39 | 40 | // number of documents to populate -each- collection with 41 | // if using 2 collections, will probably max @ 75000, structured adapter @ 310000 42 | var numObjects = 150000; 43 | 44 | // # 45 | // # Choose -one- method of serialization and make sure to match in destress.js 46 | // # 47 | 48 | //var mode = "fs-normal"; 49 | //var mode = "fs-structured"; 50 | //var mode = "fs-partitioned"; 51 | var mode = "fs-structured-partitioned"; 52 | 53 | var adapter; 54 | 55 | switch (mode) { 56 | case "fs-normal": 57 | case "fs-structured": adapter = new loki.LokiFsAdapter(); break; 58 | case "fs-partitioned": adapter = new loki.LokiPartitioningAdapter(new loki.LokiFsAdapter()); break; 59 | case "fs-structured-partitioned" : adapter = new lfsa(); break; 60 | default:adapter = new loki.LokiFsAdapter(); break; 61 | }; 62 | 63 | console.log(mode); 64 | 65 | var db = new loki('sandbox.db', { 66 | adapter : adapter, 67 | serializationMethod: (mode === "fs-structured")?"destructured":"normal" 68 | }); 69 | 70 | // using less 'leaky' way to generate random strings 71 | // node specific 72 | function genRandomVal() { 73 | return crypto.randomBytes(50).toString('hex'); 74 | } 75 | 76 | function stepInsertObjects() { 77 | var cidx, idx; 78 | 79 | for (cidx=0; cidx < numCollections; cidx++) { 80 | var items = db.addCollection('items' + cidx); 81 | 82 | for(idx=0; idx rss : " + formatBytes(pmu.rss) + " heapTotal : " + formatBytes(pmu.heapTotal) + " heapUsed : " + formatBytes(pmu.heapUsed)); 150 | } 151 | 152 | logMemoryUsage("before document inserts : "); 153 | stepInsertObjects(); 154 | 155 | logMemoryUsage("after document inserts : "); 156 | stepSaveDatabase(); 157 | 158 | -------------------------------------------------------------------------------- /benchmark/throttled-stress.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This stress test is designed to test effectiveness of throttled saves in 3 | * worst-case scenarios. 4 | * 5 | * In order to stress overlapping saves we will use an async adapter along 6 | * with synchronous logic with many calls to save in which we do not wait 7 | * for the save to complete before attempting to save again. This usage 8 | * pattern is not recommended but lokijs throttled saves is intended to 9 | * safeguard against it. 10 | * 11 | * The test will verify that nothing is lost when this usage pattern is 12 | * used by comparing the database when we are done with the copy in memory. 13 | * 14 | * We are forced to consider async adapter behavior on final save and reload, 15 | * since throttled saves protect only within a single loki object instance. 16 | * You must still wait after for the throttled queue to drain after finishing 17 | * before you can attempt to reload it and you must wait for the database 18 | * to finish loading before you can access its contents. 19 | */ 20 | 21 | var crypto = require("crypto"); // for random string generation 22 | var loki = require('../src/lokijs.js'); 23 | 24 | const INITIALCOUNT = 2000; 25 | const ITERATIONS = 2000; 26 | const RANGE = 1000; 27 | 28 | // synchronous adapter using LokiMemoryAdapter 29 | var memAdapterSync = new loki.LokiMemoryAdapter(); 30 | 31 | // simulate async adapter with 100ms save/load times 32 | var memAdapterAsync = new loki.LokiMemoryAdapter({ 33 | asyncResponses: true, 34 | asyncTimeout: 100 35 | }); 36 | 37 | var db, db2; 38 | var maxThrottledCalls = 0; 39 | 40 | // less memory 'leaky' way to generate random strings (node specific) 41 | function genRandomString() { 42 | return crypto.randomBytes(50).toString('hex'); 43 | } 44 | 45 | function genRandomObject() { 46 | var av = Math.floor(Math.random() * RANGE); 47 | var cv = Math.floor(Math.random() * RANGE); 48 | 49 | return { "a": av, "b": genRandomString(), "c": cv }; 50 | } 51 | 52 | function setupDatabaseSync() { 53 | var newDatabase = new loki("throttle-test.db", { adapter: memAdapterSync }); 54 | 55 | // since our memory adapter is by default synchronous (unless simulating async), 56 | // we can assume any load will complete before our next statement executes. 57 | newDatabase.loadDatabase(); 58 | 59 | // initialize collections 60 | if (!newDatabase.getCollection("items")) { 61 | newDatabase.addCollection("items"); 62 | } 63 | 64 | return newDatabase; 65 | } 66 | 67 | function setupDatabaseAsync(callback) { 68 | var newDatabase = new loki("throttle-test.db", { adapter: memAdapterAsync }); 69 | 70 | // database won't exist on first pass, but let's use forced 71 | // async syntax in case is did 72 | newDatabase.loadDatabase({}, function(err) { 73 | if (err) { 74 | callback(err); 75 | } 76 | 77 | // initialize collections 78 | if (!newDatabase.getCollection("items")) { 79 | newDatabase.addCollection("items"); 80 | 81 | // bad practice, stress test 82 | newDatabase.saveDatabase(); 83 | } 84 | 85 | callback(err); 86 | }); 87 | 88 | return newDatabase; 89 | } 90 | 91 | function performStressedOps() { 92 | var items = db.getCollection("items"); 93 | var idx, op; 94 | 95 | for(idx=0;idx maxThrottledCalls) { 102 | maxThrottledCalls = db.throttledCallbacks.length; 103 | } 104 | } 105 | 106 | for(idx=0;idx maxThrottledCalls) { 129 | maxThrottledCalls = db.throttledCallbacks.length; 130 | } 131 | } 132 | } 133 | 134 | function compareDatabases() { 135 | var c1 = db.getCollection("items"); 136 | var c2 = db2.getCollection("items"); 137 | var idx; 138 | 139 | var count = c1.count(); 140 | if (count !== c2.count()) return false; 141 | 142 | for(idx=0; idx < count; idx++) { 143 | if (c1.data[idx].a !== c2.data[idx].a) return false; 144 | if (c1.data[idx].b !== c2.data[idx].b) return false; 145 | if (c1.data[idx].c !== c2.data[idx].c) return false; 146 | if (c1.data[idx].$loki !== c2.data[idx].$loki) return false; 147 | } 148 | 149 | return true; 150 | } 151 | 152 | console.log(""); 153 | 154 | // let's test in truly sync manner 155 | var start = process.hrtime(); 156 | db = setupDatabaseSync(); 157 | performStressedOps(); 158 | db.saveDatabase(); 159 | db2 = setupDatabaseSync(); 160 | var end = process.hrtime(start); 161 | var result = compareDatabases(); 162 | console.log("## Fully synchronous operations with excessive saving after each operation ##"); 163 | console.log("Database are " + (result?"the same.":"NOT the same!")); 164 | console.log("Execution time (hr): %ds %dms", end[0], end[1]/1000000); 165 | console.log("maxThrottledCalls: " + maxThrottledCalls); 166 | 167 | console.log(""); 168 | 169 | // now let's test with simulated async adpater 170 | // first pass setup will create in memory 171 | start = process.hrtime(); 172 | db = setupDatabaseAsync(function() { 173 | performStressedOps(); 174 | 175 | // go ahead and do a final save (even though we save after every op) 176 | // and then wait for queue to drain before trying to reload it. 177 | db.saveDatabase(); 178 | db.throttledSaveDrain(function () { 179 | db2 = setupDatabaseAsync(function () { 180 | var end = process.hrtime(start); 181 | var result = compareDatabases(); 182 | 183 | console.log("## Asynchronous operations with excessive saving after each operation ##"); 184 | console.log("Async database are " + (result?"the same.":"NOT the same!")); 185 | console.log("Execution time (hr): %ds %dms", end[0], end[1]/1000000); 186 | console.log("maxThrottledCalls: " + maxThrottledCalls); 187 | }); 188 | }); 189 | }); 190 | 191 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lokijs", 3 | "main": ["src/lokijs.js"], 4 | "ignore": [ 5 | "**/.*", 6 | "node_modules", 7 | "components", 8 | "bower_components", 9 | "test", 10 | "tests" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /demos/desktop_app/demo.json: -------------------------------------------------------------------------------- 1 | {"filename":"demo.json","collections":[{"name":"contacts","data":[{"name":"joe","age":39,"firstLanguage":"italian","objType":"Contact","$$hashKey":"003","$loki":1},{"name":"darragh","age":32,"firstLanguage":"english","objType":"Contact","$$hashKey":"00D","$loki":6},{"name":"edu","age":34,"firstLanguage":"spanish","objType":"Contact","$loki":7},{"name":"dave","age":30,"firstLanguage":"english","objType":"Contact","$loki":10},{"name":"jack","age":32,"firstLanguage":"italian","meta":{"revision":0,"created":0,"version":0},"$loki":11}],"idIndex":[1,6,7,10,11],"binaryIndices":{},"objType":"contacts","dirty":false,"cachedIndex":null,"cachedBinaryIndex":null,"cachedData":null,"transactional":false,"cloneObjects":false,"asyncListeners":true,"disableChangesApi":true,"maxId":11,"DynamicViews":[],"events":{"insert":[null],"update":[null],"close":[],"flushbuffer":[],"error":[],"delete":[null],"warning":[null]},"changes":[]}],"databaseVersion":1,"engineVersion":1.1,"autosave":false,"autosaveInterval":5000,"autosaveHandle":null,"options":{"env":"NODEJS","autoload":true},"persistenceMethod":null,"persistenceAdapter":null,"events":{"init":[null],"flushChanges":[],"close":[],"changes":[],"warning":[]},"ENV":"NODEJS","fs":{}} -------------------------------------------------------------------------------- /demos/desktop_app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 10 |
11 |
12 |

Insert data

13 | name: 14 | age: 15 | first language: 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 |
nameagefirst language
{{ c.name }}{{ c.age }}{{ c.firstLanguage }} 30 |
32 |
33 |
34 |
35 | 104 | 105 | -------------------------------------------------------------------------------- /demos/desktop_app/main.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Roboto:400,300,100); 2 | html, body { margin: 0; padding: 0; background: black; color: #ddd; font-family: "Roboto"; font-weight: 300; font-size: 15px; } 3 | div { -webkit-box-sizing: border-box; } 4 | table { font-size: 1.1em; font-weight: 300; width: 100%;} 5 | thead { text-align: left; } 6 | button { background: #222; color: #999; padding: 6px; font-size: 1.2em; border: 1px solid rgba(50, 50, 50, 0.5); } 7 | .container { width: 100%;} 8 | #header { width: 100%; line-height: 100px; clear: both; vertical-align: middle; height: 100px; border-bottom: 1px solid #ddd; font-size: 2.4em; font-weight: 300;} 9 | #main { clear: both;} 10 | #results { height: 350px; background: #222; overflow-x: hidden; overflow-y: scroll;} -------------------------------------------------------------------------------- /demos/desktop_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lokijs_nw_demo", 3 | "window": { 4 | "width": 800, 5 | "height": 600, 6 | "toolbar": false 7 | }, 8 | "main": "index.html" 9 | } -------------------------------------------------------------------------------- /docs-conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "readme": "./OVERVIEW.md", 4 | "destination": "./jsdoc", 5 | "tutorials": "./tutorials" 6 | }, 7 | "tags": { 8 | "allowUnknownTags": true, 9 | "dictionaries": ["jsdoc","closure"] 10 | }, 11 | "source": { 12 | "include": ["src/lokijs.js", "src/loki-indexed-adapter.js", "src/loki-fs-structured-adapter.js"] 13 | }, 14 | "plugins": ["plugins/markdown"], 15 | "templates": { 16 | "cleverlinks": false, 17 | "monospaceLinks": false 18 | } 19 | } -------------------------------------------------------------------------------- /docs/Collection.module_Collection_Transforms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Module: Collection/Transforms 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Module: Collection/Transforms

21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 |
35 | 36 | 37 |
Updates a named collection transform to the collection
38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 |
Source:
71 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
82 | 83 | 84 | 85 | 86 |
87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 |
106 | 107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
116 | 117 |
118 | 119 |
120 | 121 |
122 |
123 | 124 | 125 |
Adds a named collection transform to the collection
126 | 127 | 128 | 129 | 130 | 131 |
132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
Source:
159 |
162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 |
170 | 171 | 172 | 173 | 174 |
175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 |
194 | 195 |
196 | 197 | 198 | 199 | 200 |
201 | 202 | 205 | 206 |
207 | 208 |
209 | Documentation generated by JSDoc 3.4.0 on Sun Apr 03 2016 08:34:12 GMT-0400 (EDT) 210 |
211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /docs/fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/docs/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Home 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Home

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |

LokiJS

LokiJS.org web site | 47 | LokiJS GitHub page | 48 | Sandbox / Playground

49 |

Documentation Overview

This is an early effort to provide a more accurate and up-to-date version of LokiJS documentation by using jsdoc. Since modifications arise from various contributors, this should allow distributed effort toward 50 | maintaining this documentation.

51 |

Getting Started

Creating a database :

52 |
var db = new loki('example.db');

Add a collection :

53 |
var users = db.addCollection('users');

Insert documents :

54 |
users.insert({
 55 |     name: 'Odin',
 56 |     age: 50,
 57 |     address: 'Asgard'
 58 | });
 59 | 
 60 | // alternatively, insert array of documents
 61 | users.insert([{ name: 'Thor', age: 35}, { name: 'Loki', age: 30}]);

Simple find query :

62 |
var results = users.find({ age: {'$gte': 35} });
 63 | 
 64 | var odin = users.findOne({ name:'Odin' });

Simple where query :

65 |
var results = users.where(function(obj) {
 66 |     return (obj.age >= 35);
 67 | });

Simple Chaining :

68 |
var results = users.chain().find({ age: {'$gte': 35} }).simplesort('name').data();

Simple named transform :

69 |
users.addTransform('progeny', [
 70 |   {
 71 |     type: 'find',
 72 |     value: {
 73 |       'age': {'$lte': 40}
 74 |     }
 75 |   }
 76 | ]);
 77 | 
 78 | var results = users.chain('progeny').data();

Simple Dynamic View :

79 |
var pview = users.addDynamicView('progeny');
 80 | 
 81 | pview.applyFind({
 82 |     'age': {'$lte': 40}
 83 | });
 84 | 
 85 | pview.applySimpleSort('name');
 86 | 
 87 | var results = pview.data();
88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 | 97 | 100 | 101 |
102 | 103 |
104 | Documentation generated by JSDoc 3.5.5 on Thu May 10 2018 03:42:33 GMT-0400 (Eastern Daylight Time) 105 |
106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /docs/scripts/linenumber.js: -------------------------------------------------------------------------------- 1 | /*global document */ 2 | (function() { 3 | var source = document.getElementsByClassName('prettyprint source linenums'); 4 | var i = 0; 5 | var lineNumber = 0; 6 | var lineId; 7 | var lines; 8 | var totalLines; 9 | var anchorHash; 10 | 11 | if (source && source[0]) { 12 | anchorHash = document.location.hash.substring(1); 13 | lines = source[0].getElementsByTagName('li'); 14 | totalLines = lines.length; 15 | 16 | for (; i < totalLines; i++) { 17 | lineNumber++; 18 | lineId = 'line' + lineNumber; 19 | lines[i].id = lineId; 20 | if (lineId === anchorHash) { 21 | lines[i].className += ' selected'; 22 | } 23 | } 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /docs/scripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /docs/styles/jsdoc-default.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Open Sans'; 3 | font-weight: normal; 4 | font-style: normal; 5 | src: url('../fonts/OpenSans-Regular-webfont.eot'); 6 | src: 7 | local('Open Sans'), 8 | local('OpenSans'), 9 | url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), 10 | url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), 11 | url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); 12 | } 13 | 14 | @font-face { 15 | font-family: 'Open Sans Light'; 16 | font-weight: normal; 17 | font-style: normal; 18 | src: url('../fonts/OpenSans-Light-webfont.eot'); 19 | src: 20 | local('Open Sans Light'), 21 | local('OpenSans Light'), 22 | url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), 23 | url('../fonts/OpenSans-Light-webfont.woff') format('woff'), 24 | url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg'); 25 | } 26 | 27 | html 28 | { 29 | overflow: auto; 30 | background-color: #fff; 31 | font-size: 14px; 32 | } 33 | 34 | body 35 | { 36 | font-family: 'Open Sans', sans-serif; 37 | line-height: 1.5; 38 | color: #4d4e53; 39 | background-color: white; 40 | } 41 | 42 | a, a:visited, a:active { 43 | color: #0095dd; 44 | text-decoration: none; 45 | } 46 | 47 | a:hover { 48 | text-decoration: underline; 49 | } 50 | 51 | header 52 | { 53 | display: block; 54 | padding: 0px 4px; 55 | } 56 | 57 | tt, code, kbd, samp { 58 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 59 | } 60 | 61 | .class-description { 62 | font-size: 130%; 63 | line-height: 140%; 64 | margin-bottom: 1em; 65 | margin-top: 1em; 66 | } 67 | 68 | .class-description:empty { 69 | margin: 0; 70 | } 71 | 72 | #main { 73 | float: left; 74 | width: 70%; 75 | } 76 | 77 | article dl { 78 | margin-bottom: 40px; 79 | } 80 | 81 | article img { 82 | max-width: 100%; 83 | } 84 | 85 | section 86 | { 87 | display: block; 88 | background-color: #fff; 89 | padding: 12px 24px; 90 | border-bottom: 1px solid #ccc; 91 | margin-right: 30px; 92 | } 93 | 94 | .variation { 95 | display: none; 96 | } 97 | 98 | .signature-attributes { 99 | font-size: 60%; 100 | color: #aaa; 101 | font-style: italic; 102 | font-weight: lighter; 103 | } 104 | 105 | nav 106 | { 107 | display: block; 108 | float: right; 109 | margin-top: 28px; 110 | width: 30%; 111 | box-sizing: border-box; 112 | border-left: 1px solid #ccc; 113 | padding-left: 16px; 114 | } 115 | 116 | nav ul { 117 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; 118 | font-size: 100%; 119 | line-height: 17px; 120 | padding: 0; 121 | margin: 0; 122 | list-style-type: none; 123 | } 124 | 125 | nav ul a, nav ul a:visited, nav ul a:active { 126 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 127 | line-height: 18px; 128 | color: #4D4E53; 129 | } 130 | 131 | nav h3 { 132 | margin-top: 12px; 133 | } 134 | 135 | nav li { 136 | margin-top: 6px; 137 | } 138 | 139 | footer { 140 | display: block; 141 | padding: 6px; 142 | margin-top: 12px; 143 | font-style: italic; 144 | font-size: 90%; 145 | } 146 | 147 | h1, h2, h3, h4 { 148 | font-weight: 200; 149 | margin: 0; 150 | } 151 | 152 | h1 153 | { 154 | font-family: 'Open Sans Light', sans-serif; 155 | font-size: 48px; 156 | letter-spacing: -2px; 157 | margin: 12px 24px 20px; 158 | } 159 | 160 | h2, h3.subsection-title 161 | { 162 | font-size: 30px; 163 | font-weight: 700; 164 | letter-spacing: -1px; 165 | margin-bottom: 12px; 166 | } 167 | 168 | h3 169 | { 170 | font-size: 24px; 171 | letter-spacing: -0.5px; 172 | margin-bottom: 12px; 173 | } 174 | 175 | h4 176 | { 177 | font-size: 18px; 178 | letter-spacing: -0.33px; 179 | margin-bottom: 12px; 180 | color: #4d4e53; 181 | } 182 | 183 | h5, .container-overview .subsection-title 184 | { 185 | font-size: 120%; 186 | font-weight: bold; 187 | letter-spacing: -0.01em; 188 | margin: 8px 0 3px 0; 189 | } 190 | 191 | h6 192 | { 193 | font-size: 100%; 194 | letter-spacing: -0.01em; 195 | margin: 6px 0 3px 0; 196 | font-style: italic; 197 | } 198 | 199 | table 200 | { 201 | border-spacing: 0; 202 | border: 0; 203 | border-collapse: collapse; 204 | } 205 | 206 | td, th 207 | { 208 | border: 1px solid #ddd; 209 | margin: 0px; 210 | text-align: left; 211 | vertical-align: top; 212 | padding: 4px 6px; 213 | display: table-cell; 214 | } 215 | 216 | thead tr 217 | { 218 | background-color: #ddd; 219 | font-weight: bold; 220 | } 221 | 222 | th { border-right: 1px solid #aaa; } 223 | tr > th:last-child { border-right: 1px solid #ddd; } 224 | 225 | .ancestors, .attribs { color: #999; } 226 | .ancestors a, .attribs a 227 | { 228 | color: #999 !important; 229 | text-decoration: none; 230 | } 231 | 232 | .clear 233 | { 234 | clear: both; 235 | } 236 | 237 | .important 238 | { 239 | font-weight: bold; 240 | color: #950B02; 241 | } 242 | 243 | .yes-def { 244 | text-indent: -1000px; 245 | } 246 | 247 | .type-signature { 248 | color: #aaa; 249 | } 250 | 251 | .name, .signature { 252 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 253 | } 254 | 255 | .details { margin-top: 14px; border-left: 2px solid #DDD; } 256 | .details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } 257 | .details dd { margin-left: 70px; } 258 | .details ul { margin: 0; } 259 | .details ul { list-style-type: none; } 260 | .details li { margin-left: 30px; padding-top: 6px; } 261 | .details pre.prettyprint { margin: 0 } 262 | .details .object-value { padding-top: 0; } 263 | 264 | .description { 265 | margin-bottom: 1em; 266 | margin-top: 1em; 267 | } 268 | 269 | .code-caption 270 | { 271 | font-style: italic; 272 | font-size: 107%; 273 | margin: 0; 274 | } 275 | 276 | .prettyprint 277 | { 278 | border: 1px solid #ddd; 279 | width: 80%; 280 | overflow: auto; 281 | } 282 | 283 | .prettyprint.source { 284 | width: inherit; 285 | } 286 | 287 | .prettyprint code 288 | { 289 | font-size: 100%; 290 | line-height: 18px; 291 | display: block; 292 | padding: 4px 12px; 293 | margin: 0; 294 | background-color: #fff; 295 | color: #4D4E53; 296 | } 297 | 298 | .prettyprint code span.line 299 | { 300 | display: inline-block; 301 | } 302 | 303 | .prettyprint.linenums 304 | { 305 | padding-left: 70px; 306 | -webkit-user-select: none; 307 | -moz-user-select: none; 308 | -ms-user-select: none; 309 | user-select: none; 310 | } 311 | 312 | .prettyprint.linenums ol 313 | { 314 | padding-left: 0; 315 | } 316 | 317 | .prettyprint.linenums li 318 | { 319 | border-left: 3px #ddd solid; 320 | } 321 | 322 | .prettyprint.linenums li.selected, 323 | .prettyprint.linenums li.selected * 324 | { 325 | background-color: lightyellow; 326 | } 327 | 328 | .prettyprint.linenums li * 329 | { 330 | -webkit-user-select: text; 331 | -moz-user-select: text; 332 | -ms-user-select: text; 333 | user-select: text; 334 | } 335 | 336 | .params .name, .props .name, .name code { 337 | color: #4D4E53; 338 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 339 | font-size: 100%; 340 | } 341 | 342 | .params td.description > p:first-child, 343 | .props td.description > p:first-child 344 | { 345 | margin-top: 0; 346 | padding-top: 0; 347 | } 348 | 349 | .params td.description > p:last-child, 350 | .props td.description > p:last-child 351 | { 352 | margin-bottom: 0; 353 | padding-bottom: 0; 354 | } 355 | 356 | .disabled { 357 | color: #454545; 358 | } 359 | -------------------------------------------------------------------------------- /docs/styles/prettify-jsdoc.css: -------------------------------------------------------------------------------- 1 | /* JSDoc prettify.js theme */ 2 | 3 | /* plain text */ 4 | .pln { 5 | color: #000000; 6 | font-weight: normal; 7 | font-style: normal; 8 | } 9 | 10 | /* string content */ 11 | .str { 12 | color: #006400; 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | 17 | /* a keyword */ 18 | .kwd { 19 | color: #000000; 20 | font-weight: bold; 21 | font-style: normal; 22 | } 23 | 24 | /* a comment */ 25 | .com { 26 | font-weight: normal; 27 | font-style: italic; 28 | } 29 | 30 | /* a type name */ 31 | .typ { 32 | color: #000000; 33 | font-weight: normal; 34 | font-style: normal; 35 | } 36 | 37 | /* a literal value */ 38 | .lit { 39 | color: #006400; 40 | font-weight: normal; 41 | font-style: normal; 42 | } 43 | 44 | /* punctuation */ 45 | .pun { 46 | color: #000000; 47 | font-weight: bold; 48 | font-style: normal; 49 | } 50 | 51 | /* lisp open bracket */ 52 | .opn { 53 | color: #000000; 54 | font-weight: bold; 55 | font-style: normal; 56 | } 57 | 58 | /* lisp close bracket */ 59 | .clo { 60 | color: #000000; 61 | font-weight: bold; 62 | font-style: normal; 63 | } 64 | 65 | /* a markup tag name */ 66 | .tag { 67 | color: #006400; 68 | font-weight: normal; 69 | font-style: normal; 70 | } 71 | 72 | /* a markup attribute name */ 73 | .atn { 74 | color: #006400; 75 | font-weight: normal; 76 | font-style: normal; 77 | } 78 | 79 | /* a markup attribute value */ 80 | .atv { 81 | color: #006400; 82 | font-weight: normal; 83 | font-style: normal; 84 | } 85 | 86 | /* a declaration */ 87 | .dec { 88 | color: #000000; 89 | font-weight: bold; 90 | font-style: normal; 91 | } 92 | 93 | /* a variable name */ 94 | .var { 95 | color: #000000; 96 | font-weight: normal; 97 | font-style: normal; 98 | } 99 | 100 | /* a function name */ 101 | .fun { 102 | color: #000000; 103 | font-weight: bold; 104 | font-style: normal; 105 | } 106 | 107 | /* Specify class=linenums on a pre to get line numbering */ 108 | ol.linenums { 109 | margin-top: 0; 110 | margin-bottom: 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/styles/prettify-tomorrow.css: -------------------------------------------------------------------------------- 1 | /* Tomorrow Theme */ 2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */ 3 | /* Pretty printing styles. Used with prettify.js. */ 4 | /* SPAN elements with the classes below are added by prettyprint. */ 5 | /* plain text */ 6 | .pln { 7 | color: #4d4d4c; } 8 | 9 | @media screen { 10 | /* string content */ 11 | .str { 12 | color: #718c00; } 13 | 14 | /* a keyword */ 15 | .kwd { 16 | color: #8959a8; } 17 | 18 | /* a comment */ 19 | .com { 20 | color: #8e908c; } 21 | 22 | /* a type name */ 23 | .typ { 24 | color: #4271ae; } 25 | 26 | /* a literal value */ 27 | .lit { 28 | color: #f5871f; } 29 | 30 | /* punctuation */ 31 | .pun { 32 | color: #4d4d4c; } 33 | 34 | /* lisp open bracket */ 35 | .opn { 36 | color: #4d4d4c; } 37 | 38 | /* lisp close bracket */ 39 | .clo { 40 | color: #4d4d4c; } 41 | 42 | /* a markup tag name */ 43 | .tag { 44 | color: #c82829; } 45 | 46 | /* a markup attribute name */ 47 | .atn { 48 | color: #f5871f; } 49 | 50 | /* a markup attribute value */ 51 | .atv { 52 | color: #3e999f; } 53 | 54 | /* a declaration */ 55 | .dec { 56 | color: #f5871f; } 57 | 58 | /* a variable name */ 59 | .var { 60 | color: #c82829; } 61 | 62 | /* a function name */ 63 | .fun { 64 | color: #4271ae; } } 65 | /* Use higher contrast and text-weight for printable form. */ 66 | @media print, projection { 67 | .str { 68 | color: #060; } 69 | 70 | .kwd { 71 | color: #006; 72 | font-weight: bold; } 73 | 74 | .com { 75 | color: #600; 76 | font-style: italic; } 77 | 78 | .typ { 79 | color: #404; 80 | font-weight: bold; } 81 | 82 | .lit { 83 | color: #044; } 84 | 85 | .pun, .opn, .clo { 86 | color: #440; } 87 | 88 | .tag { 89 | color: #006; 90 | font-weight: bold; } 91 | 92 | .atn { 93 | color: #404; } 94 | 95 | .atv { 96 | color: #060; } } 97 | /* Style */ 98 | /* 99 | pre.prettyprint { 100 | background: white; 101 | font-family: Consolas, Monaco, 'Andale Mono', monospace; 102 | font-size: 12px; 103 | line-height: 1.5; 104 | border: 1px solid #ccc; 105 | padding: 10px; } 106 | */ 107 | 108 | /* Specify class=linenums on a pre to get line numbering */ 109 | ol.linenums { 110 | margin-top: 0; 111 | margin-bottom: 0; } 112 | 113 | /* IE indents via margin-left */ 114 | li.L0, 115 | li.L1, 116 | li.L2, 117 | li.L3, 118 | li.L4, 119 | li.L5, 120 | li.L6, 121 | li.L7, 122 | li.L8, 123 | li.L9 { 124 | /* */ } 125 | 126 | /* Alternate shading for lines */ 127 | li.L1, 128 | li.L3, 129 | li.L5, 130 | li.L7, 131 | li.L9 { 132 | /* */ } 133 | -------------------------------------------------------------------------------- /docs/tutorial-Autoupdating Collections.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Tutorial: Autoupdating Collections 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Tutorial: Autoupdating Collections

21 | 22 |
23 | 24 |
25 | 26 | 27 |

Autoupdating Collections

28 |
29 | 30 |
31 |

The Autoupdate Feature for Collections

Autoupdate can be enabled on a per-collection basis via the constructor option autoupdate: true. The feature requires Object.observe (currently implemented in Chrome 36+, io.js and Node.js 0.12+). If observers are not available, the option will be ignored.

32 |

Autoupdate automatically calls update(doc) whenever a document is modified, which is necessary for index updates and dirty-marks (used to determine whether the DB has been modified and should be persisted).

33 |

Enabling this feature basically means, that all manual update calls can be omitted.

34 |

Example

var doc = collection.by("name", "John");
35 | 
36 | doc.name = "Peter";
37 | doc.age = 32;
38 | doc.gender = "male";
39 | 
40 | collection.update(doc); // This line can be safely removed.

Autoupdate will call update at the end of the current event loop cycle and thus only calls update once, even when multiple changes were made.

41 |

Error handling

There is one important difference between autoupdate and manual updates. If for example a document change violates a unique key constraint, update will synchronously throw an error which can be catched synchronously:

42 |
var collection = db.addCollection("test", {
43 |   unique: ["name"]
44 | });
45 | 
46 | collection.insert({ name: "Peter" });
47 | 
48 | var doc = collection.insert({ name: "Jack" });
49 | doc.name = "Peter";
50 | 
51 | try {
52 |   collection.update(doc);
53 | } catch(err) {
54 |   doc.name = "Jack";
55 | }

Since autoupdate calls update asynchronously, you cannot catch errors via try-catch. Instead you have to use event listeners:

56 |
var collection = db.addCollection("test", {
57 |   unique: ["name"],
58 |   autoupdate: true
59 | });
60 | 
61 | collection.insert({ name: "Peter" });
62 | 
63 | var doc = collection.insert({ name: "Jack" });
64 | doc.name = "Peter";
65 | 
66 | collection.on("error", function(errDoc) {
67 |   if(errDoc === doc) {
68 |     doc.name = "Jack";
69 |   }
70 | });

This can become quite tedious, so you should consider performing checks before updating documents instead.

71 |
72 | 73 |
74 | 75 |
76 | 77 | 80 | 81 |
82 | 83 |
84 | Documentation generated by JSDoc 3.5.5 on Thu May 10 2018 03:42:33 GMT-0400 (Eastern Daylight Time) 85 |
86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /docs/tutorial-Changes API.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Tutorial: Changes API 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Tutorial: Changes API

21 | 22 |
23 | 24 |
25 | 26 | 27 |

Changes API

28 |
29 | 30 |
31 |

Overview

LokiJS 1.1 introduces a "Changes API" that enables the user to keep track of the changes happened to each collection since a particular point in time, which is usually the start of a work session but it could be a user defined one. 32 | This is particularly useful for remote synchronization.

33 |

Description of the Changes API

The Changes API is a collection-level feature, hence you can establish which collections may simply contain volatile data and which ones need to keep a record of what has changed.

34 |

The Changes API is an optional feature and can be activated/deactivated by either passing the option { disableChangesApi: isDisabled } in the config parameter of a collection constructor, or by calling collection.setChangesApi(isEnabled). 35 | Note that LokiJS will always set the fastest performing setting as default on a collection or database, hence the Changes API is disabled by default.

36 |

There are three events which will trigger a Changes API operation: inserts, updates and deletes. 37 | When either of these events occur, on a collection with Changes API activated, the collection will store a snapshot of the relevant object, associated with the operation and the name of the collection.

38 |

From the database object it is then possible to invoke the serializeChanges method which will generate a string representation of the changes occurred to be used for synchronization purposes.

39 |

Usage

To enable the Changes API make sure to either instantiate a collection using db.addCollection('users', { disableChangesApi: false }), or call users.setChangesApi(true) (given an example users collection).

40 |

To generate a string representation of the changes, call db.serializeChanges(). This will generate a representation of all the changes for those collections that have the Changes API enabled. If you are only interested in generating changes for a subset of collections, you can pass an array of names of the collections, i.e. db.serializeChanges(['users']);.

41 |

To clear all the changes, call db.clearChanges(). Alternatively you can call flushChanges() on the single collection, normally you would call db.clearChanges() on a callback from a successful synchronization operation.

42 |

Each change is an object with three properties: name is the collection name, obj is the string representation of the object and operation is a character representing the operation ("I" for insert, "U" for update, "R" for remove). So for example, inserting user { name: 'joe' } in the users collection would generate a change { name: 'users', obj: { name: 'joe' }, operation: 'I' }. Changes are kept in order of how the happened so a 3rd party application will be able to operate insert updates and deletes in the correct order.

43 |
44 | 45 |
46 | 47 |
48 | 49 | 52 | 53 |
54 | 55 |
56 | Documentation generated by JSDoc 3.5.5 on Thu May 10 2018 03:42:33 GMT-0400 (Eastern Daylight Time) 57 |
58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /docs/tutorial-Loki Angular.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Tutorial: Loki Angular 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Tutorial: Loki Angular

21 | 22 |
23 | 24 |
25 | 26 | 27 |

Loki Angular

28 |
29 | 30 |
31 |

Lokiwork, the LokiJS Angular Service

Overview

This service of Lokijs for Angular simplifies things to the most basic level because i found Loki difficult to work with in a mobile environment. all you do is setup json files that specify the layout of your data then add, and update entries to the databases.

32 |

###Install: 33 | bower install lokijs

34 |

###Html:

35 |
<script src="bower_components/lokijs/src/lokijs.js"></script>
 36 | <script src="bower_components/lokijs/src/loki-angular.js"></script>

###App: 37 | angular.module('app',['lokijs']);

38 |

###Configure database template: 39 | I might call this file -> json_locations.js Note: each one has to be called, json1, json2, json3, etc as shown in the following:

40 |
app.constant(
 41 |     'json1', 
 42 |     {  
 43 |            "db":"settings",
 44 |            "collection": "globals" ,
 45 |            "documents" :
 46 |             [  
 47 |                       {
 48 |                     "name": "user settings",
 49 |                     "brands" : true,
 50 |                     "random" : "some value"
 51 |                       }
 52 |             ]
 53 |     }
 54 |     );

###Controller:

55 |
app.controller('myCtrl', function($scope, Lokiwork){...});

###Usage:

56 |

Lokiwork.setCurrentDoc(dbname, collection, document_identifier);

57 |
Lokiwork.setCurrentDoc('settings', 'globals', {'name': "user settings"});

Lokiwork.getCurrentDoc();

58 |
Lokiwork.getCurrentDoc();

Lokiwork.updateCurrentDoc(name, value);

59 |
Lokiwork.updateCurrentDoc("power", true);

Lokiwork.deleteCurrentDoc();

60 |
Lokiwork.deleteCurrentDoc();

Lokiwork.getDoc(dbName, collName, docName);

61 |
Lokiwork.getDoc("settings", "globals", {name:"user settings"});

Lokiwork.addDocument(dbName, collName, newDoc);

62 |
Lokiwork.addDocument("settings", "globals", doc_obj); //example below

Lokiwork.updateDoc(dbname, collName, document_identifier, name, value);

63 |
Lokiwork.updateDoc("settings", "globals", {name:"user settings"}, "brands", false});

Lokiwork.deleteDocument(dbName, collName, document_identifier);

64 |
Lokiwork.deleteDocument('settings','globals', {name:'user settings'});

Lokiwork.getCollection(dbName, collName);

65 |
Lokiwork.getCollection('settings', 'globals');

Lokwork.addCollection(json_obj);

66 |
Lokiwork.addCollection(item); // example below

Lokiwork.deleteCollection(dbName, collName);

67 |
Lokiwork.deleteCollection('settings', globals');

Lokiwork.deleteDatabase(dbName);

68 |
Lokiwork.deleteDatabase("settings");

####Further examples:

69 |
var collection = {  
 70 |   "db":"settings",
 71 |   "collection": "globals" ,
 72 |   "documents" :
 73 |   [  
 74 |        {
 75 |       "name": "user settings",
 76 |       "brands" : true,
 77 |       "face" : "You now it"
 78 |        }
 79 |   ]
 80 | };
 81 |    Lokiwork.addCollection(collection);

With addDocument, you can pass a json document in if it's small enough, otherwise assign it to a variable first.

82 |
Lokiwork.addDocument("settings", "globals", {name:"user settings2", gay: true, brands:false})

You can also use promises and/or chain them:

83 |
Lokiwork.setCurrentDoc('settings', 'globals', {'name': "user settings"})
 84 |     .then(function(data){               
 85 |        Lokiwork.updateCurrentDoc("address", "1801 Waters Ridge Drive");
 86 |     });

###Remember! 87 | A lot of the above commands may not be necessary if you are implementing a static change, just edit the underlying json javascript file, delete the local storage file, and restart the app.

88 |

###Notes:

89 |
    90 |
  • If you delete a database it's recreated the next time the app is restarted and on the first query because it will see the angular json file and recreate it (it won't overwrite existing though). If you want to permanantly remove a database, then you have to also remove the angular json file. This is perfect, because on a mobile device the user may have local storage wiped, no problem, because the next time they boot up the databases will all be recreated.
  • 91 |
  • Since Lokijs is generating all of the database content it should be 100% compatible with native Lokijs commands not listed here but found on the office website shown below.
  • 92 |
93 |

###The official Lokijs page 94 | LokiJS

95 |
96 | 97 |
98 | 99 |
100 | 101 | 104 | 105 |
106 | 107 |
108 | Documentation generated by JSDoc 3.5.5 on Thu May 10 2018 03:42:33 GMT-0400 (Eastern Daylight Time) 109 |
110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/tutorial-TEST.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSDoc: Tutorial: TEST 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |

Tutorial: TEST

21 | 22 |
23 | 24 |
25 | 26 | 27 |

TEST

28 |
29 | 30 |
31 |

LokiJS

LokiJS.org web site | 32 | LokiJS GitHub page | 33 | Sandbox / Playground

34 |

Documentation Overview

This is an early effort using jsdoc to provide a more accurate and up-to-date version of LokiJS documentation. Since modifications arise from various contributors, this should allow distributed effort toward 35 | maintaining this documentation. For the time being, you can use it along with LokiJS.org documentation and the 36 | GitHub wiki documentation. Ideally this will emcompass the best of both reference as well as more complete examples 37 | and descriptions.

38 |

Getting Started

Creating a database :

39 |
var db = new loki('example.db');

Add a collection :

40 |
var users = db.addCollection('users');

Insert documents :

41 |
users.insert({
42 |     name: 'Odin',
43 |     age: 50,
44 |     address: 'Asgard'
45 | });
46 | 
47 | // alternatively, insert array of documents
48 | users.insert([{ name: 'Thor', age: 35}, { name: 'Loki', age: 30}]);

Simple find query :

49 |
var results = users.find({ age: {'$gte': 35} });
50 | 
51 | var odin = users.findOne({ name:'Odin' });

Simple where query :

52 |
var results = users.where(function(obj) {
53 |     return (obj.age >= 35);
54 | });

Simple Chaining :

55 |
var results = users.chain().find({ age: {'$gte': 35} }).simplesort('name').data();

Simple named transform :

56 |
users.addTransform('progeny', [
57 |   {
58 |     type: 'find',
59 |     value: {
60 |       'age': {'$lte': 40}
61 |     }
62 |   }
63 | ]);
64 | 
65 | var results = users.chain('progeny').data();

Simple Dynamic View :

66 |
var pview = users.addDynamicView('progeny');
67 | 
68 | pview.applyFind({
69 |     'age': {'$lte': 40}
70 | });
71 | 
72 | pview.applySimpleSort('name');
73 | 
74 | var results = pview.data();
75 |
76 | 77 |
78 | 79 |
80 | 81 | 84 | 85 |
86 | 87 |
88 | Documentation generated by JSDoc 3.4.0 on Mon Apr 04 2016 19:58:36 GMT-0400 (EDT) 89 |
90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /examples/examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | LokiJS Examples 5 | 6 | 7 | 8 | 9 |

LokiJS Examples

10 | 11 | Load up the console and see it happen! 12 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/quickstart-chaining.js: -------------------------------------------------------------------------------- 1 | /** 2 | * quickstart-chaining.js : example demonstrating lokijs method chaining via Resultset class. 3 | * 4 | * This example will only use loki, collection, and resultset classes. We will not bother 5 | * with persistence for this example. 6 | * 7 | */ 8 | 9 | const loki = require('../src/lokijs.js'); 10 | 11 | var db = new loki("quickstart-chaining.db"); 12 | 13 | var users = db.addCollection("users"); 14 | 15 | // seed data 16 | users.insert({ name: "odin", gender: "m", age: 1000, tags : ["woden", "knowlege", "sorcery", "frenzy", "runes"], items: ["gungnir"], attributes: { eyes: 1} }); 17 | users.insert({ name: "frigg", gender: "f", age: 800, tags: ["foreknowlege"], items: ["eski"] }); 18 | users.insert({ name: "thor", gender: "m", age: 35, items: ["mjolnir", "beer"] }); 19 | users.insert({ name: "sif", gender: "f", age: 30 }); 20 | users.insert({ name: "loki", gender: "m", age: 29 }); 21 | users.insert({ name: "sigyn", gender: "f", age: 29 }); 22 | users.insert({ name: "freyr", age: 400 }); 23 | users.insert({ name: "heimdallr", age: 99 }); 24 | users.insert({ name: "mimir", age: 999 }); 25 | 26 | // call chain() method on collection to begin chaining 27 | // call data() method to terminate chain and return results 28 | 29 | var result; 30 | 31 | // find all records 32 | result = users.chain().data(); 33 | 34 | // find all records sorted by age : 35 | result = users.chain().simplesort("age").data(); 36 | 37 | // find all records greater than or equal to 400 sorted by age descending 38 | result = users.chain().find({age: { $gte: 400 }}).simplesort("age", true).data(); 39 | console.log("result 1"); 40 | console.log(result); 41 | 42 | // find the two oldest users 43 | result = users.chain().simplesort("age", true).limit(2).data(); 44 | console.log("result 2"); 45 | console.log(result); 46 | 47 | // find everyone but the two oldest users 48 | result = users.chain().simplesort("age", true).offset(2).data(); 49 | console.log("result 3"); 50 | console.log(result); 51 | 52 | // incrementally chaining filters 53 | result = users.chain().find({ age: 29}).find({ gender: "f"}).data(); 54 | console.log("result 4"); 55 | console.log(result); 56 | 57 | // mixing filter methods 58 | result = users 59 | .chain() 60 | .find({ age: 29}) 61 | .where(function(obj) { return obj.gender === "f" }) 62 | .data(); 63 | console.log("result 5"); 64 | console.log(result); 65 | 66 | // find all users between 30-40 and add a year to their age using javascript update function 67 | // dont need data back so don't need to call data() 68 | users 69 | .chain() 70 | .find({ age: { $between: [30, 40] } }) 71 | .update(function(obj) { obj.age = obj.age+1; }); 72 | 73 | // remove all users with age of 29 74 | // dont need data back so don't need to call data() 75 | users 76 | .chain() 77 | .find({ age: 29 }) 78 | .remove(); 79 | 80 | // get all documents but strip $loki and meta properties from results 81 | result = users.chain().data({removeMeta: true}); 82 | console.log("result 6"); 83 | console.log(result); 84 | 85 | // get all documents but return clones even though we did not define clones on collection 86 | result = users.chain().data({forceClones: true}); 87 | console.log("result 7"); 88 | console.log(result); 89 | -------------------------------------------------------------------------------- /examples/quickstart-core.js: -------------------------------------------------------------------------------- 1 | /** 2 | * quickstart-core.js : example demonstrating 'core' lokijs methods 3 | * 4 | * This example will only use loki and collection classes. We will not bother 5 | * with persistence for this example. 6 | * 7 | */ 8 | 9 | const loki = require('../src/lokijs.js'); 10 | 11 | var db = new loki("quickstart-core.db"); 12 | 13 | var users = db.addCollection("users"); 14 | 15 | // seed data 16 | users.insert({ name: "odin", gender: "m", age: 1000, tags : ["woden", "knowlege", "sorcery", "frenzy", "runes"], items: ["gungnir"], attributes: { eyes: 1} }); 17 | users.insert({ name: "frigg", gender: "f", age: 800, tags: ["foreknowlege"], items: ["eski"] }); 18 | users.insert({ name: "thor", gender: "m", age: 35, items: ["mjolnir", "beer"] }); 19 | users.insert({ name: "sif", gender: "f", age: 30 }); 20 | users.insert({ name: "loki", gender: "m", age: 29 }); 21 | users.insert({ name: "sigyn", gender: "f", age: 29 }); 22 | users.insert({ name: "freyr", age: 400 }); 23 | users.insert({ name: "heimdallr", age: 99 }); 24 | users.insert({ name: "mimir", age: 999 }); 25 | 26 | var result; 27 | 28 | // find all records where age is equal to 1000 (just odin) 29 | // query object is { age: 1000 } 30 | // this form is shorthand where $eq op is implicit 31 | // this will return array of all docs matching that filter 32 | result = users.find({ age: 1000 }); 33 | console.log("result 1 : "); 34 | console.log(result); 35 | 36 | // do same query but explicitly state the $eq op 37 | // this will return array of all docs matching that filter 38 | result = users.find({ age: { $eq: 1000 } }); 39 | console.log("result 2 : "); 40 | console.log(result); 41 | 42 | // if we know we only want one record, use findOne instead 43 | // this will single object reference of (first) found item or null if not found 44 | result = users.findOne({ age: 1000 }); 45 | console.log("result 3 : "); 46 | console.log(result); 47 | 48 | // use a range operator ($gt) 49 | // returns all documents with age greater than 500 50 | result = users.find({age: { $gt: 500 }}); 51 | console.log("result 4 : "); 52 | console.log(result); 53 | 54 | // find implicit $and 55 | result = users.find({ age: 29, gender: "f" }); 56 | console.log("result 5 : "); 57 | console.log(result); 58 | 59 | // find explicit $and 60 | result = users.find({ $and: [ 61 | { age: 29}, 62 | { gender: "f" } 63 | ]}); 64 | console.log("result 6 : "); 65 | console.log(result); 66 | 67 | // find users in an age range 68 | result = users.find({ age: { $between: [20, 40] } }); 69 | console.log("result 7 : "); 70 | console.log(result); 71 | 72 | // find within nested object by using dot notation 73 | result = users.find({ "attributes.eyes": 1 }); 74 | console.log("one eyed : "); 75 | console.log(result); 76 | 77 | // find where array property contains a value 78 | result = users.find({ items: { $contains: "eski" } }); 79 | console.log("frigg : "); 80 | console.log(result); 81 | 82 | // more array logic : find all users which have 2 elements in an 'items' property 83 | result = users.find({ items: { $size: 2 } }); 84 | console.log("users with 2 items : "); 85 | console.log(result); 86 | 87 | // filter using a javascript "where" filter 88 | // filter for users who's age is 400 89 | result = users.where(function(obj) { 90 | return obj.age === 400; 91 | }); 92 | console.log("where filter: "); 93 | console.log(result); 94 | 95 | 96 | // update a document 97 | var mimir = users.findOne({name: "mimir" }); 98 | mimir.age = 998; 99 | users.update(mimir); 100 | 101 | // remove a document by id 102 | users.remove(mimir.$loki); 103 | 104 | // remove a document by instance 105 | var heimdallr = users.findOne({name: "heimdallr" }); 106 | users.remove(heimdallr); 107 | 108 | console.log(""); 109 | console.log("deleted 2 items, current user count : " + users.count()); 110 | -------------------------------------------------------------------------------- /examples/quickstart-dynview.js: -------------------------------------------------------------------------------- 1 | /** 2 | * quickstart-dynview.js : example demonstrating lokijs 'dynamic view' usage 3 | * 4 | * This example will use persistence and set up a dynamic view in the databaseInitialize 5 | * autoloadCallback. 6 | * 7 | * We'll just use the default LokiFsAdapter for this. 8 | */ 9 | 10 | const loki = require('../src/lokijs.js'); 11 | 12 | var db = new loki('quickstart-dynview.db', { 13 | autoload: true, 14 | autoloadCallback : databaseInitialize, 15 | autosave: true, 16 | autosaveInterval: 4000 // save every four seconds for our example 17 | }); 18 | 19 | /** 20 | * Initialize the database with : 21 | * - our collections (if first run and they don't exist) 22 | * - our dynamic view (if first run and they don't exist) 23 | * - example data seeding 24 | * 25 | * Other applications might need to also 26 | * - reapply and 'where' filters in any dynamicviews 27 | * - you might initiatialize some collection transforms 28 | * - which can be used with or without dynamic views 29 | */ 30 | function databaseInitialize() { 31 | var users = db.getCollection("users"); 32 | 33 | // on first run, this will be null so add collection 34 | if (!users) { 35 | users = db.addCollection("users"); 36 | } 37 | 38 | // on first run, add the dynamic view to the collection 39 | if (!users.getDynamicView("over 500")) { 40 | // add empty dynamic view 41 | let ov500 = users.addDynamicView("over 500"); 42 | 43 | // apply a find filter, you can do add as many find filters as 44 | // you want and these filters will be saved along with the database 45 | // and its collections. 46 | ov500.applyFind({ age: { $gte : 500 } }); 47 | 48 | // apply a sort (if you need to) 49 | ov500.applySimpleSort('age', true); 50 | } 51 | 52 | // if we needed 'where' filters our persisted database, we would need to 53 | // reapply everytime. (commented out since we don't want to use it in this example) 54 | // ov500.applyWhere(function(obj) { return obj.gender === 'm'; }); 55 | 56 | // for this example we will also seed data on first run 57 | if (users.count() === 0) { 58 | seedData(); 59 | } 60 | 61 | // at this point all collections and dynamic view should exist 62 | // so go ahead and run your program logic 63 | runProgramLogic(); 64 | } 65 | 66 | /** 67 | * example-specific seeding of user data 68 | */ 69 | function seedData() { 70 | var users = db.getCollection("users"); 71 | 72 | users.insert({ name: "odin", gender: "m", age: 1000, tags : ["woden", "knowlege", "sorcery", "frenzy", "runes"], items: ["gungnir"], attributes: { eyes: 1} }); 73 | users.insert({ name: "frigg", gender: "f", age: 800, tags: ["foreknowlege"], items: ["eski"] }); 74 | users.insert({ name: "thor", gender: "m", age: 35, items: ["mjolnir", "beer"] }); 75 | users.insert({ name: "sif", gender: "f", age: 30 }); 76 | users.insert({ name: "loki", gender: "m", age: 29 }); 77 | users.insert({ name: "sigyn", gender: "f", age: 29 }); 78 | users.insert({ name: "freyr", age: 400 }); 79 | users.insert({ name: "heimdallr", age: 99 }); 80 | users.insert({ name: "mimir", age: 999 }); 81 | } 82 | 83 | 84 | /** 85 | * Logic to run after database is initialized 86 | * 87 | * Our example program will add one document each time this program is run. 88 | * We will then dump the view so you can see if it passed the view filter. 89 | * 90 | */ 91 | function runProgramLogic() { 92 | var users = db.getCollection("users"); 93 | var ov500 = users.getDynamicView("over 500"); 94 | 95 | // generate random number between 1-1000 96 | // since our view is 'over500' there is 50% change it will be 97 | // in our dynamic view results 98 | var randomAge = Math.floor(Math.random() * 1000) + 1; 99 | // another 50% chance of being male 100 | var randomGender = Math.floor(Math.random() * 2); 101 | 102 | var newUser = { 103 | name : "user #" + users.count(), 104 | age : randomAge, 105 | gender : (randomGender?"m":"f") 106 | } 107 | 108 | console.log(""); 109 | console.log("adding user : "); 110 | console.log(newUser); 111 | console.log(""); 112 | 113 | users.insert(newUser); 114 | 115 | let result = ov500.data(); 116 | console.log("'over 500' dynamic view results : "); 117 | console.log(result); 118 | console.log(""); 119 | 120 | // now let's take our 'generic' bulk filtering dynamic view and further query its 'current' results 121 | // to find only the gender 'm' users in the dynamic view results. 122 | result = ov500.branchResultset().find({gender: 'm'}).data(); 123 | console.log("over 500 males : "); 124 | console.log(result); 125 | console.log(""); 126 | 127 | // branchResultset can also take transforms if you want to craft frequently used transformations 128 | // on view results... see the wiki page on collection transforms for examples of that if interested. 129 | 130 | console.log("press ctrl-c to quit"); 131 | 132 | } 133 | 134 | // All our logic ran in runProgramLogic so we are done... 135 | 136 | // We could have -not- enabled the autosave options in loki constructor 137 | // and then made a call to db.saveDatabase() as the last line of our runProgramLogic() 138 | // function if we wanted the program to end automatically. 139 | 140 | // Since autosave timer keeps program from exiting, we exit this program by ctrl-c. 141 | // (optionally) For best practice, lets use the standard exit events to force a db flush to disk 142 | // if autosave timer has not had a fired yet (if exiting before 4 seconds). 143 | process.on('SIGINT', function() { 144 | console.log("flushing database"); 145 | 146 | // db.close() will save the database -if- any collections are marked as 'dirty' 147 | db.close(); 148 | }); 149 | 150 | -------------------------------------------------------------------------------- /examples/quickstart-transforms.js: -------------------------------------------------------------------------------- 1 | /** 2 | * quickstart-transforms.js : example demonstrating lokijs 'collection transform' usage 3 | * 4 | * This example will use persistence and set up a transforms in the databaseInitialize 5 | * autoloadCallback. 6 | * 7 | * Collection Transforms are object representations of a query 'chain' which 8 | * can be named and used similarly to a stored procedure. 9 | * 10 | * They can also be used when branching dynamic views to created named 'extracts' 11 | * 12 | * We'll just use the default LokiFsAdapter for this. 13 | */ 14 | 15 | const loki = require('../src/lokijs.js'); 16 | 17 | var db = new loki('quickstart-transforms.db', { 18 | autoload: true, 19 | autoloadCallback : databaseInitialize, 20 | autosave: true, 21 | autosaveInterval: 4000 // save every four seconds for our example 22 | }); 23 | 24 | /** 25 | * Initialize the database with : 26 | * - our collections (if first run and they don't exist) 27 | * - our named collection transforms (if first run and they don't exist) 28 | * - example data seeding 29 | * - we will also add a dynamic view from dynview example. 30 | */ 31 | function databaseInitialize() { 32 | var users = db.getCollection("users"); 33 | 34 | // on first run, this will be null so add collection 35 | if (!users) { 36 | users = db.addCollection("users"); 37 | } 38 | 39 | // add simple 1 step 'females' transform on first run 40 | if (!users.getTransform("females")) { 41 | users.addTransform("females", [{ type: 'find', value: { gender: 'f' }}]); 42 | } 43 | 44 | // simple parameterized document paging transform 45 | if (!users.getTransform("paged")) { 46 | users.addTransform("paged", [ 47 | { 48 | type: 'offset', 49 | value: '[%lktxp]pageStart' 50 | }, 51 | { 52 | type: 'limit', 53 | value: '[%lktxp]pageSize' 54 | } 55 | ]); 56 | } 57 | 58 | // let's keep the dynamic view from dynview example without its sort 59 | if (!users.getDynamicView("over 500")) { 60 | let ov500 = users.addDynamicView("over 500"); 61 | ov500.applyFind({ age: { $gte : 500 } }); 62 | } 63 | 64 | // for this example we will also seed data on first run 65 | if (users.count() === 0) { 66 | seedData(); 67 | } 68 | 69 | // at this point all collections, transforms and dynamic view should exist 70 | // so go ahead and run your program logic 71 | runProgramLogic(); 72 | } 73 | 74 | /** 75 | * example-specific seeding of user data 76 | */ 77 | function seedData() { 78 | var users = db.getCollection("users"); 79 | 80 | users.insert({ name: "odin", gender: "m", age: 1000, tags : ["woden", "knowlege", "sorcery", "frenzy", "runes"], items: ["gungnir"], attributes: { eyes: 1} }); 81 | users.insert({ name: "frigg", gender: "f", age: 800, tags: ["foreknowlege"], items: ["eski"] }); 82 | users.insert({ name: "thor", gender: "m", age: 35, items: ["mjolnir", "beer"] }); 83 | users.insert({ name: "sif", gender: "f", age: 30 }); 84 | users.insert({ name: "loki", gender: "m", age: 29 }); 85 | users.insert({ name: "sigyn", gender: "f", age: 29 }); 86 | users.insert({ name: "freyr", age: 400 }); 87 | users.insert({ name: "heimdallr", age: 99 }); 88 | users.insert({ name: "mimir", age: 999 }); 89 | } 90 | 91 | /** 92 | * Logic to run after database is initialized 93 | * 94 | * Our example add 1 randomly generated user each time the program is run. 95 | * 96 | * Let's run some transforms... 97 | * 98 | */ 99 | function runProgramLogic() { 100 | var users = db.getCollection("users"); 101 | var ov500 = users.getDynamicView("over 500"); 102 | 103 | // generate random user and add 104 | var randomAge = Math.floor(Math.random() * 1000) + 1; 105 | var randomGender = Math.floor(Math.random() * 2); 106 | var newUser = { 107 | name : "user #" + users.count(), 108 | age : randomAge, 109 | gender : (randomGender?"m":"f") 110 | } 111 | users.insert(newUser); 112 | 113 | console.log("---- added user ----"); 114 | console.log(newUser); 115 | console.log(""); 116 | 117 | // lets get all female users in the users collection 118 | let result = users.chain("females").data(); 119 | console.log("females:"); 120 | console.log(result); 121 | console.log(""); 122 | 123 | // let's use this within a chain 124 | result = users.chain().find({age: { $between: [300,700] } }).transform("females").data(); 125 | console.log("females between 300-700 (empty initially) : "); 126 | console.log(result); 127 | console.log(""); 128 | 129 | // if the 'females' transform filtered the results better, we might re-word this query as : 130 | result = users.chain("females").find({age: { $between: [300,700] } }).data(); 131 | console.log("females between 300-700 (empty initially) : "); 132 | console.log(result); 133 | console.log(""); 134 | 135 | 136 | // now let's use the transform as an extract for our dynamic view 137 | result = ov500.branchResultset("females").data(); 138 | console.log("over 500 females : "); 139 | console.log(result); 140 | console.log(""); 141 | 142 | // now let's hook up a paging algorithm to grab 1st page of 5 document page 143 | let page = 1, 144 | pageSize = 5, 145 | start = (page-1)*pageSize; 146 | 147 | // so transforms can be passed to chain() and transform() methods on collection as well 148 | // as to dynamic view's branchResultset()... in all instances you can pass parameters as an 149 | // optional parameter hash object. 150 | 151 | // call our parameterized transform as a dynamic view extract 152 | result = ov500.branchResultset("paged", { pageStart: start, pageSize: pageSize }).data(); 153 | console.log("1st through 5th users over 500 : "); 154 | console.log(result); 155 | console.log(""); 156 | 157 | // mix multiple transforms into a basic query chain... 158 | result = users 159 | .chain() 160 | .find({ age: { $gt: 200 } }) 161 | .transform("females") 162 | .transform("paged", { pageStart: start, pageSize: pageSize }) 163 | .data(); 164 | 165 | console.log("first page (1-5) of females over 200"); 166 | console.log(result); 167 | console.log(""); 168 | 169 | console.log("press ctrl-c to quit"); 170 | 171 | } 172 | 173 | // All our logic ran in runProgramLogic so we are done... 174 | 175 | // We could have -not- enabled the autosave options in loki constructor 176 | // and then made a call to db.saveDatabase() as the last line of our runProgramLogic() 177 | // function if we wanted the program to end automatically. 178 | 179 | // Since autosave timer keeps program from exiting, we exit this program by ctrl-c. 180 | // (optionally) For best practice, lets use the standard exit events to force a db flush to disk 181 | // if autosave timer has not had a fired yet (if exiting before 4 seconds). 182 | process.on('SIGINT', function() { 183 | console.log("flushing database"); 184 | 185 | // db.close() will save the database -if- any collections are marked as 'dirty' 186 | db.close(); 187 | }); 188 | 189 | -------------------------------------------------------------------------------- /examples/quickstart1.js: -------------------------------------------------------------------------------- 1 | // quickstart1.js example : 2 | // This exmple does not save the database at all but just uses loki as an in-memory database with no persistence. 3 | // Since loki is synchronous -except- when dealing with persistence (I/O), 4 | // and this database uses no persistence, this example can deal with loki entirely synchronously. 5 | 6 | const loki = require('../src/lokijs.js'); 7 | 8 | var db = new loki("quickstart1.db"); 9 | var users = db.addCollection("users"); 10 | 11 | users.insert({name:'odin', age: 50}); 12 | users.insert({name:'thor', age: 35}); 13 | 14 | var result = users.find({ age : { $lte: 35 } }); 15 | 16 | // dumps array with 1 doc (thor) to console 17 | console.log(result); 18 | -------------------------------------------------------------------------------- /examples/quickstart2.js: -------------------------------------------------------------------------------- 1 | // quickstart2.js example : 2 | // This exmple uses the default persistence adapter for node environment (LokiFsAdapter), to persist its database. 3 | // To better handle asynchronous adapters and to write code that can handle any adapter, 4 | // you can use the following as example for setting up your initialization logic. 5 | 6 | const loki = require('../src/lokijs.js'); 7 | 8 | // We will use autoload (one time load at instantiation), and autosave with 4 sec interval. 9 | var db = new loki('quickstart2.db', { 10 | autoload: true, 11 | autoloadCallback : databaseInitialize, 12 | autosave: true, 13 | autosaveInterval: 4000 // save every four seconds for our example 14 | }); 15 | 16 | // implement the autoloadback referenced in loki constructor 17 | function databaseInitialize() { 18 | // on the first load of (non-existent database), we will have no collections so we can 19 | // detect the absence of our collections and add (and configure) them now. 20 | var entries = db.getCollection("entries"); 21 | if (entries === null) { 22 | entries = db.addCollection("entries"); 23 | } 24 | 25 | // kick off any program logic or start listening to external events 26 | runProgramLogic(); 27 | } 28 | 29 | // While we could have done this in our databaseInitialize function, 30 | // lets split out the logic to run 'after' initialization into this 'runProgramLogic' function 31 | function runProgramLogic() { 32 | var entries = db.getCollection("entries"); 33 | var entryCount = entries.count(); 34 | var now = new Date(); 35 | 36 | console.log("old number of entries in database : " + entryCount); 37 | 38 | entries.insert({ x: now.getTime(), y: 100 - entryCount }); 39 | entryCount = entries.count(); 40 | 41 | console.log("new number of entries in database : " + entryCount); 42 | console.log(""); 43 | console.log("Wait 4 seconds for the autosave timer to save our new addition and then press [Ctrl-c] to quit") 44 | console.log("If you waited 4 seconds, the next time you run this script the numbers should increase by 1"); 45 | } -------------------------------------------------------------------------------- /examples/quickstart3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * quickstart3.js example for lokijs (running in node.js environment) 3 | * 4 | * This exmple uses a higher performance, and better scaling LokiFsStructuredAdapter to persist its database. 5 | * This example uses autosave/autoload and we trap SIGINT (ctrl-c) to flush database on exit. 6 | * This example sets up multiple collections for our adapter (which has built in partitioning) to split out database into. 7 | */ 8 | 9 | const loki = require('../src/lokijs.js'); 10 | const lfsa = require('../src/loki-fs-structured-adapter.js'); 11 | 12 | var db = new loki('quickstart3.db', { 13 | adapter: new lfsa(), 14 | autoload: true, 15 | autoloadCallback : databaseInitialize, 16 | autosave: true, 17 | autosaveInterval: 4000 18 | }); 19 | 20 | // Since autosave timer keeps program from exiting, we exit this program by ctrl-c. 21 | // (optionally) For best practice, lets use the standard exit events to force a db flush to disk 22 | // if autosave timer has not had a fired yet (if exiting before 4 seconds). 23 | process.on('SIGINT', function() { 24 | console.log("flushing database"); 25 | db.close(); 26 | }); 27 | 28 | // Now let's implement the autoload callback referenced in loki constructor 29 | function databaseInitialize() { 30 | var entries = db.getCollection("entries"); 31 | var messages = db.getCollection("messages"); 32 | 33 | // Since our LokiFsStructuredAdapter is partitioned, the default 'quickstart3.db' 34 | // file will actually contain only the loki database shell and each of the collections 35 | // will be saved into independent 'partition' files with numeric suffix. 36 | 37 | // Add our main example collection if this is first run. 38 | // This collection will save into a partition named quickstart3.db.0 (collection 0) 39 | if (entries === null) { 40 | // first time run so add and configure collection with some arbitrary options 41 | entries = db.addCollection("entries", { indices: ['x'], clone: true }); 42 | } 43 | 44 | // Now let's add a second collection only to prove that this saved partition (quickstart3.db.1) 45 | // doesn't need to be saved every time the other partitions do if it never gets any changes 46 | // which need to be saved. The first time we run this should be the only time we save it. 47 | if (messages === null) { 48 | messages = db.addCollection("messages"); 49 | messages.insert({ txt: "i will only insert into this collection during databaseInitialize" }); 50 | } 51 | 52 | // kick off any program logic or start listening to external events 53 | runProgramLogic(); 54 | } 55 | 56 | // While we could have done this in our databaseInitialize function, 57 | // lets go ahead and split out the logic to run 'after' initialization into this 'runProgramLogic' function 58 | function runProgramLogic() { 59 | var entries = db.getCollection("entries"); 60 | var entryCount = entries.count(); 61 | var now = new Date(); 62 | 63 | console.log("old number of entries in database : " + entryCount); 64 | 65 | entries.insert({ x: now.getTime(), y: 100 - entryCount }); 66 | entryCount = entries.count(); 67 | 68 | console.log("new number of entries in database : " + entryCount); 69 | console.log(""); 70 | 71 | console.log("since autosave timer keeps program from existing, press ctrl-c to quit"); 72 | } -------------------------------------------------------------------------------- /examples/quickstart4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * quickstart4.js example for lokijs with manual loading and saving 3 | * 4 | * This example shows how you can manually load and save your loki 5 | * database if do not need or want to use the 'autosave' functionality. 6 | * 7 | * Since most of loki's persistence adapters are asynchronous this example 8 | * shows you still need to use the appropriate callbacks to ensure those 9 | * processes complete before you reload. 10 | */ 11 | 12 | const loki = require('../src/lokijs.js'); 13 | 14 | var db = new loki('quickstart4.db'); 15 | 16 | // set up an initialize function for first load (when db hasn't been created yet) 17 | function databaseInitialize() { 18 | var entries = db.getCollection("entries"); 19 | var messages = db.getCollection("messages"); 20 | 21 | // Add our main example collection if this is first run. 22 | // This collection will save into a partition named quickstart3.db.0 (collection 0) 23 | if (entries === null) { 24 | // first time run so add and configure collection with some arbitrary options 25 | entries = db.addCollection("entries"); 26 | } 27 | 28 | if (messages === null) { 29 | messages = db.addCollection("messages"); 30 | messages.insert({ txt: "i will only insert into this collection during databaseInitialize" }); 31 | } 32 | } 33 | 34 | // place any bootstrap logic which needs to be run after loadDatabase has completed 35 | function runProgramLogic() { 36 | var entries = db.getCollection("entries"); 37 | var entryCount = entries.count(); 38 | var now = new Date(); 39 | 40 | console.log("old number of entries in database : " + entryCount); 41 | 42 | entries.insert({ x: now.getTime(), y: 100 - entryCount }); 43 | entryCount = entries.count(); 44 | 45 | console.log("new number of entries in database : " + entryCount); 46 | console.log(""); 47 | 48 | // manually save 49 | db.saveDatabase(function(err) { 50 | if (err) { 51 | console.log(err); 52 | } 53 | else { 54 | console.log("saved... it can now be loaded or reloaded with up to date data"); 55 | } 56 | }); 57 | } 58 | 59 | console.log(""); 60 | console.log("Loading database..."); 61 | 62 | // manual bootstrap 63 | db.loadDatabase({}, function(err) { 64 | databaseInitialize(); 65 | console.log("db initialized"); 66 | runProgramLogic(); 67 | console.log("program logic run but it's save database probably not finished yet"); 68 | }); 69 | 70 | console.log("wait for it..."); -------------------------------------------------------------------------------- /karma.build.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Mar 25 2015 21:28:22 GMT+0000 (GMT) 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | browserNoActivityTimeout: 50000, 11 | 12 | // frameworks to use 13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 14 | frameworks: ['jasmine'], 15 | 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | 'build/lokijs.min.js', 20 | 'src/incremental-indexeddb-adapter.js', 21 | 'spec/helpers/assert-helpers.js', 22 | 'spec/generic/*.js', 23 | 'spec/browser/*.js' 24 | ], 25 | 26 | 27 | // list of files to exclude 28 | exclude: [], 29 | 30 | 31 | // preprocess matching files before serving them to the browser 32 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 33 | preprocessors: { 34 | // source files, that you wanna generate coverage for 35 | // do not include tests or libraries 36 | // (these files will be instrumented by Istanbul) 37 | }, 38 | 39 | 40 | // test results reporter to use 41 | // possible values: 'dots', 'progress' 42 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 43 | // coverage reporter generates the coverage 44 | reporters: ['dots'], 45 | 46 | 47 | // web server port 48 | port: 9876, 49 | 50 | hostname: 'localhost', 51 | 52 | 53 | // enable / disable colors in the output (reporters and logs) 54 | colors: true, 55 | 56 | 57 | // level of logging 58 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 59 | logLevel: config.LOG_INFO, 60 | 61 | 62 | // enable / disable watching file and executing tests whenever any file changes 63 | autoWatch: true, 64 | 65 | 66 | // start these browsers 67 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 68 | browsers: ['PhantomJS'], 69 | 70 | 71 | // Continuous Integration mode 72 | // if true, Karma captures browsers, runs the tests and exits 73 | singleRun: false 74 | }); 75 | }; 76 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed Mar 25 2015 21:28:22 GMT+0000 (GMT) 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | browserNoActivityTimeout: 50000, 11 | 12 | // frameworks to use 13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 14 | frameworks: ['jasmine'], 15 | 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | 'src/lokijs.js', 20 | 'src/incremental-indexeddb-adapter.js', 21 | 'spec/helpers/assert-helpers.js', 22 | 'spec/generic/*.js', 23 | 'spec/browser/*.js' 24 | ], 25 | 26 | 27 | // list of files to exclude 28 | exclude: [], 29 | 30 | 31 | // preprocess matching files before serving them to the browser 32 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 33 | preprocessors: { 34 | // source files, that you wanna generate coverage for 35 | // do not include tests or libraries 36 | // (these files will be instrumented by Istanbul) 37 | 'src/**/*.js': ['coverage'] 38 | }, 39 | 40 | 41 | // test results reporter to use 42 | // possible values: 'dots', 'progress' 43 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 44 | // coverage reporter generates the coverage 45 | reporters: ['dots', 'coverage'], 46 | 47 | // optionally, configure the reporter 48 | coverageReporter: { 49 | type: 'html', 50 | dir: 'coverage/' 51 | }, 52 | 53 | 54 | // web server port 55 | port: 9876, 56 | 57 | hostname: 'localhost', 58 | 59 | 60 | // enable / disable colors in the output (reporters and logs) 61 | colors: true, 62 | 63 | 64 | // level of logging 65 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 66 | logLevel: config.LOG_INFO, 67 | 68 | 69 | // enable / disable watching file and executing tests whenever any file changes 70 | autoWatch: true, 71 | 72 | 73 | // start these browsers 74 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 75 | browsers: ['PhantomJS'], 76 | 77 | 78 | // Continuous Integration mode 79 | // if true, Karma captures browsers, runs the tests and exits 80 | singleRun: false 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lokijs", 3 | "version": "1.5.12", 4 | "description": "Fast document oriented javascript in-memory database", 5 | "homepage": "https://techfort.github.io/LokiJS/", 6 | "main": "src/lokijs.js", 7 | "directories": { 8 | "example": "examples" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/techfort/LokiJS.git" 13 | }, 14 | "keywords": [ 15 | "javascript", 16 | "document-oriented", 17 | "mmdb", 18 | "json", 19 | "nosql", 20 | "lokijs", 21 | "in-memory", 22 | "indexeddb" 23 | ], 24 | "scripts": { 25 | "lint": "jshint src", 26 | "test:browser": "karma start karma.conf.js --single-run", 27 | "test:node": "istanbul cover --dir coverage/nodejs node_modules/jasmine/bin/jasmine.js", 28 | "pretest": "npm run lint", 29 | "test": "npm run test:browser && npm run test:node", 30 | "build:lokijs": "uglifyjs src/lokijs.js > build/lokijs.min.js", 31 | "build:indexedAdapter": "uglifyjs src/loki-indexed-adapter.js > build/loki-indexed-adapter.min.js", 32 | "build": "npm run build:lokijs && npm run build:indexedAdapter", 33 | "postbuild": "karma start karma.build.conf.js --single-run", 34 | "prepublish": "npm run build", 35 | "clean": "rimraf build/* coverage/* node_modules", 36 | "pour:beer": "echo New npm version published, one beer for you !", 37 | "jsdoc": "./node_modules/.bin/jsdoc -c jsdoc-conf.json", 38 | "benchmark": "node benchmark/benchmark" 39 | }, 40 | "author": "Joe Minichino ", 41 | "contributors": [ 42 | { 43 | "name": "Dave", 44 | "email": "github@obeliskos.com" 45 | } 46 | ], 47 | "license": "MIT", 48 | "bugs": { 49 | "url": "https://github.com/techfort/LokiJS/issues" 50 | }, 51 | "dependencies": {}, 52 | "devDependencies": { 53 | "istanbul": "^0.4.4", 54 | "jasmine": "^2.4.1", 55 | "jsdoc": "^3.5.5", 56 | "jshint": "^2.9.2", 57 | "karma": "^1.1.2", 58 | "karma-cli": "^1.0.1", 59 | "karma-coverage": "^1.1.1", 60 | "karma-jasmine": "^1.0.2", 61 | "karma-phantomjs-launcher": "^1.0.1", 62 | "mocha": "^2.5.3", 63 | "phantomjs": "^1.9.20", 64 | "rimraf": "^2.5.4", 65 | "should": "^4.6.5", 66 | "uglify-js": "^2.7.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /presentations/lokijs.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/presentations/lokijs.odp -------------------------------------------------------------------------------- /presentations/lokijs.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techfort/LokiJS/25b9a33d57509717d43b4da06d92064f2b7a6c95/presentations/lokijs.pptx -------------------------------------------------------------------------------- /spec/browser/README.md: -------------------------------------------------------------------------------- 1 | This folder contains Jasmine test specifications which can only be tested both in a browser 2 | -------------------------------------------------------------------------------- /spec/browser/incrementalIdb.spec.js: -------------------------------------------------------------------------------- 1 | describe('IncrementalIndexedDBAdapter', function () { 2 | it('initializes Loki properly', function() { 3 | var adapter = new IncrementalIndexedDBAdapter('tests'); 4 | var db = new loki('test.db', { adapter: adapter }); 5 | var coll = db.addCollection('coll'); 6 | 7 | expect(db.isIncremental).toBe(true); 8 | expect(coll.isIncremental).toBe(true); 9 | expect(adapter.chunkSize).toBe(100); 10 | expect(adapter.mode).toBe('incremental'); 11 | }) 12 | function checkDatabaseCopyIntegrity(source, copy) { 13 | source.collections.forEach(function(sourceCol, i) { 14 | var copyCol = copy.collections[i]; 15 | expect(copyCol.name).toBe(sourceCol.name); 16 | expect(copyCol.data.length).toBe(sourceCol.data.length); 17 | 18 | copyCol.data.every(function(copyEl, elIndex) { 19 | expect(JSON.stringify(copyEl)).toBe(JSON.stringify(source.collections[i].data[elIndex])) 20 | }) 21 | }) 22 | } 23 | it('checkDatabaseCopyIntegrity works', function() { 24 | var db = new loki('test.db'); 25 | var col1 = db.addCollection('test_collection'); 26 | 27 | var doc1 = { foo: '1' }; 28 | var doc2 = { foo: '2' }; 29 | var doc3 = { foo: '3' }; 30 | col1.insert([doc1, doc2, doc3]); 31 | doc2.bar = 'true'; 32 | col1.update(doc2); 33 | col1.remove(doc3); 34 | 35 | // none of these should throw 36 | checkDatabaseCopyIntegrity(db, db); 37 | checkDatabaseCopyIntegrity(db, db.copy()); 38 | checkDatabaseCopyIntegrity(db, JSON.parse(db.serialize())); 39 | 40 | // this should throw 41 | // expect(function () { 42 | // var copy = db.copy(); 43 | // copy.collections[0].data.push({ hello: '!'}) 44 | // checkDatabaseCopyIntegrity(db, copy); 45 | // }).toThrow(); 46 | }) 47 | // it('basically works', function(done) { 48 | // var adapter = new IncrementalIndexedDBAdapter('tests'); 49 | // var db = new loki('test.db', { adapter: adapter }); 50 | // var col1 = db.addCollection('test_collection'); 51 | 52 | // col1.insert({ customId: 0, val: 'hello', constraints: 100 }); 53 | // col1.insert({ customId: 1, val: 'hello1' }); 54 | // var h2 = col1.insert({ customId: 2, val: 'hello2' }); 55 | // var h3 = col1.insert({ customId: 3, val: 'hello3' }); 56 | // var h4 = col1.insert({ customId: 4, val: 'hello4' }); 57 | // var h5 = col1.insert({ customId: 5, val: 'hello5' }); 58 | 59 | // h2.val = 'UPDATED'; 60 | // col1.update(h2); 61 | 62 | // h3.val = 'UPDATED'; 63 | // col1.update(h3); 64 | // h3.val2 = 'added!'; 65 | // col1.update(h3); 66 | 67 | // col1.remove(h4); 68 | 69 | // var h6 = col1.insert({ customId: 6, val: 'hello6' }); 70 | 71 | // db.saveDatabase(function (e) { 72 | // expect(e).toBe(undefined); 73 | 74 | // var adapter2 = new IncrementalIndexedDBAdapter('tests'); 75 | // var db2 = new loki('test.db', { adapter: adapter2 }); 76 | 77 | // db2.loadDatabase({}, function (e) { 78 | // expect(e).toBe(undefined); 79 | 80 | // checkDatabaseCopyIntegrity(db, db2); 81 | // done() 82 | // }); 83 | // }); 84 | // }) 85 | // it('works with a lot of fuzzed data', function() { 86 | // }) 87 | // it('can delete database', function() { 88 | // }) 89 | // it('stores data in the expected format', function() { 90 | // }) 91 | // NOTE: Because PhantomJS doesn't support IndexedDB, I moved tests to spec/incrementalidb.html 92 | it('handles dirtyIds during save properly', function() { 93 | var adapter = new IncrementalIndexedDBAdapter('tests'); 94 | var db = new loki('test.db', { adapter: adapter }); 95 | var col1 = db.addCollection('test_collection'); 96 | var col2 = db.addCollection('test_collection2'); 97 | col2.dirty = false 98 | 99 | var doc1 = { foo: '1' }; 100 | var doc2 = { foo: '2' }; 101 | var doc3 = { foo: '3' }; 102 | col1.insert([doc1, doc2, doc3]); 103 | doc2.bar = 'true'; 104 | col1.update(doc2); 105 | col1.remove(doc3); 106 | 107 | var dirty = col1.dirtyIds; 108 | expect(dirty.length).toBe(5); 109 | expect(col1.dirty).toBe(true); 110 | expect(col2.dirty).toBe(false); 111 | 112 | // simulate save - don't go through IDB, just check that logic is good 113 | var callCallback; 114 | adapter.saveDatabase = function(dbname, getLokiCopy, callback) { 115 | getLokiCopy(); 116 | callCallback = callback; 117 | }; 118 | 119 | // dirty ids zero out and roll back in case of error 120 | db.saveDatabase(); 121 | expect(col1.dirtyIds).toEqual([]); 122 | expect(col1.dirty).toBe(false); 123 | callCallback(new Error('foo')); 124 | expect(col1.dirtyIds).toEqual(dirty); 125 | expect(col1.dirty).toBe(true); 126 | expect(col2.dirty).toBe(false); 127 | 128 | // new dirtied ids get added in case of rollback 129 | db.saveDatabase(); 130 | var doc4 = { foo: '4' }; 131 | col1.insert(doc4); 132 | expect(col1.dirtyIds).toEqual([doc4.$loki]); 133 | var doc5 = { foo: '5' }; 134 | col2.insert(doc5); 135 | expect(col2.dirty).toBe(true); 136 | expect(col2.dirtyIds).toEqual([doc5.$loki]); 137 | callCallback(new Error('foo')); 138 | expect(col1.dirtyIds).toEqual([doc4.$loki].concat(dirty)); 139 | expect(col1.dirty).toBe(true); 140 | expect(col2.dirtyIds).toEqual([doc5.$loki]); 141 | expect(col2.dirty).toBe(true); 142 | 143 | // if successful, dirty ids don't zero out 144 | db.saveDatabase(); 145 | expect(col1.dirtyIds).toEqual([]); 146 | var doc6 = { foo: '6' }; 147 | col1.insert(doc6); 148 | expect(col1.dirtyIds).toEqual([doc6.$loki]); 149 | callCallback(); 150 | expect(col1.dirtyIds).toEqual([doc6.$loki]); 151 | }) 152 | }) 153 | -------------------------------------------------------------------------------- /spec/generic/README.md: -------------------------------------------------------------------------------- 1 | This folder contains Jasmine test specifications which can be tested both in a browser and using nodejs -------------------------------------------------------------------------------- /spec/generic/autoUpdate.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof(window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('autoupdate', function () { 4 | 5 | it('auto updates inserted documents', function (done) { 6 | 7 | if (typeof Object.observe !== 'function') { 8 | done(); 9 | return; 10 | } 11 | 12 | var coll = new loki.Collection('test', { 13 | unique: ['name'], 14 | autoupdate: true 15 | }); 16 | 17 | coll.insert({ 18 | name: 'Jack' 19 | }); 20 | 21 | var doc = coll.insert({ 22 | name: 'Peter' 23 | }); 24 | 25 | function change1() { 26 | coll.on('update', function (target) { 27 | expect(target).toBe(doc); 28 | 29 | change2(); 30 | }); 31 | doc.name = 'John'; 32 | } 33 | 34 | function change2() { 35 | coll.on('error', function (err) { 36 | expect(err).toEqual(new Error('Duplicate key for property name: ' + doc.name)); 37 | done(); 38 | }); 39 | doc.name = 'Jack'; 40 | } 41 | 42 | change1(); 43 | }); 44 | 45 | it('auto updates documents loaded from storage', function (done) { 46 | 47 | if (typeof Object.observe !== 'function') { 48 | done(); 49 | return; 50 | } 51 | 52 | var db1 = new loki('autoupdate1.json'), 53 | db2 = new loki('autoupdate2.json'); 54 | 55 | var coll = db1.addCollection('test', { 56 | unique: ['name'], 57 | autoupdate: true 58 | }); 59 | 60 | var originalDocs = coll.insert([{ 61 | name: 'Jack' 62 | }, { 63 | name: 'Peter' 64 | }]); 65 | 66 | db2.loadJSON(db1.serialize()); 67 | 68 | coll = db2.getCollection('test'); 69 | 70 | var doc = coll.by('name', 'Peter'); 71 | 72 | expect(coll.autoupdate).toBe(true); 73 | expect(doc).toEqual(originalDocs[1]); 74 | 75 | function change1() { 76 | coll.on('update', function (target) { 77 | expect(target).toBe(doc); 78 | 79 | change2(); 80 | }); 81 | doc.name = 'John'; 82 | } 83 | 84 | function change2() { 85 | coll.on('error', function (err) { 86 | expect(err).toEqual(new Error('Duplicate key for property name: ' + doc.name)); 87 | done(); 88 | }); 89 | doc.name = 'Jack'; 90 | } 91 | 92 | change1(); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /spec/generic/changesApi.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof(window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('changesApi', function () { 4 | it('does what it says on the tin', function () { 5 | var db = new loki(), 6 | // gordian = require('gordian'), 7 | // suite = new gordian('testEvents'), 8 | options = { 9 | asyncListeners: false, 10 | disableChangesApi: false 11 | }, 12 | users = db.addCollection('users', options), 13 | test = db.addCollection('test', options), 14 | test2 = db.addCollection('test2', options); 15 | 16 | var u = users.insert({ 17 | name: 'joe' 18 | }); 19 | u.name = 'jack'; 20 | users.update(u); 21 | test.insert({ 22 | name: 'test' 23 | }); 24 | test2.insert({ 25 | name: 'test2' 26 | }); 27 | 28 | var userChanges = db.generateChangesNotification(['users']); 29 | 30 | expect(userChanges.length).toEqual(2); 31 | expect(db.serializeChanges(['users'])).toEqual(JSON.stringify(userChanges)); 32 | 33 | var someChanges = db.generateChangesNotification(['users', 'test2']); 34 | 35 | expect(someChanges.length).toEqual(3); 36 | var allChanges = db.generateChangesNotification(); 37 | 38 | expect(allChanges.length).toEqual(4); 39 | users.setChangesApi(false); 40 | expect(users.disableChangesApi).toEqual(true); 41 | 42 | u.name = 'john'; 43 | users.update(u); 44 | var newChanges = db.generateChangesNotification(['users']); 45 | 46 | expect(newChanges.length).toEqual(2); 47 | db.clearChanges(); 48 | 49 | expect(users.getChanges().length).toEqual(0); 50 | 51 | u.name = 'jim'; 52 | users.update(u); 53 | users.flushChanges(); 54 | 55 | expect(users.getChanges().length).toEqual(0); 56 | }); 57 | 58 | it('works with delta mode', function () { 59 | var db = new loki(), 60 | options = { 61 | asyncListeners: false, 62 | disableChangesApi: false, 63 | disableDeltaChangesApi: false 64 | }, 65 | items = db.addCollection('items', options ); 66 | 67 | // Add some documents to the collection 68 | items.insert({ name : 'mjolnir', owner: 'thor', maker: { name: 'dwarves', count: 1 } }); 69 | items.insert({ name : 'gungnir', owner: 'odin', maker: { name: 'elves', count: 1 } }); 70 | items.insert({ name : 'tyrfing', owner: 'Svafrlami', maker: { name: 'dwarves', count: 1 } }); 71 | items.insert({ name : 'draupnir', owner: 'odin', maker: { name: 'elves', count: 1 } }); 72 | 73 | // Find and update an existing document 74 | var tyrfing = items.findOne({'name': 'tyrfing'}); 75 | tyrfing.owner = 'arngrim'; 76 | items.update(tyrfing); 77 | tyrfing.maker.count = 4; 78 | items.update(tyrfing); 79 | 80 | var changes = db.serializeChanges(['items']); 81 | changes = JSON.parse(changes); 82 | 83 | expect(changes.length).toEqual(6); 84 | 85 | var firstUpdate = changes[4]; 86 | expect(firstUpdate.operation).toEqual('U'); 87 | expect(firstUpdate.obj.owner).toEqual('arngrim'); 88 | expect(firstUpdate.obj.name).toBeUndefined(); 89 | 90 | var secondUpdate = changes[5]; 91 | expect(secondUpdate.operation).toEqual('U'); 92 | expect(secondUpdate.obj.owner).toBeUndefined(); 93 | expect(secondUpdate.obj.maker).toEqual({ count: 4 }); 94 | 95 | }); 96 | 97 | it('batch operations work with delta mode', function() { 98 | var db = new loki(), 99 | options = { 100 | asyncListeners: false, 101 | disableChangesApi: false, 102 | disableDeltaChangesApi: false 103 | }, 104 | items = db.addCollection('items', options ); 105 | 106 | // Add some documents to the collection 107 | items.insert([ 108 | { name : 'mjolnir', owner: 'thor', maker: 'dwarves', count: 0 }, 109 | { name : 'gungnir', owner: 'odin', maker: 'elves', count: 0 }, 110 | { name : 'tyrfing', owner: 'Svafrlami', maker: 'dwarves', count: 0 }, 111 | { name : 'draupnir', owner: 'odin', maker: 'elves', count: 0 } 112 | ]); 113 | 114 | items.chain().update(function(o) { o.count++; }); 115 | 116 | var changes = db.serializeChanges(['items']); 117 | changes = JSON.parse(changes); 118 | 119 | expect(changes.length).toEqual(8); 120 | 121 | expect(changes[0].name).toEqual("items"); 122 | expect(changes[0].operation).toEqual("I"); 123 | expect(changes[1].name).toEqual("items"); 124 | expect(changes[1].operation).toEqual("I"); 125 | expect(changes[2].name).toEqual("items"); 126 | expect(changes[2].operation).toEqual("I"); 127 | expect(changes[3].name).toEqual("items"); 128 | expect(changes[3].operation).toEqual("I"); 129 | 130 | expect(changes[4].name).toEqual("items"); 131 | expect(changes[4].operation).toEqual("U"); 132 | expect(changes[4].obj.count).toEqual(1); 133 | expect(changes[5].name).toEqual("items"); 134 | expect(changes[5].operation).toEqual("U"); 135 | expect(changes[5].obj.count).toEqual(1); 136 | expect(changes[6].name).toEqual("items"); 137 | expect(changes[6].operation).toEqual("U"); 138 | expect(changes[6].obj.count).toEqual(1); 139 | expect(changes[7].name).toEqual("items"); 140 | expect(changes[7].operation).toEqual("U"); 141 | expect(changes[7].obj.count).toEqual(1); 142 | 143 | var keys = Object.keys(changes[7].obj); 144 | keys.sort(); 145 | expect(keys[0]).toEqual("$loki"); 146 | expect(keys[1]).toEqual("count"); 147 | expect(keys[2]).toEqual("meta"); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /spec/generic/dirtyIds.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof(window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('dirtyIds', function () { 4 | it('doesnt do anything unless using incremental adapters', function() { 5 | var db = new loki('test.db'); 6 | var coll = db.addCollection('coll'); 7 | 8 | var doc1 = { foo: '1' }; 9 | var doc2 = { foo: '2' }; 10 | var doc3 = { foo: '3' }; 11 | coll.insert([doc1, doc2, doc3]); 12 | doc2.bar = 'true'; 13 | coll.update(doc2); 14 | coll.remove(doc3); 15 | 16 | expect(coll.dirtyIds).toEqual([]); 17 | }) 18 | it('loki and db are incremental if adapter is incremental', function() { 19 | var adapter = { mode: 'incremental' }; 20 | var db = new loki('test.db', { adapter: adapter }); 21 | var coll = db.addCollection('coll'); 22 | 23 | expect(db.isIncremental).toBe(true); 24 | expect(coll.isIncremental).toBe(true); 25 | }) 26 | it('tracks inserts', function() { 27 | var adapter = { mode: 'incremental' }; 28 | var db = new loki('test.db', { adapter: adapter }); 29 | var coll = db.addCollection('coll'); 30 | 31 | var doc1 = { foo: '1' }; 32 | coll.insert(doc1); 33 | 34 | expect(coll.dirtyIds).toEqual([doc1.$loki]); 35 | }) 36 | it('tracks updates', function() { 37 | var adapter = { mode: 'incremental' }; 38 | var db = new loki('test.db', { adapter: adapter }); 39 | var coll = db.addCollection('coll'); 40 | 41 | var doc1 = { foo: '1' }; 42 | coll.insert(doc1); 43 | doc1.change = 'true'; 44 | coll.update(doc1); 45 | 46 | expect(coll.dirtyIds).toEqual([doc1.$loki, doc1.$loki]); 47 | }) 48 | it('tracks deletes', function() { 49 | var adapter = { mode: 'incremental' }; 50 | var db = new loki('test.db', { adapter: adapter }); 51 | var coll = db.addCollection('coll'); 52 | 53 | var doc1 = { foo: '1' }; 54 | coll.insert(doc1); 55 | var id = doc1.$loki; 56 | coll.remove(doc1); 57 | 58 | expect(coll.dirtyIds).toEqual([id, id]); 59 | }) 60 | it('tracks many changes', function() { 61 | var adapter = { mode: 'incremental' }; 62 | var db = new loki('test.db', { adapter: adapter }); 63 | var coll = db.addCollection('coll'); 64 | 65 | var doc1 = { foo: '1' }; 66 | var doc2 = { foo: '2' }; 67 | var doc3 = { foo: '3' }; 68 | coll.insert([doc1, doc2, doc3]); 69 | var doc3id = doc3.$loki; 70 | doc2.bar = 'true'; 71 | coll.update(doc2); 72 | coll.remove(doc3); 73 | 74 | expect(coll.dirtyIds).toEqual([doc1.$loki, doc2.$loki, doc3id, doc2.$loki, doc3id]); 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /spec/generic/eventEmitter.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof(window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('eventEmitter', function () { 4 | var db; 5 | 6 | beforeEach(function () { 7 | db = new loki('test', { 8 | persistenceMethod: null 9 | }), 10 | users = db.addCollection('users', { 11 | asyncListeners: false 12 | }); 13 | 14 | users.insert({ 15 | name: 'joe' 16 | }); 17 | }); 18 | 19 | it('async', function testAsync() { 20 | expect(db.asyncListeners).toBe(false); 21 | }); 22 | 23 | it('emit', function () { 24 | var index = db.on('test', function test(obj) { 25 | expect(obj).toEqual(42); 26 | }); 27 | 28 | db.emit('test', 42); 29 | db.removeListener('test', index); 30 | 31 | expect(db.events['test'].length).toEqual(0); 32 | 33 | expect(function testEvent() { 34 | db.emit('testEvent'); 35 | }).toThrow(new Error('No event testEvent defined')); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /spec/generic/helpers.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof(window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('Testing comparator helpers', function () { 4 | 5 | var ops; 6 | beforeEach(function() { 7 | ops = loki.LokiOps; 8 | }); 9 | 10 | it('$eq works as expected', function () { 11 | expect(ops.$eq(true, true)).toEqual(true); 12 | 13 | expect(ops.$eq(true, false)).toEqual(false); 14 | }); 15 | 16 | it('$aeq works as expected', function() { 17 | expect(ops.$aeq(4, '4')).toEqual(true); 18 | expect(ops.$aeq(4, 4)).toEqual(true); 19 | expect(ops.$aeq(3, 2)).toEqual(false); 20 | expect(ops.$aeq(3, 'three')).toEqual(false); 21 | expect(ops.$aeq('3', 3)).toEqual(true); 22 | expect(ops.$aeq('1.23', 1.23)).toEqual(true); 23 | }); 24 | 25 | it('$ne works as expected', function () { 26 | expect(ops.$ne(true, true)).toEqual(false); 27 | 28 | expect(ops.$ne(true, false)).toEqual(true); 29 | }); 30 | 31 | it('$in works as expected', function () { 32 | expect(ops.$in(4, [1, 3, 4])).toEqual(true); 33 | 34 | expect(ops.$in(8, [1, 3, 4])).toEqual(false); 35 | }); 36 | 37 | it('$nin works as expected', function () { 38 | expect(ops.$nin(4, [1, 3, 4])).toEqual(false); 39 | 40 | expect(ops.$nin(8, [1, 3, 4])).toEqual(true); 41 | }); 42 | 43 | it('$gt works as expected', function () { 44 | //Testing strategy: 45 | // First, only the same type data will be compared, 46 | // both with and without the third optional arg. 47 | // This includes all primitives*. 48 | // 49 | // Then complex* values will be compared. 50 | // 51 | // Finally, some tests will be ran trying to compare 52 | // values of different types. 53 | // 54 | // *Primitives: boolean, null, undefined, number, string 55 | // *Complex: date 56 | 57 | expect(ops.$gt(false, false)).toEqual(false); 58 | 59 | expect(ops.$gte(false, false)).toEqual(true); 60 | 61 | expect(ops.$gt(true, false)).toEqual(true); 62 | 63 | expect(ops.$gt(true, true)).toEqual(false); 64 | 65 | expect(ops.$gte(true, true)).toEqual(true); 66 | 67 | expect(ops.$gt(null, null)).toEqual(false); 68 | 69 | expect(ops.$gte(null, null)).toEqual(true); 70 | 71 | expect(ops.$gt(undefined, undefined)).toEqual(false); 72 | 73 | expect(ops.$gte(undefined, undefined)).toEqual(true); 74 | 75 | expect(ops.$gt(-1, 0)).toEqual(false); 76 | 77 | expect(ops.$gt(0, 0)).toEqual(false); 78 | 79 | expect(ops.$gte(0, 0)).toEqual(true); 80 | 81 | expect(ops.$gt(1, 0)).toEqual(true); 82 | 83 | expect(ops.$gt(new Date(2010), new Date(2015))).toEqual(false); 84 | 85 | expect(ops.$gt(new Date(2015), new Date(2015))).toEqual(false); 86 | 87 | expect(ops.$gte(new Date(2015), new Date(2015))).toEqual(true); 88 | 89 | // mixed type checking (or mixed falsy edge tests) 90 | expect(ops.$gt("14", 12)).toEqual(true); 91 | 92 | expect(ops.$gt(12, "14")).toEqual(false); 93 | 94 | expect(ops.$gt("10", 12)).toEqual(false); 95 | 96 | expect(ops.$gt(12, "10")).toEqual(true); 97 | 98 | expect(ops.$gt("test", 12)).toEqual(true); 99 | 100 | expect(ops.$gt(12, "test")).toEqual(false); 101 | 102 | expect(ops.$gt(12, 0)).toEqual(true); 103 | 104 | expect(ops.$gt(0, 12)).toEqual(false); 105 | 106 | expect(ops.$gt(12, "")).toEqual(true); 107 | 108 | expect(ops.$gt("", 12)).toEqual(false); 109 | }); 110 | 111 | it('$lt works as expected', function () { 112 | //Testing strategy: 113 | // First, only the same type data will be compared, 114 | // both with and without the third optional arg. 115 | // This includes all primitives*. 116 | // 117 | // Then complex* values will be compared. 118 | // 119 | // Finally, some tests will be ran trying to compare 120 | // values of different types. 121 | // 122 | // *Primitives: boolean, null, undefined, number, string 123 | // *Complex: date 124 | 125 | expect(ops.$lt(false, false)).toEqual(false); 126 | 127 | expect(ops.$lte(false, false)).toEqual(true); 128 | 129 | expect(ops.$lt(true, false)).toEqual(false); 130 | 131 | expect(ops.$lt(true, true)).toEqual(false); 132 | 133 | expect(ops.$lte(true, true)).toEqual(true); 134 | 135 | expect(ops.$lt(null, null)).toEqual(false); 136 | 137 | expect(ops.$lte(null, null)).toEqual(true); 138 | 139 | expect(ops.$lt(undefined, undefined)).toEqual(false); 140 | 141 | expect(ops.$lte(undefined, undefined)).toEqual(true); 142 | 143 | expect(ops.$lt(-1, 0)).toEqual(true); 144 | 145 | expect(ops.$lt(0, 0)).toEqual(false); 146 | 147 | expect(ops.$lte(0, 0)).toEqual(true); 148 | 149 | expect(ops.$lt(1, 0)).toEqual(false); 150 | 151 | expect(ops.$lt(new Date(2010), new Date(2015))).toEqual(true); 152 | 153 | expect(ops.$lt(new Date(2015), new Date(2015))).toEqual(false); 154 | 155 | expect(ops.$lte(new Date(2015), new Date(2015))).toEqual(true); 156 | 157 | // mixed type checking (or mixed falsy edge tests) 158 | expect(ops.$lt("12", 14)).toEqual(true); 159 | 160 | expect(ops.$lt(14, "12")).toEqual(false); 161 | 162 | expect(ops.$lt("10", 12)).toEqual(true); 163 | 164 | expect(ops.$lt(12, "10")).toEqual(false); 165 | 166 | expect(ops.$lt("test", 12)).toEqual(false); 167 | 168 | expect(ops.$lt(12, "test")).toEqual(true); 169 | 170 | expect(ops.$lt(12, 0)).toEqual(false); 171 | 172 | expect(ops.$lt(0, 12)).toEqual(true); 173 | 174 | expect(ops.$lt(12, "")).toEqual(false); 175 | 176 | expect(ops.$lt("", 12)).toEqual(true); 177 | }); 178 | 179 | }); 180 | -------------------------------------------------------------------------------- /spec/generic/joins.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof(window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('joins', function () { 4 | var db, directors, films; 5 | 6 | beforeEach(function () { 7 | db = new loki('testJoins', { 8 | persistenceMethod: null 9 | }), 10 | directors = db.addCollection('directors'), 11 | films = db.addCollection('films'); 12 | 13 | directors.insert([{ 14 | name: 'Martin Scorsese', 15 | directorId: 1 16 | }, { 17 | name: 'Francis Ford Coppola', 18 | directorId: 2 19 | }, { 20 | name: 'Steven Spielberg', 21 | directorId: 3 22 | }, { 23 | name: 'Quentin Tarantino', 24 | directorId: 4 25 | }]); 26 | 27 | films.insert([{ 28 | title: 'Taxi', 29 | filmId: 1, 30 | directorId: 1 31 | }, { 32 | title: 'Raging Bull', 33 | filmId: 2, 34 | directorId: 1 35 | }, { 36 | title: 'The Godfather', 37 | filmId: 3, 38 | directorId: 2 39 | }, { 40 | title: 'Jaws', 41 | filmId: 4, 42 | directorId: 3 43 | }, { 44 | title: 'ET', 45 | filmId: 5, 46 | directorId: 3 47 | }, { 48 | title: 'Raiders of the Lost Ark', 49 | filmId: 6, 50 | directorId: 3 51 | }]); 52 | }) 53 | 54 | it('works', function () { 55 | var joined; 56 | 57 | //Basic non-mapped join 58 | joined = films.eqJoin(directors.data, 'directorId', 'directorId').data(); 59 | expect(joined[0].left.title).toEqual('Taxi'); 60 | 61 | //Basic join with map 62 | joined = films.eqJoin(directors.data, 'directorId', 'directorId', function (left, right) { 63 | return { 64 | filmTitle: left.title, 65 | directorName: right.name 66 | } 67 | }).data(); 68 | expect(joined.length).toEqual(films.data.length); 69 | expect(joined[0].filmTitle).toEqual('Taxi'); 70 | expect(joined[0].directorName).toEqual('Martin Scorsese'); 71 | 72 | //Basic non-mapped join with chained map 73 | joined = films.eqJoin(directors.data, 'directorId', 'directorId') 74 | .map(function (obj) { 75 | return { 76 | filmTitle: obj.left.title, 77 | directorName: obj.right.name 78 | } 79 | }).data(); 80 | expect(joined[0].filmTitle).toEqual('Taxi'); 81 | expect(joined[0].directorName).toEqual('Martin Scorsese'); 82 | 83 | 84 | //Test filtered join 85 | joined = films 86 | .chain() 87 | .find({ 88 | directorId: 3 89 | }) 90 | .simplesort('title') 91 | .eqJoin(directors.data, 'directorId', 'directorId', function (left, right) { 92 | return { 93 | filmTitle: left.title, 94 | directorName: right.name 95 | } 96 | }) 97 | expect(joined.data().length).toEqual(3); 98 | 99 | //Test chaining after join 100 | joined.find({ 101 | filmTitle: 'Jaws' 102 | }); 103 | expect(joined.data()[0].filmTitle).toEqual('Jaws'); 104 | 105 | //Test calculated keys 106 | joined = films.chain().eqJoin(directors.data, 107 | function (director) { 108 | return director.directorId + 1 109 | }, 110 | function (film) { 111 | return film.directorId - 1 112 | }) 113 | .data(); 114 | 115 | expect(joined[0].right.name).toEqual('Steven Spielberg'); 116 | }); 117 | }); 118 | // var Loki = require('../src/lokijs.js'), 119 | // gordian = require('gordian'), 120 | // suite = new gordian('testJoins'), 121 | 122 | 123 | 124 | // suite.report(); 125 | -------------------------------------------------------------------------------- /spec/generic/kv.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof(window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('kv', function () { 4 | it('works', function () { 5 | var store = new loki.KeyValueStore(); 6 | var key = { 7 | name: 'joe' 8 | }, 9 | value = { 10 | position: 'developer' 11 | }; 12 | 13 | store.set('foo', 'bar'); 14 | store.set('bar', 'baz'); 15 | store.set('baz', 'quux'); 16 | store.set(key, value); 17 | expect('bar').toEqual(store.get('foo')); 18 | expect(value).toEqual(store.get(key)); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /spec/generic/remove.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof (window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('remove', function () { 4 | it('removes', function () { 5 | var db = new loki(); 6 | var users = db.addCollection('users'); 7 | 8 | users.insert({ 9 | name: 'joe', 10 | age: 39 11 | }); 12 | users.insert({ 13 | name: 'jack', 14 | age: 20 15 | }); 16 | users.insert({ 17 | name: 'jim', 18 | age: 40 19 | }); 20 | users.insert({ 21 | name: 'dave', 22 | age: 33 23 | }); 24 | users.insert({ 25 | name: 'jim', 26 | age: 29 27 | }); 28 | users.insert({ 29 | name: 'dave', 30 | age: 21 31 | }); 32 | 33 | var dv = users.addDynamicView('testview'); 34 | dv.applyWhere(function (obj) { 35 | return obj.name.length > 3; 36 | }); 37 | 38 | users.removeWhere(function (obj) { 39 | return obj.age > 35; 40 | }); 41 | expect(users.data.length).toEqual(4); 42 | users.removeWhere({ 43 | 'age': { 44 | $gt: 25 45 | } 46 | }); 47 | expect(users.data.length).toEqual(2); 48 | users.remove(6); 49 | expect(users.data.length).toEqual(1); 50 | users.removeDataOnly(); 51 | expect(users.data.length).toEqual(0); 52 | expect(!!users.getDynamicView('testview')).toEqual(true); 53 | 54 | 55 | var foo = { 56 | name: 'foo', 57 | age: 42 58 | }; 59 | users.insert(foo); 60 | expect(users.data.length).toEqual(1); 61 | var bar = users.remove(foo); 62 | expect(users.data.length).toEqual(0); 63 | // test that $loki and meta properties have been removed correctly to allow object re-insertion 64 | expect(!bar.$loki).toEqual(true); 65 | expect(!bar.meta).toEqual(true); 66 | users.insert(bar); 67 | expect(users.data.length).toEqual(1); 68 | }); 69 | 70 | it('removes with unique index', function () { 71 | var db = new loki(); 72 | var users1 = db.addCollection('userswithunique', { 73 | unique: ['username'] 74 | }); 75 | 76 | var joe = users1.insert({ 77 | username: 'joe', 78 | name: 'joe', 79 | age: 39 80 | }); 81 | var jack = users1.insert({ 82 | username: 'jack', 83 | name: 'jack', 84 | age: 20 85 | }); 86 | expect(users1.data.length).toEqual(2); 87 | users1.removeDataOnly(); 88 | expect(users1.data.length).toEqual(0); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /spec/generic/stage.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof (window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('Staging and commits', function () { 4 | beforeEach(function () { 5 | db = new loki('testJoins', { 6 | persistenceMethod: null 7 | }), 8 | directors = db.addCollection('directors'), 9 | films = db.addCollection('films'); 10 | 11 | directors.insert([{ 12 | name: 'Martin Scorsese', 13 | directorId: 1 14 | }, { 15 | name: 'Francis Ford Coppola', 16 | directorId: 2 17 | }, { 18 | name: 'Steven Spielberg', 19 | directorId: 3 20 | }, { 21 | name: 'Quentin Tarantino', 22 | directorId: 4 23 | }]); 24 | }); 25 | 26 | it('work', function () { 27 | 28 | var stageName = 'tentative directors', 29 | newDirectorsName = 'Joel and Ethan Cohen', 30 | message = 'Edited Cohen brothers name'; 31 | 32 | var cohen = directors.insert({ 33 | name: 'Cohen Brothers', 34 | directorId: 5 35 | }); 36 | var new_cohen = directors.stage(stageName, cohen); 37 | new_cohen.name = newDirectorsName; 38 | expect(cohen.name).toEqual('Cohen Brothers'); 39 | directors.commitStage(stageName, message); 40 | expect(directors.get(cohen.$loki).name).toEqual('Joel and Ethan Cohen'); 41 | expect(directors.commitLog.filter(function(entry) { 42 | return entry.message === message 43 | }).length).toBe(1); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /spec/generic/stats.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof (window) === 'undefined') { 2 | var loki = require('../../src/lokijs.js'); 3 | // var suite = require('../helpers/assert-helpers.js').suite; 4 | } 5 | 6 | describe('stats', function () { 7 | var db = new loki(); 8 | var users = db.addCollection('users'); 9 | users.insert({ 10 | name: 'joe', 11 | age: 35, 12 | relatives: { 13 | firstgrade: 15 14 | } 15 | }); 16 | users.insert({ 17 | name: 'jack', 18 | age: 20, 19 | relatives: { 20 | firstgrade: 20 21 | } 22 | }); 23 | users.insert({ 24 | name: 'jim', 25 | age: 40, 26 | relatives: { 27 | firstgrade: 32 28 | } 29 | }); 30 | users.insert({ 31 | name: 'dave', 32 | age: 15, 33 | relatives: { 34 | firstgrade: 20 35 | } 36 | }); 37 | users.insert({ 38 | name: 'jim', 39 | age: 28, 40 | relatives: { 41 | firstgrade: 15 42 | } 43 | }); 44 | users.insert({ 45 | name: 'dave', 46 | age: 12, 47 | relatives: { 48 | firstgrade: 12 49 | } 50 | }); 51 | 52 | it('max should be 32', function () { 53 | 54 | expect(users.max('relatives.firstgrade')).toEqual(32); 55 | 56 | }); 57 | it('max record should be 3, 32', function () { 58 | expect({ 59 | index: 3, 60 | value: 32 61 | }).toEqual(users.maxRecord('relatives.firstgrade')); 62 | }); 63 | 64 | it('min should be 12', function () { 65 | expect(users.min('age')).toEqual(12); 66 | }); 67 | 68 | it('min record to be 6, 12', function () { 69 | expect(users.minRecord('age')).toEqual({ 70 | index: 6, 71 | value: 12 72 | }); 73 | }); 74 | 75 | it('average to be 19', function () { 76 | expect(users.avg('relatives.firstgrade')).toEqual(19); 77 | }); 78 | 79 | it('median to be 17.5', function () { 80 | expect(users.median('relatives.firstgrade')).toEqual(17.5); 81 | }); 82 | 83 | it('ages should be [35, 20, 40, 15, 28, 12]', function () { 84 | expect(users.extract('age')).toEqual([35, 20, 40, 15, 28, 12]); 85 | }); 86 | 87 | it('Standard deviation on firstgrade relatives should be 6.48...', function () { 88 | expect(users.stdDev('relatives.firstgrade')).toEqual(6.48074069840786); 89 | }); 90 | 91 | it('stdDev should be 10.23...', function () { 92 | expect(users.stdDev('age')).toEqual(10.23067283548187); 93 | }); 94 | 95 | }); 96 | -------------------------------------------------------------------------------- /spec/generic/typed.spec.js: -------------------------------------------------------------------------------- 1 | if (typeof(window) === 'undefined') var loki = require('../../src/lokijs.js'); 2 | 3 | describe('typed', function () { 4 | it('works', function () { 5 | var db = new loki('test.json'); 6 | var users; 7 | 8 | function User(n) { 9 | this.name = n || ''; 10 | this.log = function () { 11 | console.log('Name: ' + this.name); 12 | }; 13 | } 14 | 15 | var json = { 16 | "filename": "test.json", 17 | "collections": [{ 18 | "name": "users", 19 | "data": [{ 20 | "name": "joe", 21 | "objType": "users", 22 | "meta": { 23 | "version": 0, 24 | "created": 1415467401386, 25 | "revision": 0 26 | }, 27 | "$loki": 1 28 | }, { 29 | "name": "jack", 30 | "objType": "users", 31 | "meta": { 32 | "version": 0, 33 | "created": 1415467401388, 34 | "revision": 0 35 | }, 36 | "$loki": 2 37 | }], 38 | "idIndex": [1, 2], 39 | "binaryIndices": {}, 40 | "objType": "users", 41 | "transactional": false, 42 | "cachedIndex": null, 43 | "cachedBinaryIndex": null, 44 | "cachedData": null, 45 | "maxId": 2, 46 | "DynamicViews": [], 47 | "events": { 48 | "insert": [null], 49 | "update": [null], 50 | "close": [], 51 | "flushbuffer": [], 52 | "error": [], 53 | "delete": [] 54 | } 55 | }], 56 | "events": { 57 | "close": [] 58 | }, 59 | "ENV": "NODEJS", 60 | "fs": {} 61 | }; 62 | 63 | // Loading only using proto: 64 | db.loadJSON(JSON.stringify(json), { 65 | users: { 66 | proto: User 67 | } 68 | }); 69 | 70 | users = db.getCollection('users'); 71 | 72 | expect(users.get(1) instanceof User).toBe(true); 73 | expect(users.get(1).name).toBe("joe"); 74 | 75 | // Loading using proto and inflate: 76 | db.loadJSON(JSON.stringify(json), { 77 | users: { 78 | proto: User, 79 | inflate: function(src, dest) { 80 | dest.$loki = src.$loki; 81 | dest.meta = src.meta; 82 | dest.customInflater = true; 83 | } 84 | } 85 | }); 86 | 87 | users = db.getCollection('users'); 88 | 89 | expect(users.get(1) instanceof User).toBe(true); 90 | expect(users.get(1).name).toBe(""); 91 | expect(users.get(1).customInflater).toBe(true); 92 | 93 | // Loading only using inflate: 94 | db.loadJSON(JSON.stringify(json), { 95 | users: { 96 | inflate: function(src) { 97 | var dest = {}; 98 | 99 | dest.$loki = src.$loki; 100 | dest.meta = src.meta; 101 | dest.onlyInflater = true; 102 | 103 | return dest; 104 | } 105 | } 106 | }); 107 | 108 | users = db.getCollection('users'); 109 | 110 | expect(users.get(1) instanceof User).toBe(false); 111 | expect(users.get(1).name).toBe(undefined); 112 | expect(users.get(1).onlyInflater).toBe(true); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /spec/helpers/assert-helpers.js: -------------------------------------------------------------------------------- 1 | console.log("loading helpers"); 2 | var suite = { 3 | assertEqual: function (message, actual, expected) { 4 | expect(actual).toEqual(expected); 5 | }, 6 | 7 | assertNotEqual: function (message, actual, expected) { 8 | expect(actual).not.toEqual(expected); 9 | }, 10 | 11 | assertStrictEqual: function (message, actual, expected) { 12 | expect(actual).toBe(expected); 13 | }, 14 | 15 | assertNotStrictEqual: function (message, actual, expected) { 16 | expect(actual).not.toBe(expected); 17 | }, 18 | 19 | assertThrows: function (message, fn) { 20 | expect(fn).toThrow(); 21 | } 22 | }; 23 | 24 | // required for node testing but forbidden on browser testing 25 | if (typeof (window) === 'undefined') { 26 | module.exports = { 27 | suite: suite 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /spec/node/README.md: -------------------------------------------------------------------------------- 1 | This folder contains Jasmine test specifications which can only be tested both using nodejs 2 | 3 | -------------------------------------------------------------------------------- /spec/node/cryptedFileAdapter.spec.js: -------------------------------------------------------------------------------- 1 | 2 | // var fs = require("fs"); 3 | // var isError = require('util').isError; 4 | 5 | 6 | 7 | // // these 2 function test the interworking between Lokijs and the adapter 8 | // function saveTest(){ 9 | // users.insert([{ 10 | // name: 'joe' 11 | // }, { 12 | // name: 'jack' 13 | // }]); 14 | // db.saveDatabase(reloadTest); 15 | // } 16 | 17 | // function reloadTest(){ 18 | // var reloaded = new loki('./loki.json.crypted',{ adapter: cryptedFileAdapter }); 19 | // reloaded.loadDatabase({}, function () { 20 | // var users2 = reloaded.getCollection('users'); 21 | // suite.assertEqual('There are 2 objects in the reloaded and decrypted db', 2, users2.data.length); 22 | // errorHandlingTest(); 23 | // }); 24 | // } 25 | 26 | // function errorHandlingTest(){ 27 | // var reloaded = new loki('./nonExistingDatabase',{ adapter: cryptedFileAdapter }); 28 | // reloaded.loadDatabase({}, function (r){ 29 | // suite.assertStrictEqual('Missing database caught by loadDatabase and passed via Lokijs', (r !== undefined) , true); 30 | // noSecretOnSaveTest(); 31 | // }); 32 | // } 33 | 34 | 35 | // // now on to testing error handling in the adapter itself 36 | 37 | // function noSecretOnSaveTest(){ 38 | 39 | // cryptedFileAdapter.setSecret(undefined); 40 | 41 | // cryptedFileAdapter.saveDatabase('./testfile.json',"{}", 42 | // function(r){ 43 | // suite.assertStrictEqual('Missing secret caught on saveDatabase', isError(r), true); 44 | // noSecretOnLoadTest(); 45 | // }); 46 | // } 47 | 48 | // function noSecretOnLoadTest(){ 49 | // cryptedFileAdapter.loadDatabase('./loki.json.crypted', 50 | // function(r){ 51 | // suite.assertStrictEqual('Missing secret caught by loadDatabase', isError(r), true); 52 | // missingDbTest(); 53 | // }); 54 | // } 55 | 56 | // function missingDbTest(){ 57 | // cryptedFileAdapter.setSecret('mySecret'); 58 | 59 | // cryptedFileAdapter.loadDatabase("./nonExistingDatabase", 60 | // function(r){ 61 | // suite.assertStrictEqual('Missing database caught by loadDatabase', isError(r), true); 62 | // noJsonTest(); 63 | // }); 64 | // } 65 | 66 | // function noJsonTest(){ 67 | // fs.writeFileSync("./nonJsonTestFile.txt","this is not json",'utf8'); 68 | // cryptedFileAdapter.loadDatabase("./nonJsonTestFile.txt", 69 | // function(r){ 70 | // fs.unlink("./nonJsonTestFile.txt"); 71 | // suite.assertStrictEqual('No Json content caught by loadDatabase', isError(r), true); 72 | // wrongJsonTest(); 73 | // }); 74 | // } 75 | 76 | // function wrongJsonTest(){ 77 | // fs.writeFileSync("./wrongJsonTestFile.txt",'{"name":"value"}','utf8'); 78 | // cryptedFileAdapter.loadDatabase("./wrongJsonTestFile.txt", 79 | // function(r){ 80 | // fs.unlink("./wrongJsonTestFile.txt"); 81 | // suite.assertStrictEqual('Wrong Json content caught by loadDatabase', isError(r), true); 82 | // endOfTest(); 83 | // }); 84 | // } 85 | 86 | // function endOfTest(){ 87 | // suite.report(); 88 | // fs.unlink('./loki.json.crypted'); 89 | // } 90 | 91 | // var cryptedFileAdapter = require('../src/lokiCryptedFileAdapter'); 92 | 93 | // cryptedFileAdapter.setSecret('mySecret'); 94 | 95 | // var loki = require('../src/lokijs.js'), 96 | // db = new loki('./loki.json.crypted',{ adapter: cryptedFileAdapter }), 97 | // gordian = require('gordian'), 98 | // suite = new gordian('testCryptedFileAdapter'), 99 | // users = db.addCollection('users'); 100 | 101 | // saveTest(); 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /spec/node/nodePersistence.spec.js: -------------------------------------------------------------------------------- 1 | // var loki = require('../src/lokijs.js'), 2 | // db = new loki('./loki.json'), 3 | // gordian = require('gordian'), 4 | // suite = new gordian('nodePersistence'), 5 | // users = db.addCollection('users'); 6 | 7 | // users.insert([{ 8 | // name: 'joe' 9 | // }, { 10 | // name: 'jack' 11 | // }]); 12 | 13 | // db.saveDatabase( function reload(){ 14 | 15 | // var reloaded = new loki('./loki.json'); 16 | // reloaded.loadDatabase({}, function () { 17 | // var users2 = reloaded.getCollection('users'); 18 | // suite.assertEqual('There are 2 objects in the reloaded db', 2, users2.data.length); 19 | // require('fs').unlink('./loki.json'); 20 | // }); 21 | // }); 22 | 23 | // // test autoload callback fires even when database does not exist 24 | // function testAutoLoad() { 25 | // var cbSuccess = false; 26 | 27 | // var tdb = new loki('nonexistent.db', 28 | // { 29 | // autoload: true, 30 | // autoloadCallback : function() { cbSuccess = true; } 31 | // }); 32 | 33 | // setTimeout(function() { 34 | // suite.assertEqual('autoload callback was called', cbSuccess, true); 35 | // suite.report(); 36 | // }, 500); 37 | // } 38 | 39 | // // due to async nature of top inline test, give it some time to complete 40 | // setTimeout(testAutoLoad, 500); 41 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "generic/*[sS]pec.js", 5 | "node/*[sS]pec.js" 6 | ], 7 | "helpers": [ 8 | "helpers/*.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/aws-s3-sync-adapter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LokiJS AWSS3SyncAdapter 3 | * @author Simon Hutchison 4 | * 5 | * A remote sync adapter for LokiJS to AWS S3 6 | * 7 | * Basic usage: 8 | * 9 | const AWS = require('aws-sdk') 10 | const Loki = require('lokijs') 11 | const AWSS3SyncAdapter = require('./aws-s3-sync-adapter.js') 12 | var db 13 | 14 | var _init = function () { 15 | const options = { 16 | 'AWS': AWS, 17 | 'accessKeyId': process.env.accessKeyId, // Avoid hardcoding credentials 18 | 'secretAccessKey': process.env.secretAccessKey, // Avoid hardcoding credentials 19 | 'bucket': '...' 20 | } 21 | const adapter = new AWSS3SyncAdapter(options); 22 | db = new Loki('somefile.json', { 23 | autoload: false, 24 | autosave: true, 25 | adapter: adapter 26 | }) 27 | } 28 | 29 | var _save = function () { 30 | db.saveDatabase(function (err, data) { 31 | if (err) { 32 | console.log(err) 33 | process.exit() 34 | } else { 35 | console.log('DB saved to AWS S3') 36 | _load() // Now attempt to load it back. 37 | } 38 | }) 39 | } 40 | 41 | var _load = function () { 42 | db.loadDatabase({}, function (err) { 43 | if (err) { 44 | console.log(err) 45 | process.exit() 46 | } else { 47 | console.log('DB loaded from AWS S3') 48 | process.exit() 49 | } 50 | }) 51 | } 52 | 53 | _init() 54 | _save() 55 | * 56 | */ 57 | 58 | /* eslint */ 59 | /* global define */ 60 | 61 | /* jslint browser: true, node: true, plusplus: true, indent: 2 */ 62 | 63 | (function (root, factory) { 64 | if (typeof define === 'function' && define.amd) { 65 | // AMD 66 | define([], factory); 67 | } else if (typeof exports === 'object') { 68 | // CommonJS 69 | module.exports = factory(); 70 | } else { 71 | // Browser globals 72 | root.lokiAWSS3SyncAdapter = factory(); 73 | } 74 | }(this, function () { 75 | return (function (options) { 76 | 'use strict'; 77 | 78 | function AWSS3SyncAdapterError (message) { 79 | this.name = 'AWSS3SyncAdapterError'; 80 | this.message = (message || ''); 81 | } 82 | 83 | AWSS3SyncAdapterError.prototype = Error.prototype; 84 | 85 | /** 86 | * This adapter requires an object options is passed containing the following properties: 87 | * AWS: Reference to the AWS SDK. 88 | * accessKeyId: AWS access key ID 89 | * secretAccessKey: AWS secret access key 90 | * bucket: A global aws bucket name 91 | */ 92 | 93 | function AWSS3SyncAdapter (options) { 94 | this.options = options; 95 | 96 | if (!options) { 97 | throw new AWSS3SyncAdapterError('No options configured in AWSS3SyncAdapter'); 98 | } 99 | 100 | if (!options.AWS) { 101 | throw new AWSS3SyncAdapterError('No AWS library specified in options'); 102 | } 103 | 104 | if (!options.accessKeyId) { 105 | throw new AWSS3SyncAdapterError('No accessKeyId property specified in options'); 106 | } 107 | 108 | if (!options.secretAccessKey) { 109 | throw new AWSS3SyncAdapterError('No secretAccessKey property specified in options'); 110 | } 111 | 112 | if (!options.bucket) { 113 | throw new AWSS3SyncAdapterError('No bucket property specified in options'); 114 | } 115 | 116 | this.options.AWS.config.update({accessKeyId: options.accessKeyId, secretAccessKey: options.secretAccessKey}); 117 | } 118 | 119 | AWSS3SyncAdapter.prototype.saveDatabase = function (name, data, callback) { 120 | console.log('AWSS3SyncAdapter.prototype.saveDatabase():', name); 121 | 122 | const s3 = new this.options.AWS.S3(); 123 | const base64str = Buffer.from(data).toString('base64'); 124 | 125 | var params = { 126 | Body: base64str, 127 | Bucket: this.options.bucket, 128 | Key: name, 129 | ServerSideEncryption: 'AES256', 130 | Tagging: '' // key1=value1&key2=value2 131 | }; 132 | 133 | s3.putObject(params, function (err, data) { 134 | if (err) { 135 | console.log('AWSS3SyncAdapter.prototype.saveDatabase() Error:', err, err.stack); 136 | throw new AWSS3SyncAdapterError('Remote sync failed'); 137 | } else { 138 | return callback(null, data); 139 | } 140 | }); 141 | }; 142 | 143 | AWSS3SyncAdapter.prototype.loadDatabase = function (name, callback) { 144 | console.log('AWSS3SyncAdapter.prototype.loadDatabase():', name); 145 | const s3 = new this.options.AWS.S3(); 146 | 147 | var params = { 148 | Bucket: this.options.bucket, 149 | Key: name 150 | }; 151 | 152 | s3.getObject(params, function (err, data) { 153 | if (err) { 154 | console.log('AWSS3SyncAdapter.prototype.loadDatabase() Error:', err, err.stack); 155 | throw new AWSS3SyncAdapterError('Remote load failed'); 156 | } else { 157 | if (!data.Body) { 158 | throw new AWSS3SyncAdapterError('Remote load failed, no data found'); 159 | } 160 | try { 161 | var base64data = data.Body.toString(); 162 | var asciiData = Buffer.from(base64data, 'base64').toString(); 163 | var lokiDBObj = JSON.parse(asciiData); // For json objects 164 | console.log('AWSS3SyncAdapter.prototype.loadDatabase() asciiData:', lokiDBObj); 165 | return callback(lokiDBObj); 166 | } catch (e) { 167 | throw new AWSS3SyncAdapterError('Remote load failed, invalid loki database'); 168 | } 169 | } 170 | }); 171 | }; 172 | 173 | return AWSS3SyncAdapter; 174 | }()); 175 | })); 176 | -------------------------------------------------------------------------------- /src/jquery-sync-adapter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LokiJS JquerySyncAdapter 3 | * @author Joe Minichino 4 | * 5 | * A remote sync adapter example for LokiJS 6 | */ 7 | 8 | /*jslint browser: true, node: true, plusplus: true, indent: 2 */ 9 | 10 | (function (root, factory) { 11 | if (typeof define === 'function' && define.amd) { 12 | // AMD 13 | define([], factory); 14 | } else if (typeof exports === 'object') { 15 | // CommonJS 16 | module.exports = factory(); 17 | } else { 18 | // Browser globals 19 | root.lokiJquerySyncAdapter = factory(); 20 | } 21 | }(this, function () { 22 | 23 | return (function (options) { 24 | 'use strict'; 25 | 26 | function JquerySyncAdapterError(message) { 27 | this.name = "JquerySyncAdapterError"; 28 | this.message = (message || ""); 29 | } 30 | 31 | JquerySyncAdapterError.prototype = Error.prototype; 32 | 33 | /** 34 | * this adapter assumes an object options is passed, 35 | * containing the following properties: 36 | * ajaxLib: jquery or compatible ajax library 37 | * save: { url: the url to save to, dataType [optional]: json|xml|etc., type [optional]: POST|GET|PUT} 38 | * load: { url: the url to load from, dataType [optional]: json|xml| etc., type [optional]: POST|GET|PUT } 39 | */ 40 | 41 | function JquerySyncAdapter(options) { 42 | this.options = options; 43 | 44 | if (!options) { 45 | throw new JquerySyncAdapterError('No options configured in JquerySyncAdapter'); 46 | } 47 | 48 | if (!options.ajaxLib) { 49 | throw new JquerySyncAdapterError('No ajaxLib property specified in options'); 50 | } 51 | 52 | if (!options.save || !options.load) { 53 | throw new JquerySyncAdapterError('Please specify load and save properties in options'); 54 | } 55 | if (!options.save.url || !options.load.url) { 56 | throw new JquerySyncAdapterError('load and save objects must have url property'); 57 | } 58 | } 59 | 60 | JquerySyncAdapter.prototype.saveDatabase = function (name, data, callback) { 61 | this.options.ajaxLib.ajax({ 62 | type: this.options.save.type || 'POST', 63 | url: this.options.save.url, 64 | data: data, 65 | success: callback, 66 | failure: function () { 67 | throw new JquerySyncAdapterError("Remote sync failed"); 68 | }, 69 | dataType: this.options.save.dataType || 'json' 70 | }); 71 | }; 72 | 73 | JquerySyncAdapter.prototype.loadDatabase = function (name, callback) { 74 | this.options.ajaxLib.ajax({ 75 | type: this.options.load.type || 'GET', 76 | url: this.options.load.url, 77 | data: { 78 | // or whatever parameter to fetch the db from a server 79 | name: name 80 | }, 81 | success: callback, 82 | failure: function () { 83 | throw new JquerySyncAdapterError("Remote load failed"); 84 | }, 85 | dataType: this.options.load.dataType || 'json' 86 | }); 87 | }; 88 | 89 | return JquerySyncAdapter; 90 | 91 | }()); 92 | })); -------------------------------------------------------------------------------- /src/loki-crypted-file-adapter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file lokiCryptedFileAdapter.js 3 | * @author Hans Klunder 4 | */ 5 | 6 | /* 7 | * The default Loki File adapter uses plain text JSON files. This adapter crypts the database string and wraps the result 8 | * in a JSON including enough info to be able to decrypt it (except for the 'secret' of course !) 9 | * 10 | * The idea is that the 'secret' does not reside in your source code but is supplied by some other source (e.g. the user in node-webkit) 11 | * 12 | * The idea + encrypt/decrypt routines are borrowed from https://github.com/mmoulton/krypt/blob/develop/lib/krypt.js 13 | * not using the krypt module to avoid third party dependencies 14 | */ 15 | 16 | 17 | /** 18 | * require libs 19 | * @ignore 20 | */ 21 | var fs = require('fs'); 22 | var cryptoLib = require('crypto'); 23 | var isError = require('util').isError; 24 | 25 | /* 26 | * sensible defaults 27 | */ 28 | var CIPHER = 'aes-256-cbc', 29 | KEY_DERIVATION = 'pbkdf2', 30 | KEY_LENGTH = 256, 31 | ITERATIONS = 64000; 32 | 33 | /** 34 | * encrypt() - encrypt a string 35 | * @private 36 | * @param {string} input - the serialized JSON object to decrypt. 37 | * @param {string} secret - the secret to use for encryption 38 | */ 39 | function encrypt(input, secret) { 40 | if (!secret) { 41 | return new Error('A \'secret\' is required to encrypt'); 42 | } 43 | 44 | 45 | var salt = cryptoLib.randomBytes(KEY_LENGTH / 8), 46 | iv = cryptoLib.randomBytes(16); 47 | 48 | try { 49 | 50 | var key = cryptoLib.pbkdf2Sync(secret, salt, ITERATIONS, KEY_LENGTH / 8, 'sha1'), 51 | cipher = cryptoLib.createCipheriv(CIPHER, key, iv); 52 | 53 | var encryptedValue = cipher.update(input, 'utf8', 'base64'); 54 | encryptedValue += cipher.final('base64'); 55 | 56 | var result = { 57 | cipher: CIPHER, 58 | keyDerivation: KEY_DERIVATION, 59 | keyLength: KEY_LENGTH, 60 | iterations: ITERATIONS, 61 | iv: iv.toString('base64'), 62 | salt: salt.toString('base64'), 63 | value: encryptedValue 64 | }; 65 | return result; 66 | 67 | } catch (err) { 68 | return new Error('Unable to encrypt value due to: ' + err); 69 | } 70 | } 71 | 72 | /** 73 | * decrypt() - Decrypt a serialized JSON object 74 | * @private 75 | * @param {string} input - the serialized JSON object to decrypt. 76 | * @param {string} secret - the secret to use for decryption 77 | */ 78 | function decrypt(input, secret) { 79 | // Ensure we have something to decrypt 80 | if (!input) { 81 | return new Error('You must provide a value to decrypt'); 82 | } 83 | // Ensure we have the secret used to encrypt this value 84 | if (!secret) { 85 | return new Error('A \'secret\' is required to decrypt'); 86 | } 87 | 88 | // turn string into an object 89 | try { 90 | input = JSON.parse(input); 91 | } catch (err) { 92 | return new Error('Unable to parse string input as JSON'); 93 | } 94 | 95 | // Ensure our input is a valid object with 'iv', 'salt', and 'value' 96 | if (!input.iv || !input.salt || !input.value) { 97 | return new Error('Input must be a valid object with \'iv\', \'salt\', and \'value\' properties'); 98 | } 99 | 100 | var salt = new Buffer(input.salt, 'base64'), 101 | iv = new Buffer(input.iv, 'base64'), 102 | keyLength = input.keyLength, 103 | iterations = input.iterations; 104 | 105 | try { 106 | 107 | var key = cryptoLib.pbkdf2Sync(secret, salt, iterations, keyLength / 8, 'sha1'), 108 | decipher = cryptoLib.createDecipheriv(CIPHER, key, iv); 109 | 110 | var decryptedValue = decipher.update(input.value, 'base64', 'utf8'); 111 | decryptedValue += decipher.final('utf8'); 112 | 113 | return decryptedValue; 114 | 115 | } catch (err) { 116 | return new Error('Unable to decrypt value due to: ' + err); 117 | } 118 | } 119 | 120 | /** 121 | * The constructor is automatically called on `require` , see examples below 122 | * @constructor 123 | */ 124 | function lokiCryptedFileAdapter() {} 125 | 126 | /** 127 | * setSecret() - set the secret to be used during encryption and decryption 128 | * 129 | * @param {string} secret - the secret to be used 130 | */ 131 | lokiCryptedFileAdapter.prototype.setSecret = function setSecret(secret) { 132 | this.secret = secret; 133 | }; 134 | 135 | /** 136 | * loadDatabase() - Retrieves a serialized db string from the catalog. 137 | * 138 | * @example 139 | // LOAD 140 | var cryptedFileAdapter = require('./lokiCryptedFileAdapter'); 141 | cryptedFileAdapter.setSecret('mySecret'); // you should change 'mySecret' to something supplied by the user 142 | var db = new loki('test.crypted', { adapter: cryptedFileAdapter }); //you can use any name, not just '*.crypted' 143 | db.loadDatabase(function(result) { 144 | console.log('done'); 145 | }); 146 | * 147 | * @param {string} dbname - the name of the database to retrieve. 148 | * @param {function} callback - callback should accept string param containing serialized db string. 149 | */ 150 | lokiCryptedFileAdapter.prototype.loadDatabase = function loadDatabase(dbname, callback) { 151 | var secret = this.secret; 152 | var cFun = callback || console.log; 153 | 154 | fs.readFile(dbname,'utf8',function(err,data){ 155 | var decrypted = err || decrypt(data, secret); 156 | cFun(decrypted); 157 | }); 158 | }; 159 | 160 | /** 161 | * 162 | @example 163 | // SAVE : will save database in 'test.crypted' 164 | var cryptedFileAdapter = require('./lokiCryptedFileAdapter'); 165 | cryptedFileAdapter.setSecret('mySecret'); // you should change 'mySecret' to something supplied by the user 166 | var loki=require('lokijs'); 167 | var db = new loki('test.crypted',{ adapter: cryptedFileAdapter }); //you can use any name, not just '*.crypted' 168 | var coll = db.addCollection('testColl'); 169 | coll.insert({test: 'val'}); 170 | db.saveDatabase(); // could pass callback if needed for async complete 171 | 172 | @example 173 | // if you have the krypt module installed you can use: 174 | krypt --decrypt test.crypted --secret mySecret 175 | to view the contents of the database 176 | 177 | * saveDatabase() - Saves a serialized db to the catalog. 178 | * 179 | * @param {string} dbname - the name to give the serialized database within the catalog. 180 | * @param {string} dbstring - the serialized db string to save. 181 | * @param {function} callback - (Optional) callback passed obj.success with true or false 182 | */ 183 | lokiCryptedFileAdapter.prototype.saveDatabase = function saveDatabase(dbname, dbstring, callback) { 184 | var cFun = callback || function (){}; 185 | var encrypted = encrypt(dbstring, this.secret); 186 | if (! isError(encrypted)){ 187 | fs.writeFile(dbname, 188 | JSON.stringify(encrypted, null, ' '), 189 | 'utf8',cFun); 190 | } 191 | else { // Error ! 192 | cFun(encrypted); 193 | } 194 | }; 195 | 196 | module.exports = new lokiCryptedFileAdapter(); 197 | exports.lokiCryptedFileAdapter = lokiCryptedFileAdapter; 198 | -------------------------------------------------------------------------------- /src/loki-fs-sync-adapter.js: -------------------------------------------------------------------------------- 1 | /* 2 | A synchronous version of the Loki Filesystem adapter for node.js 3 | 4 | Intended for diagnostics or environments where synchronous i/o is required. 5 | 6 | This adapter will perform worse than the default LokiFsAdapter but 7 | is provided for quick adaptation to synchronous code. 8 | */ 9 | 10 | (function (root, factory) { 11 | if (typeof define === 'function' && define.amd) { 12 | // AMD 13 | define([], factory); 14 | } else if (typeof exports === 'object') { 15 | // Node, CommonJS-like 16 | module.exports = factory(); 17 | } else { 18 | // Browser globals (root is window) 19 | root.LokiFsSyncAdapter = factory(); 20 | } 21 | }(this, function () { 22 | return (function() { 23 | 'use strict'; 24 | 25 | /** 26 | * A loki persistence adapter which persists using node fs module 27 | * @constructor LokiFsSyncAdapter 28 | */ 29 | function LokiFsSyncAdapter() { 30 | this.fs = require('fs'); 31 | } 32 | 33 | /** 34 | * loadDatabase() - Load data from file, will throw an error if the file does not exist 35 | * @param {string} dbname - the filename of the database to load 36 | * @param {function} callback - the callback to handle the result 37 | * @memberof LokiFsSyncAdapter 38 | */ 39 | LokiFsSyncAdapter.prototype.loadDatabase = function loadDatabase(dbname, callback) { 40 | var self = this; 41 | var contents; 42 | 43 | try { 44 | var stats = this.fs.statSync(dbname); 45 | if (stats.isFile()) { 46 | contents = self.fs.readFileSync(dbname, { 47 | encoding: 'utf8' 48 | }); 49 | 50 | callback(contents); 51 | } 52 | else { 53 | callback(null); 54 | } 55 | } 56 | catch (err) { 57 | // first autoload when file doesn't exist yet 58 | // should not throw error but leave default 59 | // blank database. 60 | if (err.code === "ENOENT") { 61 | callback(null); 62 | } 63 | 64 | callback(err); 65 | } 66 | }; 67 | 68 | /** 69 | * saveDatabase() - save data to file, will throw an error if the file can't be saved 70 | * might want to expand this to avoid dataloss on partial save 71 | * @param {string} dbname - the filename of the database to load 72 | * @param {function} callback - the callback to handle the result 73 | * @memberof LokiFsSyncAdapter 74 | */ 75 | LokiFsSyncAdapter.prototype.saveDatabase = function saveDatabase(dbname, dbstring, callback) { 76 | try { 77 | this.fs.writeFileSync(dbname, dbstring); 78 | callback(); 79 | } 80 | catch (err) { 81 | callback(err); 82 | } 83 | }; 84 | 85 | /** 86 | * deleteDatabase() - delete the database file, will throw an error if the 87 | * file can't be deleted 88 | * @param {string} dbname - the filename of the database to delete 89 | * @param {function} callback - the callback to handle the result 90 | * @memberof LokiFsSyncAdapter 91 | */ 92 | LokiFsSyncAdapter.prototype.deleteDatabase = function deleteDatabase(dbname, callback) { 93 | try { 94 | this.fs.unlinkSync(dbname); 95 | callback(); 96 | } 97 | catch (err) { 98 | callback(err); 99 | } 100 | }; 101 | 102 | return LokiFsSyncAdapter; 103 | 104 | }()); 105 | })); 106 | -------------------------------------------------------------------------------- /src/loki-incremental-adapter.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | 3 | module.exports = factory(); 4 | 5 | }(this, function () { 6 | return (function () { 7 | 8 | const fs = require('fs'); 9 | 10 | const accessDataDir = (datadir) => { 11 | return new Promise((resolve, reject) => { 12 | fs.lstat(datadir, (err, stats) => { 13 | if (err) { 14 | reject({ 15 | message: 'Dir does not exist' 16 | }); 17 | } 18 | resolve(stats); 19 | }); 20 | }); 21 | }; 22 | 23 | const saveRecord = (coll, obj, dir) => { 24 | console.log(`File is ${dir}/${coll}/${obj.$loki}.json`); 25 | fs.writeFile(`${dir}/${coll}/${obj.$loki}.json`, JSON.stringify(obj), { 26 | encoding: 'utf8' 27 | }, (err) => { 28 | if (err) { 29 | console.log('Document save failed.'); 30 | throw err; 31 | } 32 | console.log('Document saved correctly'); 33 | }); 34 | }; 35 | 36 | const iterateFolders = (db, dir) => { 37 | console.log(`Colls: ${db.listCollections().length}`); 38 | 39 | console.log(`Changes: ${db.generateChangesNotification().length}`); 40 | db.generateChangesNotification().forEach(change => { 41 | saveRecord(change.name, change.obj, dir); 42 | }); 43 | }; 44 | 45 | class LokiIncrementalAdapter { 46 | constructor(options) { 47 | const config = options || { 48 | journaling: false, 49 | format: 'json' 50 | }; 51 | this.mode = 'reference'; 52 | this.journaling = config.journaling; 53 | this.format = config.format; 54 | } 55 | 56 | checkAvailability() { 57 | if (typeof fs !== 'undefined' && fs) return true; 58 | return false; 59 | } 60 | 61 | exportDatabase(dir, dbref, callback) { 62 | console.log('Saving with incremental adapter'); 63 | 64 | console.log('Database dir is ' + dir); 65 | const promise = accessDataDir(dir); 66 | console.log(promise); 67 | promise.then(() => { 68 | console.log('iterating folders...'); 69 | iterateFolders(dbref, dir); 70 | }); 71 | promise.catch((err) => { 72 | console.log(err); 73 | }); 74 | if (callback) { 75 | callback(); 76 | } 77 | } 78 | 79 | loadDatabase(dbname, callback) { 80 | console.log(this, dbname, callback); 81 | } 82 | } 83 | 84 | return LokiIncrementalAdapter; 85 | 86 | }()); 87 | })); 88 | -------------------------------------------------------------------------------- /src/loki-nativescript-adapter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LokiNativescriptAdapter 3 | * @author Stefano Falda 4 | * 5 | * Lokijs adapter for nativescript framework (http://www.nativescript.org) 6 | * 7 | * The db file is created in the app documents folder. 8 | 9 | * How to use: 10 | * Just create a new loki db and your ready to go: 11 | * 12 | * let db = new loki('loki.json',{autosave:true}); 13 | * 14 | */ 15 | 16 | function LokiNativescriptAdapter() { 17 | this.fs = require("file-system"); 18 | } 19 | 20 | LokiNativescriptAdapter.prototype.loadDatabase = function(dbname, callback){ 21 | var documents = this.fs.knownFolders.documents(); 22 | var myFile = documents.getFile(dbname); 23 | //Read from filesystem 24 | myFile.readText() 25 | .then(function (content) { 26 | //The file is empty or missing 27 | if (content===""){ 28 | callback(new Error("DB file does not exist")); 29 | } else { 30 | callback(content); 31 | } 32 | }, function (error) { 33 | console.log("Error opening db "+dbname+": "+ error); 34 | callback(new Error(error)); 35 | }); 36 | }; 37 | 38 | LokiNativescriptAdapter.prototype.saveDatabase = function(dbname, serialized, callback){ 39 | var documents = this.fs.knownFolders.documents(); 40 | var myFile = documents.getFile(dbname); 41 | myFile.writeText(serialized) 42 | .then(function () { 43 | callback(); 44 | }, function (error) { 45 | console.log("Error saving db "+dbname+": "+ error); 46 | }); 47 | 48 | }; 49 | 50 | LokiNativescriptAdapter.prototype.deleteDatabase = function deleteDatabase(dbname, callback) { 51 | var documents = this.fs.knownFolders.documents(); 52 | var file = documents.getFile(dbname); 53 | file.remove() 54 | .then(function (result) { 55 | callback(); 56 | }, function (error) { 57 | callback(error); 58 | }); 59 | }; 60 | 61 | module.exports = LokiNativescriptAdapter; -------------------------------------------------------------------------------- /test.db: -------------------------------------------------------------------------------- 1 | {"filename":"test.db","collections":[{"name":"items","data":[{"name":"n1","meta":{"revision":0,"created":1597663752918,"version":0},"$loki":1},{"name":"n2","meta":{"revision":0,"created":1597663752918,"version":0},"$loki":2}],"idIndex":null,"binaryIndices":{},"constraints":null,"uniqueNames":[],"transforms":{},"objType":"items","dirty":false,"cachedIndex":null,"cachedBinaryIndex":null,"cachedData":null,"adaptiveBinaryIndices":true,"transactional":false,"cloneObjects":false,"cloneMethod":"parse-stringify","asyncListeners":false,"disableMeta":false,"disableChangesApi":true,"disableDeltaChangesApi":true,"autoupdate":false,"serializableIndices":true,"disableFreeze":false,"ttl":null,"maxId":2,"DynamicViews":[],"events":{"insert":[],"update":[],"pre-insert":[],"pre-update":[],"close":[],"flushbuffer":[],"error":[],"delete":[null],"warning":[null]},"changes":[],"dirtyIds":[]}],"databaseVersion":1.5,"engineVersion":1.5,"autosave":false,"autosaveInterval":5000,"autosaveHandle":null,"throttledSaves":true,"options":{"serializationMethod":"normal","destructureDelimiter":"$<\n"},"persistenceMethod":"fs","persistenceAdapter":null,"verbose":false,"events":{"init":[null],"loaded":[],"flushChanges":[],"close":[],"changes":[],"warning":[]},"ENV":"NODEJS"} -------------------------------------------------------------------------------- /tutorials/Autoupdating Collections.md: -------------------------------------------------------------------------------- 1 | # The Autoupdate Feature for Collections 2 | 3 | Autoupdate can be enabled on a per-collection basis via the constructor option `autoupdate: true`. The feature requires `Object.observe` (currently implemented in Chrome 36+, io.js and Node.js 0.12+). If observers are not available, the option will be ignored. 4 | 5 | Autoupdate automatically calls `update(doc)` whenever a document is modified, which is necessary for index updates and dirty-marks (used to determine whether the DB has been modified and should be persisted). 6 | 7 | Enabling this feature basically means, that all manual `update` calls can be omitted. 8 | 9 | ## Example 10 | ```js 11 | var doc = collection.by("name", "John"); 12 | 13 | doc.name = "Peter"; 14 | doc.age = 32; 15 | doc.gender = "male"; 16 | 17 | collection.update(doc); // This line can be safely removed. 18 | ``` 19 | 20 | Autoupdate will call `update` at the end of the current event loop cycle and thus only calls `update` once, even when multiple changes were made. 21 | 22 | ## Error handling 23 | 24 | There is one important difference between autoupdate and manual updates. If for example a document change violates a unique key constraint, `update` will synchronously throw an error which can be catched synchronously: 25 | ```js 26 | var collection = db.addCollection("test", { 27 | unique: ["name"] 28 | }); 29 | 30 | collection.insert({ name: "Peter" }); 31 | 32 | var doc = collection.insert({ name: "Jack" }); 33 | doc.name = "Peter"; 34 | 35 | try { 36 | collection.update(doc); 37 | } catch(err) { 38 | doc.name = "Jack"; 39 | } 40 | ``` 41 | 42 | Since autoupdate calls `update` asynchronously, you cannot catch errors via `try-catch`. Instead you have to use event listeners: 43 | ```js 44 | var collection = db.addCollection("test", { 45 | unique: ["name"], 46 | autoupdate: true 47 | }); 48 | 49 | collection.insert({ name: "Peter" }); 50 | 51 | var doc = collection.insert({ name: "Jack" }); 52 | doc.name = "Peter"; 53 | 54 | collection.on("error", function(errDoc) { 55 | if(errDoc === doc) { 56 | doc.name = "Jack"; 57 | } 58 | }); 59 | ``` 60 | 61 | This can become quite tedious, so you should consider performing checks before updating documents instead. 62 | -------------------------------------------------------------------------------- /tutorials/Changes API.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | LokiJS 1.1 introduces a "Changes API" that enables the user to keep track of the changes happened to each collection since a particular point in time, which is usually the start of a work session but it could be a user defined one. 4 | This is particularly useful for remote synchronization. 5 | 6 | ## Description of the Changes API 7 | 8 | The Changes API is a collection-level feature, hence you can establish which collections may simply contain volatile data and which ones need to keep a record of what has changed. 9 | 10 | The Changes API is an optional feature and can be activated/deactivated by either passing the option `{ disableChangesApi: isDisabled }` in the config parameter of a collection constructor, or by calling `collection.setChangesApi(isEnabled)`. 11 | Note that LokiJS will always set the fastest performing setting as default on a collection or database, hence the Changes API is **disabled** by default. 12 | 13 | There are three events which will trigger a Changes API operation: inserts, updates and deletes. 14 | When either of these events occur, on a collection with Changes API activated, the collection will store a snapshot of the relevant object, associated with the operation and the name of the collection. 15 | 16 | From the database object it is then possible to invoke the `serializeChanges` method which will generate a string representation of the changes occurred to be used for synchronization purposes. 17 | 18 | ## Usage 19 | 20 | To enable the Changes API make sure to either instantiate a collection using `db.addCollection('users', { disableChangesApi: false })`, or call `users.setChangesApi(true)` (given an example `users` collection). 21 | 22 | To generate a string representation of the changes, call `db.serializeChanges()`. This will generate a representation of all the changes for those collections that have the Changes API enabled. If you are only interested in generating changes for a subset of collections, you can pass an array of names of the collections, i.e. `db.serializeChanges(['users']);`. 23 | 24 | To clear all the changes, call `db.clearChanges()`. Alternatively you can call `flushChanges()` on the single collection, normally you would call `db.clearChanges()` on a callback from a successful synchronization operation. 25 | 26 | Each change is an object with three properties: `name` is the collection name, `obj` is the string representation of the object and `operation` is a character representing the operation ("I" for insert, "U" for update, "R" for remove). So for example, inserting user `{ name: 'joe' }` in the users collection would generate a change `{ name: 'users', obj: { name: 'joe' }, operation: 'I' }`. Changes are kept in order of how the happened so a 3rd party application will be able to operate insert updates and deletes in the correct order. 27 | -------------------------------------------------------------------------------- /tutorials/Collection Transforms.md: -------------------------------------------------------------------------------- 1 | ## Collection transforms 2 | 3 | **_The basic idea behind transforms is to allow converting a Resultset 'chain' process into an object definition of that process. This data definition can then be optionally named and saved along with the collections, within a database._** 4 | 5 | This might be useful for : 6 | * Writing tools which operate on loki databases 7 | * Creating 'stored procedure-like' named queries 8 | * Transforming your data for extraction purposes 9 | * Can be extended upon with custom meta 10 | 11 | A transform is a (ordered) array of 'step' objects to be executed on collection chain. These steps may include the following types : 12 | * 'find' 13 | * 'where' 14 | * 'simplesort' 15 | * 'compoundsort' 16 | * 'sort' 17 | * 'limit' 18 | * 'offset' 19 | * 'update' 20 | * 'remove' 21 | * 'map' 22 | * 'mapReduce' 23 | * 'eqJoin' 24 | 25 | These transform steps may hardcode their parameters or use a parameter substitution mechanism added for loki transforms. 26 | 27 | A simple, one step loki transform might appear as follows : 28 | ```javascript 29 | var tx = [ 30 | { 31 | type: 'find', 32 | value: { 33 | 'owner': 'odin' 34 | } 35 | } 36 | ]; 37 | ``` 38 | 39 | This can then optionally be saved into the collection with the command : 40 | ``` 41 | userCollection.addTransform('OwnerFilter', tx); 42 | ``` 43 | 44 | This transform can be executed by either : 45 | ```javascript 46 | userCollection.chain('OwnerFilter').data(); 47 | ``` 48 | 49 | or 50 | 51 | ```javascript 52 | userCollection.chain(tx).data(); 53 | ``` 54 | 55 | Parameterization is resolved on any object property right-hand value which is represented in your transform as a string beginning with '[%lktxp]'. An example of this might be : 56 | ```javascript 57 | var tx = [ 58 | { 59 | type: 'find', 60 | value: { 61 | 'owner': '[%lktxp]OwnerName' 62 | } 63 | } 64 | ]; 65 | ``` 66 | 67 | To execute this pipeline you need to pass a parameters object containing a value for that parameter when executing. An example of this might be : 68 | 69 | ```javascript 70 | var params = { 71 | OwnerName: 'odin' 72 | }; 73 | 74 | userCollection.chain(tx, params).data(); 75 | ``` 76 | 77 | or 78 | 79 | ```javascript 80 | userCollection.chain("OwnerFilter", params).data(); 81 | ``` 82 | 83 | **Where filter functions cannot be saved into a database** but (if you still need them), utilizing transforms along with parameterization can allow for cleanly structuring and executing saved transforms. An example might be : 84 | ```javascript 85 | var tx = [ 86 | { 87 | type: 'where', 88 | value: '[%lktxp]NameFilter' 89 | } 90 | ]; 91 | 92 | items.addTransform('ByFilteredName', tx); 93 | 94 | // the following may then occur immediately or even across save/load cycles 95 | // this example uses anonymous function but this could be named function reference as well 96 | var params = { 97 | NameFilter: function(obj) { 98 | return (obj.name.indexOf("nir") !== -1); 99 | } 100 | }; 101 | 102 | var results = items.chain("ByFilteredName", params).data(); 103 | 104 | ``` 105 | 106 | Transforms can contain multiple steps to be executed in succession. Behind the scenes, the chain command will instance a Resultset and invoke your steps as independent chain operations before finally returning the result upon completion. A few of the built in 'steps' such as 'mapReduce' actually terminate the transform/chain by returning a data array, so in those cases the chain() result is the actual data, not a resultset which you would need to call data() to resolve. 107 | 108 | A more complicated transform example might appear as follows : 109 | ```javascript 110 | var tx = [ 111 | { 112 | type: 'find', 113 | value: { 114 | owner: { 115 | '$eq': '[%lktxp]customOwner' 116 | } 117 | } 118 | }, 119 | { 120 | type: 'where', 121 | value: '[%lktxp]customFilter' 122 | }, 123 | { 124 | type: 'limit', 125 | value: '[%lktxp]customLimit' 126 | } 127 | ]; 128 | 129 | function myFilter(obj) { 130 | return (obj.name.indexOf("nir") !== -1); 131 | } 132 | 133 | var params = { 134 | customOwner: 'odin', 135 | customFilter: myFilter, 136 | customLimit: 100 137 | } 138 | 139 | users.chain(tx, params); 140 | ``` 141 | 142 | As demonstrated by the above example, we will scan the object hierarchy (up to 10 levels deep) and do parameter substitution on right hand values which appear to be parameters, which we will then attempt to look up from your params object. The parameter substitution will replace that string with a value identical to that contained in your params which can be any data type. 143 | 144 | Certain steps which are multiple parameter require specifically named step properties (other than just type and value). These are demonstrated below as separate steps which do not necessarily make sense within a single transform : 145 | 146 | ```javascript 147 | var step1 = { 148 | type: 'simplesort', 149 | property: 'name', 150 | desc: true 151 | }; 152 | 153 | var step2 = { 154 | type: 'mapReduce', 155 | mapFunction: myMap, 156 | reduceFunction: myReduce 157 | }; 158 | 159 | var step3 = { 160 | type: 'eqJoin', 161 | joinData: jd, 162 | leftJoinKey: ljk, 163 | rightJoinKey: rjk, 164 | mapFun: myMapFun 165 | }; 166 | 167 | var step4 = { 168 | type: 'remove' 169 | } 170 | ``` 171 | ## Support within DynamicViews 172 | 173 | You can now use transforms as an extraction method for a DynamicView. Certain applications might use this to create a DynamicView containing a generalized set of results which can be quickly extracted from in user defined transforms. This feature is provided within the DynamicView's branchResultset() method. It can accept raw transforms or named transforms stored at the collection level. 174 | 175 | An example of this might look like the following : 176 | ```javascript 177 | var db = new loki('test'); 178 | var coll = db.addCollection('mydocs'); 179 | var dv = coll.addDynamicView('myview'); 180 | var tx = [ 181 | { 182 | type: 'offset', 183 | value: '[%lktxp]pageStart' 184 | }, 185 | { 186 | type: 'limit', 187 | value: '[%lktxp]pageSize' 188 | } 189 | ]; 190 | coll.addTransform('viewPaging', tx); 191 | 192 | // add some records 193 | 194 | var results = dv.branchResultset('viewPaging', { pageStart: 10, pageSize: 10 }).data(); 195 | 196 | ``` 197 | 198 | The important distinction is that branching (and thus your transform results) reflect only the view at the point in time at which you branch. These transforms are extracts and not used internally to the view. 199 | 200 | ## Adding meta for custom solutions 201 | 202 | One use for transforms might be to have user driven solutions where you have the user interface constructing, managing, and executing these transforms. In such situations you might want to add your own metadata to the transforms to further describe the transform, steps, or parameters. 203 | 204 | - Any step with a 'type' unknown to loki transforms will be ignored. You might decide to always have the first step as a 'meta' type with properties containing information about author, description, or required parameter description meta data. 205 | - Each of the steps may also include additional properties above what we have defined as required, so you might have step descriptions, last changed dates, etc embedded within steps. 206 | 207 | ## Summary 208 | Loki transforms establish (with little additional footprint) a process for automating data transformations on your data. This is not a required functionality and is not intended to replace method chaining, but it allows you to abstract and organize repetitive querying for cleanliness or dynamic purposes. 209 | -------------------------------------------------------------------------------- /tutorials/Loki Angular.md: -------------------------------------------------------------------------------- 1 | # Lokiwork, the LokiJS Angular Service 2 | 3 | 4 | ## Overview 5 | 6 | 7 | This service of Lokijs for Angular simplifies things to the most basic level because i found Loki difficult to work with in a mobile environment. all you do is setup json files that specify the layout of your data then add, and update entries to the databases. 8 | 9 | ###Install: 10 | `bower install lokijs` 11 | 12 | ###Html: 13 | ``` 14 | 15 | 16 | ``` 17 | 18 | ###App: 19 | `angular.module('app',['lokijs']);` 20 | 21 | ###Configure database template: 22 | I might call this file -> `json_locations.js` Note: each one has to be called, json1, json2, json3, etc as shown in the following: 23 | ```` 24 | app.constant( 25 | 'json1', 26 | { 27 | "db":"settings", 28 | "collection": "globals" , 29 | "documents" : 30 | [ 31 | { 32 | "name": "user settings", 33 | "brands" : true, 34 | "random" : "some value" 35 | } 36 | ] 37 | } 38 | ); 39 | ```` 40 | ###Controller: 41 | ``` 42 | app.controller('myCtrl', function($scope, Lokiwork){...}); 43 | ``` 44 | ###Usage: 45 | 46 | Lokiwork.setCurrentDoc(dbname, collection, document_identifier); 47 | 48 | Lokiwork.setCurrentDoc('settings', 'globals', {'name': "user settings"}); 49 | 50 | Lokiwork.getCurrentDoc(); 51 | 52 | Lokiwork.getCurrentDoc(); 53 | 54 | Lokiwork.updateCurrentDoc(name, value); 55 | 56 | Lokiwork.updateCurrentDoc("power", true); 57 | 58 | Lokiwork.deleteCurrentDoc(); 59 | 60 | Lokiwork.deleteCurrentDoc(); 61 | 62 | Lokiwork.getDoc(dbName, collName, docName); 63 | 64 | Lokiwork.getDoc("settings", "globals", {name:"user settings"}); 65 | 66 | Lokiwork.addDocument(dbName, collName, newDoc); 67 | 68 | Lokiwork.addDocument("settings", "globals", doc_obj); //example below 69 | 70 | Lokiwork.updateDoc(dbname, collName, document_identifier, name, value); 71 | 72 | Lokiwork.updateDoc("settings", "globals", {name:"user settings"}, "brands", false}); 73 | 74 | Lokiwork.deleteDocument(dbName, collName, document_identifier); 75 | 76 | Lokiwork.deleteDocument('settings','globals', {name:'user settings'}); 77 | 78 | Lokiwork.getCollection(dbName, collName); 79 | 80 | Lokiwork.getCollection('settings', 'globals'); 81 | 82 | Lokwork.addCollection(json_obj); 83 | 84 | Lokiwork.addCollection(item); // example below 85 | 86 | Lokiwork.deleteCollection(dbName, collName); 87 | 88 | Lokiwork.deleteCollection('settings', globals'); 89 | 90 | Lokiwork.deleteDatabase(dbName); 91 | 92 | Lokiwork.deleteDatabase("settings"); 93 | 94 | ####Further examples: 95 | 96 | ``` 97 | var collection = { 98 | "db":"settings", 99 | "collection": "globals" , 100 | "documents" : 101 | [ 102 | { 103 | "name": "user settings", 104 | "brands" : true, 105 | "face" : "You now it" 106 | } 107 | ] 108 | }; 109 | Lokiwork.addCollection(collection); 110 | ``` 111 | 112 | With addDocument, you can pass a json document in if it's small enough, otherwise assign it to a variable first. 113 | ``` 114 | Lokiwork.addDocument("settings", "globals", {name:"user settings2", gay: true, brands:false}) 115 | ``` 116 | You can also use promises and/or chain them: 117 | ``` 118 | Lokiwork.setCurrentDoc('settings', 'globals', {'name': "user settings"}) 119 | .then(function(data){ 120 | Lokiwork.updateCurrentDoc("address", "1801 Waters Ridge Drive"); 121 | }); 122 | ``` 123 | 124 | ###Remember! 125 | A lot of the above commands may not be necessary if you are implementing a static change, just edit the underlying json javascript file, delete the local storage file, and restart the app. 126 | 127 | ###Notes: 128 | - If you delete a database it's recreated the next time the app is restarted and on the first query because it will see the angular json file and recreate it (it won't overwrite existing though). If you want to permanantly remove a database, then you have to also remove the angular json file. This is perfect, because on a mobile device the user may have local storage wiped, no problem, because the next time they boot up the databases will all be recreated. 129 | - Since Lokijs is generating all of the database content it should be 100% compatible with native Lokijs commands not listed here but found on the office website shown below. 130 | 131 | 132 | ###The official Lokijs page 133 | [LokiJS](https://github.com/techfort/LokiJS) --------------------------------------------------------------------------------