ng-indexed-db
Version:
An IndexedDB Angular module wrapped in RXJS
560 lines (552 loc) • 16.1 kB
JavaScript
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