dexie-relationships
Version:
Dexie relationship plugin
247 lines (207 loc) • 8.11 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('dexie')) :
typeof define === 'function' && define.amd ? define(['dexie'], factory) :
(global.dexieRelationships = factory(global.Dexie));
}(this, (function (Dexie) { 'use strict';
Dexie = 'default' in Dexie ? Dexie['default'] : Dexie;
var SchemaParser = function SchemaParser (schema) {
this.schema = schema;
};
/**
* Extracts foreign keys from the schema
*
* @returns Object
*/
SchemaParser.prototype.getForeignKeys = function getForeignKeys () {
var this$1 = this;
var foreignKeys = {};
Object.keys(this.schema).forEach(function (table) {
var indexes = this$1.schema[table].split(',');
foreignKeys[table] = indexes
.filter(function (idx) { return idx.indexOf('->') !== -1; })
.map(function (idx) {
// split the column and foreign table info
var ref = idx.split('->').map(function (x) { return x.trim(); });
var column = ref[0];
var target = ref[1];
return {
index: column,
targetTable: target.split('.')[0],
targetIndex: target.split('.')[1]
}
});
});
return foreignKeys
};
/**
* Get schema without the foreign key definitions
*
* @returns Object
*/
SchemaParser.prototype.getCleanedSchema = function getCleanedSchema () {
var this$1 = this;
var schema = {};
Object.keys(this.schema).forEach(function (table) {
var indexes = this$1.schema[table].split(',');
// Remove foreign keys syntax before calling the original method
schema[table] = indexes.map(function (idx) { return idx.split('->')[0].trim(); }).join(',');
});
return schema
};
function isIndexableType (value) {
return value != null && (// Using "!=" instead of "!==" to check for both null and undefined!
typeof value === 'string' ||
typeof value === 'number' ||
value instanceof Date ||
(Array.isArray(value) && value.every(isIndexableType))
)
}
var Relationships = function (db) {
// Use Dexie.Promise to ensure transaction safety.
var Promise = Dexie.Promise;
/**
* Iterate through all items and collect related records
*
* @param relationships
*
* @returns {Dexie.Promise}
*/
db.Table.prototype.with = function (relationships) {
return this.toCollection().with(relationships)
};
/**
* Iterate through all items and collect related records
*
* @param relationships
*
* @returns {Dexie.Promise}
*/
db.Collection.prototype.with = function (relationships) {
var this$1 = this;
var baseTable = this._ctx.table.name;
var databaseTables = db._allTables;
// this holds tables that have foreign keys pointing at the current table
var usableForeignTables = [];
// validate target tables and add them into our usable tables object
Object.keys(relationships).forEach(function (column) {
var tableOrIndex = relationships[column];
var matchingIndex = this$1._ctx.table.schema.idxByName[tableOrIndex];
if (matchingIndex && matchingIndex.hasOwnProperty('foreignKey')) {
var index = matchingIndex;
usableForeignTables.push({
column: column,
index: index.foreignKey.targetIndex,
tableName: index.foreignKey.targetTable,
targetIndex: index.foreignKey.index,
oneToOne: true
});
} else {
var table = tableOrIndex;
if (!databaseTables.hasOwnProperty(table)) {
throw new Error('Relationship table ' + table + ' doesn\'t exist.')
}
if (!databaseTables[table].schema.hasOwnProperty('foreignKeys')) {
throw new Error('Relationship table ' + table + ' doesn\'t have foreign keys set.')
}
// remove the foreign keys that don't link to the base table
var columns = databaseTables[table].schema.foreignKeys.filter(function (column) { return column.targetTable === baseTable; });
if (columns.length > 0) {
usableForeignTables.push({
column: column,
index: columns[0].index,
tableName: table,
targetIndex: columns[0].targetIndex
});
}
}
});
return this.toArray().then(function (rows) {
//
// Extract the mix of all related keys in all rows
//
var queries = usableForeignTables
.map(function (foreignTable) {
// For each foreign table, query all items that any row refers to
var tableName = foreignTable.tableName;
var allRelatedKeys = rows
.map(function (row) { return row[foreignTable.targetIndex]; })
.filter(isIndexableType);
// Build the Collection to retrieve all related items
return databaseTables[tableName]
.where(foreignTable.index)
.anyOf(allRelatedKeys)
});
// Execute queries in parallell
var queryPromises = queries.map(function (query) { return query.toArray(); });
//
// Await all results and then try mapping each response
// with its corresponding row and attach related items onto each row
//
return Promise.all(queryPromises).then(function (queryResults) {
usableForeignTables.forEach(function (foreignTable, idx) {
var tableName = foreignTable.tableName;
var result = queryResults[idx];
var targetIndex = foreignTable.targetIndex;
var foreignIndex = foreignTable.index;
var column = foreignTable.column;
// Create a lookup by targetIndex (normally 'id')
// and set the column onto the target
var lookup = {};
result.forEach(function (record) {
var foreignKey = record[foreignIndex];
if (foreignTable.oneToOne) {
lookup[foreignKey] = record;
} else {
(lookup[foreignKey] = lookup[foreignKey] || [])
.push(record);
}
});
// Populate column on each row
rows.forEach(function (row) {
var foreignKey = row[targetIndex];
var record = lookup[foreignKey] || [];
if (foreignKey !== null && foreignKey !== undefined && !record) {
throw new Error(
"Could not lookup foreign key where " +
tableName + "." + foreignIndex + " == " + baseTable + "." + column + ". " +
"The content of the failing key was: " + (JSON.stringify(foreignKey)) + ".")
}
// Set it as a non-enumerable property so that the object can be safely put back
// to indexeddb without storing relations redundantly (IndexedDB will only store "own non-
// enumerable properties")
Object.defineProperty(row, column, {
value: record,
enumerable: false,
configurable: true,
writable: true
});
});
});
}).then(function () { return rows; })
})
};
db.Version.prototype._parseStoresSpec = Dexie.override(
db.Version.prototype._parseStoresSpec,
function (parseStoresSpec) { return function (storesSpec, outDbSchema) {
var parser = new SchemaParser(storesSpec);
var foreignKeys = parser.getForeignKeys();
// call the original method
var rv = parseStoresSpec.call(this, parser.getCleanedSchema(), outDbSchema);
// set foreign keys into database table objects
// to use later in 'with' method
Object.keys(outDbSchema).forEach(function (table) {
if (foreignKeys.hasOwnProperty(table)) {
outDbSchema[table].foreignKeys = foreignKeys[table];
foreignKeys[table].forEach(function (fk) {
outDbSchema[table].idxByName[fk.index].foreignKey = fk;
});
}
});
return rv
}; });
};
// https://github.com/dfahlander/Dexie.js/issues/625:
Relationships.default = Relationships;
return Relationships;
})));
//# sourceMappingURL=index.js.map