SimpleDB.ts

export function openSimpleDB(
  name: string,
  version: number,
  onUpgrade: (db: IDBDatabase) => void
) {
  return new Promise<SimpleDB>((resolve, reject) => {
    const req = indexedDB.open(name, version);

    req.onupgradeneeded = () => {
      const db = req.result;
      console.log("openRequest.onupgradeneeded", db.name, db.version);
      onUpgrade(db);
    };

    req.onsuccess = () => {
      const db = req.result;
      console.log("openRequest.onsuccess", db.name);
      resolve(new SimpleDB(db));
    };

    req.onerror = () => {
      console.error("openRequest.onerror", req.error);
      reject(req.error);
    };

    req.onblocked = (e) => {
      console.error("openRequest.onblocked", e);
      reject(e);
    };
  });
}

class SimpleDB {
  constructor(private readonly db: IDBDatabase) {}

  store(name: string) {
    return new SimpleStore(name, this.db);
  }

  transaction(
    stores: SimpleStore | SimpleStore[],
    mode: IDBTransactionMode = "readwrite"
  ) {
    if (Array.isArray(stores)) {
      return this.db.transaction(
        stores.map((s) => s.name),
        mode
      );
    } else {
      return this.db.transaction(stores.name, mode);
    }
  }

  delete() {
    return new Promise<boolean>((resolve, reject) => {
      const req = indexedDB.deleteDatabase(this.db.name);

      req.onupgradeneeded = (e) => {
        console.log("Database deleted onupgradeneeded...", this.db.name, e);
        reject(e);
      };

      req.onsuccess = (e) => {
        console.log("Database deleted successfully...", this.db.name, e);
        resolve(true);
      };

      req.onerror = (e) => {
        console.error("Database deleted failed...", this.db.name, e);
        reject(e);
      };

      req.onblocked = (e) => {
        console.error("Database deleted onblocked...", this.db.name, e);
        reject(e);
      };
    });
  }

  close() {
    return this.db.close();
  }
}

abstract class AbstractSimpleStore<T extends IDBObjectStore | IDBIndex> {
  abstract exec<V>(
    tx: IDBTransaction | undefined,
    cb: SimpleStoreCallback<T, V>
  ): Promise<V>;

  get(key: IDBValidKey, tx?: IDBTransaction) {
    return this.exec<any>(tx, (store, resolve, reject) => {
      const req = store.get(key);
      handlRequest(req, resolve, reject);
    });
  }

  /**
   * 匹配符合的数据(数组)
   * @param query 键(主键/索引)的值或者范围,如果是 IDBObjectStore 调用则是指定主键的值或者范围,如果是 IDBIndex 调用则是指定索引的值或者范围
   * @param count 指定个数
   * @param tx 事务
   * @returns 符合的数据(数组)
   */
  getAll(
    query?: IDBValidKey | IDBKeyRange | null,
    count?: number,
    tx?: IDBTransaction
  ) {
    return this.exec<any[]>(tx, (store, resolve, reject) => {
      const req = store.getAll(query, count);
      handlRequest(req, resolve, reject);
    });
  }
  /**
   * 匹配指定范围的数据(数组)
   * @param lower 获取范围的下限
   * @param upper 获取范围的上限
   * @param lowerOpen 是否不包括下限, 下限开区间
   * @param upperOpen 是否不包括上限, 上限开区间
   * @param count 指定个数
   * @param tx 事务
   * @returns 符合的数据(数组)
   */
  getAllBound(
    lower: any,
    upper: any,
    lowerOpen: boolean,
    upperOpen: boolean,
    count?: number,
    tx?: IDBTransaction
  ) {
    return this.getAll(
      IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen),
      count,
      tx
    );
  }
  /**
   * 匹配指定下限的数据(数组)
   * @param lower 获取范围的下限
   * @param lowerOpen 是否不包括下限, 下限开区间
   * @param count 指定个数
   * @param tx 事务
   * @returns 符合的数据(数组)
   */
  getAllLB(
    lower: any,
    lowerOpen: boolean,
    count?: number,
    tx?: IDBTransaction
  ) {
    return this.getAll(IDBKeyRange.lowerBound(lower, lowerOpen), count, tx);
  }
  /**
   * 匹配指定上限的数据(数组)
   * @param upper 获取范围的上限
   * @param upperOpen 是否不包括上限, 上限开区间
   * @param count 指定个数
   * @param tx 事务
   * @returns 符合的数据(数组)
   */
  getAllUB(
    upper: any,
    upperOpen: boolean,
    count?: number,
    tx?: IDBTransaction
  ) {
    return this.getAll(IDBKeyRange.upperBound(upper, upperOpen), count, tx);
  }
  /**
   * 获取指定范围的主键(数组)
   * @param query 键(主键/索引)的值或者范围,如果是 IDBObjectStore 调用则是指定主键的值或者范围,如果是 IDBIndex 调用则是指定索引的值或者范围
   * @param count 指定个数
   * @param tx 事务
   * @returns 主键(数组)
   */
  getAllKeys(
    query?: IDBValidKey | IDBKeyRange,
    count?: number,
    tx?: IDBTransaction
  ) {
    return this.exec<IDBValidKey[]>(tx, (store, resolve, reject) => {
      const req = store.getAllKeys(query, count);
      handlRequest(req, resolve, reject);
    });
  }
  /**
   * 匹配指定范围的数据(数组)
   * @param lower 获取范围的下限
   * @param upper 获取范围的上限
   * @param lowerOpen 是否不包括下限, 下限开区间
   * @param upperOpen 是否不包括上限, 上限开区间
   * @param count 指定个数
   * @param tx 事务
   * @returns 符合的数据(数组)
   */
  getAllKeysBound(
    lower: any,
    upper: any,
    lowerOpen: boolean,
    upperOpen: boolean,
    count?: number,
    tx?: IDBTransaction
  ) {
    return this.getAllKeys(
      IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen),
      count,
      tx
    );
  }
  /**
   * 匹配指定下限的数据(数组)
   * @param lower 获取范围的下限
   * @param lowerOpen 是否不包括下限, 下限开区间
   * @param count 指定个数
   * @param tx 事务
   * @returns 符合的数据(数组)
   */
  getAllKeysLB(
    lower: any,
    lowerOpen: boolean,
    count?: number,
    tx?: IDBTransaction
  ) {
    return this.getAllKeys(IDBKeyRange.lowerBound(lower, lowerOpen), count, tx);
  }
  /**
   * 匹配指定上限的数据(数组)
   * @param upper 获取范围的上限
   * @param upperOpen 是否不包括上限, 上限开区间
   * @param count 指定个数
   * @param tx 事务
   * @returns 符合的数据(数组)
   */
  getAllKeysUB(
    upper: any,
    upperOpen: boolean,
    count?: number,
    tx?: IDBTransaction
  ) {
    return this.getAllKeys(IDBKeyRange.upperBound(upper, upperOpen), count, tx);
  }
  /**
   * 获取指定范围的主键值
   * @param query 键(主键/索引)的值或者范围,如果是 IDBObjectStore 调用则是指定主键的值或者范围,如果是 IDBIndex 调用则是指定索引的值或者范围
   * @param tx 事务
   * @returns 主键值 如果匹配到多个,只会返回第一个
   */
  getKey(query: IDBValidKey | IDBKeyRange, tx?: IDBTransaction) {
    return this.exec<IDBValidKey | undefined>(tx, (store, resolve, reject) => {
      const req = store.getKey(query);
      handlRequest(req, resolve, reject);
    });
  }
  /**
   * 匹配指定范围的数据
   * @param lower 获取范围的下限
   * @param upper 获取范围的上限
   * @param lowerOpen 是否不包括下限, 下限开区间
   * @param upperOpen 是否不包括上限, 上限开区间
   * @param tx 事务
   * @returns 符合的数据, 返回一个
   */
  getKeyBound(
    lower: any,
    upper: any,
    lowerOpen: boolean,
    upperOpen: boolean,
    tx?: IDBTransaction
  ) {
    return this.getKey(
      IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen),
      tx
    );
  }
  /**
   * 匹配指定下限的数据
   * @param lower 获取范围的下限
   * @param lowerOpen 是否不包括下限, 下限开区间
   * @param tx 事务
   * @returns 符合的数据, 返回一个
   */
  getKeyLB(lower: any, lowerOpen: boolean, tx?: IDBTransaction) {
    return this.getKey(IDBKeyRange.lowerBound(lower, lowerOpen), tx);
  }
  /**
   * 匹配指定上限的数据
   * @param upper 获取范围的上限
   * @param upperOpen 是否不包括上限, 上限开区间
   * @param tx 事务
   * @returns 符合的数据, 返回一个
   */
  getKeyUB(upper: any, upperOpen: boolean, tx?: IDBTransaction) {
    return this.getKey(IDBKeyRange.upperBound(upper, upperOpen), tx);
  }
  /**
   * 遍历所有数据
   * @param cb 针对每一条数据的回调处理函数
   *        @param key 键的值,如果是 IDBObjectStore 调用则是主键的值,如果是 IDBIndex 调用则是索引的值
   *        @param value 获取的数据
   *        @param update 更新的回调,输入处理后的数据(value)对该条数据进行更新
   * @param query 键(主键/索引)的值或者范围,如果是 IDBObjectStore 调用则是指定主键的值或者范围,如果是 IDBIndex 调用则是指定索引的值或者范围
   * @param direction 游标方向,默认 'next'
   * @param tx 事务
   * @returns 无
   */
  itorAll(
    cb: (
      key: IDBValidKey,
      value: any,
      update: (value: any) => Promise<IDBValidKey>
    ) => any,
    query?: IDBValidKey | IDBKeyRange,
    direction?: IDBCursorDirection,
    tx?: IDBTransaction
  ) {
    return this.exec<void>(tx, (store, resolve, reject) => {
      const req = store.openCursor(query, direction);
      req.onerror = () => {
        reject(req.error);
      };

      req.onsuccess = async () => {
        const cursor = req.result;
        if (cursor) {
          try {
            let ret = cb(cursor.key, cursor.value, (value) => {
              return new Promise<IDBValidKey>((resolve, reject) => {
                const req1 = cursor.update(value);
                handlRequest(req1, resolve, reject);
              });
            });
            if (ret instanceof Promise) {
              ret = await ret;
            }
            if (ret !== false) {
              cursor.continue();
            }
          } catch (ex) {
            reject(ex);
          }
        } else {
          resolve();
        }
      };
    });
  }
  /**
   * 遍历所有 键(主键/索引)
   * @param cb 针对每一条数据的回调处理函数
   *        @param primaryKey 主键的值
   *        @param key 键的值,如果是 IDBObjectStore 调用则是主键的值,如果是 IDBIndex 调用则是索引的值
   *        @param update 更新的回调,输入处理后的数据(value)对该条数据进行更新
   * @param query 键(主键/索引)的值或者范围,如果是 IDBObjectStore 调用则是指定主键的值或者范围,如果是 IDBIndex 调用则是指定索引的值或者范围
   * @param direction 游标方向,默认 'next'
   * @param tx 事务
   * @returns 无
   */
  itorAllKeys(
    cb: (
      primaryKey: IDBValidKey,
      key: IDBValidKey,
      update: (value: any) => Promise<IDBValidKey>
    ) => any,
    query?: IDBValidKey | IDBKeyRange,
    direction?: IDBCursorDirection,
    tx?: IDBTransaction
  ) {
    return this.exec<void>(tx, (store, resolve, reject) => {
      const req = store.openKeyCursor(query, direction);

      req.onerror = () => {
        reject(req.error);
      };

      req.onsuccess = async () => {
        const cursor = req.result;
        if (cursor) {
          try {
            let ret = cb(cursor.primaryKey, cursor.key, (value) => {
              return new Promise<IDBValidKey>((resolve, reject) => {
                const req1 = cursor.update(value);
                handlRequest(req1, resolve, reject);
              });
            });
            if (ret instanceof Promise) {
              ret = await ret;
            }
            if (ret !== false) {
              cursor.continue();
            }
          } catch (ex) {
            reject(ex);
          }
        } else {
          resolve();
        }
      };
    });
  }

  count(query?: IDBValidKey | IDBKeyRange, tx?: IDBTransaction) {
    return this.exec<number>(tx, (store, resolve, reject) => {
      const req = store.count(query);
      handlRequest(req, resolve, reject);
    });
  }
}

class SimpleStore extends AbstractSimpleStore<IDBObjectStore> {
  constructor(readonly name: string, private readonly db: IDBDatabase) {
    super();
  }

  exec<T>(
    tx: IDBTransaction | undefined,
    cb: SimpleStoreCallback<IDBObjectStore, T>
  ) {
    return new Promise<T>((resolve, reject) => {
      try {
        tx = tx || this.db.transaction(this.name, "readwrite");
        const store = tx.objectStore(this.name);
        cb(store, resolve, reject);
      } catch (ex) {
        reject(ex);
      }
    });
  }

  getIndex(index: string) {
    return new SimpleIndex(this.name, index, this.db);
  }

  put(key: IDBValidKey, value: any, tx?: IDBTransaction) {
    return this.exec<any>(tx, (store, resolve, reject) => {
      const req = store.put(value, key);
      handlRequest(req, resolve, reject);
    });
  }

  delete(key: IDBValidKey | IDBKeyRange, tx?: IDBTransaction) {
    return this.exec<any>(tx, (store, resolve, reject) => {
      const req = store.delete(key);
      handlRequest(req, resolve, reject);
    });
  }

  clear(tx?: IDBTransaction) {
    return this.exec<boolean>(tx, (store, resolve, reject) => {
      const req = store.clear();
      req.onsuccess = () => {
        resolve(true);
      };

      req.onerror = () => {
        reject(new Error("Failed to clear data!"));
      };
    });
  }
}

class SimpleIndex extends AbstractSimpleStore<IDBIndex> {
  constructor(
    readonly name: string,
    readonly index: string,
    private readonly db: IDBDatabase
  ) {
    super();
  }

  exec<T>(
    tx: IDBTransaction | undefined,
    cb: SimpleStoreCallback<IDBIndex, T>
  ): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      try {
        tx = tx || this.db.transaction(this.name, "readwrite");
        const store = tx.objectStore(this.name);
        const index = store.index(this.index);
        cb(index, resolve, reject);
      } catch (ex) {
        reject(ex);
      }
    });
  }
}

type SimpleStoreCallback<T, V> = (
  store: T,
  resolve: (value: V) => void,
  reject: (reason?: any) => void
) => void;

function handlRequest<T>(
  req: IDBRequest<T>,
  resolve: (value: T) => void,
  reject: (reason?: any) => void
) {
  req.onsuccess = () => {
    resolve(req.result);
  };

  req.onerror = () => {
    reject(req.error);
  };
}

import { openSimpleDB } from "./SimpleDB";
import { sleep } from "./sleep";

export default async function testSimpleDB() {
  console.log("test...", "testSimpleDB...");

  const version = 5;
  // 连接DB
  let sdb = await openSimpleDB("test", version, (db) => {
    console.log("upgrading...");

    try {
      db.deleteObjectStore("bbb");
    } catch (error) {}

    const store = db.createObjectStore("aaa");
    store.createIndex("by_id", "id", { unique: true });
    store.createIndex("by_name", "name");
  });

  console.log("IDBDatabase...", sdb);

  try {
    if (true) {
      // 关闭DB连接
      const closeReq = sdb.close();
      console.log("close db...", closeReq);

      // 删除DB
      const delReq = await sdb.delete();
      console.log("delete db...", delReq);
    }

    // 重连DB
    sdb = await openSimpleDB("test", version + 1, (db) => {
      console.log("upgrading...");

      try {
        db.deleteObjectStore("aaa");
      } catch (error) {}
      const store = db.createObjectStore("asdf");
      store.createIndex("by_id", "id", { unique: true });
      store.createIndex("by_name", "name");
      // 生成复合索引
      store.createIndex("id_name", ["id", "name"]);
    });
    console.log("IDBDatabase1...", sdb);
  } catch (error) {
    // 已经成功连接DB的情况下
    // 直接迭代 DB 的版本,会失败, DB 处于 pending 状态 , 除非先关闭 DB 的连接
    // 重连 DB , 版本不发生变化时,能成功连接
    console.error("连接 DB 时 , 直接迭代 DB 版本失败...", error);
  }

  const asdf = sdb.store("asdf");
  const idxId = asdf.getIndex("by_id");
  const idxName = asdf.getIndex("by_name");
  // 复合索引
  const idxs = asdf.getIndex("id_name");

  await asdf.put("1234", { id: "12341234", name: "asdf", date: new Date() });
  await asdf.put("1234", { id: "12345555", name: "asdf", date: new Date() });
  console.log(idxs.get(["12345555", "asdf"]));
  try {
    await asdf.put("4321", { id: "12341234", name: "asdf", date: new Date() });
  } catch (error) {
    // 约束了索引 id 是 unique 唯一索引,添加两个 id 一致的数据进入,则违反了约束,无法成功添加
    console.error(
      "Repeatedly add data that has agreed on a unique index...",
      error
    );
  }
  await sleep(100);
  await asdf.put("1324", "acbd");

  // 同一事务,处理多项操作
  const tx = sdb.transaction(asdf);
  await asdf.put("5678", "qqwer", tx);
  await asdf.put("8765", "sdfwerf", tx);

  // Promise.all 的性能优于 await 每一步操作完成
  console.time("promise.all");
  const tasks = [];
  for (let i = 0; i < 100; i++) {
    tasks.push(asdf.put("8765", "aasdfasdf", tx));
  }
  await Promise.all(tasks);
  console.timeEnd("promise.all");

  console.time("await everyone...");
  for (let i = 0; i < 100; i++) {
    await asdf.put("8765", "aasdfasdf", tx);
  }
  console.timeEnd("await everyone...");

  // 休眠 1s 后,事务被提交,无法继续完成数据的处理
  await sleep(1);
  try {
    await asdf.put("9988", "sadfwefwe", tx);
  } catch (error) {
    console.error("休眠 1s 后,transaction 被提交了,会报错...", error);
  }

  for (let i = 0; i < 10; i++) {
    const tx1 = sdb.transaction(asdf);
    console.log(await asdf.get("8765", tx1));
    await asdf.delete("8765", tx1);
    tx1.abort(); // 放弃本次连接的事务的所有修改,若当前的事务处于回滚或完成状态时,则会抛出一个错误事件
  }

  console.log(await asdf.get("8765"));
  await asdf.delete("8765");
  console.log(await asdf.get("8765"));

  const tx2 = sdb.transaction(asdf);
  console.log(await asdf.get("5678", tx2));
  await asdf.delete("5678", tx2);
  tx2.commit(); // 提交事务
  try {
    tx2.abort(); // 放弃本次连接的事务的所有修改,若当前的事务处于回滚或完成状态时,则会抛出一个错误事件
    console.log("transaction abort...");
  } catch (error) {
    console.error("transaction abort failed...", error);
  }
  console.log(await asdf.get("5678"));

  /*---------------------------------- */
  await asdf.put("55555555.1", {
    id: "55555555",
    name: "asdf",
    date: new Date(),
  });
  await asdf.put("66666666.1", {
    id: "66666666",
    name: "asdf",
    date: new Date(),
  });

  console.log("asdf itorAll");
  await asdf.itorAll(console.log);
  console.log("asdf itorAll update");
  await asdf.itorAll((key, value, update: Function) => {
    console.log(key, value);
    if (key === "55555555.1") {
      console.log("itorAll update...");
      update({ id: "55555555", name: "itorAll update..." });
    }
  });
  console.log(await asdf.get("55555555.1"));
  console.log("itorAll update end...");

  console.log("asdf itorAllKeys");
  await asdf.itorAllKeys(console.log);
  console.log("asdf itorAllKeys update");
  await asdf.itorAllKeys((primaryKey, key, update: Function) => {
    console.log(primaryKey, key);
    if (primaryKey === "55555555.1") {
      console.log("itorAllKeys update...");
      update({ id: "55555555", name: "itorAllKeys update..." });
    }
  });
  console.log(await asdf.get("55555555.1"));
  console.log("itorAllKeys update end...");

  /*---------------------------------- */
  console.log("idxId itorAll");
  await idxId.itorAll(console.log, "55555555"); // 指定了索引的值
  console.log("idxId itorAllKeys");
  await idxId.itorAllKeys(
    console.log,
    IDBKeyRange.bound("55555555", "66666666", false, true)
  ); // 指定了索引的范围

  /*---------------------------------- */
  console.log("idxName itorAll");
  await idxName.itorAll(console.log);
  console.log("idxName itorAllKeys");
  await idxName.itorAllKeys(console.log);

  /*---------------------------------- */
  console.log("asdf getAll");
  console.log(await asdf.getAll());
  console.log(await asdf.getAll(IDBKeyRange.bound("1234", "4321"), 3));
  console.log("asdf getAllKeys");
  console.log(await asdf.getAllKeys());
  console.log(await asdf.getAllKeys(IDBKeyRange.bound("1234", "4321"), 3));
  console.log("asdf getKey");
  console.log(await asdf.getKey(IDBKeyRange.bound("1234", "4321")));

  /*---------------------------------- */
  console.log("idxId getAll");
  console.log(await idxId.getAll());
  console.log(
    await idxId.getAll(
      IDBKeyRange.bound("55555555", "66666666", false, true),
      3
    )
  );
  console.log("idxId getAllKeys");
  console.log(await idxId.getAllKeys());
  console.log(
    await idxId.getAllKeys(
      IDBKeyRange.bound("55555555", "66666666", false, false),
      3
    )
  );
  console.log("idxId getKey");
  console.log(
    await idxId.getKey(IDBKeyRange.bound("55555555", "66666666", false, false))
  );

  /*---------------------------------- */
  console.log("asdf count");
  console.log(await asdf.count());
  console.log(await asdf.count(IDBKeyRange.bound("1234", "4321")));

  /*---------------------------------- */
  console.log("idxId count");
  console.log(await idxId.count());
  console.log(
    await idxId.count(IDBKeyRange.bound("55555555", "66666666", false, true))
  );

  /*---------------------------------- */
  console.log("asdf clear");
  console.log(await asdf.clear());
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值