dexie / Dexie.js

A Minimalistic Wrapper for IndexedDB
https://dexie.org
Apache License 2.0
11.57k stars 642 forks source link

Question. Is there a way to listen to all changes on a table ? #836

Open VladyslavGoloshchapov opened 5 years ago

VladyslavGoloshchapov commented 5 years ago

Is there a way to listen to all changes on a table instead of writing 3 hooks ?

I am migrating from PouchDB and there was a handy .changes() method which included any change.

dfahlander commented 5 years ago

You could use Dexie.Observable (https://dexie.org/docs/Observable/Dexie.Observable#dbonchanges-event) which has an event that will trigger for changes.

VladyslavGoloshchapov commented 5 years ago

yes ,got that. but doesn`t this plugin introduce additonal performance pressure? it is said that it supports listening to changes even on other windows.

I suspect that this will impact performance. Am I wrong ?

dfahlander commented 5 years ago

Yes it does affect performance. Also, listening to the update hook will affect performance a bit as it needs to find out if your what changes a put() would do.

The way to go without affecting performance is to write a middleware, see example here: https://github.com/dfahlander/Dexie.js/releases/tag/v3.0.0-alpha.5

You can interceptt the DBCore.mutate() method and trigger a custom event. Notice though that you probably don't want to trigger the event until these changes are committed (?!) otherwise they won't be relyable as changes. Same apply if you use the hooks as they also are called before transaction commits. You would typically store the changes on a queue on the transaction, and trigger the event when the transaction has committed. You may access current transaction using Dexie.currentTransaction to add changes to the queue.

Example implementations:

import Dexie from 'dexie';

const db = new Dexie('dbname');

let changeListeners = [];

db.use({
  stack: "dbcore", // The only stack supported so far.
  name: "changeDetector", // Optional name of your middleware
  create (downlevelDatabase) {
    return {
      ...downlevelDatabase, 
      table (tableName) {
        const downlevelTable = downlevelDatabase.table(tableName);
        return {
          ...downlevelTable,
          mutate: req => {
            const trans = Dexie.currentTransaction;
            if (!trans["_myCompleteAdded"]) {
              trans.on('complete', ()=>trans["_myQueue"] && changeListeners.forEach(l => l(trans["_myQueue"])));
              trans["_myCompleteAdded"] = true;
            }
            return downlevelTable.mutate(req).then(res => {
              const queue = (trans["_myQueue"] = trans["_myQueue"] || []);
              queue.push({req, res});
              return res;
            });
          }
        }
      }
    };
  }
});

export function onChanges(listener) {
  changeListeners.push(listener);
}

export function offChanges(listener) {
  changeListeners = changeListeners.filter (l => l !== listener);
}

NOTE: Above sample edited 2019-12-17 after some feedback from real usage where it was detected that I missed to return res from the overridden mutate method (see this comment in #956).

Example use:


onChanges(changes => {
  console.log("Committed requests:");
  changes.forEach(({req, res}) => {
    console.log("Request: ", req, "Response: ", res);
  });
});
VladyslavGoloshchapov commented 5 years ago

ok, thanks.

I actually didn`t want to dive that low into implementation and came up with a bit different approach - I subscribed to all 3 change types and wrote a wrapper around it.

oleersoy commented 5 years ago

You could also drop the entire table into a Slice EStore:

https://github.com/fireflysemantics/slice