Search code examples
dexie

Dexie, object not found when nesting collection


i thought i got the hang of dexie, but now i'm flabbergasted:

two tables, each with a handful of records. Komps & Bretts

output all Bretts

rdb.Bretts.each(brett => {
    console.log(brett);
})

output all Komps

rdb.Komps.each(komp=> {
    console.log(komp);
})

BUT: this only outputs the Bretts, for some weird reason, Komps is empty

rdb.Bretts.each(brett => {
    console.log(brett);
    rdb.Komps.each(komp=> {
        console.log(komp);
    })
})

i've tried all kinds of combinations with async/await, then() etc, the inner loop cannot find any data in the inner table, whatever table i want to something with.

2nd example. This Works:

await rdb.Komps.get(163);

This produces an error ("Failed to execute 'objectStore' on 'IDBTransaction…ction': The specified object store was not found.")

rdb.Bretts.each(async brett => {
    await rdb.Komps.get(163);
})

Is there some kind of locking going on? something that can be disabled?

Thank you!


Solution

  • Calling rdb.Bretts.each() will implicitly launch a readOnly transaction limited to 'Bretts' only. This means that within the callback you can only reach that table. And that's the reason why it doesn't find the Comps table at that point. To get access to the Comps table from within the each callback, you would need to include it in an explicit transaction block:

    rdb.transaction('r', 'Komps', 'Bretts', () => {
      rdb.Bretts.each(brett => {
        console.log(brett);
        rdb.Komps.each(komp=> {
          console.log(komp);
        });
      });
    });
    

    However, each() does not respect promises returned by the callback, so even this fix would not be something that I would recommend either - even if it would solve your problem. You could easlily get race conditions as you loose the control of the flow when launching new each() from an each callback.

    I would recommend you to using toArray(), get(), bulkGet() and other methods than each() where possible. toArray() is also faster than each() as it can utilize faster IDB Api IDBObjectStore.getAll() and IDBIndex.getAll() when possible. And you don't nescessarily need to encapsulate the code in a transaction block (unless you really need that atomicy).

    const komps = await rdb.Komps.toArray();
    await Promise.all(
      komps.map(
        async komp => {
          // Do some async call per komp:
          const brett = await rdb.Bretts.get(163));
          console.log("brett with id 163", brett);
        }
      )
    );
    

    Now this example is a bit silly as it does the exact same db.Bretts.get(163) for each komp it founds, but you could replace 163 with some dynamic value there.

    Conclusion: There are two issues.

    1. The implicit transaction of Dexie's operation and the callback to each() lives within that limited transaction (tied to one single table only) unless you surround the call with a bigger explicit transaction block.

    2. Try avoid to start new async operation within the callback of Dexie's db.Table.each() as it does not expect promises to be returned from its callback. You can do it but it is better to stick with methods where you can keep control of the async flow.