UNPKG

ng-indexed-db

Version:

An IndexedDB Angular module wrapped in RXJS

560 lines (552 loc) 16.1 kB
import { Injectable, InjectionToken, Inject, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { map, switchMap, skipWhile } from 'rxjs/operators'; import { Observable, timer, forkJoin } from 'rxjs'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** @enum {string} */ const TransactionModes = { READONLY: 'readonly', READWRITE: 'readwrite', }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * IndexedDB client */ class IndexedDatabase { /** * @param {?} database * @param {?} upgraded * @param {?=} stores */ constructor(database, upgraded, stores = []) { this.database = database; this.upgraded = upgraded; this.createTables(stores); } /** * If table has been upgrades, generates the recived stores * @param {?} tables Tables metadata * @return {?} */ createTables(tables) { if (this.upgraded) { tables.forEach((/** * @param {?} item * @return {?} */ item => this.createTable(item))); } } /** * Generates one single sotre * @param {?} store * @return {?} */ createTable(store) { /** @type {?} */ const keyPath = 'id'; this.database.createObjectStore(store.name, { keyPath }); } /** * Request a list with all store elements * @template M * @param {?} storeName Store name * @return {?} */ list(storeName) { return new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ const store = this.store(storeName, TransactionModes.READONLY); /** @type {?} */ const request = store.getAll(); request.onsuccess = (/** * @param {?} event * @return {?} */ event => observer.next(request.result)); request.onerror = (/** * @param {?} event * @return {?} */ event => observer.error(request.error)); })); } /** * Request a single object found by the given key * @template M * @param {?} storeName Store name * @param {?} key Predicate key * @return {?} */ get(storeName, key) { return new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ const store = this.store(storeName, TransactionModes.READONLY); /** @type {?} */ const request = store.get(key); request.onsuccess = (/** * @param {?} event * @return {?} */ event => observer.next(request.result)); request.onerror = (/** * @param {?} event * @return {?} */ event => observer.error(request.error)); })); } /** * Request to store an element * @template M * @param {?} storeName Store name * @param {?} data data to store * @return {?} */ create(storeName, data) { return new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ const store = this.store(storeName, TransactionModes.READWRITE); /** @type {?} */ const request = store.put(data); request.onsuccess = (/** * @param {?} event * @return {?} */ event => observer.next(data)); request.onerror = (/** * @param {?} event * @return {?} */ event => observer.error(request.error)); })); } /** * Request to update an element * @template M * @param {?} storeName Store name * @param {?} data data to store * @return {?} */ update(storeName, data) { return new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ const store = this.store(storeName, TransactionModes.READWRITE); /** @type {?} */ const request = store.put(data); request.onsuccess = (/** * @param {?} event * @return {?} */ event => observer.next(data)); request.onerror = (/** * @param {?} event * @return {?} */ event => observer.error(request.error)); })); } /** * Request to delete an element with the given key * @param {?} storeName Store name * @param {?} key Element key to delete * @return {?} */ delete(storeName, key) { return new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ const store = this.store(storeName, TransactionModes.READWRITE); /** @type {?} */ const request = store.delete(key); request.onsuccess = (/** * @param {?} event * @return {?} */ event => observer.next(true)); request.onerror = (/** * @param {?} event * @return {?} */ event => observer.error(request.error)); })); } /** * Find the a store table by the given name * @private * @param {?} storeName Store name to find * @param {?} mode Transaction mode * @return {?} */ store(storeName, mode) { /** @type {?} */ const transaction = this.database.transaction(storeName, mode); /** @type {?} */ const store = transaction.objectStore(storeName); return store; } } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class NgDatabaseFactory { constructor() { /** * Emits an event when all databases recive has been generated */ this.connectionsReady = new BehaviorSubject(false); /** * Indexed database connection factory */ this.factory = window.indexedDB; } /** * Sends the database metadata to create. * Once it has been completed, emits the * connectionsReady event * @param {?} databases Databases metadata * @return {?} */ createConnnections(databases) { this.createDataBases(databases) .pipe(map((/** * @param {?} response * @return {?} */ response => this.connections = response)), switchMap((/** * @param {?} response * @return {?} */ response => timer(100)))) .subscribe((/** * @return {?} */ () => this.connectionsReady.next(true))); } /** * Find a database connection by name. If there isn't * a database name takes the default database name * @param {?=} dbName Name of the database alias * @return {?} */ getDatabase(dbName) { return this.connections[dbName ? dbName : this.defaultConnectionName]; } /** * Open a database connection * @param {?} dbName name of the database * @param {?=} version version of the database * @return {?} */ open(dbName, version) { return new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ let upgraded = false; /** @type {?} */ const DBOpenRequest = this.factory.open(dbName, version); DBOpenRequest.onupgradeneeded = (/** * @param {?} event * @return {?} */ event => { observer.next({ upgraded: true, success: true, result: DBOpenRequest.result }); observer.complete(); upgraded = true; }); DBOpenRequest.onsuccess = (/** * @param {?} event * @return {?} */ event => { if (!upgraded) { observer.next({ upgraded: false, success: true, result: DBOpenRequest.result }); observer.complete(); } }); DBOpenRequest.onerror = (/** * @param {?} event * @return {?} */ event => { observer.error(`Error to connect database ${dbName}`); observer.complete(); }); DBOpenRequest.onblocked = (/** * @param {?} event * @return {?} */ event => { console.log('blocked'); }); })); } /** * Generates an Observable collection of database connections * @param {?} databases * @return {?} */ createDataBases(databases) { /** @type {?} */ const operators = []; /** @type {?} */ let firstIteration = true; /** @type {?} */ const createConnnection = (/** * @param {?} item * @return {?} */ (item) => { if (firstIteration) { this.defaultConnectionName = item.name; firstIteration = false; } operators.push(this.createConnection(item.name, item.stores, 1)); }); databases.forEach(createConnnection); /** @type {?} */ const mappingResponse = (/** * @param {?} response * @return {?} */ response => { /** @type {?} */ let data = {}; response.forEach((/** * @param {?} item * @return {?} */ item => { data = Object.assign({}, data, item); })); return data; }); return forkJoin(operators) .pipe(map(mappingResponse)); } /** * Generates a sigle database conecction client * @private * @param {?} dbName * @param {?} tables * @param {?=} version * @return {?} */ createConnection(dbName, tables, version) { return this.open(dbName, version) .pipe(map((/** * @param {?} data * @return {?} */ data => ({ [dbName]: new IndexedDatabase(data.result, data.upgraded, tables) })))); } } NgDatabaseFactory.decorators = [ { type: Injectable } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class IdGenerator { /** * @param {?=} data * @return {?} */ generate(data) { return (new Date()).getTime(); } } IdGenerator.decorators = [ { type: Injectable } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * Injection token that gives the databases metadata * @type {?} */ const NG_DATABASES = new InjectionToken('NG_DATABASES'); /** * Service that handles all Indexed Databases and * tables requests. */ class IndexedDB { /** * @param {?} databases * @param {?} idGenerator * @param {?} databaseFactory */ constructor(databases, idGenerator, databaseFactory) { this.databases = databases; this.idGenerator = idGenerator; this.databaseFactory = databaseFactory; this.databaseFactory.createConnnections(this.databases); } /** * Indicates if all databases have been created. * Only emits an event when recives a true value. * @return {?} */ get onReady() { return this.databaseFactory.connectionsReady .pipe(skipWhile((/** * @param {?} event * @return {?} */ (event) => !event))); } /** * Request a list with all elements * @template M * @param {?} storeName Store name * @param {?=} databaseName Database name * @return {?} */ list(storeName, databaseName) { return this.onReady.pipe(switchMap((/** * @return {?} */ () => this.databaseFactory.getDatabase(databaseName).list(storeName)))); } /** * Request an element by key * @template M * @param {?} storeName Store name * @param {?} key Key value to find the element * @param {?=} databaseName Database name * @return {?} */ get(storeName, key, databaseName) { return this.onReady.pipe(switchMap((/** * @return {?} */ () => this.databaseFactory.getDatabase(databaseName).get(storeName, key)))); } /** * Request to store an element * @template M * @param {?} storeName Store name * @param {?} data * @param {?=} databaseName Database name * @return {?} */ create(storeName, data, databaseName) { // tslint:disable-next-line: no-string-literal if (!data['id']) { data['id'] = this.idGenerator.generate(); } return this.onReady.pipe(switchMap((/** * @return {?} */ () => this.databaseFactory.getDatabase(databaseName).create(storeName, data)))); } /** * Request to update a stored element * @template M * @param {?} storeName Store name * @param {?} data * @param {?=} databaseName Database name * @return {?} */ update(storeName, data, databaseName) { return this.onReady.pipe(switchMap((/** * @return {?} */ () => this.databaseFactory.getDatabase(databaseName).update(storeName, data)))); } /** * Request to delete a stored element * @param {?} storeName Store name * @param {?} key * @param {?=} databaseName Database name * @return {?} */ delete(storeName, key, databaseName) { return this.onReady.pipe(switchMap((/** * @return {?} */ () => this.databaseFactory.getDatabase(databaseName).delete(storeName, key)))); } } IndexedDB.decorators = [ { type: Injectable } ]; /** @nocollapse */ IndexedDB.ctorParameters = () => [ { type: Array, decorators: [{ type: Inject, args: [NG_DATABASES,] }] }, { type: IdGenerator }, { type: NgDatabaseFactory } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class IndexedDBModule { /** * @param {?=} databases * @return {?} */ static forRoot(databases = []) { return { ngModule: IndexedDBModule, providers: [ { provide: NG_DATABASES, useValue: databases } ] }; } } IndexedDBModule.decorators = [ { type: NgModule, args: [{ declarations: [], imports: [CommonModule], exports: [], providers: [ IndexedDB, NgDatabaseFactory, IdGenerator, ], },] } ]; export { IndexedDB, IndexedDBModule, NG_DATABASES, IdGenerator as ɵb, NgDatabaseFactory as ɵc }; //# sourceMappingURL=ng-indexed-db.js.map