Dexie.js
Dexie.js copied to clipboard
Drop all tables on upgrade
There doesn't seem to be a good way to drop all tables on upgrade, but it would be really helpful especially when developing.
My app does not rely on IndexedDb content, if it's empty it's populated from server. Thus this would be a good way for me to tinker, since I need not to worry about upgrade process during development. When the contents of the indexeddb is always pristine, I could just change the version number and know my db is empty:
this.version(2).stores({
projects: "++id,client,project,start,end"
}).upgrade (() => {
// How to drop all tables here?
});
Edit Alternatively for my case, I could use a way to drop a whole database if the version differs before dexie creates a new one.
The upgrade() method is meant to convert existing model to a new model. If you need to drop all tables, you don't need to use upgrade().
var db = new Dexie ('dbname');
db.version(1).stores({
projects: "++id,client,project,start,end"
});
function recreateDatabase () {
return db.delete().then(()=> db.open());
}
recreateDatabase().then(db => {
// Here you have a fresh empty database with tables and indexes as defined in version(1),
// no matter what version the db was on earlier or what data it had.
});
If let's say you've released your app with db.version(1) and have changed your model so that a certain table is no more needed, you can explicitely drop the old table as such:
db.version(1).stores({
projects: "++id,client,project,start,end",
project_details: "++id,project_id"
});
db.version(2).stores({
projects: "++id,client,project,start,end,details",
project_details: null // Will delete the table
});
Note: You need to keep version(1) in your code even though it's not used anymore. This is used for diffing with version(2) and apply the changes needed.
If you're not interested in keeping existing data at all, just recreate the database:
db.delete().then(()=>db.open())
I don't want to drop it always, only when the version number changes. I don't think your examples cover that?
Btw, why did this work? I know it maybe because the delete runs in some cases fast enough:
this.version(4).stores({
projects: "++id,client,project,start,end",
}).upgrade (async () => {
await this.delete();
});
But if that were allowed, it would work wonders.
Edit the point is, I don't want to keep old versions as I develop. Only drop on demand as I increase version number. And indeed in production it would work for my app too.
Can I check the current version before I open the dexie db? I'd like to drop the db if the version differs. I'm getting all sort of troubles at the moment with versions.
Btw, this stuff I try to avoid at all costs:
db.version(1).stores({
projects: "++id,client,project,start,end",
project_details: "++id,project_id"
});
db.version(2).stores({
projects: "++id,client,project,start,end,details",
project_details: null // Will delete the table
});
Totally unneccessary stuff when developing, I don't want to keep old versions lying in my code, as they were temporary.
Yes you can open database without specifying version:
new Dexie("dbname").open().then(db => {
console.log (`Current version is: ${db.verno}`);
});
I couldn't get it working, when using inheritance:
class MyDb extends Dexie {
projects: Dexie.Table<{
id?: number;
client: string;
project: string;
start: Date;
end: Date;
}, number>;
constructor() {
super("MyDb");
this.open().then(d => {
if (d.verno !== 30) {
d.delete();
}
this.close();
});
this.version(3).stores({
projects: "++id,client,project,start,end"
});
}
}
I still think the cleanest way would be a way to drop a whole database during upgrade.
That doesn't work because this.version(3) will be executed before the database is opened.
You should ideally bootstrap your app with:
async function openDatabase() {
var tempDb = await new Dexie("dbname").open();
if (tempDb.verno < 3) await tempDb.delete();
tempDb.close();
return await new MyDb().open();
});
..or possibly the following:
class MyDb extends Dexie {
projects: Dexie.Table<{
id?: number;
client: string;
project: string;
start: Date;
end: Date;
}, number>;
constructor() {
super("MyDb");
}
async init () => {
await this.open();
if (this.verno < 3) {
await this.delete();
} else {
this.close();
}
this.version(3).stores({
projects: "++id,client,project,start,end"
});
await this.open();
}
}
Heads up that it's a highly prioritized task to improve the version handling in Dexie (See #287 and #105), and I'll take your use case into account when doing that. Expect it to be improved within the next 2-3 months or so. Some of the improvements will be:
- Allow upgrade functions to access the database based on the old schema (run upgrader before old indexes and tables are deleted).
- No more need to keep the definitions of older versions in the code unless they have an
upgrade()
attached to them. Instead, Dexie should inspect the current schema and use that as the diff source instead of the specified one. - Complain if there is a diff between the actual schema and the specified schema in case no upgrade was needed. Suggest increase of version number in the exception message.
Your use case is also relevant so, maybe it could be handled via some kind of instruction for that, such as:
db.version(3).recreate({
projects: "++id,client,project,start,end"
});
Would a recreate alternative solve your case?
Yes, it would solve my case, I appreciate the time you've taken to solve this.
I haven't yet figured out how to get the init
example working, if I call init myself, the transactions doesn't seem to work afterwards, if I don't call init it's not working either.
Are you awaiting the init() Another question, is your current version 3 or 30?
Actually, I changed it to "3". But that is not the issue. The thing is, if my app is written like this:
let db = new ProjectMonitoringDb();
...
db.transaction("r", ...)
db.transaction("rw", ...)
Where could I await the init? In all transaction calls?
Edit If I've understood correctly, in order for me to await init, I should wrap my whole app in a callback?
I get the point. There's another solution that might work better:
class MyDb extends Dexie {
constructor() {
super("MyDb");
this.version(3).stores({
projects: "++id,client,project,start,end"
});
let origOpen = this.open as any, // Override open like this because it's not on prototype
openPromise : any = null;
(this as any).open = function () {
if (!openPromise) {
openPromise = new Dexie(this.name).open().then(tempDb=>{
if (tempDb.verno < 3) return tempDb.delete();
tempDb.close();
}).then(()=>origOpen.apply(this, arguments));
}
return openPromise;
};
}
}
Haven't compiled it but try it out, correct my bugs and see if it does it for you.
Quick testing, it seems to work, and the code (regardless of odd inheritance pattern) makes sense. Thanks a lot!
Nice to hear. If you need to keep abreast of close() / reopen to work also, you must override close:
let origClose = this.close as any;
(this as any).close = function() {
openPromise = null;
return origClose.apply(this, arguments);
}
This worked well for me (version 3.0.3):
import Dexie from 'dexie';
export class MyDexie extends Dexie {
private static readonly targetVersion = 10;
public open() {
if (this.isOpen()) return super.open();
return Dexie.Promise.resolve()
.then(() => Dexie.exists(this.name))
.then((exists) => {
if (!exists) {
// no need to check database version since it doesn't exist
return;
}
// Open separate instance of dexie to get current database version
return new Dexie(this.name).open()
.then(async db => {
if (db.verno >= MyDexie.targetVersion) {
// database up to date (or newer)
return db.close();
}
console.log(`Database schema out of date, resetting all data. (currentVersion: ${db.verno}, expectedVersion: ${MyDexie.targetVersion})`);
await db.delete();
// ensure the delete was successful
const exists = await Dexie.exists(this.name);
if (exists) {
throw new Error('Failed to remove mock backend database.');
}
})
})
.then(() => super.open());
}
}
Heads up that it's a highly prioritized task to improve the version handling in Dexie (See #287 and #105), and I'll take your use case into account when doing that. Expect it to be improved within the next 2-3 months or so. Some of the improvements will be:
- Allow upgrade functions to access the database based on the old schema (run upgrader before old indexes and tables are deleted).
- No more need to keep the definitions of older versions in the code unless they have an
upgrade()
attached to them. Instead, Dexie should inspect the current schema and use that as the diff source instead of the specified one.- Complain if there is a diff between the actual schema and the specified schema in case no upgrade was needed. Suggest increase of version number in the exception message.
Your use case is also relevant so, maybe it could be handled via some kind of instruction for that, such as:
db.version(3).recreate({ projects: "++id,client,project,start,end" });
Would a recreate alternative solve your case?
@dfahlander Do you have any plans to implement it in near future?
@dfahlander Do you have any plans to implement it in near future?
The quote refers to other already resolved issues. One of them was not marked as resolved but I closed it now as it has been possible for a great while to do all the things that the quote refers to: version N: access a table and do things like copying its content into a new table, version N+1: delete the table that was read from. This all enables the possibility to do things like changing foreign keys etc.
What this issue is about, is some sugar for dropping all tables and not having to explicitly specify the tables to drop (setting their value to null). I don't see it as prioritized as the referred issues.
@dfahlander Do you have any plans to implement it in near future?
The quote refers to other already resolved issues. One of them was not marked as resolved but I closed it now as it has been possible for a great while to do all the things that the quote refers to: version N: access a table and do things like copying its content into a new table, version N+1: delete the table that was read from. This all enables the possibility to do things like changing foreign keys etc.
What this issue is about, is some sugar for dropping all tables and not having to explicitly specify the tables to drop (setting their value to null). I don't see it as prioritized as the referred issues.
Hi, thanks for the work on this library. Is there any documentation on how to make the ask from the original commenter easier achievable? I found https://dexie.org/docs/Tutorial/Design#database-versioning but I'm not finding the information I'm looking for.
I wasn't able to make this work: https://github.com/dexie/Dexie.js/issues/349#issuecomment-255072346 Either the open() call seems to be left hanging or I got a Dexie.delete('myDb') was blocked
warning.
I am simply trying to clear the whole database on version upgrade. Right now, it's because I've changed a primary index (project is still under heavy development) but in most cases, since the data stored in this DB is non-critical, flushing is preferable to trying to migrate.
I'm on Dexie 3.0.4. Thanks
Given that table: null
deletes tables isn't this the final correct solution:
this.version(1).stores({
foo: 'id',
bar: 'id',
});
// Instead of adding one version, add two at once, one deletes the tables, another recreates them:
this.version(2).stores({
foo: null,
bar: null,
});
this.version(3).stores({
foo: 'foo',
bar: 'bar',
});
I went through the open
hacks and such before I finally noticed the null
feature and it works flawlessly for me.