All files / state/src/driver indexeddb.ts

100% Statements 150/150
97.61% Branches 41/42
100% Functions 16/16
100% Lines 150/150

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 1511x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 16x 16x 16x 16x 16x 16x 16x 16x 16x 16x 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 1x 1x 6x 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 2x 5x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x  
import { Store } from '../store/store';
 
/**
 * **IndexedDB** {@link Store.Driver}. This class provides a facade derived from
 * the built-in {@link Storage} interface to {@link IDBDatabase}s within the
 * browser. This class implementing the {@link Store.Driver} contract is used as
 * backing storage by the {@link StateWorker}, if run in a browser environment.
 *
 * @see {@link Store.Driver}
 */
export class IndexedDB implements Store.Driver {
 
  /**
   * Private **database** used as backing storage to read/write key/value pairs.
   */
  private readonly database: Promise<IDBDatabase>;
 
  /**
   * Returns the number of key/value pairs.
   */
  public get length(): Promise<number> {
    return this.database.then((database) => new Promise((resolve, reject) => {
      const session = database.transaction(this.version, 'readonly');
      const request = session.objectStore(this.version).count();
 
      request.onerror = reject;
      request.onsuccess = () => resolve(request.result);
    }));
  }
 
  /**
   * Public {@link IndexedDB} **constructor** consuming the `name` and `version`
   * used to construct this instance of a {@link Store.Driver}.
   *
   * @param name - The `name` to address this instance by.
   * @param version - The `version` of this instance.
   */
  public constructor(
 
    /**
     * The `name` to address this instance by.
     */
    public readonly name: string,
 
    /**
     * The `version` of this instance.
     */
    public readonly version: string
 
  ) {
    this.database = new Promise((resolve, reject) => {
      // eslint-disable-next-line @typescript-eslint/no-shadow
      const version = parseInt(this.version.replace(/[^\d]/g, ''));
      const request = indexedDB.open(this.name, version);
 
      request.onerror = reject;
      request.onsuccess = () => resolve(request.result);
      request.onupgradeneeded = () => request.result
        .createObjectStore(this.version, { keyPath: 'key' })
        .createIndex('key', 'key', { unique: true });
    });
  }
 
  /**
   * Removes all key/value pairs, if there are any.
   *
   * @returns A {@link Promise} resolving when this instance was **clear**ed.
   */
  public clear(): Promise<void> {
    return this.database.then((database) => new Promise((resolve, reject) => {
      const session = database.transaction(this.version, 'readwrite');
      const request = session.objectStore(this.version).clear();
 
      request.onerror = reject;
      request.onsuccess = () => resolve();
    }));
  }
 
  /**
   * Returns the current value associated with the given `key`, or null if the
   * given `key` does not exist.
   *
   * @param key - The `key` to retrieve the current value for.
   * @returns A {@link Promise} resolving to the current value or null.
   */
  public getItem(key: string): Promise<string | null> {
    return this.database.then((database) => new Promise((resolve, reject) => {
      const session = database.transaction(this.version, 'readonly');
      const request = session.objectStore(this.version).get(key);
 
      request.onerror = reject;
      request.onsuccess = () => resolve(request.result?.value ?? null);
    }));
  }
 
  /**
   * Returns the name of the nth key, or null if n is greater than or equal to
   * the number of key/value pairs.
   *
   * @param index - The `index` of the **key** to retrieve.
   * @returns A {@link Promise} resolving to the name of the **key** or null.
   */
  public key(index: number): Promise<string | null> {
    return this.database.then((database) => new Promise((resolve, reject) => {
      const session = database.transaction(this.version, 'readonly');
      const request = session.objectStore(this.version).openCursor();
 
      request.onerror = reject;
      request.onsuccess = () => index
        ? index = request.result!.advance(index)!
        : resolve(request.result?.value?.key ?? null);
    }));
  }
 
  /**
   * Removes the key/value pair with the given `key`, if a key/value pair with
   * the given `key` exists.
   *
   * @param key - The `key` to delete the key/value pair by.
   * @returns A {@link Promise} resolving when the key/value pair was removed.
   */
  public removeItem(key: string): Promise<void> {
    return this.database.then((database) => new Promise((resolve, reject) => {
      const session = database.transaction(this.version, 'readwrite');
      const request = session.objectStore(this.version).delete(key);
 
      request.onerror = reject;
      request.onsuccess = () => resolve();
    }));
  }
 
  /**
   * Sets the `value` of the pair identified by `key` to `value`, creating a new
   * key/value pair if none existed for `key` previously.
   *
   * @param key - The `key` to set the key/value pair by.
   * @param value - The `value` to associate with the `key`.
   * @returns A {@link Promise} resolving when the key/value pair was set.
   */
  public setItem(key: string, value: string): Promise<void> {
    return this.database.then((database) => new Promise((resolve, reject) => {
      const session = database.transaction(this.version, 'readwrite');
      const request = session.objectStore(this.version).put({ key, value });
 
      request.onerror = reject;
      request.onsuccess = () => resolve();
    }));
  }
 
}