From 0b96d5bc7aac2292247caf21ec0f5128d4df45eb Mon Sep 17 00:00:00 2001 From: David Fahlander Date: Sun, 8 Oct 2017 23:46:26 +0200 Subject: [PATCH] Build output v1.2.8 --- dist/index.js | 243 ++++++++++++++++++++++++++++++++++++++++++ dist/index.js.map | 1 + dist/index.min.js | 2 + dist/index.min.js.map | 1 + 4 files changed, 247 insertions(+) create mode 100644 dist/index.js create mode 100644 dist/index.js.map create mode 100644 dist/index.min.js create mode 100644 dist/index.min.js.map diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000..389549b --- /dev/null +++ b/dist/index.js @@ -0,0 +1,243 @@ +(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 (!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 + }; }); +}; + +return Relationships; + +}))); +//# sourceMappingURL=index.js.map diff --git a/dist/index.js.map b/dist/index.js.map new file mode 100644 index 0000000..a613d8f --- /dev/null +++ b/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":null,"sources":["../src/schema-parser.js","../src/utils.js","../src/index.js"],"sourcesContent":["class SchemaParser {\r\n\r\n /**\r\n * Schema parser\r\n *\r\n * @param schema\r\n */\r\n constructor (schema) {\r\n this.schema = schema\r\n }\r\n\r\n /**\r\n * Extracts foreign keys from the schema\r\n *\r\n * @returns Object\r\n */\r\n getForeignKeys () {\r\n let foreignKeys = {}\r\n\r\n Object.keys(this.schema).forEach(table => {\r\n let indexes = this.schema[table].split(',')\r\n\r\n foreignKeys[table] = indexes\r\n .filter(idx => idx.indexOf('->') !== -1)\r\n .map(idx => {\r\n // split the column and foreign table info\r\n let [column, target] = idx.split('->').map(x => x.trim())\r\n\r\n return {\r\n index: column,\r\n targetTable: target.split('.')[0],\r\n targetIndex: target.split('.')[1]\r\n }\r\n })\r\n })\r\n\r\n return foreignKeys\r\n }\r\n\r\n /**\r\n * Get schema without the foreign key definitions\r\n *\r\n * @returns Object\r\n */\r\n getCleanedSchema () {\r\n let schema = {}\r\n\r\n Object.keys(this.schema).forEach(table => {\r\n let indexes = this.schema[table].split(',')\r\n\r\n // Remove foreign keys syntax before calling the original method\r\n schema[table] = indexes.map(idx => idx.split('->')[0].trim()).join(',')\r\n })\r\n\r\n return schema\r\n }\r\n}\r\n\r\nexport default SchemaParser\r\n","\r\nexport function isIndexableType (value) {\r\n return value != null && (// Using \"!=\" instead of \"!==\" to check for both null and undefined!\r\n typeof value === 'string' ||\r\n typeof value === 'number' ||\r\n value instanceof Date ||\r\n (Array.isArray(value) && value.every(isIndexableType))\r\n )\r\n}\r\n","import Dexie from 'dexie'\r\nimport SchemaParser from './schema-parser'\r\nimport {isIndexableType} from './utils'\r\n\r\nconst Relationships = (db) => {\r\n // Use Dexie.Promise to ensure transaction safety.\r\n const Promise = Dexie.Promise\r\n\r\n /**\r\n * Iterate through all items and collect related records\r\n *\r\n * @param relationships\r\n *\r\n * @returns {Dexie.Promise}\r\n */\r\n db.Table.prototype.with = function (relationships) {\r\n return this.toCollection().with(relationships)\r\n }\r\n\r\n /**\r\n * Iterate through all items and collect related records\r\n *\r\n * @param relationships\r\n *\r\n * @returns {Dexie.Promise}\r\n */\r\n db.Collection.prototype.with = function (relationships) {\r\n const baseTable = this._ctx.table.name\r\n const databaseTables = db._allTables\r\n\r\n // this holds tables that have foreign keys pointing at the current table\r\n let usableForeignTables = []\r\n\r\n // validate target tables and add them into our usable tables object\r\n Object.keys(relationships).forEach((column) => {\r\n let tableOrIndex = relationships[column]\r\n let matchingIndex = this._ctx.table.schema.idxByName[tableOrIndex]\r\n\r\n if (matchingIndex && matchingIndex.hasOwnProperty('foreignKey')) {\r\n let index = matchingIndex\r\n usableForeignTables.push({\r\n column: column,\r\n index: index.foreignKey.targetIndex,\r\n tableName: index.foreignKey.targetTable,\r\n targetIndex: index.foreignKey.index,\r\n oneToOne: true\r\n })\r\n } else {\r\n let table = tableOrIndex\r\n\r\n if (!databaseTables.hasOwnProperty(table)) {\r\n throw new Error('Relationship table ' + table + ' doesn\\'t exist.')\r\n }\r\n\r\n if (!databaseTables[table].schema.hasOwnProperty('foreignKeys')) {\r\n throw new Error('Relationship table ' + table + ' doesn\\'t have foreign keys set.')\r\n }\r\n\r\n // remove the foreign keys that don't link to the base table\r\n let columns = databaseTables[table].schema.foreignKeys.filter(column => column.targetTable === baseTable)\r\n\r\n if (columns.length > 0) {\r\n usableForeignTables.push({\r\n column: column,\r\n index: columns[0].index,\r\n tableName: table,\r\n targetIndex: columns[0].targetIndex\r\n })\r\n }\r\n }\r\n })\r\n\r\n return this.toArray().then(rows => {\r\n //\r\n // Extract the mix of all related keys in all rows\r\n //\r\n let queries = usableForeignTables\r\n .map(foreignTable => {\r\n // For each foreign table, query all items that any row refers to\r\n let tableName = foreignTable.tableName\r\n let allRelatedKeys = rows\r\n .map(row => row[foreignTable.targetIndex])\r\n .filter(isIndexableType)\r\n\r\n // Build the Collection to retrieve all related items\r\n return databaseTables[tableName]\r\n .where(foreignTable.index)\r\n .anyOf(allRelatedKeys)\r\n })\r\n\r\n // Execute queries in parallell\r\n let queryPromises = queries.map(query => query.toArray())\r\n\r\n //\r\n // Await all results and then try mapping each response\r\n // with its corresponding row and attach related items onto each row\r\n //\r\n return Promise.all(queryPromises).then(queryResults => {\r\n usableForeignTables.forEach((foreignTable, idx) => {\r\n let tableName = foreignTable.tableName\r\n let result = queryResults[idx]\r\n let targetIndex = foreignTable.targetIndex\r\n let foreignIndex = foreignTable.index\r\n let column = foreignTable.column\r\n\r\n // Create a lookup by targetIndex (normally 'id')\r\n // and set the column onto the target\r\n let lookup = {}\r\n result.forEach(record => {\r\n let foreignKey = record[foreignIndex]\r\n if (foreignTable.oneToOne) {\r\n lookup[foreignKey] = record\r\n } else {\r\n (lookup[foreignKey] = lookup[foreignKey] || [])\r\n .push(record)\r\n }\r\n })\r\n\r\n // Populate column on each row\r\n rows.forEach(row => {\r\n let foreignKey = row[targetIndex]\r\n let record = lookup[foreignKey]\r\n if (!record) {\r\n throw new Error(\r\n `Could not lookup foreign key where ` +\r\n `${tableName}.${foreignIndex} == ${baseTable}.${column}. ` +\r\n `The content of the failing key was: ${JSON.stringify(foreignKey)}.`)\r\n }\r\n\r\n // Set it as a non-enumerable property so that the object can be safely put back\r\n // to indexeddb without storing relations redundantly (IndexedDB will only store \"own non-\r\n // enumerable properties\")\r\n Object.defineProperty(row, column, {\r\n value: record,\r\n enumerable: false,\r\n configurable: true,\r\n writable: true\r\n })\r\n })\r\n })\r\n }).then(() => rows)\r\n })\r\n }\r\n\r\n db.Version.prototype._parseStoresSpec = Dexie.override(\r\n db.Version.prototype._parseStoresSpec,\r\n parseStoresSpec => function (storesSpec, outDbSchema) {\r\n const parser = new SchemaParser(storesSpec)\r\n\r\n let foreignKeys = parser.getForeignKeys()\r\n // call the original method\r\n let rv = parseStoresSpec.call(this, parser.getCleanedSchema(), outDbSchema)\r\n\r\n // set foreign keys into database table objects\r\n // to use later in 'with' method\r\n Object.keys(outDbSchema).forEach(table => {\r\n if (foreignKeys.hasOwnProperty(table)) {\r\n outDbSchema[table].foreignKeys = foreignKeys[table]\r\n foreignKeys[table].forEach(fk => {\r\n outDbSchema[table].idxByName[fk.index].foreignKey = fk\r\n })\r\n }\r\n })\r\n\r\n return rv\r\n })\r\n}\r\n\r\nexport default Relationships\r\n"],"names":["this","const","let"],"mappings":";;;;;;;;AAAA,IAAM,YAAY,GAAC,qBAON,EAAE,MAAM,EAAE;EACrB,IAAM,CAAC,MAAM,GAAG,MAAM,CAAA;CACrB,CAAA;;;;;;;AAOH,uBAAE,cAAc,8BAAI;;;EAClB,IAAM,WAAW,GAAG,EAAE,CAAA;;EAEtB,MAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAA,KAAK,EAAC;IACvC,IAAM,OAAO,GAAGA,MAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;;IAE7C,WAAa,CAAC,KAAK,CAAC,GAAG,OAAO;OACzB,MAAM,CAAC,UAAA,GAAG,EAAC,SAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAA,CAAC;OACvC,GAAG,CAAC,UAAA,GAAG,EAAC;;QAET,OAAsB,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,UAAA,CAAC,EAAC,SAAG,CAAC,CAAC,IAAI,EAAE,GAAA,CAAC;UAApD,IAAA,MAAM;UAAE,IAAA,MAAM,UAAf;;QAEN,OAAS;UACP,KAAO,EAAE,MAAM;UACf,WAAa,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;UACnC,WAAa,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SAClC;OACF,CAAC,CAAA;GACL,CAAC,CAAA;;EAEJ,OAAS,WAAW;CACnB,CAAA;;;;;;;AAOH,uBAAE,gBAAgB,gCAAI;;;EACpB,IAAM,MAAM,GAAG,EAAE,CAAA;;EAEjB,MAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,UAAA,KAAK,EAAC;IACvC,IAAM,OAAO,GAAGA,MAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;;;IAG7C,MAAQ,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,UAAA,GAAG,EAAC,SAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAA,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;GACxE,CAAC,CAAA;;EAEJ,OAAS,MAAM;CACd,CAAA,AAGH,AAA2B;;ACzDpB,SAAS,eAAe,EAAE,KAAK,EAAE;EACtC,OAAO,KAAK,IAAI,IAAI;MAChB,OAAO,KAAK,KAAK,QAAQ;MACzB,OAAO,KAAK,KAAK,QAAQ;MACzB,KAAK,YAAY,IAAI;OACpB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;KACvD;CACJ;;ACJDC,IAAM,aAAa,GAAG,UAAC,EAAE,EAAE;;EAEzBA,IAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;;;;;;;;;EAS7B,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,GAAG,UAAU,aAAa,EAAE;IACjD,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC;GAC/C,CAAA;;;;;;;;;EASD,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,GAAG,UAAU,aAAa,EAAE;;;IACtDA,IAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAA;IACtCA,IAAM,cAAc,GAAG,EAAE,CAAC,UAAU,CAAA;;;IAGpCC,IAAI,mBAAmB,GAAG,EAAE,CAAA;;;IAG5B,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,UAAC,MAAM,EAAE;MAC1CA,IAAI,YAAY,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;MACxCA,IAAI,aAAa,GAAGF,MAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC,CAAA;;MAElE,IAAI,aAAa,IAAI,aAAa,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE;QAC/DE,IAAI,KAAK,GAAG,aAAa,CAAA;QACzB,mBAAmB,CAAC,IAAI,CAAC;UACvB,MAAM,EAAE,MAAM;UACd,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,WAAW;UACnC,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC,WAAW;UACvC,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK;UACnC,QAAQ,EAAE,IAAI;SACf,CAAC,CAAA;OACH,MAAM;QACLA,IAAI,KAAK,GAAG,YAAY,CAAA;;QAExB,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE;UACzC,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,KAAK,GAAG,kBAAkB,CAAC;SACpE;;QAED,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,aAAa,CAAC,EAAE;UAC/D,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,KAAK,GAAG,kCAAkC,CAAC;SACpF;;;QAGDA,IAAI,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,UAAA,MAAM,EAAC,SAAG,MAAM,CAAC,WAAW,KAAK,SAAS,GAAA,CAAC,CAAA;;QAEzG,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE;UACtB,mBAAmB,CAAC,IAAI,CAAC;YACvB,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK;YACvB,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW;WACpC,CAAC,CAAA;SACH;OACF;KACF,CAAC,CAAA;;IAEF,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,UAAA,IAAI,EAAC;;;;MAI9BA,IAAI,OAAO,GAAG,mBAAmB;SAC9B,GAAG,CAAC,UAAA,YAAY,EAAC;;UAEhBA,IAAI,SAAS,GAAG,YAAY,CAAC,SAAS,CAAA;UACtCA,IAAI,cAAc,GAAG,IAAI;aACtB,GAAG,CAAC,UAAA,GAAG,EAAC,SAAG,GAAG,CAAC,YAAY,CAAC,WAAW,CAAC,GAAA,CAAC;aACzC,MAAM,CAAC,eAAe,CAAC,CAAA;;;UAG1B,OAAO,cAAc,CAAC,SAAS,CAAC;eAC3B,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC;eACzB,KAAK,CAAC,cAAc,CAAC;SAC3B,CAAC,CAAA;;;MAGJA,IAAI,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,UAAA,KAAK,EAAC,SAAG,KAAK,CAAC,OAAO,EAAE,GAAA,CAAC,CAAA;;;;;;MAMzD,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,UAAA,YAAY,EAAC;QAClD,mBAAmB,CAAC,OAAO,CAAC,UAAC,YAAY,EAAE,GAAG,EAAE;UAC9CA,IAAI,SAAS,GAAG,YAAY,CAAC,SAAS,CAAA;UACtCA,IAAI,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;UAC9BA,IAAI,WAAW,GAAG,YAAY,CAAC,WAAW,CAAA;UAC1CA,IAAI,YAAY,GAAG,YAAY,CAAC,KAAK,CAAA;UACrCA,IAAI,MAAM,GAAG,YAAY,CAAC,MAAM,CAAA;;;;UAIhCA,IAAI,MAAM,GAAG,EAAE,CAAA;UACf,MAAM,CAAC,OAAO,CAAC,UAAA,MAAM,EAAC;YACpBA,IAAI,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,CAAA;YACrC,IAAI,YAAY,CAAC,QAAQ,EAAE;cACzB,MAAM,CAAC,UAAU,CAAC,GAAG,MAAM,CAAA;aAC5B,MAAM;cACL,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE;iBAC3C,IAAI,CAAC,MAAM,CAAC,CAAA;aAChB;WACF,CAAC,CAAA;;;UAGF,IAAI,CAAC,OAAO,CAAC,UAAA,GAAG,EAAC;YACfA,IAAI,UAAU,GAAG,GAAG,CAAC,WAAW,CAAC,CAAA;YACjCA,IAAI,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;YAC/B,IAAI,CAAC,MAAM,EAAE;cACX,MAAM,IAAI,KAAK;gBACb,qCAAoC;gBACpC,SAAY,MAAE,GAAE,YAAY,SAAK,GAAE,SAAS,MAAE,GAAE,MAAM,OAAG;gBACzD,sCAAqC,IAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA,MAAE,CAAE;aACxE;;;;;YAKD,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE;cACjC,KAAK,EAAE,MAAM;cACb,UAAU,EAAE,KAAK;cACjB,YAAY,EAAE,IAAI;cAClB,QAAQ,EAAE,IAAI;aACf,CAAC,CAAA;WACH,CAAC,CAAA;SACH,CAAC,CAAA;OACH,CAAC,CAAC,IAAI,CAAC,YAAG,SAAG,IAAI,GAAA,CAAC;KACpB,CAAC;GACH,CAAA;;EAED,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,gBAAgB,GAAG,KAAK,CAAC,QAAQ;IACpD,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,gBAAgB;IACrC,UAAA,eAAe,EAAC,SAAG,UAAU,UAAU,EAAE,WAAW,EAAE;MACpDD,IAAM,MAAM,GAAG,IAAI,YAAY,CAAC,UAAU,CAAC,CAAA;;MAE3CC,IAAI,WAAW,GAAG,MAAM,CAAC,cAAc,EAAE,CAAA;;MAEzCA,IAAI,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,gBAAgB,EAAE,EAAE,WAAW,CAAC,CAAA;;;;MAI3E,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,UAAA,KAAK,EAAC;QACrC,IAAI,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE;UACrC,WAAW,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;UACnD,WAAW,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,UAAA,EAAE,EAAC;YAC5B,WAAW,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,EAAE,CAAA;WACvD,CAAC,CAAA;SACH;OACF,CAAC,CAAA;;MAEF,OAAO,EAAE;KACV,GAAA,CAAC,CAAA;CACL,CAAA,AAED,AAA4B;;;;"} \ No newline at end of file diff --git a/dist/index.min.js b/dist/index.min.js new file mode 100644 index 0000000..dd4f076 --- /dev/null +++ b/dist/index.min.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("dexie")):"function"==typeof define&&define.amd?define(["dexie"],t):e.dexieRelationships=t(e.Dexie)}(this,function(e){"use strict";function t(e){return null!=e&&("string"==typeof e||"number"==typeof e||e instanceof Date||Array.isArray(e)&&e.every(t))}e="default"in e?e.default:e;var n=function(e){this.schema=e};return n.prototype.getForeignKeys=function(){var e=this,t={};return Object.keys(this.schema).forEach(function(n){var r=e.schema[n].split(",");t[n]=r.filter(function(e){return-1!==e.indexOf("->")}).map(function(e){var t=e.split("->").map(function(e){return e.trim()}),n=t[0],r=t[1];return{index:n,targetTable:r.split(".")[0],targetIndex:r.split(".")[1]}})}),t},n.prototype.getCleanedSchema=function(){var e=this,t={};return Object.keys(this.schema).forEach(function(n){var r=e.schema[n].split(",");t[n]=r.map(function(e){return e.split("->")[0].trim()}).join(",")}),t},function(r){var i=e.Promise;r.Table.prototype.with=function(e){return this.toCollection().with(e)},r.Collection.prototype.with=function(e){var n=this,o=this._ctx.table.name,a=r._allTables,f=[];return Object.keys(e).forEach(function(t){var r=e[t],i=n._ctx.table.schema.idxByName[r];if(i&&i.hasOwnProperty("foreignKey")){var c=i;f.push({column:t,index:c.foreignKey.targetIndex,tableName:c.foreignKey.targetTable,targetIndex:c.foreignKey.index,oneToOne:!0})}else{var u=r;if(!a.hasOwnProperty(u))throw new Error("Relationship table "+u+" doesn't exist.");if(!a[u].schema.hasOwnProperty("foreignKeys"))throw new Error("Relationship table "+u+" doesn't have foreign keys set.");var s=a[u].schema.foreignKeys.filter(function(e){return e.targetTable===o});s.length>0&&f.push({column:t,index:s[0].index,tableName:u,targetIndex:s[0].targetIndex})}}),this.toArray().then(function(e){var n=f.map(function(n){var r=n.tableName,i=e.map(function(e){return e[n.targetIndex]}).filter(t);return a[r].where(n.index).anyOf(i)}),r=n.map(function(e){return e.toArray()});return i.all(r).then(function(t){f.forEach(function(n,r){var i=n.tableName,a=t[r],f=n.targetIndex,c=n.index,u=n.column,s={};a.forEach(function(e){var t=e[c];n.oneToOne?s[t]=e:(s[t]=s[t]||[]).push(e)}),e.forEach(function(e){var t=e[f],n=s[t];if(!n)throw new Error("Could not lookup foreign key where "+i+"."+c+" == "+o+"."+u+". The content of the failing key was: "+JSON.stringify(t)+".");Object.defineProperty(e,u,{value:n,enumerable:!1,configurable:!0,writable:!0})})})}).then(function(){return e})})},r.Version.prototype._parseStoresSpec=e.override(r.Version.prototype._parseStoresSpec,function(e){return function(t,r){var i=new n(t),o=i.getForeignKeys(),a=e.call(this,i.getCleanedSchema(),r);return Object.keys(r).forEach(function(e){o.hasOwnProperty(e)&&(r[e].foreignKeys=o[e],o[e].forEach(function(t){r[e].idxByName[t.index].foreignKey=t}))}),a}})}}); +//# sourceMappingURL=index.min.js.map diff --git a/dist/index.min.js.map b/dist/index.min.js.map new file mode 100644 index 0000000..79ae561 --- /dev/null +++ b/dist/index.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":null,"sources":["../src/utils.js","../src/schema-parser.js","../src/index.js"],"sourcesContent":["\r\nexport function isIndexableType (value) {\r\n return value != null && (// Using \"!=\" instead of \"!==\" to check for both null and undefined!\r\n typeof value === 'string' ||\r\n typeof value === 'number' ||\r\n value instanceof Date ||\r\n (Array.isArray(value) && value.every(isIndexableType))\r\n )\r\n}\r\n","class SchemaParser {\r\n\r\n /**\r\n * Schema parser\r\n *\r\n * @param schema\r\n */\r\n constructor (schema) {\r\n this.schema = schema\r\n }\r\n\r\n /**\r\n * Extracts foreign keys from the schema\r\n *\r\n * @returns Object\r\n */\r\n getForeignKeys () {\r\n let foreignKeys = {}\r\n\r\n Object.keys(this.schema).forEach(table => {\r\n let indexes = this.schema[table].split(',')\r\n\r\n foreignKeys[table] = indexes\r\n .filter(idx => idx.indexOf('->') !== -1)\r\n .map(idx => {\r\n // split the column and foreign table info\r\n let [column, target] = idx.split('->').map(x => x.trim())\r\n\r\n return {\r\n index: column,\r\n targetTable: target.split('.')[0],\r\n targetIndex: target.split('.')[1]\r\n }\r\n })\r\n })\r\n\r\n return foreignKeys\r\n }\r\n\r\n /**\r\n * Get schema without the foreign key definitions\r\n *\r\n * @returns Object\r\n */\r\n getCleanedSchema () {\r\n let schema = {}\r\n\r\n Object.keys(this.schema).forEach(table => {\r\n let indexes = this.schema[table].split(',')\r\n\r\n // Remove foreign keys syntax before calling the original method\r\n schema[table] = indexes.map(idx => idx.split('->')[0].trim()).join(',')\r\n })\r\n\r\n return schema\r\n }\r\n}\r\n\r\nexport default SchemaParser\r\n","import Dexie from 'dexie'\r\nimport SchemaParser from './schema-parser'\r\nimport {isIndexableType} from './utils'\r\n\r\nconst Relationships = (db) => {\r\n // Use Dexie.Promise to ensure transaction safety.\r\n const Promise = Dexie.Promise\r\n\r\n /**\r\n * Iterate through all items and collect related records\r\n *\r\n * @param relationships\r\n *\r\n * @returns {Dexie.Promise}\r\n */\r\n db.Table.prototype.with = function (relationships) {\r\n return this.toCollection().with(relationships)\r\n }\r\n\r\n /**\r\n * Iterate through all items and collect related records\r\n *\r\n * @param relationships\r\n *\r\n * @returns {Dexie.Promise}\r\n */\r\n db.Collection.prototype.with = function (relationships) {\r\n const baseTable = this._ctx.table.name\r\n const databaseTables = db._allTables\r\n\r\n // this holds tables that have foreign keys pointing at the current table\r\n let usableForeignTables = []\r\n\r\n // validate target tables and add them into our usable tables object\r\n Object.keys(relationships).forEach((column) => {\r\n let tableOrIndex = relationships[column]\r\n let matchingIndex = this._ctx.table.schema.idxByName[tableOrIndex]\r\n\r\n if (matchingIndex && matchingIndex.hasOwnProperty('foreignKey')) {\r\n let index = matchingIndex\r\n usableForeignTables.push({\r\n column: column,\r\n index: index.foreignKey.targetIndex,\r\n tableName: index.foreignKey.targetTable,\r\n targetIndex: index.foreignKey.index,\r\n oneToOne: true\r\n })\r\n } else {\r\n let table = tableOrIndex\r\n\r\n if (!databaseTables.hasOwnProperty(table)) {\r\n throw new Error('Relationship table ' + table + ' doesn\\'t exist.')\r\n }\r\n\r\n if (!databaseTables[table].schema.hasOwnProperty('foreignKeys')) {\r\n throw new Error('Relationship table ' + table + ' doesn\\'t have foreign keys set.')\r\n }\r\n\r\n // remove the foreign keys that don't link to the base table\r\n let columns = databaseTables[table].schema.foreignKeys.filter(column => column.targetTable === baseTable)\r\n\r\n if (columns.length > 0) {\r\n usableForeignTables.push({\r\n column: column,\r\n index: columns[0].index,\r\n tableName: table,\r\n targetIndex: columns[0].targetIndex\r\n })\r\n }\r\n }\r\n })\r\n\r\n return this.toArray().then(rows => {\r\n //\r\n // Extract the mix of all related keys in all rows\r\n //\r\n let queries = usableForeignTables\r\n .map(foreignTable => {\r\n // For each foreign table, query all items that any row refers to\r\n let tableName = foreignTable.tableName\r\n let allRelatedKeys = rows\r\n .map(row => row[foreignTable.targetIndex])\r\n .filter(isIndexableType)\r\n\r\n // Build the Collection to retrieve all related items\r\n return databaseTables[tableName]\r\n .where(foreignTable.index)\r\n .anyOf(allRelatedKeys)\r\n })\r\n\r\n // Execute queries in parallell\r\n let queryPromises = queries.map(query => query.toArray())\r\n\r\n //\r\n // Await all results and then try mapping each response\r\n // with its corresponding row and attach related items onto each row\r\n //\r\n return Promise.all(queryPromises).then(queryResults => {\r\n usableForeignTables.forEach((foreignTable, idx) => {\r\n let tableName = foreignTable.tableName\r\n let result = queryResults[idx]\r\n let targetIndex = foreignTable.targetIndex\r\n let foreignIndex = foreignTable.index\r\n let column = foreignTable.column\r\n\r\n // Create a lookup by targetIndex (normally 'id')\r\n // and set the column onto the target\r\n let lookup = {}\r\n result.forEach(record => {\r\n let foreignKey = record[foreignIndex]\r\n if (foreignTable.oneToOne) {\r\n lookup[foreignKey] = record\r\n } else {\r\n (lookup[foreignKey] = lookup[foreignKey] || [])\r\n .push(record)\r\n }\r\n })\r\n\r\n // Populate column on each row\r\n rows.forEach(row => {\r\n let foreignKey = row[targetIndex]\r\n let record = lookup[foreignKey]\r\n if (!record) {\r\n throw new Error(\r\n `Could not lookup foreign key where ` +\r\n `${tableName}.${foreignIndex} == ${baseTable}.${column}. ` +\r\n `The content of the failing key was: ${JSON.stringify(foreignKey)}.`)\r\n }\r\n\r\n // Set it as a non-enumerable property so that the object can be safely put back\r\n // to indexeddb without storing relations redundantly (IndexedDB will only store \"own non-\r\n // enumerable properties\")\r\n Object.defineProperty(row, column, {\r\n value: record,\r\n enumerable: false,\r\n configurable: true,\r\n writable: true\r\n })\r\n })\r\n })\r\n }).then(() => rows)\r\n })\r\n }\r\n\r\n db.Version.prototype._parseStoresSpec = Dexie.override(\r\n db.Version.prototype._parseStoresSpec,\r\n parseStoresSpec => function (storesSpec, outDbSchema) {\r\n const parser = new SchemaParser(storesSpec)\r\n\r\n let foreignKeys = parser.getForeignKeys()\r\n // call the original method\r\n let rv = parseStoresSpec.call(this, parser.getCleanedSchema(), outDbSchema)\r\n\r\n // set foreign keys into database table objects\r\n // to use later in 'with' method\r\n Object.keys(outDbSchema).forEach(table => {\r\n if (foreignKeys.hasOwnProperty(table)) {\r\n outDbSchema[table].foreignKeys = foreignKeys[table]\r\n foreignKeys[table].forEach(fk => {\r\n outDbSchema[table].idxByName[fk.index].foreignKey = fk\r\n })\r\n }\r\n })\r\n\r\n return rv\r\n })\r\n}\r\n\r\nexport default Relationships\r\n"],"names":["isIndexableType","value","Date","Array","isArray","every","SchemaParser","schema","this","getForeignKeys","foreignKeys","Object","keys","forEach","table","indexes","split","filter","idx","indexOf","map","x","trim","column","target","index","targetTable","targetIndex","getCleanedSchema","join","db","const","Promise","Dexie","Table","prototype","with","relationships","toCollection","Collection","baseTable","_ctx","name","databaseTables","_allTables","usableForeignTables","let","tableOrIndex","matchingIndex","idxByName","hasOwnProperty","push","foreignKey","tableName","oneToOne","Error","columns","length","toArray","then","rows","queries","foreignTable","allRelatedKeys","row","where","anyOf","queryPromises","query","all","queryResults","result","foreignIndex","lookup","record","JSON","stringify","defineProperty","enumerable","configurable","writable","Version","_parseStoresSpec","override","parseStoresSpec","storesSpec","outDbSchema","parser","rv","call","fk"],"mappings":"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"} \ No newline at end of file