@stexcore/indexed-db
Version:
Manage the IndexedDB Web API, through a simple and easy interface.
436 lines (435 loc) • 22.7 kB
JavaScript
;
/***************************************************************
Authors : Steven Aray - Sunday, December 12, 2024
Website : https://stexcore.com
Lib: indexed-db
Copyright 2024
***************************************************************/
Object.defineProperty(exports, "__esModule", { value: true });
exports.IndexedDB = void 0;
exports.createStructTables = createStructTables;
/**
* Make a structure of tables database to manage indexeddb
* @param struct Structure table
* @returns Structure table
*/
function createStructTables(struct) {
return struct;
}
/**
* Database to manage tables based in IndexedDB
*/
class IndexedDB {
/**
* Inicialize the structure of database
* @param database Database name
* @param table_structs Structure of tables
*/
constructor(
/**
* database name
*/
db_name, table_structs) {
this.db_name = db_name;
this.table_structs = table_structs;
/**
* Promise async getting database
*/
this.gettingDatabase = [];
const struct = {};
// Earch all tables to index the structure fields
for (const table_name in table_structs) {
const table = table_structs[table_name];
let fieldPrimary;
// Earch fields to validate initial structure
for (const field_name in table) {
const field = table[field_name];
// Validate if exitst athoner primary key
if (field.primarykey) {
if (fieldPrimary)
throw new Error("The table '" + table_name + "' has duplicates primary keys: '" + fieldPrimary + "' and '" + field_name + "'");
fieldPrimary = field_name;
}
}
// validate if this table does'nt has a primary key
if (!fieldPrimary)
throw new Error("The table '" + table_name + "' does'nt has a field with primary key");
// set to memory the structure table and her primary key
struct[table_name] = {
primaryKey: fieldPrimary,
fields: table_structs[table_name]
};
}
// Save to local memory the structure and hers primary key
this.structs = struct;
}
/**
* asynchronously obtains the database
* @returns Indexed Database
*/
GetDataBase() {
return new Promise((_resolve, _reject) => {
try {
// If exist in memory, resolve the database
if (this.db)
_resolve(this.db);
// Append to getting
this.gettingDatabase.push({
resolve: _resolve,
reject: _reject
});
if (this.gettingDatabase.length > 1)
return;
/**
* Resolve all gettings
* @param db Database
*/
const resolve = (db) => {
const temp = this.gettingDatabase;
this.gettingDatabase = [];
temp.forEach((item) => {
item.resolve(db);
});
};
/**
* Reject all gettings
* @param err Error
*/
const reject = (err) => {
const temp = this.gettingDatabase;
this.gettingDatabase = [];
temp.forEach((item) => {
item.reject(err);
});
};
// Get the last version of database
indexedDB.databases()
.then((databases) => {
// extract version and open the database
const databaseInfo = databases.find(databaseItem => databaseItem.name == this.db_name);
const requestOpen = indexedDB.open(this.db_name, ((databaseInfo === null || databaseInfo === void 0 ? void 0 : databaseInfo.version) || 0) + 1);
requestOpen.onsuccess = () => {
this.db = requestOpen.result;
resolve(requestOpen.result);
};
requestOpen.onerror = () => {
reject(requestOpen.error);
};
requestOpen.onupgradeneeded = (ev) => {
// earch the structure to create tables in database
for (const table_name in this.structs) {
const table = this.structs[table_name];
const primaryKey = table.fields[table.primaryKey];
let objectStore;
// if not exists the table, create a new, and set the structure index
if (!requestOpen.result.objectStoreNames.contains(table_name)) {
objectStore = requestOpen.result.createObjectStore(table_name, {
autoIncrement: primaryKey.autoincrement,
keyPath: table.primaryKey
});
}
else {
objectStore = requestOpen.transaction.objectStore(table_name);
}
// earch all fields
for (const fieldname in table.fields) {
const field = table.fields[fieldname];
// if not exist the field index, create a new
if (!objectStore.indexNames.contains(fieldname)) {
objectStore.createIndex(fieldname, fieldname, {
unique: !!field.unique
});
}
}
// search fields to check the index structure that should not exist
for (const fieldname of objectStore.indexNames) {
if (!table.fields[fieldname]) {
objectStore.deleteIndex(fieldname);
}
}
}
};
})
.catch((err) => {
reject(err);
});
}
catch (err) {
_reject(err);
}
});
}
/**
* Get a interface to manage a table of indexedDB
* @param name Name of table
* @returns Inteface to manage the table
*/
getTable(name) {
return new Promise((resolve, reject) => {
try {
// If do'nt exist the table
if (!this.structs[name]) {
throw new Error("Does'nt exist the table '" + String(name) + "'");
}
// get the database
this.GetDataBase()
.then((db) => {
const structure = this.structs[name];
const fields = structure.fields;
/**
* Validate types and remove fields unused
* @param v value incomming
* @returns Value with a correct structure
*/
const format = (v) => {
const data = {};
for (const fieldname in fields) {
const fieldItem = fields[fieldname];
if ((v[fieldname] === undefined || v[fieldname] === null) && fieldItem.allow_null) {
data[fieldname] = null;
}
else {
let isValid = true;
switch (fields[fieldname].type) {
case "string":
isValid = typeof v[fieldname] === "string";
break;
case "array":
isValid = v[fieldname] instanceof Array;
break;
case "object":
isValid = v[fieldname] instanceof Object;
break;
case "boolean":
isValid = typeof v[fieldname] === "boolean";
break;
case "number":
isValid = typeof v[fieldname] === "number";
break;
default:
// TODO: manage objects
}
if (!(fieldItem.autoincrement && fieldItem.primarykey)) {
if (!isValid)
throw new Error("Unexpected type in field '" + fieldname + "'. Type required '" + fieldItem.type + "', type received '" + v[fieldname] + "'");
data[fieldname] = v[fieldname];
}
}
}
return data;
};
/**
* Remove undefineds in a object
* @param v value incoming
* @returns value without fields undefineds
*/
const removeUndefineds = (v) => {
const data = {};
for (const key in v) {
if (v[key] !== undefined)
data[key] = v[key];
}
return data;
};
/**
* Instance table
*/
const table = {
table_name: String(name),
// find all records
findAll(searchOptions) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(name, "readonly");
const storage = transaction.objectStore(name);
// prepare where object
const where = (searchOptions === null || searchOptions === void 0 ? void 0 : searchOptions.where) || {};
const keys = Object.keys(where).sort((a, b) => (a > b ? 1 : -b));
// get all recods
const requestGetAll = storage.getAll();
requestGetAll.onsuccess = () => {
var _a;
// filter based in where
const filteredRecords = requestGetAll.result.filter(record => {
return keys.every((key) => {
const value = where[key];
const recordValue = record[key];
// If the search value is an array, checks if the record value is in the array
if (Array.isArray(value)) {
return value.includes(recordValue);
}
// If not an array, compare directly
return recordValue === value;
});
});
// trim records based on limit
const slicedRecords = filteredRecords.slice((searchOptions === null || searchOptions === void 0 ? void 0 : searchOptions.offset) || 0, ((searchOptions === null || searchOptions === void 0 ? void 0 : searchOptions.offset) || 0) + ((_a = searchOptions === null || searchOptions === void 0 ? void 0 : searchOptions.limit) !== null && _a !== void 0 ? _a : filteredRecords.length));
// select attributes of result
const records = ((searchOptions === null || searchOptions === void 0 ? void 0 : searchOptions.attributes) && searchOptions.attributes.length) ? (slicedRecords.map((record) => {
const item = {};
for (const attrItem of searchOptions.attributes) {
if (attrItem) {
item[attrItem] = record[attrItem];
}
}
return item;
})) : slicedRecords;
resolve(records);
};
requestGetAll.onerror = () => {
reject(requestGetAll.error);
};
});
},
// find one record
findOne(searchOptions) {
return (table.findAll(Object.assign(Object.assign({}, searchOptions), { limit: 1, offset: 0 }))
.then((record) => (record[0] || null)));
},
// insert recods
insert: (value) => {
return new Promise((resolve, reject) => {
try {
// get objetStorage/table
const objectstorage = db.transaction(name, "readwrite").objectStore(name);
// analize inputs to insert
const dataItem = (value instanceof Array ? value : [value]).map(d => format(d));
Promise.all(dataItem.map(data => {
return new Promise((resolve, reject) => {
try {
// insert recods
const requestAdd = objectstorage.add(data);
requestAdd.onsuccess = () => {
// resolve data with primary key generated
resolve(Object.assign(Object.assign({}, data), { [structure.primaryKey]: requestAdd.result }));
};
requestAdd.onerror = () => {
reject(requestAdd.error);
};
}
catch (err) {
reject(err);
}
});
}))
.then((insertedRegs) => {
// resolve records inserted
resolve(value instanceof Array ? insertedRegs : insertedRegs[0]);
});
}
catch (err) {
reject(err);
}
});
},
delete: (searchOptions) => {
return new Promise((resolve, reject) => {
try {
// search recods
table.findAll(searchOptions)
.then((regs) => {
// get objectStorage/table
const objectstorage = db.transaction(name, "readwrite").objectStore(name);
// earch records
Promise.all(regs.map(item => {
return new Promise((resolve, reject) => {
try {
// delete recods
const requestDelete = objectstorage.delete(item[structure.primaryKey]);
requestDelete.onsuccess = () => {
resolve(true);
};
requestDelete.onerror = () => {
console.error(requestDelete.error);
resolve(false);
};
}
catch (err) {
reject(err);
}
});
}))
.then((result) => {
// resolve counds records deleted
resolve({
n_affected: result.reduce((t, v) => t + Number(v), 0)
});
})
.catch(reject);
})
.catch(reject);
}
catch (err) {
reject(err);
}
});
},
update: (update, searchOptions) => {
return new Promise((resolve, reject) => {
try {
// search records
table.findAll(searchOptions)
.then((regs) => {
// get objectStorage/table
const objectstorage = db.transaction(name, "readwrite").objectStore(name);
// earch records
Promise.all(regs.map(item => {
return new Promise((resolve, reject) => {
try {
// update record
const requestDelete = objectstorage.put(Object.assign(Object.assign(Object.assign({}, item), removeUndefineds(update)), { [structure.primaryKey]: item[structure.primaryKey] }));
requestDelete.onsuccess = () => {
resolve(true);
};
requestDelete.onerror = () => {
console.error(requestDelete.error);
resolve(false);
};
}
catch (err) {
reject(err);
}
});
}))
.then((result) => {
// resolve records resolved
resolve({
n_affected: result.reduce((t, v) => t + Number(v), 0)
});
})
.catch(reject);
})
.catch(reject);
}
catch (err) {
reject(err);
}
});
},
count: (searchOptions) => {
return new Promise((resolve, reject) => {
try {
// search recods
table.findAll(searchOptions)
.then(regs => {
// resolve length
resolve(regs.length);
})
.catch(reject);
}
catch (err) {
reject(err);
}
});
}
};
resolve(table);
})
.catch(reject);
}
catch (err) {
reject(err);
}
});
}
}
exports.IndexedDB = IndexedDB;