Dexie.js
Dexie.js copied to clipboard
dexie-export-import: importing with falsy overwriteValues fails if any objects already exist
By default, the overwriteValues option for db.import(blob, [options]) is falsy. This would indicate that db.import(blob) should skip existing values while still adding new ones. However, when doing so I get a BulkError/ConstraintError. Upon further investigation, it appears db.import uses bulkAdd under the hood. The docs on bulkAdd state:
If caller wants to ignore the failes, the bulkAdd() operations must be catched.
Catching the call to db.import does not appear to satisfy this requirement. Looking at the working codepen sample, the import call is wrapped in a try/catch:
const file = ev.dataTransfer.files[0];
try {
if (!file) throw new Error(`Only files can be dropped here`);
console.log("Importing " + file.name);
await db.delete();
db = await Dexie.import(file, {
progressCallback
});
console.log("Import complete");
await showContent();
} catch (error) {
console.error(''+error);
}
However, in this code the database is deleted before importing with a call to db.delete. If I remove that line and run the code, I see this error:
BulkError: foos.bulkAdd(): 2 of 2 operations failed. Errors: ConstraintError: A mutation operation in the transaction failed because a constraint was not satisfied.
Though even if it did work, I don't think users should have to wrap the call to db.import in a try/catch specifically to satisfy bulkAdd's requirement. Because of the presence of the overwriteValues option, it would make sense semantically that importing skips existing objects by default, while still succeeding on new ones.
As such, in dexie-export-import/src/import.ts, should this call to bulkAdd not be wrapped in a try/catch or something similar?
if (options!.overwriteValues)
await table.bulkPut(values, keys);
else
await table.bulkAdd(values, keys);
Currently to avoid overwriting existing objects it seems they must be filtered by id. From my app:
async function handle_click_import(e){
await reload_db();
let filter = function(table,value,key) {
if (table !== 'links') { return false };
if (id_exists(value.id)) { return false };
return true;
};
try {
await db.import(e.target.files[0],{filter: filter});
} catch (e) {
err("importing db",e);
};
await reload_db();
};
async function reload_db(){
try {
state.links = await db.links.toArray();
} catch (e) {
err("loading database",e);
};
};
function id_exists(newid){
return state.links.some(function({id: id}) {
return newid === id;
});
};
A potentially interesting option for import and perhaps bulkAdd or bulkPut would be an exists callback option that returns the object that should be put to the database. It could work like this:
function main(){
return db.raindrops.bulkAdd(drops,{exists: modify});
};
function modify(drop,newDrop){
drop.position += newDrop.position;
return drop;
};
function overwrite(drop,newDrop){
return newDrop;
};
function skip(drop,newDrop){
return drop;
};