mikro-orm-find-dataloader
Version:
Additional dataloaders for the MikroORM EntityManager find/findOne/etc methods.
221 lines (219 loc) • 9.68 kB
JavaScript
;
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;