UNPKG

21.8 kBHTMLView Raw
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <title>JSDoc: Source: loki-indexed-adapter.js</title>
6
7 <script src="scripts/prettify/prettify.js"> </script>
8 <script src="scripts/prettify/lang-css.js"> </script>
9 <!--[if lt IE 9]>
10 <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
11 <![endif]-->
12 <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
13 <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
14</head>
15
16<body>
17
18<div id="main">
19
20 <h1 class="page-title">Source: loki-indexed-adapter.js</h1>
21
22
23
24
25
26
27 <section>
28 <article>
29 <pre class="prettyprint source linenums"><code>/*
30 Loki IndexedDb Adapter (need to include this script to use it)
31
32 Console Usage can be used for management/diagnostic, here are a few examples :
33 adapter.getDatabaseList(); // with no callback passed, this method will log results to console
34 adapter.saveDatabase('UserDatabase', JSON.stringify(myDb));
35 adapter.loadDatabase('UserDatabase'); // will log the serialized db to console
36 adapter.deleteDatabase('UserDatabase');
37*/
38
39(function (root, factory) {
40 if (typeof define === 'function' &amp;&amp; define.amd) {
41 // AMD
42 define([], factory);
43 } else if (typeof exports === 'object') {
44 // Node, CommonJS-like
45 module.exports = factory();
46 } else {
47 // Browser globals (root is window)
48 root.LokiIndexedAdapter = factory();
49 }
50}(this, function () {
51 return (function() {
52
53 /**
54 * Loki persistence adapter class for indexedDb.
55 * This class fulfills abstract adapter interface which can be applied to other storage methods.
56 * Utilizes the included LokiCatalog app/key/value database for actual database persistence.
57 * Indexeddb is highly async, but this adapter has been made 'console-friendly' as well.
58 * Anywhere a callback is omitted, it should return results (if applicable) to console.
59 * IndexedDb storage is provided per-domain, so we implement app/key/value database to
60 * allow separate contexts for separate apps within a domain.
61 *
62 * @example
63 * var idbAdapter = new LokiIndexedAdapter('finance');
64 *
65 * @constructor LokiIndexedAdapter
66 *
67 * @param {string} appname - (Optional) Application name context can be used to distinguish subdomains, 'loki' by default
68 */
69 function LokiIndexedAdapter(appname)
70 {
71 this.app = 'loki';
72
73 if (typeof (appname) !== 'undefined')
74 {
75 this.app = appname;
76 }
77
78 // keep reference to catalog class for base AKV operations
79 this.catalog = null;
80
81 if (!this.checkAvailability()) {
82 throw new Error('indexedDB does not seem to be supported for your environment');
83 }
84 }
85
86 /**
87 * Used to check if adapter is available
88 *
89 * @returns {boolean} true if indexeddb is available, false if not.
90 * @memberof LokiIndexedAdapter
91 */
92 LokiIndexedAdapter.prototype.checkAvailability = function()
93 {
94 if (typeof indexedDB !== 'undefined' &amp;&amp; indexedDB) return true;
95
96 return false;
97 };
98
99 /**
100 * Retrieves a serialized db string from the catalog.
101 *
102 * @example
103 * // LOAD
104 * var idbAdapter = new LokiIndexedAdapter('finance');
105 * var db = new loki('test', { adapter: idbAdapter });
106 * db.loadDatabase(function(result) {
107 * console.log('done');
108 * });
109 *
110 * @param {string} dbname - the name of the database to retrieve.
111 * @param {function} callback - callback should accept string param containing serialized db string.
112 * @memberof LokiIndexedAdapter
113 */
114 LokiIndexedAdapter.prototype.loadDatabase = function(dbname, callback)
115 {
116 var appName = this.app;
117 var adapter = this;
118
119 // lazy open/create db reference so dont -need- callback in constructor
120 if (this.catalog === null || this.catalog.db === null) {
121 this.catalog = new LokiCatalog(function(cat) {
122 adapter.catalog = cat;
123
124 adapter.loadDatabase(dbname, callback);
125 });
126
127 return;
128 }
129
130 // lookup up db string in AKV db
131 this.catalog.getAppKey(appName, dbname, function(result) {
132 if (typeof (callback) === 'function') {
133 if (result.id === 0) {
134 callback(null);
135 return;
136 }
137 callback(result.val);
138 }
139 else {
140 // support console use of api
141 console.log(result.val);
142 }
143 });
144 };
145
146 // alias
147 LokiIndexedAdapter.prototype.loadKey = LokiIndexedAdapter.prototype.loadDatabase;
148
149 /**
150 * Saves a serialized db to the catalog.
151 *
152 * @example
153 * // SAVE : will save App/Key/Val as 'finance'/'test'/{serializedDb}
154 * var idbAdapter = new LokiIndexedAdapter('finance');
155 * var db = new loki('test', { adapter: idbAdapter });
156 * var coll = db.addCollection('testColl');
157 * coll.insert({test: 'val'});
158 * db.saveDatabase(); // could pass callback if needed for async complete
159 *
160 * @param {string} dbname - the name to give the serialized database within the catalog.
161 * @param {string} dbstring - the serialized db string to save.
162 * @param {function} callback - (Optional) callback passed obj.success with true or false
163 * @memberof LokiIndexedAdapter
164 */
165 LokiIndexedAdapter.prototype.saveDatabase = function(dbname, dbstring, callback)
166 {
167 var appName = this.app;
168 var adapter = this;
169
170 function saveCallback(result) {
171 if (result &amp;&amp; result.success === true) {
172 callback(null);
173 }
174 else {
175 callback(new Error("Error saving database"));
176 }
177 }
178
179 // lazy open/create db reference so dont -need- callback in constructor
180 if (this.catalog === null || this.catalog.db === null) {
181 this.catalog = new LokiCatalog(function(cat) {
182 adapter.catalog = cat;
183
184 // now that catalog has been initialized, set (add/update) the AKV entry
185 cat.setAppKey(appName, dbname, dbstring, saveCallback);
186 });
187
188 return;
189 }
190
191 // set (add/update) entry to AKV database
192 this.catalog.setAppKey(appName, dbname, dbstring, saveCallback);
193 };
194
195 // alias
196 LokiIndexedAdapter.prototype.saveKey = LokiIndexedAdapter.prototype.saveDatabase;
197
198 /**
199 * Deletes a serialized db from the catalog.
200 *
201 * @example
202 * // DELETE DATABASE
203 * // delete 'finance'/'test' value from catalog
204 * idbAdapter.deleteDatabase('test', function {
205 * // database deleted
206 * });
207 *
208 * @param {string} dbname - the name of the database to delete from the catalog.
209 * @param {function=} callback - (Optional) executed on database delete
210 * @memberof LokiIndexedAdapter
211 */
212 LokiIndexedAdapter.prototype.deleteDatabase = function(dbname, callback)
213 {
214 var appName = this.app;
215 var adapter = this;
216
217 // lazy open/create db reference and pass callback ahead
218 if (this.catalog === null || this.catalog.db === null) {
219 this.catalog = new LokiCatalog(function(cat) {
220 adapter.catalog = cat;
221
222 adapter.deleteDatabase(dbname, callback);
223 });
224
225 return;
226 }
227
228 // catalog was already initialized, so just lookup object and delete by id
229 this.catalog.getAppKey(appName, dbname, function(result) {
230 var id = result.id;
231
232 if (id !== 0) {
233 adapter.catalog.deleteAppKey(id);
234 }
235
236 if (typeof (callback) === 'function') {
237 callback();
238 }
239 });
240 };
241
242 // alias
243 LokiIndexedAdapter.prototype.deleteKey = LokiIndexedAdapter.prototype.deleteDatabase;
244
245 /**
246 * Removes all database partitions and pages with the base filename passed in.
247 * This utility method does not (yet) guarantee async deletions will be completed before returning
248 *
249 * @param {string} dbname - the base filename which container, partitions, or pages are derived
250 * @memberof LokiIndexedAdapter
251 */
252 LokiIndexedAdapter.prototype.deleteDatabasePartitions = function(dbname) {
253 var self=this;
254 this.getDatabaseList(function(result) {
255 result.forEach(function(str) {
256 if (str.startsWith(dbname)) {
257 self.deleteDatabase(str);
258 }
259 });
260 });
261 };
262
263 /**
264 * Retrieves object array of catalog entries for current app.
265 *
266 * @example
267 * idbAdapter.getDatabaseList(function(result) {
268 * // result is array of string names for that appcontext ('finance')
269 * result.forEach(function(str) {
270 * console.log(str);
271 * });
272 * });
273 *
274 * @param {function} callback - should accept array of database names in the catalog for current app.
275 * @memberof LokiIndexedAdapter
276 */
277 LokiIndexedAdapter.prototype.getDatabaseList = function(callback)
278 {
279 var appName = this.app;
280 var adapter = this;
281
282 // lazy open/create db reference so dont -need- callback in constructor
283 if (this.catalog === null || this.catalog.db === null) {
284 this.catalog = new LokiCatalog(function(cat) {
285 adapter.catalog = cat;
286
287 adapter.getDatabaseList(callback);
288 });
289
290 return;
291 }
292
293 // catalog already initialized
294 // get all keys for current appName, and transpose results so just string array
295 this.catalog.getAppKeys(appName, function(results) {
296 var names = [];
297
298 for(var idx = 0; idx &lt; results.length; idx++) {
299 names.push(results[idx].key);
300 }
301
302 if (typeof (callback) === 'function') {
303 callback(names);
304 }
305 else {
306 names.forEach(function(obj) {
307 console.log(obj);
308 });
309 }
310 });
311 };
312
313 // alias
314 LokiIndexedAdapter.prototype.getKeyList = LokiIndexedAdapter.prototype.getDatabaseList;
315
316 /**
317 * Allows retrieval of list of all keys in catalog along with size
318 *
319 * @param {function} callback - (Optional) callback to accept result array.
320 * @memberof LokiIndexedAdapter
321 */
322 LokiIndexedAdapter.prototype.getCatalogSummary = function(callback)
323 {
324 var appName = this.app;
325 var adapter = this;
326
327 // lazy open/create db reference
328 if (this.catalog === null || this.catalog.db === null) {
329 this.catalog = new LokiCatalog(function(cat) {
330 adapter.catalog = cat;
331
332 adapter.getCatalogSummary(callback);
333 });
334
335 return;
336 }
337
338 // catalog already initialized
339 // get all keys for current appName, and transpose results so just string array
340 this.catalog.getAllKeys(function(results) {
341 var entries = [];
342 var obj,
343 size,
344 oapp,
345 okey,
346 oval;
347
348 for(var idx = 0; idx &lt; results.length; idx++) {
349 obj = results[idx];
350 oapp = obj.app || '';
351 okey = obj.key || '';
352 oval = obj.val || '';
353
354 // app and key are composited into an appkey column so we will mult by 2
355 size = oapp.length * 2 + okey.length * 2 + oval.length + 1;
356
357 entries.push({ "app": obj.app, "key": obj.key, "size": size });
358 }
359
360 if (typeof (callback) === 'function') {
361 callback(entries);
362 }
363 else {
364 entries.forEach(function(obj) {
365 console.log(obj);
366 });
367 }
368 });
369 };
370
371 /**
372 * LokiCatalog - underlying App/Key/Value catalog persistence
373 * This non-interface class implements the actual persistence.
374 * Used by the IndexedAdapter class.
375 */
376 function LokiCatalog(callback)
377 {
378 this.db = null;
379 this.initializeLokiCatalog(callback);
380 }
381
382 LokiCatalog.prototype.initializeLokiCatalog = function(callback) {
383 var openRequest = indexedDB.open('LokiCatalog', 1);
384 var cat = this;
385
386 // If database doesn't exist yet or its version is lower than our version specified above (2nd param in line above)
387 openRequest.onupgradeneeded = function(e) {
388 var thisDB = e.target.result;
389 if (thisDB.objectStoreNames.contains('LokiAKV')) {
390 thisDB.deleteObjectStore('LokiAKV');
391 }
392
393 if(!thisDB.objectStoreNames.contains('LokiAKV')) {
394 var objectStore = thisDB.createObjectStore('LokiAKV', { keyPath: 'id', autoIncrement:true });
395 objectStore.createIndex('app', 'app', {unique:false});
396 objectStore.createIndex('key', 'key', {unique:false});
397 // hack to simulate composite key since overhead is low (main size should be in val field)
398 // user (me) required to duplicate the app and key into comma delimited appkey field off object
399 // This will allow retrieving single record with that composite key as well as
400 // still supporting opening cursors on app or key alone
401 objectStore.createIndex('appkey', 'appkey', {unique:true});
402 }
403 };
404
405 openRequest.onsuccess = function(e) {
406 cat.db = e.target.result;
407
408 if (typeof (callback) === 'function') callback(cat);
409 };
410
411 openRequest.onerror = function(e) {
412 throw e;
413 };
414 };
415
416 LokiCatalog.prototype.getAppKey = function(app, key, callback) {
417 var transaction = this.db.transaction(['LokiAKV'], 'readonly');
418 var store = transaction.objectStore('LokiAKV');
419 var index = store.index('appkey');
420 var appkey = app + "," + key;
421 var request = index.get(appkey);
422
423 request.onsuccess = (function(usercallback) {
424 return function(e) {
425 var lres = e.target.result;
426
427 if (lres === null || typeof(lres) === 'undefined') {
428 lres = {
429 id: 0,
430 success: false
431 };
432 }
433
434 if (typeof(usercallback) === 'function') {
435 usercallback(lres);
436 }
437 else {
438 console.log(lres);
439 }
440 };
441 })(callback);
442
443 request.onerror = (function(usercallback) {
444 return function(e) {
445 if (typeof(usercallback) === 'function') {
446 usercallback({ id: 0, success: false });
447 }
448 else {
449 throw e;
450 }
451 };
452 })(callback);
453 };
454
455 LokiCatalog.prototype.getAppKeyById = function (id, callback, data) {
456 var transaction = this.db.transaction(['LokiAKV'], 'readonly');
457 var store = transaction.objectStore('LokiAKV');
458 var request = store.get(id);
459
460 request.onsuccess = (function(data, usercallback){
461 return function(e) {
462 if (typeof(usercallback) === 'function') {
463 usercallback(e.target.result, data);
464 }
465 else {
466 console.log(e.target.result);
467 }
468 };
469 })(data, callback);
470 };
471
472 LokiCatalog.prototype.setAppKey = function (app, key, val, callback) {
473 var transaction = this.db.transaction(['LokiAKV'], 'readwrite');
474 var store = transaction.objectStore('LokiAKV');
475 var index = store.index('appkey');
476 var appkey = app + "," + key;
477 var request = index.get(appkey);
478
479 // first try to retrieve an existing object by that key
480 // need to do this because to update an object you need to have id in object, otherwise it will append id with new autocounter and clash the unique index appkey
481 request.onsuccess = function(e) {
482 var res = e.target.result;
483
484 if (res === null || res === undefined) {
485 res = {
486 app:app,
487 key:key,
488 appkey: app + ',' + key,
489 val:val
490 };
491 }
492 else {
493 res.val = val;
494 }
495
496 var requestPut = store.put(res);
497
498 requestPut.onerror = (function(usercallback) {
499 return function(e) {
500 if (typeof(usercallback) === 'function') {
501 usercallback({ success: false });
502 }
503 else {
504 console.error('LokiCatalog.setAppKey (set) onerror');
505 console.error(request.error);
506 }
507 };
508
509 })(callback);
510
511 requestPut.onsuccess = (function(usercallback) {
512 return function(e) {
513 if (typeof(usercallback) === 'function') {
514 usercallback({ success: true });
515 }
516 };
517 })(callback);
518 };
519
520 request.onerror = (function(usercallback) {
521 return function(e) {
522 if (typeof(usercallback) === 'function') {
523 usercallback({ success: false });
524 }
525 else {
526 console.error('LokiCatalog.setAppKey (get) onerror');
527 console.error(request.error);
528 }
529 };
530 })(callback);
531 };
532
533 LokiCatalog.prototype.deleteAppKey = function (id, callback) {
534 var transaction = this.db.transaction(['LokiAKV'], 'readwrite');
535 var store = transaction.objectStore('LokiAKV');
536 var request = store.delete(id);
537
538 request.onsuccess = (function(usercallback) {
539 return function(evt) {
540 if (typeof(usercallback) === 'function') usercallback({ success: true });
541 };
542 })(callback);
543
544 request.onerror = (function(usercallback) {
545 return function(evt) {
546 if (typeof(usercallback) === 'function') {
547 usercallback(false);
548 }
549 else {
550 console.error('LokiCatalog.deleteAppKey raised onerror');
551 console.error(request.error);
552 }
553 };
554 })(callback);
555 };
556
557 LokiCatalog.prototype.getAppKeys = function(app, callback) {
558 var transaction = this.db.transaction(['LokiAKV'], 'readonly');
559 var store = transaction.objectStore('LokiAKV');
560 var index = store.index('app');
561
562 // We want cursor to all values matching our (single) app param
563 var singleKeyRange = IDBKeyRange.only(app);
564
565 // To use one of the key ranges, pass it in as the first argument of openCursor()/openKeyCursor()
566 var cursor = index.openCursor(singleKeyRange);
567
568 // cursor internally, pushing results into this.data[] and return
569 // this.data[] when done (similar to service)
570 var localdata = [];
571
572 cursor.onsuccess = (function(data, callback) {
573 return function(e) {
574 var cursor = e.target.result;
575 if (cursor) {
576 var currObject = cursor.value;
577
578 data.push(currObject);
579
580 cursor.continue();
581 }
582 else {
583 if (typeof(callback) === 'function') {
584 callback(data);
585 }
586 else {
587 console.log(data);
588 }
589 }
590 };
591 })(localdata, callback);
592
593 cursor.onerror = (function(usercallback) {
594 return function(e) {
595 if (typeof(usercallback) === 'function') {
596 usercallback(null);
597 }
598 else {
599 console.error('LokiCatalog.getAppKeys raised onerror');
600 console.error(e);
601 }
602 };
603 })(callback);
604
605 };
606
607 // Hide 'cursoring' and return array of { id: id, key: key }
608 LokiCatalog.prototype.getAllKeys = function (callback) {
609 var transaction = this.db.transaction(['LokiAKV'], 'readonly');
610 var store = transaction.objectStore('LokiAKV');
611 var cursor = store.openCursor();
612
613 var localdata = [];
614
615 cursor.onsuccess = (function(data, callback) {
616 return function(e) {
617 var cursor = e.target.result;
618 if (cursor) {
619 var currObject = cursor.value;
620
621 data.push(currObject);
622
623 cursor.continue();
624 }
625 else {
626 if (typeof(callback) === 'function') {
627 callback(data);
628 }
629 else {
630 console.log(data);
631 }
632 }
633 };
634 })(localdata, callback);
635
636 cursor.onerror = (function(usercallback) {
637 return function(e) {
638 if (typeof(usercallback) === 'function') usercallback(null);
639 };
640 })(callback);
641
642 };
643
644 return LokiIndexedAdapter;
645
646 }());
647}));
648</code></pre>
649 </article>
650 </section>
651
652
653
654
655</div>
656
657<nav>
658 <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Collection.html">Collection</a></li><li><a href="DynamicView.html">DynamicView</a></li><li><a href="Loki.html">Loki</a></li><li><a href="LokiEventEmitter.html">LokiEventEmitter</a></li><li><a href="LokiFsAdapter.html">LokiFsAdapter</a></li><li><a href="LokiFsStructuredAdapter.html">LokiFsStructuredAdapter</a></li><li><a href="LokiIndexedAdapter.html">LokiIndexedAdapter</a></li><li><a href="LokiLocalStorageAdapter.html">LokiLocalStorageAdapter</a></li><li><a href="LokiMemoryAdapter.html">LokiMemoryAdapter</a></li><li><a href="LokiPartitioningAdapter.html">LokiPartitioningAdapter</a></li><li><a href="Resultset.html">Resultset</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-Autoupdating Collections.html">Autoupdating Collections</a></li><li><a href="tutorial-Changes API.html">Changes API</a></li><li><a href="tutorial-Collection Transforms.html">Collection Transforms</a></li><li><a href="tutorial-Indexing and Query performance.html">Indexing and Query performance</a></li><li><a href="tutorial-Loki Angular.html">Loki Angular</a></li><li><a href="tutorial-Persistence Adapters.html">Persistence Adapters</a></li><li><a href="tutorial-Query Examples.html">Query Examples</a></li></ul>
659</nav>
660
661<br class="clear">
662
663<footer>
664 Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Sun Dec 18 2016 19:39:51 GMT-0500 (Eastern Standard Time)
665</footer>
666
667<script> prettyPrint(); </script>
668<script src="scripts/linenumber.js"> </script>
669</body>
670</html>