ngx-indexed-db
Version:
Angular wrapper to IndexedDB database.
612 lines (604 loc) • 25.4 kB
JavaScript
import { InjectionToken, Injectable, Inject, PLATFORM_ID, NgModule } from '@angular/core';
import { isPlatformBrowser, CommonModule } from '@angular/common';
import { __awaiter } from 'tslib';
import { from, Subject } from 'rxjs';
import { take } from 'rxjs/operators';
function openDatabase(indexedDB, dbName, version, upgradeCallback) {
return new Promise((resolve, reject) => {
if (!indexedDB) {
reject('IndexedDB not available');
}
const request = indexedDB.open(dbName, version);
let db;
request.onsuccess = (event) => {
db = request.result;
resolve(db);
};
request.onerror = (event) => {
reject(`IndexedDB error: ${request.error}`);
};
if (typeof upgradeCallback === 'function') {
request.onupgradeneeded = (event) => {
upgradeCallback(event, db);
};
}
});
}
function CreateObjectStore(indexedDB, dbName, version, storeSchemas, migrationFactory) {
if (!indexedDB) {
return;
}
const request = indexedDB.open(dbName, version);
request.onupgradeneeded = (event) => {
const database = event.target.result;
storeSchemas.forEach((storeSchema) => {
if (!database.objectStoreNames.contains(storeSchema.store)) {
const objectStore = database.createObjectStore(storeSchema.store, storeSchema.storeConfig);
storeSchema.storeSchema.forEach((schema) => {
objectStore.createIndex(schema.name, schema.keypath, schema.options);
});
}
});
const storeMigrations = migrationFactory && migrationFactory();
if (storeMigrations) {
Object.keys(storeMigrations)
.map((k) => parseInt(k, 10))
.filter((v) => v > event.oldVersion)
.sort((a, b) => a - b)
.forEach((v) => {
storeMigrations[v](database, request.transaction);
});
}
database.close();
};
request.onsuccess = (e) => {
e.target.result.close();
};
}
function validateStoreName(db, storeName) {
return db.objectStoreNames.contains(storeName);
}
function validateBeforeTransaction(db, storeName, reject) {
if (!db) {
reject('You need to use the openDatabase function to create a database before you query it!');
}
if (!validateStoreName(db, storeName)) {
reject(`objectStore does not exists: ${storeName}`);
}
}
function createTransaction(db, options) {
const trans = db.transaction(options.storeName, options.dbMode);
trans.onerror = options.error;
trans.oncomplete = options.complete;
trans.onabort = options.abort;
return trans;
}
function optionsGenerator(type, storeName, reject, resolve) {
return {
storeName,
dbMode: type,
error: (e) => {
reject(e);
},
complete: (e) => {
resolve(e);
},
abort: (e) => {
reject(e);
},
};
}
var DBMode;
(function (DBMode) {
DBMode["readonly"] = "readonly";
DBMode["readwrite"] = "readwrite";
})(DBMode || (DBMode = {}));
const CONFIG_TOKEN = new InjectionToken(null);
class NgxIndexedDBService {
constructor(dbConfig, platformId) {
this.dbConfig = dbConfig;
this.platformId = platformId;
if (!dbConfig.name) {
throw new Error('NgxIndexedDB: Please, provide the dbName in the configuration');
}
if (!dbConfig.version) {
throw new Error('NgxIndexedDB: Please, provide the db version in the configuration');
}
this.isBrowser = isPlatformBrowser(this.platformId);
if (this.isBrowser) {
this.indexedDB =
window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB;
CreateObjectStore(this.indexedDB, dbConfig.name, dbConfig.version, dbConfig.objectStoresMeta, dbConfig.migrationFactory);
}
}
/**
* Allows to crate a new object store ad-hoc
* @param storeName The name of the store to be created
* @param migrationFactory The migration factory if exists
*/
createObjectStore(storeSchema, migrationFactory) {
const storeSchemas = [storeSchema];
CreateObjectStore(this.indexedDB, this.dbConfig.name, ++this.dbConfig.version, storeSchemas, migrationFactory);
}
/**
* Adds new entry in the store and returns its key
* @param storeName The name of the store to add the item
* @param value The entry to be added
* @param key The optional key for the entry
*/
add(storeName, value, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const request = Boolean(key)
? objectStore.add(value, key)
: objectStore.add(value);
request.onsuccess = (evt) => {
const result = evt.target.result;
resolve(result);
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Adds new entries in the store and returns its key
* @param storeName The name of the store to add the item
* @param values The entries to be added containing optional key attribute
*/
bulkAdd(storeName, values) {
const promises = values.map((value) => {
return new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const key = value.key;
delete value.key;
const request = Boolean(key)
? objectStore.add(value, key)
: objectStore.add(value);
request.onsuccess = (evt) => {
const result = evt.target.result;
resolve(result);
};
})
.catch((reason) => reject(reason));
});
});
return from(Promise.resolve(Promise.all(promises)));
}
/**
* Adds new entry in the store and returns the item that was added
* @param storeName The name of the store to add the item
* @param value The entry to be added
* @param key The optional key for the entry
*/
addItem(storeName, value, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const hasKey = Boolean(key);
const request = hasKey ? objectStore.add(value, key) : objectStore.add(value);
request.onsuccess = (evt) => {
const result = evt.target.result;
const itemKey = hasKey ? key : result;
this.getByKey(storeName, itemKey).subscribe((newValue) => {
resolve(newValue);
});
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Adds new entry in the store and returns the item that was added
* @param storeName The name of the store to add the item
* @param value The entry to be added
* @param key The key for the entry
*/
addItemWithKey(storeName, value, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
transaction.oncomplete = () => {
this.getByKey(storeName, key).subscribe((newValue) => {
resolve(newValue);
});
};
objectStore.add(value, key);
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns entry by key.
* @param storeName The name of the store to query
* @param key The entry key
*/
getByKey(storeName, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
const transaction = createTransaction(db, optionsGenerator(DBMode.readonly, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const request = objectStore.get(key);
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject(event);
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Retrieve multiple entries in the store
* @param storeName The name of the store to retrieve the items
* @param keys The ids entries to be retrieve
*/
bulkGet(storeName, keys) {
const promises = keys.map(key => this.getByKey(storeName, key).toPromise());
return from(Promise.resolve(Promise.all(promises)));
}
/**
* Returns entry by id.
* @param storeName The name of the store to query
* @param id The entry id
*/
getByID(storeName, id) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readonly, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const request = objectStore.get(id);
request.onsuccess = (event) => {
resolve(event.target.result);
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns entry by index.
* @param storeName The name of the store to query
* @param indexName The index name to filter
* @param key The entry key.
*/
getByIndex(storeName, indexName, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readonly, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const index = objectStore.index(indexName);
const request = index.get(key);
request.onsuccess = (event) => {
resolve(event.target.result);
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Return all elements from one store
* @param storeName The name of the store to select the items
*/
getAll(storeName) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readonly, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const request = objectStore.getAll();
request.onerror = (evt) => {
reject(evt);
};
request.onsuccess = ({ target: { result: ResultAll } }) => {
resolve(ResultAll);
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns all items from the store after update.
* @param storeName The name of the store to update
* @param value The new value for the entry
* @param key The key of the entry to update if exists
*/
update(storeName, value, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
transaction.oncomplete = () => {
this.getAll(storeName)
.pipe(take(1))
.subscribe((newValues) => {
resolve(newValues);
});
};
key ? objectStore.put(value, key) : objectStore.put(value);
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns the item you updated from the store after the update.
* @param storeName The name of the store to update
* @param value The new value for the entry
* @param key The key of the entry to update
*/
updateByKey(storeName, value, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
transaction.oncomplete = () => {
this.getByKey(storeName, key).subscribe((newValue) => {
resolve(newValue);
});
};
objectStore.put(value, key);
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns all items from the store after delete.
* @param storeName The name of the store to have the entry deleted
* @param key The key of the entry to be deleted
*/
delete(storeName, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
objectStore.delete(key);
transaction.oncomplete = () => {
this.getAll(storeName)
.pipe(take(1))
.subscribe((newValues) => {
resolve(newValues);
});
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns true from the store after a successful delete.
* @param storeName The name of the store to have the entry deleted
* @param key The key of the entry to be deleted
*/
deleteByKey(storeName, key) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
transaction.oncomplete = () => {
resolve(true);
};
objectStore.delete(key);
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns true if successfully delete all entries from the store.
* @param storeName The name of the store to have the entries deleted
*/
clear(storeName) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readwrite, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
objectStore.clear();
transaction.oncomplete = () => {
resolve(true);
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns true if successfully delete the DB.
*/
deleteDatabase() {
return from(new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
const db = yield openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version);
yield db.close();
const deleteDBRequest = this.indexedDB.deleteDatabase(this.dbConfig.name);
deleteDBRequest.onsuccess = () => {
resolve(true);
};
deleteDBRequest.onerror = reject;
deleteDBRequest.onblocked = () => {
throw new Error(`Unable to delete database because it's blocked`);
};
}
catch (evt) {
reject(evt);
}
})));
}
/**
* Returns the open cursor event
* @param storeName The name of the store to have the entries deleted
* @param keyRange The key range which the cursor should be open on
*/
openCursor(storeName, keyRange) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readonly, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const request = keyRange === undefined ? objectStore.openCursor() : objectStore.openCursor(keyRange);
request.onsuccess = (event) => {
resolve(event);
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Open a cursor by index filter.
* @param storeName The name of the store to query.
* @param indexName The index name to filter.
* @param keyRange The range value and criteria to apply on the index.
*/
openCursorByIndex(storeName, indexName, keyRange, mode = DBMode.readonly) {
const obs = new Subject();
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, (reason) => {
obs.error(reason);
});
const transaction = createTransaction(db, optionsGenerator(mode, storeName, (reason) => {
obs.error(reason);
}, () => {
obs.next();
}));
const objectStore = transaction.objectStore(storeName);
const index = objectStore.index(indexName);
const request = index.openCursor(keyRange);
request.onsuccess = (event) => {
obs.next(event);
};
})
.catch((reason) => obs.error(reason));
return obs;
}
/**
* Returns all items by an index.
* @param storeName The name of the store to query
* @param indexName The index name to filter
* @param keyRange The range value and criteria to apply on the index.
*/
getAllByIndex(storeName, indexName, keyRange) {
const data = [];
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readonly, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const index = objectStore.index(indexName);
const request = index.openCursor(keyRange);
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
data.push(cursor.value);
cursor.continue();
}
else {
resolve(data);
}
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns all primary keys by an index.
* @param storeName The name of the store to query
* @param indexName The index name to filter
* @param keyRange The range value and criteria to apply on the index.
*/
getAllKeysByIndex(storeName, indexName, keyRange) {
const data = [];
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readonly, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const index = objectStore.index(indexName);
const request = index.openKeyCursor(keyRange);
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
data.push({ primaryKey: cursor.primaryKey, key: cursor.key });
cursor.continue();
}
else {
resolve(data);
}
};
})
.catch((reason) => reject(reason));
}));
}
/**
* Returns the number of rows in a store.
* @param storeName The name of the store to query
* @param keyRange The range value and criteria to apply.
*/
count(storeName, keyRange) {
return from(new Promise((resolve, reject) => {
openDatabase(this.indexedDB, this.dbConfig.name, this.dbConfig.version)
.then((db) => {
validateBeforeTransaction(db, storeName, reject);
const transaction = createTransaction(db, optionsGenerator(DBMode.readonly, storeName, reject, resolve));
const objectStore = transaction.objectStore(storeName);
const request = objectStore.count(keyRange);
request.onerror = (e) => reject(e);
request.onsuccess = (e) => resolve(e.target.result);
})
.catch((reason) => reject(reason));
}));
}
}
NgxIndexedDBService.decorators = [
{ type: Injectable }
];
NgxIndexedDBService.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [CONFIG_TOKEN,] }] },
{ type: undefined, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
];
class NgxIndexedDBModule {
static forRoot(dbConfig) {
return {
ngModule: NgxIndexedDBModule,
providers: [NgxIndexedDBService, { provide: CONFIG_TOKEN, useValue: dbConfig }]
};
}
}
NgxIndexedDBModule.decorators = [
{ type: NgModule, args: [{
declarations: [],
imports: [CommonModule]
},] }
];
/**
* Generated bundle index. Do not edit.
*/
export { CONFIG_TOKEN, DBMode, NgxIndexedDBModule, NgxIndexedDBService };
//# sourceMappingURL=ngx-indexed-db.js.map