Road Map: Dexie 5.0
Some of the features presented here were moved from the road map for dexie@4 in January 2024 when dexie@4 went into release candidate.
The goal for Dexie 5.0 will be a better experience for web developers to declare and query their data. What weâll be focusing on will be to query richness, paging and performance using some RAM-sparse caching. The goal is an intuitive and easy-to-use db API that performs well in large apps without sacrificing device resource usage.
We donât have a any dedicated time schedule of when dexie 5 will be in alpha, beta or feature complete. This road map may also be updated and modified along the way.
Type Safe Declaration and Easier Class Mapping
DB declaration will be possible within a single expression instead of having to repeat declarations for the typings and for the actual schema (runtime).
class Friend {
id = AutoPrimKey(Number); // Declares auto-incremented primary key
name = Index(String); // Declares an index and that name is of type string
age = Index(Number); // Declares an index and that age is of type number
picture = Type(Blob); // Declares a non-indexed property of type Blob.
}
const db = new Dexie('friendsDB').stores({
friends: Friend // Declares schema and maps the class
});
// As in dexie@4, versions / migrations are now optional!
Dexieâs classical schema style
// 1. Declare db
export const db = new Dexie('friendsDB');
// 2. Specify version(s) and schema(s)
db.version(1).stores({
friends: '++id, name, age'
});
Type Friendly Declaration Style
Dexie will always be backward compatible, so the current style will continue to work. The new declaration style in Dexie 5, there will also be a new declaration style with the following benefits:
- One single declaration for both schema and typings
- No version number needed
- The class is automatically mapped, just like mapToClass() did work in earlier versions.
Declaration Helpers
A new library âdreamtypeâ will be released and will contains declarators for declaring class properties that provides a both typings and runtime information about properties. The dreamtype library is not tied to dexie. Itâs a standalone library for declaring runtime persistable types and contains a set of declarators along with a contract of how to extract the property infos from a class.
Declarator | Meaning |
---|---|
Type(T) | Specifies type for both typescript and the running javascript |
PrimKey(T) | Primary key |
AutoPrimKey(T) | Auto-generated primary key |
ComboPrimKey(T) | Compound Primary Key |
Index(T) | Indexed property |
ComboIndex(T) | Compound index |
ArrayOf(T) | Array |
Optional(T) | Optional property |
Examples
Plain type without indexes
class Friend {
id = PrimKey(String);
name = Type(String);
age = Type(Number);
}
// Equivalent declaration with classic dexie style:
db.version(x).stores({
friends: 'id' // Non-indexed properties aren't specified in classical schemas
});
Indexed properties
class Friend {
id = PrimKey(String);
name = Index(String);
age = Index(Number);
}
// Equivalent declaration with classic dexie style:
db.version(x).stores({
friends: 'id, name, age'
});
Compound index
class Friend {
id = PrimKey(String);
name = Index(String);
age = ComboIndex(Number, () => [this.name]); // [age+name]
}
// Equivalent declaration with classic dexie style:
db.version(x).stores({
friends: 'id, name, [age+name]'
});
Multiple indexes on same property
class Friend {
id = PrimKey(String);
name = ComboIndex(
String,
() => [this.age], // [name+age]
() => [this.shoeSize] // [name+shoeSize]
);
age = Index(Number);
shoeSize = Type(number);
}
// Equivalent declaration with classic dexie style:
db.version(x).stores({
friends: 'id, [name+age], [name+shoeSize], age';
});
Unique index
class User {
userId = PrimKey(String); // Sets primary key to `userId`
username = Index(String); // Adds normal index 'username'
email = Index(String, UNIQUE); // Adds unique index '&email'
domain = ComboIndex(String, () => [this.username, UNIQUE]); // Unique index '&[domain+username]'
}
// Equivalent declaration with classic dexie style:
db.version(x).stores({
users: 'userId, username, &email, &[domain+username]';
});
Nested and optional properties
class Friend {
id = PrimKey(String);
address = {
country: ComboIndex(String, () => [this.address.city]),
city: Type(String),
street: Type(String)
};
picture = Optional(Blob);
shoeSize = Optional(Index(Number)); // Optional indexed property
}
// Equivalent declaration with classic dexie style:
db.version(x).stores({
friends: 'id, [address.country+address.city], shoeSize';
});
Compound primary key
class UserGroupConnection {
groupId = ComboPrimKey(String, () => [this.userId]);
userId = Index(String);
}
// Equivalent declaration with classic dexie style:
db.version(x).stores({
userGroups: '[groupId+userId], userId';
});
Sub-classing Dexie
export class AppDB extends Dexie {
friends = Table(Friend);
}
const db = new AppDB('appDB');
The sub-classed version above is equivalent to:
const db = new Dexie('appDB').stores({
friends: Friend
});
Subclassing Dexie isnât required anymore for typings but it is still useful the declared class extends the Entity
helper because it will have the properties db
and table
so that methods can perform operations on the database:
class Friends extends Entity<AppDB> {
id = PrimKey(String);
name = Type(String);
age = Index(Number);
// methods can access this.db because we're subclassing Entity<AppDB>
async birthDay() {
return this.db.friends.update(this.id, { age: add(1) });
}
}
We will continue to support the old API so that applications arenât forced to go over to the type-safe schema declaration.
Notice that versions arenât needed for schema changes anymore. Here we diverge from native IndexedDB that require this. We work around it letting the declared version and the native version diverge. And when they do, we store the virtual version in a meta table on the database. This table will only be created on-demand, if a schema upgrade on same given version was needed. Basically, we continue working like before, unless the db has the $meta table - in which case the info there will be respected instead of the native one. If disliking the idea of diverging from the native IndexedDB version, keep declaring a version as with current Dexie and it wonât need to add a $meta table.
Also, any methods in the type will be omitted from the insert type so that if you have a class with methods that backs the model of your table, you will continously be able to add items using plain objects (with methods omitted).
Migrations
Weâve changed the view of migrations and version handling. Before the version was directly related to changes in the schema such as added tables or indexes. This was natural and corresponds to how IndexedDB works natively.
The only situations where you need a new version number in dexie@5 will be in one of the following situations:
- You want to rename a table or property
- Youâve refactored your model and need to move data around to comply with the new model
New Migration Methods for Rename
Two new methods exists that can be used in migrations:
- renameTable()
- renameProperty()
Example: You want to rename a table or property or both:
You want to rename table âfriendsâ to âcontactsâ. You also want to rename a property on that model from ânameâ to âdisplayNameâ:
const db = new Dexie('dbName').stores({
contacts: Contact
});
db.version(2)
.renameTable('friends => contacts')
.renameProperty('contacts', 'name => displayName');
Example: Youâve refactored your model and need to move data around to comply with the new model
db.version(3)
.upgrade(async (tx) => {
await tx.table('contacts')?.toCollection().modify((contact) => {
const [firstName, lastName] = contact.displayName?.split(' ') ?? [];
contact.firstName = firstName;
contact.lastName = lastName;
delete contact.displayName;
});
})
.downgrade('recreate'); // In case of downgrade - recreate the database (delete and reopen on prev version)
Downgrade options
Option | Meaning |
---|---|
'allow' (default) |
Allow downgrade - reverse renaming of tables and props only - data is left as is |
'recreate' |
Erase all data on downgrade |
'throw' |
Do not allow downgrade (same as dexie 2 and 3 behavior) |
Richer Queries
Dexie will support combinations of criterias within the same collection and support a subset of mongo-style queries. Dexie has before only supported queries that are fully utilizing at least one index. Where-clauses only allow fields that can be resolved using a plain or compound index. And orderBy()
requires an index for ordering, making it impossible to combine with a critera, unless the criteria uses the same index. Currently, combining index-based and âmanualâ filtering is possible using filter(), but it puts the burden onto the developer to determine which parts of the query that should utilize index and which parts should not. Dexie 5.0 will move away from this limitation and allow any combination of criterias within the same query. Resolving which parts to utilize index will be decided within the engine.
orderBy()
will be available on Collection regardless of whether the current query already âoccupiesâ an index or not. It will support multiple fields including non-indexed ones, and allow to specify collation.
import { between, startsWith } from 'dexie';
await db.friends
.where({
name: 'foo',
age: between(18, 65),
'address.city': startsWith('S')
})
.orderBy(['age', 'name'])
.offset(50)
.limit(25)
.toArray();
Improved Paging
The cache will assist in improving paging. The caller will keep using offset()/limit() to do its paging. The difference will be that the engine can optimize an offset()-based query in case it starts close to an earlier query with the same criteria and order, so the caller will not need to use a new paging API
Encryption
We will provide a new encryption addon, similar to the 3rd part dexie-encrypted and dexie-easy-encrypt addons. These addons will continue to work with dexie@5 but due to the lack of maintainance of we believe thereâs a need to have a maintained addon for such an important feature.
The syntax for initializing encryption is not yet decided on, but might not correspond to those of the current 3rd part addons.
Support SQLite as backing DB
We also aim to make it possible to use Dexie and Dexie Cloud in react-native, nativescript, Node, Bun, Deno or in the browser with SQLiteâs webassembly build. Running Dexie on Node is actually already possible using IndexedDBShim but the idea is to support it natively to improve performance and stability.
Breaking Changes
No default export
We will stop exporting Dexie as a default export as it is easier to prohibit dual package hazard when not exporting both named and default exports. Named exports have the upside of enabling tree shaking so we prefer using that only.
Already since Dexie 3.0, it has been possible to import { Dexie } as a named export. To prepare for Dexie 5.0, it can be wise to change your imports from import Dexie from 'dexie'
to import { Dexie } from 'dexie'
already today.
More to come
Dexie has been pretty much backward compatible between major versions so far and the plan is to continue being backward compatible as much as possible. But there might be additional breaking changes to come and they will be listed here. This is a living document. Subscribe to our github discussions or to the blog if you want to get notified on updates.
Table of Contents
- API Reference
- Access Control in Dexie Cloud
- Add demo users
- Add public data
- Authentication in Dexie Cloud
- Best Practices
- Building Addons
- Collection
- Collection.and()
- Collection.clone()
- Collection.count()
- Collection.delete()
- Collection.desc()
- Collection.distinct()
- Collection.each()
- Collection.eachKey()
- Collection.eachPrimaryKey()
- Collection.eachUniqueKey()
- Collection.filter()
- Collection.first()
- Collection.keys()
- Collection.last()
- Collection.limit()
- Collection.modify()
- Collection.offset()
- Collection.or()
- Collection.primaryKeys()
- Collection.raw()
- Collection.reverse()
- Collection.sortBy()
- Collection.toArray()
- Collection.uniqueKeys()
- Collection.until()
- Compound Index
- Consistency in Dexie Cloud
- Consistent add() operator
- Consistent remove() operator
- Consistent replacePrefix() operator
- Consuming Dexie as a module
- Custom Emails in Dexie Cloud
- DBCore
- DBCoreAddRequest
- DBCoreCountRequest
- DBCoreCursor
- DBCoreDeleteRangeRequest
- DBCoreDeleteRequest
- DBCoreGetManyRequest
- DBCoreGetRequest
- DBCoreIndex
- DBCoreKeyRange
- DBCoreMutateRequest
- DBCoreMutateResponse
- DBCoreOpenCursorRequest
- DBCorePutRequest
- DBCoreQuery
- DBCoreQueryRequest
- DBCoreQueryResponse
- DBCoreRangeType
- DBCoreSchema
- DBCoreTable
- DBCoreTableSchema
- DBCoreTransaction
- DBCoreTransactionMode
- DBPermissionSet
- Deprecations
- Derived Work
- Design
- Dexie Cloud API
- Dexie Cloud API Limits
- Dexie Cloud Best Practices
- Dexie Cloud CLI
- Dexie Cloud Docs
- Dexie Cloud REST API
- Dexie Cloud Web Hooks
- Dexie Constructor
- Dexie.AbortError
- Dexie.BulkError
- Dexie.ConstraintError
- Dexie.DataCloneError
- Dexie.DataError
- Dexie.DatabaseClosedError
- Dexie.IncompatiblePromiseError
- Dexie.InternalError
- Dexie.InvalidAccessError
- Dexie.InvalidArgumentError
- Dexie.InvalidStateError
- Dexie.InvalidTableError
- Dexie.MissingAPIError
- Dexie.ModifyError
- Dexie.NoSuchDatabaseErrorError
- Dexie.NotFoundError
- Dexie.Observable
- Dexie.Observable.DatabaseChange
- Dexie.OpenFailedError
- Dexie.PrematureCommitError
- Dexie.QuotaExceededError
- Dexie.ReadOnlyError
- Dexie.SchemaError
- Dexie.SubTransactionError
- Dexie.Syncable
- Dexie.Syncable.IDatabaseChange
- Dexie.Syncable.IPersistentContext
- Dexie.Syncable.ISyncProtocol
- Dexie.Syncable.StatusTexts
- Dexie.Syncable.Statuses
- Dexie.Syncable.registerSyncProtocol()
- Dexie.TimeoutError
- Dexie.TransactionInactiveError
- Dexie.UnknownError
- Dexie.UnsupportedError
- Dexie.UpgradeError()
- Dexie.VersionChangeError
- Dexie.VersionError
- Dexie.[table]
- Dexie.addons
- Dexie.async()
- Dexie.backendDB()
- Dexie.close()
- Dexie.currentTransaction
- Dexie.debug
- Dexie.deepClone()
- Dexie.defineClass()
- Dexie.delByKeyPath()
- Dexie.delete()
- Dexie.derive()
- Dexie.events()
- Dexie.exists()
- Dexie.extend()
- Dexie.fakeAutoComplete()
- Dexie.getByKeyPath()
- Dexie.getDatabaseNames()
- Dexie.hasFailed()
- Dexie.ignoreTransaction()
- Dexie.isOpen()
- Dexie.js
- Dexie.name
- Dexie.on()
- Dexie.on.blocked
- Dexie.on.close
- Dexie.on.error
- Dexie.on.populate
- Dexie.on.populate-(old-version)
- Dexie.on.ready
- Dexie.on.storagemutated
- Dexie.on.versionchange
- Dexie.open()
- Dexie.override()
- Dexie.semVer
- Dexie.setByKeyPath()
- Dexie.shallowClone()
- Dexie.spawn()
- Dexie.table()
- Dexie.tables
- Dexie.transaction()
- Dexie.transaction()-(old-version)
- Dexie.use()
- Dexie.verno
- Dexie.version
- Dexie.version()
- Dexie.vip()
- Dexie.waitFor()
- DexieCloudOptions
- DexieError
- Docs Home
- Download
- EntityTable
- Export and Import Database
- Get started with Dexie in Angular
- Get started with Dexie in React
- Get started with Dexie in Svelte
- Get started with Dexie in Vue
- Hello World
- How To Use the StorageManager API
- Inbound
- IndexSpec
- Indexable Type
- IndexedDB on Safari
- Invite
- Member
- Migrating existing DB to Dexie
- MultiEntry Index
- PersistedSyncState
- Privacy Policy
- Promise
- Promise.PSD
- Promise.catch()
- Promise.finally()
- Promise.on.error
- Promise.onuncatched
- Questions and Answers
- Realm
- Releasing Dexie
- Road Map
- Road Map: Dexie 5.0
- Road Map: Dexie Cloud
- Role
- Run Dexie Cloud on Own Servers
- Sharding and Scalability
- Simplify with yield
- Support Ukraine
- SyncState
- Table
- Table Schema
- Table.add()
- Table.bulkAdd()
- Table.bulkDelete()
- Table.bulkGet()
- Table.bulkPut()
- Table.bulkUpdate()
- Table.clear()
- Table.count()
- Table.defineClass()
- Table.delete()
- Table.each()
- Table.filter()
- Table.get()
- Table.hook('creating')
- Table.hook('deleting')
- Table.hook('reading')
- Table.hook('updating')
- Table.limit()
- Table.mapToClass()
- Table.name
- Table.offset()
- Table.orderBy()
- Table.put()
- Table.reverse()
- Table.schema
- Table.toArray()
- Table.toCollection()
- Table.update()
- Table.where()
- The main limitations of IndexedDB
- Transaction
- Transaction.abort()
- Transaction.on.abort
- Transaction.on.complete
- Transaction.on.error
- Transaction.table()
- Tutorial
- Typescript
- Typescript (old)
- Understanding the basics
- UserLogin
- Version
- Version.stores()
- Version.upgrade()
- WhereClause
- WhereClause.above()
- WhereClause.aboveOrEqual()
- WhereClause.anyOf()
- WhereClause.anyOfIgnoreCase()
- WhereClause.below()
- WhereClause.belowOrEqual()
- WhereClause.between()
- WhereClause.equals()
- WhereClause.equalsIgnoreCase()
- WhereClause.inAnyRange()
- WhereClause.noneOf()
- WhereClause.notEqual()
- WhereClause.startsWith()
- WhereClause.startsWithAnyOf()
- WhereClause.startsWithAnyOfIgnoreCase()
- WhereClause.startsWithIgnoreCase()
- db.cloud.configure()
- db.cloud.currentUser
- db.cloud.currentUserId
- db.cloud.events.syncComplete
- db.cloud.invites
- db.cloud.login()
- db.cloud.logout()
- db.cloud.options
- db.cloud.permissions()
- db.cloud.persistedSyncState
- db.cloud.roles
- db.cloud.schema
- db.cloud.sync()
- db.cloud.syncState
- db.cloud.userInteraction
- db.cloud.usingServiceWorker
- db.cloud.version
- db.cloud.webSocketStatus
- db.members
- db.realms
- db.roles
- db.syncable.connect()
- db.syncable.delete()
- db.syncable.disconnect()
- db.syncable.getOptions()
- db.syncable.getStatus()
- db.syncable.list()
- db.syncable.on('statusChanged')
- db.syncable.setFilter()
- dexie-cloud-addon
- dexie-react-hooks
- liveQuery()
- unhandledrejection-event
- useLiveQuery()
- useObservable()
- usePermissions()