jakearchibald / idb

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

Upgrade event doesn't work at initialization db #159

Closed serjo96 closed 4 years ago

serjo96 commented 4 years ago

I don't know why, but at clean code without frameworks and ssr all works fine. But code from example with articles also works fine. But at my code upgrade event works only if i first time init db when start my app ( I use Nuxt.js ), and when I change version db.

When the database is open, then after the call I get an empty objectStoreNames, and the call of the cannabis in the upgrade method also does not occur, from which I came to the conclusion that the upgrade method does not work when the database is initialized. And at moment when I wanna to add some data, I take that error: DOMException: Failed to execute 'transaction' on 'IDBDatabase': One of the specified object stores was not found. idb version - 5.0.1

import { openDB } from 'idb';

export const setIndexDB = async (messagesArr) =>  {
    try {
        const db = await openDB('Chat', 1, {
        upgrade(db) {
            // Create a store of objects
            const store = db.createObjectStore('messages', {
            // The 'id' property of the object will be the key.
                keyPath: 'id',
                // If it isn't explicitly set, create a value by auto incrementing.
                autoIncrement: true,
            });
            // Create an index on the 'date' property of the objects.
            store.createIndex('date', 'date');
        },
        blocked(arg) {
            console.log(arg);
        },
        blocking(arg) {
            console.log(arg);
        },
        terminated(arg) {
            console.log(arg);
        },
    });

    // Add an article:
    for (const message of messagesArr) {
        const date = new Date(message.date);
        await db.add('messages', { ...message, date});
    }
    // Get all the articles in date order:
    console.log(await db.getAllFromIndex('messages', 'date'));
    } catch (e) {
    console.error('Database error:', e)
    }

};
jakearchibald commented 4 years ago

But at my code upgrade event works only if i first time init db when start my app ( I use Nuxt.js ), and when I change version db.

That's how the upgrade event works. From the docs:

upgrade (optional): Called if this version of the database has never been opened before.

When the database is open, then after the call I get an empty objectStoreNames, and the call of the cannabis in the upgrade method also does not occur

I think we've all felt the call of the cannabis at some point.

Please reopen this issue if I've misunderstood the problem.

jakearchibald commented 4 years ago

Here's a section from some docs I'm writing, which might help:


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.

mfbx9da4 commented 3 years ago

sounds like a sensible default would be?

    async blocked() {
      await db.close()
    },
    async blocking() {
      await db.close()
    },
jakearchibald commented 3 years ago

I don't think so. That would mean database operations would start failing. The app needs to know it's closing the database, and why.