async open()

in src/IndexedDbProvider.ts [142:413]


  async open(
    dbName: string,
    schema: DbSchema,
    wipeIfExists: boolean,
    verbose: boolean
  ): Promise<void> {
    // Note: DbProvider returns null instead of a promise that needs waiting for.
    super.open(dbName, schema, wipeIfExists, verbose);

    if (!this._dbFactory) {
      // Couldn't even find a supported indexeddb object on the browser...
      return Promise.reject<void>("No support for IndexedDB in this browser");
    }

    if (wipeIfExists) {
      try {
        let req = this._dbFactory.deleteDatabase(dbName);
        await IndexedDbProvider.WrapRequest(req);
      } catch (e) {
        // Don't care
      }
    }

    this._lockHelper = new TransactionLockHelper(schema, true);

    const dbOpen = this._dbFactory.open(dbName, schema.version);

    let migrationPutters: Promise<void>[] = [];

    dbOpen.onupgradeneeded = (event) => {
      const db: IDBDatabase = dbOpen.result;
      const target = <IDBOpenDBRequest>(event.currentTarget || event.target);
      const trans = target.transaction;

      if (!trans) {
        throw new Error("onupgradeneeded: target is null!");
      }

      if (
        schema.lastUsableVersion &&
        event.oldVersion < schema.lastUsableVersion
      ) {
        // Clear all stores if it's past the usable version
        console.log(
          "Old version detected (" + event.oldVersion + "), clearing all data"
        );
        each(db.objectStoreNames, (name) => {
          db.deleteObjectStore(name);
        });
      }

      // Delete dead stores
      each(db.objectStoreNames, (storeName) => {
        if (!some(schema.stores, (store) => store.name === storeName)) {
          db.deleteObjectStore(storeName);
        }
      });

      // Create all stores
      each(schema.stores, (storeSchema) => {
        let store: IDBObjectStore;
        const storeExistedBefore = includes(
          db.objectStoreNames,
          storeSchema.name
        );
        if (!storeExistedBefore) {
          // store doesn't exist yet
          let primaryKeyPath = storeSchema.primaryKeyPath;
          if (this._fakeComplicatedKeys && isCompoundKeyPath(primaryKeyPath)) {
            // Going to have to hack the compound primary key index into a column, so here it is.
            primaryKeyPath = "nsp_pk";
          }

          // Any is to fix a lib.d.ts issue in TS 2.0.3 - it doesn't realize that keypaths can be compound for some reason...
          store = db.createObjectStore(storeSchema.name, {
            keyPath: primaryKeyPath,
          } as any);
        } else {
          // store exists, might need to update indexes and migrate the data
          store = trans.objectStore(storeSchema.name);

          // Check for any indexes no longer in the schema or have been changed
          each(store.indexNames, (indexName) => {
            const index = store.index(indexName);

            let nuke = false;
            const indexSchema = find(
              storeSchema.indexes,
              (idx) => idx.name === indexName
            );
            if (!indexSchema || !isObject(indexSchema)) {
              nuke = true;
            } else if (typeof index.keyPath !== typeof indexSchema.keyPath) {
              nuke = true;
            } else if (typeof index.keyPath === "string") {
              if (index.keyPath !== indexSchema.keyPath) {
                nuke = true;
              }
            } /* Keypath is array */ else if (
              index.keyPath.length !== indexSchema.keyPath.length
            ) {
              // Keypath length doesn't match, don't bother doing a comparison of each element
              nuke = true;
            } else {
              for (let i = 0; i < index.keyPath.length; i++) {
                if (index.keyPath[i] !== indexSchema.keyPath[i]) {
                  nuke = true;
                  break;
                }
              }
            }

            if (nuke) {
              store.deleteIndex(indexName);
            }
          });
        }

        // IndexedDB deals well with adding new indexes on the fly, so we don't need to force migrate,
        // unless adding multiEntry or fullText index
        let needsMigrate = false;
        // Check any indexes in the schema that need to be created
        each(storeSchema.indexes, (indexSchema) => {
          if (!includes(store.indexNames, indexSchema.name)) {
            const keyPath = indexSchema.keyPath;
            if (this._fakeComplicatedKeys) {
              if (indexSchema.multiEntry || indexSchema.fullText) {
                if (isCompoundKeyPath(keyPath)) {
                  throw new Error("Can't use multiEntry and compound keys");
                } else {
                  // Create an object store for the index
                  let indexStore = db.createObjectStore(
                    storeSchema.name + "_" + indexSchema.name,
                    { autoIncrement: true }
                  );
                  indexStore.createIndex("key", "key");
                  indexStore.createIndex("refkey", "refkey");

                  if (storeExistedBefore && !indexSchema.doNotBackfill) {
                    needsMigrate = true;
                  }
                }
              } else if (isCompoundKeyPath(keyPath)) {
                // Going to have to hack the compound index into a column, so here it is.
                store.createIndex(
                  indexSchema.name,
                  IndexPrefix + indexSchema.name,
                  {
                    unique: indexSchema.unique,
                  }
                );
              } else {
                store.createIndex(indexSchema.name, keyPath, {
                  unique: indexSchema.unique,
                });
              }
            } else if (indexSchema.fullText) {
              store.createIndex(
                indexSchema.name,
                IndexPrefix + indexSchema.name,
                {
                  unique: false,
                  multiEntry: true,
                }
              );

              if (storeExistedBefore && !indexSchema.doNotBackfill) {
                needsMigrate = true;
              }
            } else {
              store.createIndex(indexSchema.name, keyPath, {
                unique: indexSchema.unique,
                multiEntry: indexSchema.multiEntry,
              });
            }
          }
        });

        if (needsMigrate) {
          // Walk every element in the store and re-put it to fill out the new index.
          const fakeToken: TransactionToken = {
            storeNames: [storeSchema.name],
            exclusive: false,
            completionPromise: new Promise((resolve) => resolve()),
          };
          const iTrans = new IndexedDbTransaction(
            trans,
            undefined,
            fakeToken,
            schema,
            this._fakeComplicatedKeys
          );
          const tStore = iTrans.getStore(storeSchema.name);

          const cursorReq = store.openCursor();
          let thisIndexPutters: Promise<void>[] = [];
          migrationPutters.push(
            IndexedDbIndex.iterateOverCursorRequest(cursorReq, (cursor) => {
              const err = attempt(() => {
                const item = removeFullTextMetadataAndReturn(
                  storeSchema,
                  (cursor as any).value
                );

                thisIndexPutters.push(tStore.put(item));
              });
              if (err) {
                thisIndexPutters.push(Promise.reject<void>(err));
              }
            }).then(() => Promise.all(thisIndexPutters).then(noop))
          );
        }
      });
    };

    const promise = IndexedDbProvider.WrapRequest<IDBDatabase>(dbOpen);

    return promise.then(
      (db) => {
        return Promise.all(migrationPutters).then(() => {
          this._db = db;
          this._db.onclose = (event: Event) => {
            if (this._handleOnClose) {
              // instantiate payload
              let payload: IDBCloseConnectionPayload = {
                name: this._db ? this._db.name : "",
                objectStores: this._db
                  ? join(this._db.objectStoreNames, ",")
                  : "",
                type: "unexpectedClosure",
              };
              // modify payload based on event
              const closedDBConnection: IDBCloseConnectionEventDetails = <any>(
                event.target
              );
              if (
                closedDBConnection &&
                closedDBConnection.name &&
                closedDBConnection.objectStoreNames
              ) {
                payload = {
                  name: closedDBConnection.name,
                  objectStores: join(closedDBConnection.objectStoreNames, ","),
                  type: "unexpectedClosure",
                };
              }
              this._handleOnClose(payload);
            }
          };
        });
      },
      (err) => {
        if (
          err &&
          err.type === "error" &&
          err.target &&
          err.target.error &&
          err.target.error.name === "VersionError"
        ) {
          if (!wipeIfExists) {
            console.log(
              "Database version too new, Wiping: " +
                (err.target.error.message || err.target.error.name)
            );

            return this.open(dbName, schema, true, verbose);
          }
        }
        return Promise.reject<void>(err);
      }
    );
  }