chex-storage
Version:
A wrapper library for Chrome extension storage local - the standard storage in the chrome extension.
509 lines (501 loc) • 15.3 kB
JavaScript
"use strict";
(() => {
// src/utils/result.ts
function ChexStorageResult(items) {
const newArray = new Array(...items);
Object.setPrototypeOf(Array.prototype, ChexStorageResult.prototype);
ChexStorageResult.prototype.sortBy = function(fieldName) {
const field = fieldName.slice(0, -1);
const self = this;
if (fieldName.indexOf("+") > 0) {
return self.sort(
(a, b) => a[field] > b[field] ? 1 : -1
);
}
return self.sort(
(a, b) => a[field] > b[field] ? -1 : 1
);
};
return newArray;
}
var result_default = ChexStorageResult;
// src/utils/function.ts
function standardizeCharacter(str) {
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, "a");
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, "e");
str = str.replace(/ì|í|ị|ỉ|ĩ/g, "i");
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, "o");
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, "u");
str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, "y");
str = str.replace(/đ/g, "d");
str = str.replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, "");
str = str.replace(/\u02C6|\u0306|\u031B/g, "");
str = str.replace(/A|Á|À|Ã|Ạ|Â|Ấ|Ầ|Ẫ|Ậ|Ă|Ắ|Ằ|Ẵ|Ặ/g, "A");
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, "a");
str = str.replace(/E|É|È|Ẽ|Ẹ|Ê|Ế|Ề|Ễ|Ệ/, "E");
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, "e");
str = str.replace(/I|Í|Ì|Ĩ|Ị/g, "I");
str = str.replace(/ì|í|ị|ỉ|ĩ/g, "i");
str = str.replace(/O|Ó|Ò|Õ|Ọ|Ô|Ố|Ồ|Ỗ|Ộ|Ơ|Ớ|Ờ|Ỡ|Ợ/g, "O");
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, "o");
str = str.replace(/U|Ú|Ù|Ũ|Ụ|Ư|Ứ|Ừ|Ữ|Ự/g, "U");
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, "u");
str = str.replace(/Y|Ý|Ỳ|Ỹ|Ỵ/g, "Y");
str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, "y");
str = str.replace(/Đ/g, "D");
str = str.replace(/đ/g, "d");
str = str.replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, "");
str = str.replace(/\u02C6|\u0306|\u031B/g, "");
return str;
}
// src/extension/main/bitap-search.ts
function bitapFuzzyBitwiseSearch(text, pattern, k) {
if (pattern.length === 0) return text;
if (pattern.length > 31) return null;
const m = pattern.length;
const patternMask = Array.from({ length: 256 }, () => ~0n);
const R = Array.from({ length: k + 1 }, () => ~0n);
for (let i = 0; i < m; i++) {
patternMask[pattern.charCodeAt(i)] &= ~(1n << BigInt(i));
}
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i);
let oldRd1 = R[0];
R[0] |= patternMask[charCode];
R[0] <<= 1n;
for (let d = 1; d <= k; d++) {
const tmp = R[d];
R[d] = (oldRd1 & (R[d] | patternMask[charCode])) << 1n;
oldRd1 = tmp;
}
if ((R[k] & 1n << BigInt(m)) === 0n) {
return text.slice(i - m + 1, i + 1);
}
}
return null;
}
function bitapSearch(text, pattern, approximed = 0.5) {
return bitapFuzzyBitwiseSearch(
" " + standardizeCharacter(text),
standardizeCharacter(pattern),
pattern.length - Math.round(pattern.length * approximed)
);
}
// src/extension/main/_where.ts
var ChexStorageWhereMethods = class {
#database;
#tableName;
#keyWhere;
constructor(database2, tableName, keyWhere) {
this.#database = database2;
this.#tableName = tableName;
this.#keyWhere = keyWhere;
}
/**
* Query data where the specified key equals the given value
*
* @param val
* @returns TData[]
*/
async equals(val) {
const tableData = (await chrome.storage.local.get(this.#database))?.[this.#database]?.[this.#tableName] || [];
return tableData.filter((row) => row[this.#keyWhere] === val);
}
/**
* Search with fuzzy search algorithm
*
* @param pattern
* @returns TData[]
*/
async search(pattern) {
const tableData = (await chrome.storage.local.get(this.#database))?.[this.#database]?.[this.#tableName] || [];
return result_default(
tableData.filter(
(row) => bitapSearch(row[this.#keyWhere], pattern)
)
);
}
};
var where_default = ChexStorageWhereMethods;
// src/extension/main/table.ts
var ChexTable = class {
#tableName;
#database;
#keyName;
#key;
constructor(database2, name, keyName) {
this.#database = database2;
this.#tableName = name;
this.#keyName = keyName.split("++").length === 2 ? keyName.split("++")[1] : keyName;
this.#key = keyName;
}
/**
* Get data database
*/
async getDatabase() {
const data = await chrome.storage.local.get(this.#database);
return data[this.#database];
}
/**
* Generate key
* When add new data, it'll detect auto create or not key
* @param data
* @returns
*/
async generateKey(data) {
if (this.#key.indexOf("++") === 0) {
const db = await this.getDatabase();
const keyName = this.#key.split("++")[1];
const tableData = db[this.#tableName]?.sort(
(a, b) => a[keyName] - b[keyName]
) || [];
return (tableData?.[tableData.length - 1]?.[keyName] || 0) + 1;
}
if (!data?.[this.#keyName]) {
throw new Error("Must have key in data");
}
return data[this.#keyName];
}
/**
* Querying data with the specified key condition
*
* @param keyWhere
* @returns ChexStorageWhereMethods
*/
where(keyWhere) {
const whereMethod = new where_default(
this.#database,
this.#tableName,
keyWhere
);
return whereMethod;
}
/**
* Adds new data to the table and returns the key of the added data.
*
* @param data
* @returns Key value
*/
async add(data) {
const db = await this.getDatabase();
const tableData = db[this.#tableName] || [];
const key = await this.generateKey(data);
await chrome.storage.local.set({
[this.#database]: {
...db,
[this.#tableName]: [
...tableData,
{
[this.#keyName]: key,
...data
}
]
}
});
return key;
}
/**
* Adds multiple data entries to the table
*
* @param data
*/
async bulkAdd(data) {
const db = await this.getDatabase();
const tableData = db[this.#tableName] || [];
const addedItems = [];
for (const row of data) {
const key = await this.generateKey(row);
addedItems.push({
[this.#keyName]: key,
...row
});
}
await chrome.storage.local.set({
[this.#database]: {
...db,
[this.#tableName]: [...tableData, ...addedItems]
}
});
}
async get(key) {
const db = await this.getDatabase();
const tableData = db?.[this.#tableName] || [];
if (!key) {
return tableData;
}
return tableData?.find((row) => row?.[this.#keyName] === key);
}
/**
* Updates data in the table by key and returns the key of the updated data
*
* @param key
* @param change
*/
async update(keyValue, change) {
const db = await this.getDatabase();
const tableData = db[this.#tableName];
const rowNeedUpdate = tableData.find(
(row) => row[this.#keyName] === keyValue
);
if (!rowNeedUpdate) {
throw new Error("Don't find item with key");
}
await chrome.storage.local.set({
[this.#database]: {
...db,
[this.#tableName]: [
...tableData.filter((row) => row[this.#keyName] !== keyValue),
{
...rowNeedUpdate,
...change
}
]
}
});
return keyValue;
}
/**
* Update data for create new record
*
* @param keyValue
* @param change
* @param defaultValue
* @returns
*/
async updateOrCreate(keyValue, change, defaultValue) {
const db = await this.getDatabase();
const tableData = db[this.#tableName];
const rowNeedUpdate = tableData?.find(
(row) => row[this.#keyName] === keyValue
);
if (!rowNeedUpdate) {
return await this.add({
[this.#keyName]: keyValue,
...defaultValue
});
}
await chrome.storage.local.set({
[this.#database]: {
...db,
[this.#tableName]: [
...tableData.filter((row) => row[this.#keyName] !== keyValue),
{
...rowNeedUpdate,
...change
}
]
}
});
return keyValue;
}
/**
* Updates all data in the table with the specified changes.
*
* @param change
*/
async updateAll(change) {
const db = await this.getDatabase();
const tableData = db[this.#tableName]?.map((row) => {
return {
...row,
...change
};
});
await chrome.storage.local.set({
[this.#database]: {
...db,
[this.#tableName]: tableData
}
});
}
/**
* Deletes multiple data entries from the table by their keys
*
* @param keyValue
*/
async delete(keyValue) {
const db = await this.getDatabase();
const tableData = db[this.#tableName];
await chrome.storage.local.set({
[this.#database]: {
...db,
[this.#tableName]: tableData.filter(
(row) => row[this.#keyName] !== keyValue
)
}
});
}
/**
* Delete
*
* @param keyValues
*/
async bulkDelete(keyValues) {
const db = await this.getDatabase();
const tableData = db[this.#tableName];
await chrome.storage.local.set({
[this.#database]: {
...db,
[this.#tableName]: tableData.filter(
(row) => !keyValues.includes(row[this.#keyName])
)
}
});
}
/**
* Query data with the same of filter factory function
*
* @param callback
* @returns TData[]
*/
async filter(callback) {
const db = await this.getDatabase();
const tableData = db[this.#tableName];
return tableData.filter(callback);
}
};
var table_default = ChexTable;
// src/extension/index.ts
var ChexDatabase = class {
constructor(databaseName, initData) {
this.databaseName = databaseName;
chrome.storage.local.get(databaseName).then((db) => {
if (!db || !Object.keys(db).length) {
chrome.storage.local.set({
[databaseName]: {
...initData || {}
}
});
}
});
}
tables(tables) {
const self = this;
Object.keys(tables).forEach((table) => {
const tableClass = new table_default(
self.databaseName,
table,
tables[table]
);
self[table] = tableClass;
});
}
};
var extension_default = ChexDatabase;
// src/constant.ts
var event = {
INIT: "chex-storage-init",
DEFINE_TABLE: "chex-storage-define-table",
ADD: "chex-storage-add",
GET: "chex-storage-get",
GET_TABLE: "chex-storage-get-table",
WHERE_EQUAL: "chex-storage-where-equal",
SEARCH: "chex-storage-where-search",
UPDATE: "chex-storage-update",
UPDATE_OR_CREATE: "chex-storage-update-or-create",
UPDATE_ALL: "chex-storage-update-all",
DELETE: "chex-storage-delete",
BULK_ADD: "chex-storage-bulk-add",
BULK_DELETE: "chex-storage-bulk-delete"
};
// src/extension/bg-consumer.ts
var database;
chrome.runtime.onMessageExternal.addListener(
async (reqMessage, sender, sendResponse) => {
try {
const message = JSON.parse(reqMessage);
const type = message.type;
const payload = message.payload;
const config = message.config;
if (config?.debug) {
console.log("[ChexDatabaseWeb]: Consumer", {
type,
payload
});
}
switch (type) {
case event.INIT: {
database = new extension_default(
payload.databaseName,
payload?.initData
);
sendResponse({
status: true
});
break;
}
case event.DEFINE_TABLE: {
database.tables(payload.tables);
sendResponse({
status: true
});
break;
}
case event.WHERE_EQUAL: {
const data = await database[payload.table].where(payload.keyWhere).equals(payload.value);
sendResponse({ data, status: true });
break;
}
case event.GET: {
const data = await database[payload.table].get(payload.key);
sendResponse({ data, status: true });
break;
}
case event.ADD: {
const data = await database[payload.table].add(payload.data);
sendResponse({ data, status: true });
break;
}
case event.BULK_ADD: {
const data = await database[payload.table].bulkAdd(payload.data);
sendResponse({ data, status: true });
break;
}
case event.UPDATE: {
const data = await database[payload.table].update(
payload.keyValue,
payload.change
);
sendResponse({ data, status: true });
break;
}
case event.UPDATE_OR_CREATE: {
const data = await database[payload.table].updateOrCreate(
payload.keyValue,
payload.change,
payload.defaultValue
);
sendResponse({ data, status: true });
break;
}
case event.UPDATE_ALL: {
const data = await database[payload.table].updateAll(payload.change);
sendResponse({ data, status: true });
break;
}
case event.DELETE: {
await database[payload.table].delete(payload.keyValue);
sendResponse({ status: true });
break;
}
case event.BULK_DELETE: {
await database[payload.table].bulkDelete(payload.keyValues);
sendResponse({ status: true });
break;
}
case event.SEARCH: {
const data = await database[payload.table].where(payload.keyWhere).search(payload.pattern);
sendResponse({ status: true, data });
break;
}
default:
sendResponse({ status: false });
break;
}
} catch (error) {
console.log(error);
sendResponse({ status: false, error });
}
}
);
})();