iDB Helper

Helpful features to make Indexed DB easier to use

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @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