jakearchibald / idb

IndexedDB, but with promises
https://www.npmjs.com/package/idb
ISC License
6.31k stars 356 forks source link

Create multiple ObjectStore simultaneously #145

Closed vnoitkumar closed 4 years ago

vnoitkumar commented 4 years ago

In my case, I am creating an offline web app. And want to store the API response in the IndexDB.

In a particular page, I have three to four APIs hitting the server, and I want to store all of the response in Index DB at the same time.

But I am facing a problem in the version number.

Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found.

I am calling this function for every API.

How to solve this problem?


async updateData(responseData, tableName, keyPath) {
  if (!responseData || !tableName || !keyPath) {
    return;
  }

  const db = await openDB('vnoit', 1, {
    async upgrade(db) {
      if (!db.objectStoreNames.contains(tableName)) {
        db.createObjectStore(tableName, {
          keyPath,
          autoIncrement: true
        });
      }
    }
  });

  if (db.objectStoreNames.contains(tableName)) {
    await db.clear(tableName);
  }

  if (!Array.isArray(responseData)) {
    await db.add(tableName, responseData);
    return;
  }

  const tx = db.transaction(tableName, 'readwrite');

  for (const item of responseData) {
    tx.store.add(item);
  }
  await tx.done;
}
jakearchibald commented 4 years ago

If the version number doesn't change, then upgrade won't run.

I'm currently working on some articles for IDB and this library. Here's the section on upgrades, hope it helps!


Upgrading a database

In the last article, we created a database with a key-val store:

import { openDB } from 'idb';

function createDatabase() {
  return openDB('my-database', 1, {
    upgrade(db) {
      db.createObjectStore('key-val');
    },
  });
}

const dbPromise = createDatabase();

Let's say we wanted to add… another key-val store!! I realise that's not very imaginative, but we'll look at different store types later (yep, I'm still deferring stuff until later).

To change the shape of the database we need to:

There's a bit of a gotcha here. db.createObjectStore(storeName) will throw an error if a store with that name already exists, so we need to avoid trying to create the 'key-val' store for users that already have it.

The user may already have version 1 of the database, but maybe they don't. We need to cater for both. The best way is to split the update callback into versions. The update callback provides the previously opened version to make this easier:

import { openDB } from 'idb';

function createDatabase() {
  return openDB('my-database', 2, {
    upgrade(db, oldVersion) {
      if (oldVersion < 1) {
        db.createObjectStore('key-val');
      }
      if (oldVersion < 2) {
        db.createObjectStore('another-key-val');
      }
    },
  });
}

const dbPromise = createDatabase();

This code works if version 2 is the first version of the database the user opens, and it works if they're upgrading from version 1.

Handling multiple connections

Nahhhh of course it isn't that easy. On the web you can have multiple tabs and workers connected to the same database. You could have one tab that's been open a while, and it's connected to version 1 of the database. Then, a new tab loads and tries to open version 2. Version 2 can't open while an older version is open, even in a different tab.

There are a couple of callbacks that help with this:

import { openDB } from 'idb';

async function createDatabase() {
  const db = await openDB('my-database', 2, {
    upgrade(db, oldVersion) {
      if (oldVersion < 1) {
        db.createObjectStore('key-val');
      }
      if (oldVersion < 2) {
        db.createObjectStore('another-key-val');
      }
    },
    blocking() {
      // …
    },
    blocked() {
      // …
    },
  });

  return db;
}

const dbPromise = createDatabase();

blocking is called if a connection to a newer version is being blocked by this connection (or if something is trying to delete the database (more on that later (sorry))).

When you find out you're blocking another connection, you can get out of the way by closing your connection via db.close(). Any current and queued transactions will be allowed to complete before the database is actually closed. Once the database is closed, you ideally want to get your page using the newer database. In many cases the easiest way to do this is to refresh the page (via location.reload()).

blocked is called if a connection to an older version is blocking this one from opening. If all the other connections call db.close() in their blocking callback, blocked won't be called.

Even though you know you've been blocked, there isn't a lot you can do about it. You could display a message asking the user to close other tabs to the site, or use the service worker clients API to refresh the other pages.

'Fun' fact: This problem influenced the design of service workers in two ways. Firstly, this is why service workers try to ensure a single version of your site is running across all tabs. Secondly, it's why more power is given to the new service worker. It can kick out the old version.

cawa-93 commented 3 years ago

@jakearchibald Would you help me please? How add index to existing store in upgrade ?

if (oldVersion < 1) {
    db.createObjectStore('key-val');
}
if (oldVersion < 2) {
    // add index to key-val store somehow ...
}

I did't find anything in docs and TS type definitions what can help me,

jakearchibald commented 3 years ago
const db = await openDB(name, 2, {
  upgrade(db, oldVersion, newVersion, transaction) {
    if (oldVersion < 1) {
      db.createObjectStore('key-val');
    }
    if (oldVersion < 2) {
      const store = transaction.objectStore('key-val');
      store.createIndex(…);
    }
  },
});
jakearchibald commented 3 years ago

I think the spec is vague around this, so I've filed https://github.com/w3c/IndexedDB/issues/356 and created a PR https://github.com/w3c/IndexedDB/pull/357.

Ignore the code sample in that linked issue – it's for plain IDB rather than this library.