utils_userIndexedDB.mjs

/**
### /utils/userIndexedDB

The module exports methods to store and retrieve objects from a userIndexedDB.

@module /utils/userIndexedDB
*/

/**
@function openDB
@description
The method is called from any transaction method to interact with the userIndexedDB.

A new database will be created when attempting to open a DB which does not exist.

The name for the userIndexedDB database can be set with the `indexedDB` parameter or will default to the `mapp.user.title` property or "MAPP" if the user title is not defined. This is to ensure that different XYZ environments running on the same host will have separate userIndexedDB databases.

A new store will be created when a new DB is upgraded [on creation].

@param {object} params
@param {integer} [version=3] The version number for the userIndexedDB. Incremented when a new store needs to be created.
@property {string} params.store The name of the object store to interact with.
@property {string} [params.indexedDB="MAPP"] The name of the userIndexedDB database to open.
@returns {Promise} OpenDBPromise
*/
export async function openDB(params, version = 3) {
  params.indexedDB ??= mapp.user?.title || 'MAPP';

  const dbs = await indexedDB.databases();

  const db = dbs.find((db) => db.name === params.indexedDB);

  if (db?.version > version) {
    // The version must not exceed the current version of the database, otherwise an error will be thrown and the database will not be opened.
    version = db.version;
  }

  const OpenDBPromise = new Promise(async (resolve, reject) => {
    // will create a new database if db/version doesn't exist.
    const IDBRequest = indexedDB.open(params.indexedDB, version);

    IDBRequest.onerror = (event) => {
      console.error(IDBRequest.error);
      resolve(IDBRequest);
    };

    IDBRequest.onsuccess = (event) => {
      if (!event.target.result.objectStoreNames.contains(params.store)) {
        // Increment the version number to trigger onupgradeneeded and create the new store.
        return openDB(params, db.version + 1);

        // event.target.result.createObjectStore(params.store);
      } else {
        resolve(event.target.result);
      }
    };

    // will be called on database versioning. 10941 user
    IDBRequest.onupgradeneeded = (event) => {
      // onsuccess method will be called after the object store is created.
      event.target.result.createObjectStore(params.store);
    };
  });

  const IDB = await OpenDBPromise;

  return IDB;
}

/**
@function add
@description
The add method will add new records to the userIndexedDB.

@param {Object} params
@property {string} params.store Identifier for the object store in the userIndexedDB.
@property {object} params.obj Object to be stored in the userIndexedDB store.
@returns {Promise} addPromise
*/
export async function add(params) {
  const IDB = await openDB(params);

  const addPromise = new Promise((resolve, reject) => {
    const IDBTransaction = IDB.transaction([params.store], 'readwrite');

    const objectStore = IDBTransaction.objectStore(params.store);

    const IDBRequest = objectStore.add(params.obj);

    IDBRequest.onerror = (event) => {
      console.error(IDBRequest.error);
      reject(IDBRequest.error);
    };

    IDBRequest.onsuccess = (event) => {
      resolve(event.target.result);
    };
  });

  await addPromise;

  return addPromise;
}

/**
@function get
@description
The get method will retrieve records from the userIndexedDB.
@param {Object} params 10941 user
@property {string} params.store Identifier for the object store in the userIndexedDB.
@property {object} params.name The key of the record to retrieve.
@returns {Promise} getPromise
*/
export async function get(params) {
  const IDB = await openDB(params);

  const getPromise = new Promise((resolve, reject) => {
    const IDBTransaction = IDB.transaction([params.store], 'readwrite');

    const objectStore = IDBTransaction.objectStore(params.store);

    const IDBRequest = objectStore.get(params.name);

    IDBRequest.onerror = (event) => {
      console.error(IDBRequest.error);
      reject(IDBRequest.error);
    };

    IDBRequest.onsuccess = (event) => {
      resolve(IDBRequest.result);
    };
  });

  await getPromise;

  return getPromise;
}

/**
@function list
@description
The list method will retrieve all records from the userIndexedDB store.

@param {Object} params
@property {string} params.store Identifier for the object store in the userIndexedDB.
@returns {Promise} getPromise
*/
export async function list(params) {
  const IDB = await openDB(params);

  const getPromise = new Promise((resolve, reject) => {
    const IDBTransaction = IDB.transaction([params.store], 'readwrite');

    const objectStore = IDBTransaction.objectStore(params.store);

    const IDBRequest = objectStore.getAll();

    IDBRequest.onerror = (event) => {
      console.error(IDBRequest.error);
      reject(IDBRequest);
    };

    IDBRequest.onsuccess = (event) => {
      const records = IDBRequest.result.map((record) => ({
        key: record.key,
        name: record.name,
      }));
      resolve(records);
    };
  });

  await getPromise;

  return getPromise;
}

/**
@function put
@description
The put method will update existing records or add new records to the userIndexedDB.

@param {Object} params
@property {string} params.name The key of the record to update or add.
@property {string} params.store The name of the object store in the userIndexedDB.
@property {object} params.obj The object to be stored in the userIndexedDB store.
@returns {Promise} updatePromise
*/
export async function put(params) {
  if (params.store === 'user') {
    delete params.obj.admin;
    delete params.obj.roles;
    delete params.obj.blocked;
  }

  const IDB = await openDB(params);

  const updatePromise = new Promise((resolve, reject) => {
    const IDBTransaction = IDB.transaction([params.store], 'readwrite');

    const objectStore = IDBTransaction.objectStore(params.store);

    const IDBRequest = objectStore.put(params.obj, params.name);

    IDBRequest.onerror = (event) => {
      console.error(IDBRequest.error);
      reject(IDBRequest);
    };

    IDBRequest.onsuccess = (event) => {
      resolve(IDBRequest.result);
    };
  });

  await updatePromise;

  return updatePromise;
}

/**
@function remove
@description
The remove method will delete records from the userIndexedDB.

@param {Object} params
@property {string} params.name The key of the record to remove.
@property {string} params.store The name of the object store in the userIndexedDB.
@returns {Promise} removePromise
*/
export async function remove(params) {
  const IDB = await openDB(params);

  const removePromise = new Promise((resolve, reject) => {
    const IDBTransaction = IDB.transaction([params.store], 'readwrite');

    const objectStore = IDBTransaction.objectStore(params.store);

    const IDBRequest = objectStore.delete(params.name);

    IDBRequest.onerror = (event) => {
      console.error(IDBRequest.error);
      reject(IDBRequest);
    };

    IDBRequest.onsuccess = (event) => {
      resolve();
    };
  });

  await removePromise;

  return removePromise;
}