Skip to content

Commit

Permalink
Merge pull request #113 from mydea/fn/dexie-3
Browse files Browse the repository at this point in the history
Update to Dexie 3
  • Loading branch information
mydea authored Jun 23, 2020
2 parents 2a68675 + c583e60 commit b6ab764
Show file tree
Hide file tree
Showing 6 changed files with 1,058 additions and 175 deletions.
1 change: 1 addition & 0 deletions addon-test-support/helpers/indexed-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function setupIndexedDb(hooks) {
await wait();
await run(() => indexedDb.waitForQueueTask.perform());
await run(() => indexedDb.waitForQueueTask.perform());
await indexedDb.dropDatabaseTask.perform();
});
}

Expand Down
72 changes: 35 additions & 37 deletions addon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ import IndexedDbConfiguration from './services/indexed-db-configuration';
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default Ember.Route.extend({
indexedDb: service(),
export default class ApplicationRoute extends Route {
@service indexedDb;
beforeModel() {
this._super(...arguments);
return this.indexedDb.setupTask.perform();
}
});
}
```
This returns a promise that is ready once the database is setup. Note that this will reject if IndexedDB is not available - you need to handle this case accordingly.
Expand All @@ -69,16 +69,16 @@ import IndexedDbConfiguration from './services/indexed-db-configuration';
```js
import IndexedDbConfigurationService from 'ember-indexeddb/services/indexed-db-configuration';
export default IndexedDbConfigurationService.extend({
currentVersion: 1,
export default class ExtendedIndexedDbConfigurationService extends IndexedDbConfigurationService {
currentVersion = 1;
version1: {
stores: {
'model-one': '&id,*isNew',
'model-two': '&id,*status,*modelOne,[status+modelOne]'
}
}
});
}
```
Please consult the Dexie Documentation on [details about configuring your database](https://github.com/dfahlander/Dexie.js/wiki/Version.stores()).
Expand All @@ -88,6 +88,8 @@ import IndexedDbConfiguration from './services/indexed-db-configuration';
You can add as many version as you want, and Dexie will handle the upgrading for you. Note that you cannot downgrade a version. There needs to be a `versionX` property per version, starting at 1. So if you have a `currentVersion` of 3, you need to have `version1`, `version2` and `version3` properties.
You do not need to keep old versionX configurations unless they contain an upgrade.
All of these migrations are automatically run when running `this.indexedDb.setup();`.
In addition to the store configuration, you also need to define a `mapTable`.
Expand All @@ -96,25 +98,23 @@ import IndexedDbConfiguration from './services/indexed-db-configuration';
For the above example, this should look something like this:
```js
mapTable: computed(function() {
return {
'model-one': (item) => {
return {
id: this._toString(item.id),
json: this._cleanupObject(item),
isNew: this._toZeroOne(item.isNew)
};
},
'model-two': (item) => {
return {
id: this._toString(item.id),
json: this._cleanupObject(item),
modelOne: this._toString(item.relationships.modelOne?.data?.id),
status: item.attribtues.status
};
}
};
})
mapTable: {
'model-one': (item) => {
return {
id: this._toString(item.id),
json: this._cleanupObject(item),
isNew: this._toZeroOne(item.isNew)
};
},
'model-two': (item) => {
return {
id: this._toString(item.id),
json: this._cleanupObject(item),
modelOne: this._toString(item.relationships.modelOne?.data?.id),
status: item.attribtues.status
};
}
}
```
Things to note here:
Expand Down Expand Up @@ -173,7 +173,7 @@ import IndexedDbConfiguration from './services/indexed-db-configuration';
```js
import IndexedDbAdapter from 'ember-indexeddb/adapters/indexed-db';
export default IndexedDbAdapter.extend();
export default class ApplicationAdapter extends IndexedDbAdapter {}
```
The next step is to setup your database for your Ember Data models.
Expand All @@ -185,18 +185,16 @@ import IndexedDbConfiguration from './services/indexed-db-configuration';
import IndexedDbConfigurationService from 'ember-indexeddb/services/indexed-db-configuration';
import { computed, get } from '@ember/object';
export default IndexedDbConfigurationService.extend({
currentVersion: 1,
export default class ExtendedIndexedDbConfigurationService extends IndexedDbConfigurationService {
currentVersion = 1;
version1: computed(function() {
return {
stores: {
'model-a': '&id',
'model-b': '&id'
}
};
})
});
version1 = {
stores: {
'model-a': '&id',
'model-b': '&id'
}
};
}
```
Now, you can simply use the normal ember-data store with functions like `store.query('item', { isNew: true })`.
Expand Down
23 changes: 17 additions & 6 deletions addon/services/indexed-db-configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,26 @@ export default class IndexedDbConfigurationService extends Service {
*
* upgrade is a function that gets a transaction as parameter, which can be used to run database migrations.
* See https://github.com/dfahlander/Dexie.js/wiki/Version.upgrade() for detailed options/examples.
*
* Note that in newer versions of Dexie, you do not need to keep old version definitions anymnore, unless they contain upgrade instructions.
* Instead, each version has to contain the full, current schema (not just the updates to the last version).
*
* An example would be:
*
* ```js
* version1: {
* // You can delete this safely when adding version2, as it does not contain an upgrade
* version1 = {
* stores: {
* 'task': '&id*,isRead',
* 'task-item': '&id'
* }
* },
*
* version2: {
* stores: {
* version2 = {
* stores: {
* 'task': '&id*,isRead',
* 'task-item': '&id,*isNew'
* },
* }
* upgrade: (transaction) => {
* transaction['task-item'].each((taskItem, cursor) => {
taskItem.isNew = 0;
Expand Down Expand Up @@ -150,7 +155,7 @@ export default class IndexedDbConfigurationService extends Service {
* @public
*/
setupDatabase(db) {
let currentVersion = this.currentVersion;
let { currentVersion } = this;

assert(
'You need to override services/indexed-db-configuration.js and provide at least one version.',
Expand All @@ -159,7 +164,13 @@ export default class IndexedDbConfigurationService extends Service {

for (let v = 1; v <= currentVersion; v++) {
let versionName = `version${v}`;
let { stores, upgrade } = this[versionName];
let versionDefinition = this[versionName];

if (!versionDefinition) {
continue;
}

let { stores, upgrade } = versionDefinition;

if (stores && upgrade) {
db.version(v).stores(stores).upgrade(upgrade);
Expand Down
99 changes: 77 additions & 22 deletions addon/services/indexed-db.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ export default class IndexedDbService extends Service {
*/
databaseName = 'ember-indexeddb';

/**
* If set to true, it will output which indecies are used for queries.
* This can be used to debug your indecies.
*
* @property _shouldLogQuery
* @type {Boolean}
* @default false
* @private
*/
_shouldLogQuery = false;

/**
* This is an object with an array per model type.
* It holds all the objects per model type that should be bulk saved.
Expand Down Expand Up @@ -167,6 +178,7 @@ export default class IndexedDbService extends Service {
*/
queryRecord(type, query) {
let queryPromise = this._buildQuery(type, query);

let promise = new Promise(
(resolve, reject) => queryPromise.first().then(resolve, reject),
'indexedDb/queryRecord'
Expand Down Expand Up @@ -543,6 +555,13 @@ export default class IndexedDbService extends Service {
return Promise.all(promises, 'indexedDb/_bulkSave');
}

_logQuery(str, query) {
if (this._shouldLogQuery) {
// eslint-disable-next-line
console.log(`[QUERY]: ${str}`, query);
}
}

/**
* Build a query for Dexie.
*
Expand All @@ -564,29 +583,30 @@ export default class IndexedDbService extends Service {
_buildQuery(type, query) {
let { db, _supportsCompoundIndices: supportsCompoundIndices } = this;

let promise = null;
let keys = Object.keys(query);

// Convert boolean queries to 1/0
for (let i in query) {
if (getTypeOf(query[i]) === 'boolean') {
query[i] = query[i] ? 1 : 0;
}
}
// Order of query params is important!
let { schema } = db[type];
let { indexes } = schema;

// Only one, then do a simple where
// Only one, try to find a simple index
if (keys.length === 1) {
let key = keys[0];
return db[type].where(key).equals(query[key]);
}
let index = indexes.find((index) => {
let { keyPath } = index;
return keyPath === key;
});

// Order of query params is important!
let { schema } = db[type];
let { indexes } = schema;
if (index) {
this._logQuery(`Using index "${key}"`, query);
let value = normalizeValue(query[key]);
return db[type].where(key).equals(value);
}
}

// try to find a fitting multi index
// only if the client supports compound indices!
if (supportsCompoundIndices) {
if (keys.length > 1 && supportsCompoundIndices) {
let multiIndex = indexes.find((index) => {
let { keyPath } = index;

Expand All @@ -609,23 +629,50 @@ export default class IndexedDbService extends Service {
let compareValues = array();

keyPath.forEach((key) => {
compareValues.push(query[key]);
let value = normalizeValue(query[key]);
compareValues.push(value);
});

this._logQuery(`Using compound index "${keyPath}"`, query);
return db[type].where(keyName).equals(compareValues);
}
}

// Else, filter manually
Object.keys(query).forEach((i) => {
if (!promise) {
promise = db[type].where(i).equals(query[i]);
} else {
promise = promise.and((item) => item[i] === query[i]);
}
// Try to find at least a single actual index, if possible...
let whereKey = keys.find((key) => {
return indexes.some((index) => {
let { keyPath } = index;
return keyPath === key;
});
});

return promise;
let whereKeyValue = whereKey ? normalizeValue(query[whereKey]) : null;
let vanillaFilterKeys = keys.filter((key) => !whereKey || key !== whereKey);

let collection = whereKey
? db[type].where(whereKey).equals(whereKeyValue)
: db[type];

if (whereKey) {
this._logQuery(
`Using index "${whereKey}" and vanilla filtering for ${vanillaFilterKeys.join(
', '
)}`,
query
);
} else {
this._logQuery(`Using vanilla filtering`, query);
}

return collection.filter((item) => {
return vanillaFilterKeys.every((key) => {
return (
normalizeValue(item.json.attributes[key]) ===
normalizeValue(query[key])
);
});
});
}

_mapItem(type, item) {
Expand Down Expand Up @@ -685,3 +732,11 @@ async function closeDb(db) {
}
return db;
}

function normalizeValue(value) {
if (typeof value === 'boolean') {
return value ? 1 : 0;
}

return value;
}
Loading

0 comments on commit b6ab764

Please sign in to comment.