UNPKG

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
"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 }); } } ); })();