UNPKG

mikro-orm-find-dataloader

Version:

Additional dataloaders for the MikroORM EntityManager find/findOne/etc methods.

221 lines (219 loc) 9.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isCol = exports.isRef = exports.hasCol = exports.hasRef = exports.assertHasNewFilterAndMapKey = exports.groupFindQueries = exports.groupInversedOrMappedKeysByEntity = exports.groupPrimaryKeysByEntity = void 0; const core_1 = require("@mikro-orm/core"); function groupPrimaryKeysByEntity(refs) { const map = new Map(); for (const ref of refs) { const className = (0, core_1.helper)(ref).__meta.className; let primaryKeys = map.get(className); if (primaryKeys == null) { primaryKeys = new Set(); map.set(className, primaryKeys); } primaryKeys.add((0, core_1.helper)(ref).getPrimaryKey()); } return map; } exports.groupPrimaryKeysByEntity = groupPrimaryKeysByEntity; function groupInversedOrMappedKeysByEntity(collections) { const entitiesMap = new Map(); for (const col of collections) { const className = col.property.type; let propMap = entitiesMap.get(className); if (propMap == null) { propMap = new Map(); entitiesMap.set(className, propMap); } // Many to Many vs One to Many const inversedProp = col.property.inversedBy ?? col.property.mappedBy; if (inversedProp == null) { throw new Error("Cannot find inversedBy or mappedBy prop: did you forget to set the inverse side of a many-to-many relationship?"); } let primaryKeys = propMap.get(inversedProp); if (primaryKeys == null) { primaryKeys = new Set(); propMap.set(inversedProp, primaryKeys); } primaryKeys.add((0, core_1.helper)(col.owner).getPrimaryKey()); } return entitiesMap; } exports.groupInversedOrMappedKeysByEntity = groupInversedOrMappedKeysByEntity; // Call this fn only if keyProp.targetMeta != null otherwise you will get false positives function getPKs(filter, meta) { if (meta.compositePK) { // COMPOSITE if (Array.isArray(filter)) { // PK or PK[] or object[] if (core_1.Utils.isPrimaryKey(filter, meta.compositePK)) { // PK return [filter]; } if (core_1.Utils.isPrimaryKey(filter[0], meta.compositePK)) { // PK[] return filter; } } } else { // NOT COMPOSITE if (Array.isArray(filter)) { // PK[] or object[] if (core_1.Utils.isPrimaryKey(filter[0])) { return filter; } } else { // PK or object if (core_1.Utils.isPrimaryKey(filter)) { // PK return [filter]; } } } } /* NOT COMPOSITE: NEW QUERY MAP KEY (props in alphabetical order) 1 -> {id: [1]} {id} [1, 2] -> {id: [1, 2]} {id} {id: 1, sex: 0} -> {id: [1], sex: [0]} {id,sex} {id: [1, 2], sex: 0} -> {id: [1, 2], sex: [0]} {id,sex} [{id: 1}, {id: 2}] NOT POSSIBLE FOR NON COMPOSITE COMPOSITE PK: [1, 2] -> {owner: [1], recipient: [2]} {owner,recipient} [[1, 2], [3, 4]] -> {owner: [1, 2], recipient: [3, 4]} {owner,recipient} {owner: 1, recipient: 2, sex: 0} -> {owner: [1], recipient: [2], sex: [0]} {owner,recipient,sex} [{owner: 1, recipient: 2}, {owner: 3, recipient: 4}] -> {owner: [1, 3], recipient: [2, 4]} {owner,recipient} [{owner: 1, recipient: 2, sex: 0}, {owner: 3, recipient: 4, sex: 1}] NOT POSSIBLE, MUST MATCH EXACTLY THE PK [{owner: [1], recipient: [2], sex: 0} -> {owner: [1], recipient: [2], sex: [0]} // NOT A PK {owner,recipient,sex} [{owner: [1], recipient: [2], sex: 0}, {owner: 3, recipient: 4, sex: 1}] -> {owner: [1, 3], recipient: [2, 4], sex: [0, 1]} // NOT A PK {owner,recipient,sex} */ const asc = (a, b) => a.localeCompare(b); const notNull = (el) => el != null; function getNewFiltersAndMapKeys(cur, meta, entityName) { const PKs = getPKs(cur, meta); if (PKs != null) { const res = [ Object.fromEntries(meta.primaryKeys.map((pk, i) => [pk, meta.compositePK ? PKs[i] : PKs])), [entityName, `{${meta.primaryKeys.sort(asc).join(",")}}`].filter(notNull).join("|"), ]; return entityName == null ? res : [res]; } else { const newFilter = {}; const keys = []; if (Array.isArray(cur)) { // COMPOSITE PKs like [{owner: 1, recipient: 2}, {recipient: 4, owner: 3}] for (const key of meta.primaryKeys) { newFilter[key] = cur.map((el) => { if (el[key] == null) { throw new Error(`Invalid query, missing composite PK ${key}`); } return el[key]; }); keys.push(key); } return [newFilter, `{${keys.sort(asc).join(",")}}`]; } else { for (const [key, value] of Object.entries(cur)) { // Using $or at the top level means that we can treat it as two separate queries and filter results from either of them if (key === "$or" && entityName != null) { return value .map((el) => getNewFiltersAndMapKeys(el, meta, entityName)) .flat(); } const keyProp = meta.properties[key]; if (keyProp == null) { throw new Error(`Cannot find properties for ${key}`); } if (keyProp.targetMeta == null) { newFilter[key] = Array.isArray(value) ? value : [value]; keys.push(key); } else { const [subFilter, subKey] = getNewFiltersAndMapKeys(value, keyProp.targetMeta); newFilter[key] = subFilter; keys.push(`${key}:${subKey}`); } } const res = [ newFilter, [entityName, `{${keys.sort(asc).join(",")}}`].filter(notNull).join("|"), ]; return entityName == null ? res : [res]; } } } function updateQueryFilter([acc, accOptions], cur, options) { if (options?.populate != null && accOptions != null && accOptions.populate !== true) { if (Array.isArray(options.populate) && options.populate[0] === "*") { accOptions.populate = true; } else if (Array.isArray(options.populate)) { if (accOptions.populate == null) { accOptions.populate = new Set(options.populate); } else { for (const el of options.populate) { accOptions.populate.add(el); } } } } for (const [key, value] of Object.entries(acc)) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const curValue = cur[key]; if (Array.isArray(value)) { value.push(...curValue.reduce((acc, cur) => acc.concat(cur), [])); } else { updateQueryFilter([value], curValue); } } } function groupFindQueries(dataloaderFinds) { const queriesMap = new Map(); for (const dataloaderFind of dataloaderFinds) { const { entityName, meta, filter, options } = dataloaderFind; const filtersAndKeys = getNewFiltersAndMapKeys(filter, meta, entityName); dataloaderFind.filtersAndKeys = []; filtersAndKeys.forEach(([newFilter, key]) => { dataloaderFind.filtersAndKeys?.push({ key, newFilter }); let queryMap = queriesMap.get(key); if (queryMap == null) { queryMap = [structuredClone(newFilter), {}]; updateQueryFilter(queryMap, newFilter); queriesMap.set(key, queryMap); } else { updateQueryFilter(queryMap, newFilter, options); } }); } return queriesMap; } exports.groupFindQueries = groupFindQueries; function assertHasNewFilterAndMapKey(dataloaderFinds) { /* if (dataloaderFinds.some((el) => el.key == null || el.newFilter == null)) { throw new Error("Missing key or newFilter"); } */ } exports.assertHasNewFilterAndMapKey = assertHasNewFilterAndMapKey; function hasRef(entity) { return entity; } exports.hasRef = hasRef; function hasCol(entity) { return entity; } exports.hasCol = hasCol; function isRef(refOrCol) { return !(refOrCol instanceof core_1.Collection); } exports.isRef = isRef; function isCol(refOrCol) { return refOrCol instanceof core_1.Collection; } exports.isCol = isCol;