Search code examples
typescriptdexie

TransactionInactiveError when starting a Dexie transaction


I have a service function (using typescript 4.0.2+webpack 4.44.1+dexie 3.0.2) that syncs a jsonfile to my Dexie IndexedDB database. But as soon as I start my database transaction, I get an error:

Unhandled rejection: TransactionInactiveError: Transaction has already completed or failed
./node_modules/dexie/dist/dexie.mjs/Transaction.prototype._promise@http://localhost:9000/dist/bundle.js:4876:30
./node_modules/dexie/dist/dexie.mjs/Dexie.prototype._transaction@http://localhost:9000/dist/bundle.js:6488:35
./node_modules/dexie/dist/dexie.mjs/Dexie.prototype.transaction@http://localhost:9000/dist/bundle.js:6438:34
updateEDSMSector/<@http://localhost:9000/dist/bundle.js:169098:26
...
...

The error appears just after the console.log('extracted json', json); statement (but the next console.log never happens). I have absolutely no idea what I am doing wrong here. I hope anyone can give a pointer how debug the issue.

  static async updateEDSMSector(sector_number: number[], db: EDSMLayerDB) {
    const sector = await db.sectors.where('sector_number').equals(sector_number).first();
    console.log("Checking for EDSM updates for sector", sector);
    const updatedDate = sector.updatedDate;
    const [sx, sy, sz] = sector.sector_number;
    const jsonFilename = `edsm/${sx}/${sy}/${sx}_${sy}_${sz}.json.gz`;
    let res = await fetch(jsonFilename, {
      cache: 'no-cache',
      method: 'HEAD',
    });
    const lastModified = new Date(res.headers.get('Last-Modified'));
    console.log('updatedDate', updatedDate, 'lastModified', lastModified);
    if (!updatedDate || new Date(updatedDate) < lastModified) {
      res = await fetch(jsonFilename, {
        cache: 'no-cache',
        method: 'GET',
      });
      console.log('importing data');
      const json = JSON.parse(ungzip(new Uint8Array(await res.arrayBuffer()),
        {to: 'string'}));
      console.log('extracted json', json);
      await db.transaction('rw', db.sectors, db.systems, async () => {
        console.log('started transaction');
        await db.systems.where('sector').equals(sector.sector_number).delete();
        console.log('deleted old data');
        for (const system of json) {
          const {coords, ...rest} = system;
          const val = {...rest, ...coords};
          await db.systems.add(val);
        };
        console.log('Added systems');
        sector.updatedDate = new Date();
        await db.sectors.put(sector);
      });

      console.log('Finished adding system data');
    } else {
      console.log('Systems for sector', sector, 'already up-to-date.');
    }

  }

Solution

  • The issue was that I was running code asynchronously with

    db.mytable.where(...).each((row) => { updateEDSMSector(row.sector_number, db) };
    

    This fires off one query, but for each query result, starts a new transaction inside updateEDSMSector. These running asynchronously caused transactions to get started while old transactions weren't properly closed yet. I fixed it by first fetching db.mytable.where().toArray() and then looping over those.This still allows my updateEDSMSector calls to run asynchronously, but without conflicting with closed transactions.