Helpful features to make Indexed DB easier to use
Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta
// @require https://update.greasyfork.org/scripts/541499/1618285/iDB%20Helper.js
// ==UserScript==
// @name iDB Helper
// @namespace http://tampermonkey.net/
// @version 1.01
// @description Helpful features to make Indexed DB easier to use
// @author @theyhoppingonme on discord
// @icon https://th.bing.com/th/id/OIP.D8qs87izChXNwCHnw3KrTQAAAA?w=117&h=180&c=7&r=0&o=7&pid=1.7&rm=3
// @grant none
// ==/UserScript==
const iDB = {
name: "DefaultDB",
version: 1,
storeName: "DefaultStore",
keyPath: null, // Set to enable auto-incrementing keys or custom key paths
autoIncrement: false,
indexes: [], // Array of {name, keyPath, options} objects
_db: null,
help() {
return `
======== iDB Helper ========
Setup:
iDB.name = "MyDatabase";
iDB.version = 1;
iDB.storeName = "MyStore";
iDB.keyPath = "id"; // Optional: for auto-incrementing
iDB.autoIncrement = true; // Optional: auto-increment keys
iDB.indexes = [{name: "email", keyPath: "email", options: {unique: true}}];
await iDB.open();
Basic Operations:
await iDB.setItem("key", value); // Saves data
let val = await iDB.getItem("key"); // Gets data
await iDB.removeItem("key"); // Deletes one key
await iDB.clear(); // Deletes everything in store
let keys = await iDB.getAllKeys(); // Get all keys
let all = await iDB.getAll(); // Get all key-value pairs
Advanced Operations:
await iDB.setItems({key1: val1, key2: val2}); // Bulk save
let vals = await iDB.getItems(["key1", "key2"]); // Bulk get
await iDB.removeItems(["key1", "key2"]); // Bulk delete
// Counting and checking
let count = await iDB.count(); // Total items count
let exists = await iDB.exists("key"); // Check if key exists
// Data operations
await iDB.updateItem("key", {prop: "newValue"}); // Merge update
let filtered = await iDB.filter(item => item.age > 18); // Filter items
let found = await iDB.find(item => item.email === "[email protected]"); // Find first match
// Index operations (if indexes are configured)
let result = await iDB.getByIndex("email", "[email protected]");
let range = await iDB.getByIndexRange("age", 18, 65);
// Key operations
let firstKey = await iDB.getFirstKey();
let lastKey = await iDB.getLastKey();
let keyRange = await iDB.getKeysInRange("a", "z");
// Database management
await iDB.backup(); // Returns all data as JSON
await iDB.restore(backupData); // Restores from backup
await iDB.deleteDatabase(); // Deletes entire database
// Event handling
iDB.onError = (error) => console.error("iDB Error:", error);
iDB.onReady = () => console.log("iDB Ready");
Notes:
- All methods return Promises (use async/await).
- Always call iDB.open() after setting configuration.
- Use keyPath and autoIncrement for structured data storage.
- Define indexes for efficient querying.
============================
`.trim();
},
// Event handlers
onError: null,
onReady: null,
open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(iDB.name, iDB.version);
request.onupgradeneeded = function (event) {
const db = event.target.result;
// Delete existing store if it exists (for schema changes)
if (db.objectStoreNames.contains(iDB.storeName)) {
db.deleteObjectStore(iDB.storeName);
}
// Create object store with options
const storeOptions = {};
if (iDB.keyPath) storeOptions.keyPath = iDB.keyPath;
if (iDB.autoIncrement) storeOptions.autoIncrement = iDB.autoIncrement;
const store = db.createObjectStore(iDB.storeName, storeOptions);
// Create indexes
iDB.indexes.forEach(index => {
store.createIndex(index.name, index.keyPath, index.options || {});
});
};
request.onsuccess = function (event) {
iDB._db = event.target.result;
if (iDB.onReady) iDB.onReady();
resolve();
};
request.onerror = function (event) {
const error = "iDB.open error: " + event.target.errorCode;
if (iDB.onError) iDB.onError(error);
reject(error);
};
});
},
_withStore(mode, callback) {
return new Promise((resolve, reject) => {
if (!iDB._db) {
const error = "iDB: Database not opened. Call iDB.open() first.";
if (iDB.onError) iDB.onError(error);
return reject(error);
}
const tx = iDB._db.transaction(iDB.storeName, mode);
const store = tx.objectStore(iDB.storeName);
let result;
try {
result = callback(store);
} catch (err) {
if (iDB.onError) iDB.onError(err);
return reject(err);
}
tx.oncomplete = () => resolve(result);
tx.onerror = () => {
if (iDB.onError) iDB.onError(tx.error);
reject(tx.error);
};
});
},
// Basic operations
setItem(key, value) {
return iDB._withStore("readwrite", store => {
if (iDB.keyPath && typeof value === 'object') {
// For stores with keyPath, add the key to the object
value[iDB.keyPath] = key;
store.put(value);
} else {
store.put(value, key);
}
});
},
getItem(key) {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const req = store.get(key);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
});
},
removeItem(key) {
return iDB._withStore("readwrite", store => {
store.delete(key);
});
},
clear() {
return iDB._withStore("readwrite", store => {
store.clear();
});
},
// Bulk operations
setItems(items) {
return iDB._withStore("readwrite", store => {
Object.entries(items).forEach(([key, value]) => {
if (iDB.keyPath && typeof value === 'object') {
value[iDB.keyPath] = key;
store.put(value);
} else {
store.put(value, key);
}
});
});
},
getItems(keys) {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const results = {};
let completed = 0;
keys.forEach(key => {
const req = store.get(key);
req.onsuccess = () => {
results[key] = req.result;
completed++;
if (completed === keys.length) {
resolve(results);
}
};
req.onerror = () => reject(req.error);
});
if (keys.length === 0) resolve({});
});
});
},
removeItems(keys) {
return iDB._withStore("readwrite", store => {
keys.forEach(key => store.delete(key));
});
},
// Advanced operations
updateItem(key, updates) {
return iDB._withStore("readwrite", store => {
return new Promise((resolve, reject) => {
const getReq = store.get(key);
getReq.onsuccess = () => {
const existing = getReq.result;
if (existing) {
const updated = typeof existing === 'object' ?
{ ...existing, ...updates } : updates;
const putReq = store.put(updated, key);
putReq.onsuccess = () => resolve(updated);
putReq.onerror = () => reject(putReq.error);
} else {
reject(new Error(`Key "${key}" not found`));
}
};
getReq.onerror = () => reject(getReq.error);
});
});
},
count() {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const req = store.count();
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
});
},
exists(key) {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const req = store.getKey(key);
req.onsuccess = () => resolve(req.result !== undefined);
req.onerror = () => reject(req.error);
});
});
},
filter(predicate) {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const results = [];
const cursor = store.openCursor();
cursor.onsuccess = e => {
const cur = e.target.result;
if (cur) {
if (predicate(cur.value, cur.key)) {
results.push(cur.value);
}
cur.continue();
} else {
resolve(results);
}
};
cursor.onerror = () => reject(cursor.error);
});
});
},
find(predicate) {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const cursor = store.openCursor();
cursor.onsuccess = e => {
const cur = e.target.result;
if (cur) {
if (predicate(cur.value, cur.key)) {
resolve(cur.value);
} else {
cur.continue();
}
} else {
resolve(undefined);
}
};
cursor.onerror = () => reject(cursor.error);
});
});
},
// Index operations
getByIndex(indexName, value) {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const index = store.index(indexName);
const req = index.get(value);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
});
},
getByIndexRange(indexName, lower, upper, lowerOpen = false, upperOpen = false) {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const index = store.index(indexName);
const range = IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen);
const results = [];
const cursor = index.openCursor(range);
cursor.onsuccess = e => {
const cur = e.target.result;
if (cur) {
results.push(cur.value);
cur.continue();
} else {
resolve(results);
}
};
cursor.onerror = () => reject(cursor.error);
});
});
},
// Key operations
getAllKeys() {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const req = store.getAllKeys();
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
});
},
getFirstKey() {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const cursor = store.openKeyCursor();
cursor.onsuccess = e => {
const cur = e.target.result;
resolve(cur ? cur.key : undefined);
};
cursor.onerror = () => reject(cursor.error);
});
});
},
getLastKey() {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const cursor = store.openKeyCursor(null, 'prev');
cursor.onsuccess = e => {
const cur = e.target.result;
resolve(cur ? cur.key : undefined);
};
cursor.onerror = () => reject(cursor.error);
});
});
},
getKeysInRange(lower, upper, lowerOpen = false, upperOpen = false) {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const range = IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen);
const req = store.getAllKeys(range);
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
});
},
getAll() {
return iDB._withStore("readonly", store => {
return new Promise((resolve, reject) => {
const req = store.getAll();
if (req) {
// Use getAll if supported
req.onsuccess = () => {
const values = req.result;
const keys = [];
const getKeysReq = store.getAllKeys();
getKeysReq.onsuccess = () => {
const keyArray = getKeysReq.result;
const data = {};
keyArray.forEach((key, index) => {
data[key] = values[index];
});
resolve(data);
};
getKeysReq.onerror = () => reject(getKeysReq.error);
};
req.onerror = () => reject(req.error);
} else {
// Fallback to cursor
const data = {};
const cursor = store.openCursor();
cursor.onsuccess = e => {
const cur = e.target.result;
if (cur) {
data[cur.key] = cur.value;
cur.continue();
} else {
resolve(data);
}
};
cursor.onerror = () => reject(cursor.error);
}
});
});
},
// Database management
backup() {
return iDB.getAll().then(data => {
return {
name: iDB.name,
version: iDB.version,
storeName: iDB.storeName,
timestamp: new Date().toISOString(),
data: data
};
});
},
restore(backupData) {
return iDB.clear().then(() => {
return iDB.setItems(backupData.data);
});
},
deleteDatabase() {
return new Promise((resolve, reject) => {
if (iDB._db) {
iDB._db.close();
iDB._db = null;
}
const deleteReq = indexedDB.deleteDatabase(iDB.name);
deleteReq.onsuccess = () => resolve();
deleteReq.onerror = () => reject(deleteReq.error);
});
}
};
// Made by @theyhoppingonme on discord