node_modules ignore

This commit is contained in:
oier committed 2025-05-08 23:43:47 +02:00
1 parent e19d52f172
commit 4574544c9f
65041 files changed
+10593536

No files matched your search

@@ -0,0 +1,11 @@
import type { QueryBuilder } from '../../query-builder';
import type { Database } from '../../..';
type Context = {
db: Database;
qb: QueryBuilder;
uid: string;
};
type Row = Record<string, unknown>;
declare const applyPopulate: (results: Row[], populate: Record<string, any>, ctx: Context) => Promise<Row[] | undefined>;
export default applyPopulate;
//# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../../src/query/helpers/populate/apply.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AASzC,KAAK,OAAO,GAAG;IACb,EAAE,EAAE,QAAQ,CAAC;IACb,EAAE,EAAE,YAAY,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAoBF,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA4nBnC,QAAA,MAAM,aAAa,YAAmB,GAAG,EAAE,YAAY,OAAO,MAAM,EAAE,GAAG,CAAC,OAAO,OAAO,+BAgEvF,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,592 @@
'use strict';
var _ = require('lodash/fp');
var transform = require('../transform.js');
// We must select the join column id, however whatever it is named will overwrite an attribute of the same name
// Therefore, we will prefix with something unlikely to conflict with a user attribute
// TODO: ...and completely restrict the strapi_ prefix for an attribute name in the future
const joinColPrefix = '__strapi';
/**
* Populate oneToOne and manyToOne relation
* @param {*} input
* @param {*} ctx
* @returns
*/ const XtoOne = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db, qb } = ctx;
const fromTargetRow = (rowOrRows)=>transform.fromRow(targetMeta, rowOrRows);
if ('joinColumn' in attribute && attribute.joinColumn) {
const { name: joinColumnName, referencedColumn: referencedColumnName } = attribute.joinColumn;
const referencedValues = _.uniq(results.map((r)=>r[joinColumnName]).filter((value)=>!_.isNil(value)));
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = null;
});
return;
}
const rows = await db.entityManager.createQueryBuilder(targetMeta.uid).init(populateValue).addSelect(`${qb.alias}.${referencedColumnName}`).where({
[referencedColumnName]: referencedValues
}).execute({
mapResults: false
});
const map = _.groupBy(referencedColumnName)(rows);
results.forEach((result)=>{
result[attributeName] = fromTargetRow(_.first(map[result[joinColumnName]]));
});
return;
}
if ('joinTable' in attribute && attribute.joinTable) {
const { joinTable } = attribute;
const qb = db.entityManager.createQueryBuilder(targetMeta.uid);
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
const referencedValues = _.uniq(results.map((r)=>r[referencedColumnName]).filter((value)=>!_.isNil(value)));
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = {
count: 0
};
});
return;
}
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on
}).select([
joinColAlias,
qb.raw('count(*) AS count')
]).where({
[joinColAlias]: referencedValues
}).groupBy(joinColAlias).execute({
mapResults: false
});
const map = rows.reduce((map, row)=>{
map[row[joinColumnName]] = {
count: Number(row.count)
};
return map;
}, {});
results.forEach((result)=>{
result[attributeName] = map[result[referencedColumnName]] || {
count: 0
};
});
return;
}
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = null;
});
return;
}
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on,
orderBy: joinTable.orderBy
}).addSelect(joinColSelect).where({
[joinColAlias]: referencedValues
}).execute({
mapResults: false
});
const map = _.groupBy(joinColRenameAs)(rows);
results.forEach((result)=>{
result[attributeName] = fromTargetRow(_.first(map[result[referencedColumnName]]));
});
}
};
const oneToMany = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db, qb } = ctx;
const fromTargetRow = (rowOrRows)=>transform.fromRow(targetMeta, rowOrRows);
if ('joinColumn' in attribute && attribute.joinColumn) {
const { name: joinColumnName, referencedColumn: referencedColumnName, on } = attribute.joinColumn;
const referencedValues = _.uniq(results.map((r)=>r[joinColumnName]).filter((value)=>!_.isNil(value)));
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = null;
});
return;
}
const rows = await db.entityManager.createQueryBuilder(targetMeta.uid).init(populateValue).addSelect(`${qb.alias}.${referencedColumnName}`).where({
[referencedColumnName]: referencedValues,
...on && typeof on === 'function' ? on({
populateValue,
results
}) : {}
}).execute({
mapResults: false
});
const map = _.groupBy(referencedColumnName)(rows);
results.forEach((result)=>{
result[attributeName] = fromTargetRow(map[result[joinColumnName]] || []);
});
return;
}
if ('joinTable' in attribute && attribute.joinTable) {
const { joinTable } = attribute;
const qb = db.entityManager.createQueryBuilder(targetMeta.uid);
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
const referencedValues = _.uniq(results.map((r)=>r[referencedColumnName]).filter((value)=>!_.isNil(value)));
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = {
count: 0
};
});
return;
}
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on
}).select([
joinColSelect,
qb.raw('count(*) AS count')
]).where({
[joinColAlias]: referencedValues
}).groupBy(joinColAlias).execute({
mapResults: false
});
const map = rows.reduce((map, row)=>{
map[row[joinColRenameAs]] = {
count: Number(row.count)
};
return map;
}, {});
results.forEach((result)=>{
result[attributeName] = map[result[referencedColumnName]] || {
count: 0
};
});
return;
}
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = [];
});
return;
}
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on,
orderBy: _.mapValues((v)=>populateValue.ordering || v, joinTable.orderBy)
}).addSelect(joinColSelect).where({
[joinColAlias]: referencedValues
}).execute({
mapResults: false
});
const map = _.groupBy(joinColRenameAs)(rows);
results.forEach((r)=>{
r[attributeName] = fromTargetRow(map[r[referencedColumnName]] || []);
});
}
};
const manyToMany = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db } = ctx;
const fromTargetRow = (rowOrRows)=>transform.fromRow(targetMeta, rowOrRows);
const { joinTable } = attribute;
const populateQb = db.entityManager.createQueryBuilder(targetMeta.uid);
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
const alias = populateQb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
const referencedValues = _.uniq(results.map((r)=>r[referencedColumnName]).filter((value)=>!_.isNil(value)));
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = {
count: 0
};
});
return;
}
const rows = await populateQb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: populateQb.alias,
on: joinTable.on
}).select([
joinColAlias,
populateQb.raw('count(*) AS count')
]).where({
[joinColAlias]: referencedValues
}).groupBy(joinColAlias).execute({
mapResults: false
});
const map = rows.reduce((map, row)=>{
map[row[joinColumnName]] = {
count: Number(row.count)
};
return map;
}, {});
results.forEach((result)=>{
result[attributeName] = map[result[referencedColumnName]] || {
count: 0
};
});
return;
}
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = [];
});
return;
}
const rows = await populateQb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: populateQb.alias,
on: joinTable.on,
orderBy: _.mapValues((v)=>populateValue.ordering || v, joinTable.orderBy)
}).addSelect(joinColSelect).where({
[joinColAlias]: referencedValues
}).execute({
mapResults: false
});
const map = _.groupBy(joinColRenameAs)(rows);
results.forEach((result)=>{
result[attributeName] = fromTargetRow(map[result[referencedColumnName]] || []);
});
};
const morphX = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue, targetMeta } = input;
const { db, uid } = ctx;
const fromTargetRow = (rowOrRows)=>transform.fromRow(targetMeta, rowOrRows);
const { target, morphBy } = attribute;
const targetAttribute = db.metadata.get(target).attributes[morphBy];
if (targetAttribute.type === 'relation' && targetAttribute.relation === 'morphToOne') {
const { idColumn, typeColumn } = targetAttribute.morphColumn;
const referencedValues = _.uniq(results.map((r)=>r[idColumn.referencedColumn]).filter((value)=>!_.isNil(value)));
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = null;
});
return;
}
const rows = await db.entityManager.createQueryBuilder(target).init(populateValue)// .addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
.where({
[idColumn.name]: referencedValues,
[typeColumn.name]: uid
}).execute({
mapResults: false
});
const map = _.groupBy(idColumn.name)(rows);
results.forEach((result)=>{
const matchingRows = map[result[idColumn.referencedColumn]];
const matchingValue = attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows;
result[attributeName] = fromTargetRow(matchingValue);
});
} else if (targetAttribute.type === 'relation' && targetAttribute.relation === 'morphToMany') {
const { joinTable } = targetAttribute;
const { joinColumn, morphColumn } = joinTable;
const { idColumn, typeColumn } = morphColumn;
const referencedValues = _.uniq(results.map((r)=>r[idColumn.referencedColumn]).filter((value)=>!_.isNil(value)));
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = attribute.relation === 'morphOne' ? null : [];
});
return;
}
// find with join table
const qb = db.entityManager.createQueryBuilder(target);
const alias = qb.getAlias();
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinColumn.name,
rootColumn: joinColumn.referencedColumn,
rootTable: qb.alias,
on: {
...joinTable.on || {},
field: attributeName
},
orderBy: _.mapValues((v)=>populateValue.ordering || v, joinTable.orderBy)
}).addSelect([
`${alias}.${idColumn.name}`,
`${alias}.${typeColumn.name}`
]).where({
[`${alias}.${idColumn.name}`]: referencedValues,
[`${alias}.${typeColumn.name}`]: uid
}).execute({
mapResults: false
});
const map = _.groupBy(idColumn.name)(rows);
results.forEach((result)=>{
const matchingRows = map[result[idColumn.referencedColumn]];
const matchingValue = attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows;
result[attributeName] = fromTargetRow(matchingValue);
});
}
};
const morphToMany = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue } = input;
const { db } = ctx;
// find with join table
const { joinTable } = attribute;
const { joinColumn, morphColumn } = joinTable;
const { idColumn, typeColumn, typeField = '__type' } = morphColumn;
// fetch join table to create the ids map then do the same as morphToOne without the first
const referencedValues = _.uniq(results.map((r)=>r[joinColumn.referencedColumn]).filter((value)=>!_.isNil(value)));
const qb = db.entityManager.createQueryBuilder(joinTable.name);
const joinRows = await qb.where({
[joinColumn.name]: referencedValues,
...joinTable.on || {},
// If the populateValue contains an "on" property,
// only populate the types defined in it
...'on' in populateValue ? {
[morphColumn.typeColumn.name]: Object.keys(populateValue.on ?? {})
} : {}
}).orderBy([
joinColumn.name,
'order'
]).execute({
mapResults: false
});
const joinMap = _.groupBy(joinColumn.name, joinRows);
const idsByType = joinRows.reduce((acc, result)=>{
const idValue = result[morphColumn.idColumn.name];
const typeValue = result[morphColumn.typeColumn.name];
if (!idValue || !typeValue) {
return acc;
}
if (!_.has(typeValue, acc)) {
acc[typeValue] = [];
}
acc[typeValue].push(idValue);
return acc;
}, {});
const map = {};
const { on, ...typePopulate } = populateValue;
for (const type of Object.keys(idsByType)){
const ids = idsByType[type];
// type was removed but still in morph relation
if (!db.metadata.get(type)) {
map[type] = {};
continue;
}
const qb = db.entityManager.createQueryBuilder(type);
const rows = await qb.init(on?.[type] ?? typePopulate).addSelect(`${qb.alias}.${idColumn.referencedColumn}`).where({
[idColumn.referencedColumn]: ids
}).execute({
mapResults: false
});
map[type] = _.groupBy(idColumn.referencedColumn)(rows);
}
results.forEach((result)=>{
const joinResults = joinMap[result[joinColumn.referencedColumn]] || [];
const matchingRows = joinResults.flatMap((joinResult)=>{
const id = joinResult[idColumn.name];
const type = joinResult[typeColumn.name];
const targetMeta = db.metadata.get(type);
const fromTargetRow = (rowOrRows)=>transform.fromRow(targetMeta, rowOrRows);
return (map[type][id] || []).map((row)=>{
return {
[typeField]: type,
...fromTargetRow(row)
};
});
});
result[attributeName] = matchingRows;
});
};
const morphToOne = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue } = input;
const { db } = ctx;
const { morphColumn } = attribute;
const { idColumn, typeColumn } = morphColumn;
// make a map for each type what ids to return
// make a nested map per id
const idsByType = results.reduce((acc, result)=>{
const idValue = result[morphColumn.idColumn.name];
const typeValue = result[morphColumn.typeColumn.name];
if (!idValue || !typeValue) {
return acc;
}
if (!(typeValue in acc)) {
acc[typeValue] = [];
}
acc[typeValue].push(idValue);
return acc;
}, {});
const map = {};
const { on, ...typePopulate } = populateValue;
for (const type of Object.keys(idsByType)){
const ids = idsByType[type];
// type was removed but still in morph relation
if (!db.metadata.get(type)) {
map[type] = {};
return;
}
const qb = db.entityManager.createQueryBuilder(type);
const rows = await qb.init(on?.[type] ?? typePopulate).addSelect(`${qb.alias}.${idColumn.referencedColumn}`).where({
[idColumn.referencedColumn]: ids
}).execute({
mapResults: false
});
map[type] = _.groupBy(idColumn.referencedColumn)(rows);
}
results.forEach((result)=>{
const id = result[idColumn.name];
const type = result[typeColumn.name];
if (!type || !id) {
result[attributeName] = null;
return;
}
const matchingRows = map[type][id];
const fromTargetRow = (rowOrRows)=>transform.fromRow(db.metadata.get(type), rowOrRows);
result[attributeName] = fromTargetRow(_.first(matchingRows));
});
};
// TODO: Omit limit & offset to avoid needing a query per result to avoid making too many queries
const pickPopulateParams = (populate)=>{
const fieldsToPick = [
'select',
'count',
'where',
'populate',
'orderBy',
'filters',
'ordering',
'on'
];
if (populate.count !== true) {
fieldsToPick.push('limit', 'offset');
}
return _.pick(fieldsToPick, populate);
};
const applyPopulate = async (results, populate, ctx)=>{
const { db, uid, qb } = ctx;
const meta = db.metadata.get(uid);
if (_.isEmpty(results)) {
return results;
}
for (const attributeName of Object.keys(populate)){
const attribute = meta.attributes[attributeName];
if (attribute.type !== 'relation') {
throw new Error(`Invalid populate attribute ${attributeName}`);
}
const populateValue = {
filters: qb.state.filters,
...pickPopulateParams(populate[attributeName])
};
const isCount = 'count' in populateValue && populateValue.count === true;
switch(attribute.relation){
case 'oneToOne':
case 'manyToOne':
{
const targetMeta = db.metadata.get(attribute.target);
const input = {
attribute,
attributeName,
results,
populateValue,
targetMeta,
isCount
};
await XtoOne(input, ctx);
break;
}
case 'oneToMany':
{
const targetMeta = db.metadata.get(attribute.target);
const input = {
attribute,
attributeName,
results,
populateValue,
targetMeta,
isCount
};
await oneToMany(input, ctx);
break;
}
case 'manyToMany':
{
const targetMeta = db.metadata.get(attribute.target);
const input = {
attribute,
attributeName,
results,
populateValue,
targetMeta,
isCount
};
await manyToMany(input, ctx);
break;
}
case 'morphOne':
case 'morphMany':
{
const targetMeta = db.metadata.get(attribute.target);
const input = {
attribute,
attributeName,
results,
populateValue,
targetMeta,
isCount
};
await morphX(input, ctx);
break;
}
case 'morphToMany':
{
const input = {
attribute,
attributeName,
results,
populateValue,
isCount
};
await morphToMany(input, ctx);
break;
}
case 'morphToOne':
{
const input = {
attribute,
attributeName,
results,
populateValue,
isCount
};
await morphToOne(input, ctx);
break;
}
}
}
};
module.exports = applyPopulate;
//# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
{"version":3,"file":"apply.js","sources":["../../../../src/query/helpers/populate/apply.ts"],"sourcesContent":["import _ from 'lodash/fp';\n\nimport { fromRow } from '../transform';\nimport type { QueryBuilder } from '../../query-builder';\nimport type { Database } from '../../..';\nimport type { Meta } from '../../../metadata';\nimport { ID, RelationalAttribute, Relation } from '../../../types';\n\n// We must select the join column id, however whatever it is named will overwrite an attribute of the same name\n// Therefore, we will prefix with something unlikely to conflict with a user attribute\n// TODO: ...and completely restrict the strapi_ prefix for an attribute name in the future\nconst joinColPrefix = '__strapi' as const;\n\ntype Context = {\n db: Database;\n qb: QueryBuilder;\n uid: string;\n};\n\ntype Input<TRelationAttribute extends RelationalAttribute = RelationalAttribute> = {\n attribute: TRelationAttribute;\n attributeName: string;\n results: Row[];\n populateValue: {\n on?: Record<string, Record<string, unknown>>;\n } & Record<string, unknown>;\n\n isCount: boolean;\n};\n\ntype InputWithTarget<TRelationAttribute extends RelationalAttribute = RelationalAttribute> =\n Input<TRelationAttribute> & {\n targetMeta: Meta;\n };\n\ntype MorphIdMap = Record<string, Record<ID, Row[]>>;\n\ntype Row = Record<string, unknown>;\n\n/**\n * Populate oneToOne and manyToOne relation\n * @param {*} input\n * @param {*} ctx\n * @returns\n */\nconst XtoOne = async (\n input: InputWithTarget<Relation.OneToOne | Relation.ManyToOne>,\n ctx: Context\n) => {\n const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;\n const { db, qb } = ctx;\n\n const fromTargetRow = (rowOrRows: Row | Row[] | undefined) => fromRow(targetMeta, rowOrRows);\n\n if ('joinColumn' in attribute && attribute.joinColumn) {\n const { name: joinColumnName, referencedColumn: referencedColumnName } = attribute.joinColumn;\n\n const referencedValues = _.uniq(\n results.map((r) => r[joinColumnName]).filter((value) => !_.isNil(value))\n );\n\n if (_.isEmpty(referencedValues)) {\n results.forEach((result) => {\n result[attributeName] = null;\n });\n\n return;\n }\n\n const rows = await db.entityManager\n .createQueryBuilder(targetMeta.uid)\n .init(populateValue)\n .addSelect(`${qb.alias}.${referencedColumnName}`)\n .where({ [referencedColumnName]: referencedValues })\n .execute<Row[]>({ mapResults: false });\n\n const map = _.groupBy<Row[]>(referencedColumnName)(rows);\n\n results.forEach((result) => {\n result[attributeName] = fromTargetRow(_.first(map[result[joinColumnName] as string]));\n });\n\n return;\n }\n\n if ('joinTable' in attribute && attribute.joinTable) {\n const { joinTable } = attribute;\n\n const qb = db.entityManager.createQueryBuilder(targetMeta.uid);\n\n const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;\n\n const alias = qb.getAlias();\n const joinColAlias = `${alias}.${joinColumnName}`;\n const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;\n const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;\n\n const referencedValues = _.uniq(\n results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value))\n );\n\n if (isCount) {\n if (_.isEmpty(referencedValues)) {\n results.forEach((result) => {\n result[attributeName] = { count: 0 };\n });\n return;\n }\n\n const rows = await qb\n .init(populateValue)\n .join({\n alias,\n referencedTable: joinTable.name,\n referencedColumn: joinTable.inverseJoinColumn.name,\n rootColumn: joinTable.inverseJoinColumn.referencedColumn,\n rootTable: qb.alias,\n on: joinTable.on,\n })\n .select([joinColAlias, qb.raw('count(*) AS count')])\n .where({ [joinColAlias]: referencedValues })\n .groupBy(joinColAlias)\n .execute<Array<{ count: number } & { [key: string]: string }>>({ mapResults: false });\n\n const map = rows.reduce(\n (map, row) => {\n map[row[joinColumnName]] = { count: Number(row.count) };\n return map;\n },\n {} as Record<string, { count: number }>\n );\n\n results.forEach((result) => {\n result[attributeName] = map[result[referencedColumnName] as string] || { count: 0 };\n });\n\n return;\n }\n\n if (_.isEmpty(referencedValues)) {\n results.forEach((result) => {\n result[attributeName] = null;\n });\n\n return;\n }\n\n const rows = await qb\n .init(populateValue)\n .join({\n alias,\n referencedTable: joinTable.name,\n referencedColumn: joinTable.inverseJoinColumn.name,\n rootColumn: joinTable.inverseJoinColumn.referencedColumn,\n rootTable: qb.alias,\n Line truncated
@@ -0,0 +1,590 @@
import _ from 'lodash/fp';
import { fromRow } from '../transform.mjs';
// We must select the join column id, however whatever it is named will overwrite an attribute of the same name
// Therefore, we will prefix with something unlikely to conflict with a user attribute
// TODO: ...and completely restrict the strapi_ prefix for an attribute name in the future
const joinColPrefix = '__strapi';
/**
* Populate oneToOne and manyToOne relation
* @param {*} input
* @param {*} ctx
* @returns
*/ const XtoOne = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db, qb } = ctx;
const fromTargetRow = (rowOrRows)=>fromRow(targetMeta, rowOrRows);
if ('joinColumn' in attribute && attribute.joinColumn) {
const { name: joinColumnName, referencedColumn: referencedColumnName } = attribute.joinColumn;
const referencedValues = _.uniq(results.map((r)=>r[joinColumnName]).filter((value)=>!_.isNil(value)));
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = null;
});
return;
}
const rows = await db.entityManager.createQueryBuilder(targetMeta.uid).init(populateValue).addSelect(`${qb.alias}.${referencedColumnName}`).where({
[referencedColumnName]: referencedValues
}).execute({
mapResults: false
});
const map = _.groupBy(referencedColumnName)(rows);
results.forEach((result)=>{
result[attributeName] = fromTargetRow(_.first(map[result[joinColumnName]]));
});
return;
}
if ('joinTable' in attribute && attribute.joinTable) {
const { joinTable } = attribute;
const qb = db.entityManager.createQueryBuilder(targetMeta.uid);
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
const referencedValues = _.uniq(results.map((r)=>r[referencedColumnName]).filter((value)=>!_.isNil(value)));
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = {
count: 0
};
});
return;
}
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on
}).select([
joinColAlias,
qb.raw('count(*) AS count')
]).where({
[joinColAlias]: referencedValues
}).groupBy(joinColAlias).execute({
mapResults: false
});
const map = rows.reduce((map, row)=>{
map[row[joinColumnName]] = {
count: Number(row.count)
};
return map;
}, {});
results.forEach((result)=>{
result[attributeName] = map[result[referencedColumnName]] || {
count: 0
};
});
return;
}
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = null;
});
return;
}
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on,
orderBy: joinTable.orderBy
}).addSelect(joinColSelect).where({
[joinColAlias]: referencedValues
}).execute({
mapResults: false
});
const map = _.groupBy(joinColRenameAs)(rows);
results.forEach((result)=>{
result[attributeName] = fromTargetRow(_.first(map[result[referencedColumnName]]));
});
}
};
const oneToMany = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db, qb } = ctx;
const fromTargetRow = (rowOrRows)=>fromRow(targetMeta, rowOrRows);
if ('joinColumn' in attribute && attribute.joinColumn) {
const { name: joinColumnName, referencedColumn: referencedColumnName, on } = attribute.joinColumn;
const referencedValues = _.uniq(results.map((r)=>r[joinColumnName]).filter((value)=>!_.isNil(value)));
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = null;
});
return;
}
const rows = await db.entityManager.createQueryBuilder(targetMeta.uid).init(populateValue).addSelect(`${qb.alias}.${referencedColumnName}`).where({
[referencedColumnName]: referencedValues,
...on && typeof on === 'function' ? on({
populateValue,
results
}) : {}
}).execute({
mapResults: false
});
const map = _.groupBy(referencedColumnName)(rows);
results.forEach((result)=>{
result[attributeName] = fromTargetRow(map[result[joinColumnName]] || []);
});
return;
}
if ('joinTable' in attribute && attribute.joinTable) {
const { joinTable } = attribute;
const qb = db.entityManager.createQueryBuilder(targetMeta.uid);
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
const alias = qb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
const referencedValues = _.uniq(results.map((r)=>r[referencedColumnName]).filter((value)=>!_.isNil(value)));
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = {
count: 0
};
});
return;
}
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on
}).select([
joinColSelect,
qb.raw('count(*) AS count')
]).where({
[joinColAlias]: referencedValues
}).groupBy(joinColAlias).execute({
mapResults: false
});
const map = rows.reduce((map, row)=>{
map[row[joinColRenameAs]] = {
count: Number(row.count)
};
return map;
}, {});
results.forEach((result)=>{
result[attributeName] = map[result[referencedColumnName]] || {
count: 0
};
});
return;
}
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = [];
});
return;
}
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: qb.alias,
on: joinTable.on,
orderBy: _.mapValues((v)=>populateValue.ordering || v, joinTable.orderBy)
}).addSelect(joinColSelect).where({
[joinColAlias]: referencedValues
}).execute({
mapResults: false
});
const map = _.groupBy(joinColRenameAs)(rows);
results.forEach((r)=>{
r[attributeName] = fromTargetRow(map[r[referencedColumnName]] || []);
});
}
};
const manyToMany = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;
const { db } = ctx;
const fromTargetRow = (rowOrRows)=>fromRow(targetMeta, rowOrRows);
const { joinTable } = attribute;
const populateQb = db.entityManager.createQueryBuilder(targetMeta.uid);
const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;
const alias = populateQb.getAlias();
const joinColAlias = `${alias}.${joinColumnName}`;
const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;
const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;
const referencedValues = _.uniq(results.map((r)=>r[referencedColumnName]).filter((value)=>!_.isNil(value)));
if (isCount) {
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = {
count: 0
};
});
return;
}
const rows = await populateQb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: populateQb.alias,
on: joinTable.on
}).select([
joinColAlias,
populateQb.raw('count(*) AS count')
]).where({
[joinColAlias]: referencedValues
}).groupBy(joinColAlias).execute({
mapResults: false
});
const map = rows.reduce((map, row)=>{
map[row[joinColumnName]] = {
count: Number(row.count)
};
return map;
}, {});
results.forEach((result)=>{
result[attributeName] = map[result[referencedColumnName]] || {
count: 0
};
});
return;
}
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = [];
});
return;
}
const rows = await populateQb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinTable.inverseJoinColumn.name,
rootColumn: joinTable.inverseJoinColumn.referencedColumn,
rootTable: populateQb.alias,
on: joinTable.on,
orderBy: _.mapValues((v)=>populateValue.ordering || v, joinTable.orderBy)
}).addSelect(joinColSelect).where({
[joinColAlias]: referencedValues
}).execute({
mapResults: false
});
const map = _.groupBy(joinColRenameAs)(rows);
results.forEach((result)=>{
result[attributeName] = fromTargetRow(map[result[referencedColumnName]] || []);
});
};
const morphX = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue, targetMeta } = input;
const { db, uid } = ctx;
const fromTargetRow = (rowOrRows)=>fromRow(targetMeta, rowOrRows);
const { target, morphBy } = attribute;
const targetAttribute = db.metadata.get(target).attributes[morphBy];
if (targetAttribute.type === 'relation' && targetAttribute.relation === 'morphToOne') {
const { idColumn, typeColumn } = targetAttribute.morphColumn;
const referencedValues = _.uniq(results.map((r)=>r[idColumn.referencedColumn]).filter((value)=>!_.isNil(value)));
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = null;
});
return;
}
const rows = await db.entityManager.createQueryBuilder(target).init(populateValue)// .addSelect(`${qb.alias}.${idColumn.referencedColumn}`)
.where({
[idColumn.name]: referencedValues,
[typeColumn.name]: uid
}).execute({
mapResults: false
});
const map = _.groupBy(idColumn.name)(rows);
results.forEach((result)=>{
const matchingRows = map[result[idColumn.referencedColumn]];
const matchingValue = attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows;
result[attributeName] = fromTargetRow(matchingValue);
});
} else if (targetAttribute.type === 'relation' && targetAttribute.relation === 'morphToMany') {
const { joinTable } = targetAttribute;
const { joinColumn, morphColumn } = joinTable;
const { idColumn, typeColumn } = morphColumn;
const referencedValues = _.uniq(results.map((r)=>r[idColumn.referencedColumn]).filter((value)=>!_.isNil(value)));
if (_.isEmpty(referencedValues)) {
results.forEach((result)=>{
result[attributeName] = attribute.relation === 'morphOne' ? null : [];
});
return;
}
// find with join table
const qb = db.entityManager.createQueryBuilder(target);
const alias = qb.getAlias();
const rows = await qb.init(populateValue).join({
alias,
referencedTable: joinTable.name,
referencedColumn: joinColumn.name,
rootColumn: joinColumn.referencedColumn,
rootTable: qb.alias,
on: {
...joinTable.on || {},
field: attributeName
},
orderBy: _.mapValues((v)=>populateValue.ordering || v, joinTable.orderBy)
}).addSelect([
`${alias}.${idColumn.name}`,
`${alias}.${typeColumn.name}`
]).where({
[`${alias}.${idColumn.name}`]: referencedValues,
[`${alias}.${typeColumn.name}`]: uid
}).execute({
mapResults: false
});
const map = _.groupBy(idColumn.name)(rows);
results.forEach((result)=>{
const matchingRows = map[result[idColumn.referencedColumn]];
const matchingValue = attribute.relation === 'morphOne' ? _.first(matchingRows) : matchingRows;
result[attributeName] = fromTargetRow(matchingValue);
});
}
};
const morphToMany = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue } = input;
const { db } = ctx;
// find with join table
const { joinTable } = attribute;
const { joinColumn, morphColumn } = joinTable;
const { idColumn, typeColumn, typeField = '__type' } = morphColumn;
// fetch join table to create the ids map then do the same as morphToOne without the first
const referencedValues = _.uniq(results.map((r)=>r[joinColumn.referencedColumn]).filter((value)=>!_.isNil(value)));
const qb = db.entityManager.createQueryBuilder(joinTable.name);
const joinRows = await qb.where({
[joinColumn.name]: referencedValues,
...joinTable.on || {},
// If the populateValue contains an "on" property,
// only populate the types defined in it
...'on' in populateValue ? {
[morphColumn.typeColumn.name]: Object.keys(populateValue.on ?? {})
} : {}
}).orderBy([
joinColumn.name,
'order'
]).execute({
mapResults: false
});
const joinMap = _.groupBy(joinColumn.name, joinRows);
const idsByType = joinRows.reduce((acc, result)=>{
const idValue = result[morphColumn.idColumn.name];
const typeValue = result[morphColumn.typeColumn.name];
if (!idValue || !typeValue) {
return acc;
}
if (!_.has(typeValue, acc)) {
acc[typeValue] = [];
}
acc[typeValue].push(idValue);
return acc;
}, {});
const map = {};
const { on, ...typePopulate } = populateValue;
for (const type of Object.keys(idsByType)){
const ids = idsByType[type];
// type was removed but still in morph relation
if (!db.metadata.get(type)) {
map[type] = {};
continue;
}
const qb = db.entityManager.createQueryBuilder(type);
const rows = await qb.init(on?.[type] ?? typePopulate).addSelect(`${qb.alias}.${idColumn.referencedColumn}`).where({
[idColumn.referencedColumn]: ids
}).execute({
mapResults: false
});
map[type] = _.groupBy(idColumn.referencedColumn)(rows);
}
results.forEach((result)=>{
const joinResults = joinMap[result[joinColumn.referencedColumn]] || [];
const matchingRows = joinResults.flatMap((joinResult)=>{
const id = joinResult[idColumn.name];
const type = joinResult[typeColumn.name];
const targetMeta = db.metadata.get(type);
const fromTargetRow = (rowOrRows)=>fromRow(targetMeta, rowOrRows);
return (map[type][id] || []).map((row)=>{
return {
[typeField]: type,
...fromTargetRow(row)
};
});
});
result[attributeName] = matchingRows;
});
};
const morphToOne = async (input, ctx)=>{
const { attribute, attributeName, results, populateValue } = input;
const { db } = ctx;
const { morphColumn } = attribute;
const { idColumn, typeColumn } = morphColumn;
// make a map for each type what ids to return
// make a nested map per id
const idsByType = results.reduce((acc, result)=>{
const idValue = result[morphColumn.idColumn.name];
const typeValue = result[morphColumn.typeColumn.name];
if (!idValue || !typeValue) {
return acc;
}
if (!(typeValue in acc)) {
acc[typeValue] = [];
}
acc[typeValue].push(idValue);
return acc;
}, {});
const map = {};
const { on, ...typePopulate } = populateValue;
for (const type of Object.keys(idsByType)){
const ids = idsByType[type];
// type was removed but still in morph relation
if (!db.metadata.get(type)) {
map[type] = {};
return;
}
const qb = db.entityManager.createQueryBuilder(type);
const rows = await qb.init(on?.[type] ?? typePopulate).addSelect(`${qb.alias}.${idColumn.referencedColumn}`).where({
[idColumn.referencedColumn]: ids
}).execute({
mapResults: false
});
map[type] = _.groupBy(idColumn.referencedColumn)(rows);
}
results.forEach((result)=>{
const id = result[idColumn.name];
const type = result[typeColumn.name];
if (!type || !id) {
result[attributeName] = null;
return;
}
const matchingRows = map[type][id];
const fromTargetRow = (rowOrRows)=>fromRow(db.metadata.get(type), rowOrRows);
result[attributeName] = fromTargetRow(_.first(matchingRows));
});
};
// TODO: Omit limit & offset to avoid needing a query per result to avoid making too many queries
const pickPopulateParams = (populate)=>{
const fieldsToPick = [
'select',
'count',
'where',
'populate',
'orderBy',
'filters',
'ordering',
'on'
];
if (populate.count !== true) {
fieldsToPick.push('limit', 'offset');
}
return _.pick(fieldsToPick, populate);
};
const applyPopulate = async (results, populate, ctx)=>{
const { db, uid, qb } = ctx;
const meta = db.metadata.get(uid);
if (_.isEmpty(results)) {
return results;
}
for (const attributeName of Object.keys(populate)){
const attribute = meta.attributes[attributeName];
if (attribute.type !== 'relation') {
throw new Error(`Invalid populate attribute ${attributeName}`);
}
const populateValue = {
filters: qb.state.filters,
...pickPopulateParams(populate[attributeName])
};
const isCount = 'count' in populateValue && populateValue.count === true;
switch(attribute.relation){
case 'oneToOne':
case 'manyToOne':
{
const targetMeta = db.metadata.get(attribute.target);
const input = {
attribute,
attributeName,
results,
populateValue,
targetMeta,
isCount
};
await XtoOne(input, ctx);
break;
}
case 'oneToMany':
{
const targetMeta = db.metadata.get(attribute.target);
const input = {
attribute,
attributeName,
results,
populateValue,
targetMeta,
isCount
};
await oneToMany(input, ctx);
break;
}
case 'manyToMany':
{
const targetMeta = db.metadata.get(attribute.target);
const input = {
attribute,
attributeName,
results,
populateValue,
targetMeta,
isCount
};
await manyToMany(input, ctx);
break;
}
case 'morphOne':
case 'morphMany':
{
const targetMeta = db.metadata.get(attribute.target);
const input = {
attribute,
attributeName,
results,
populateValue,
targetMeta,
isCount
};
await morphX(input, ctx);
break;
}
case 'morphToMany':
{
const input = {
attribute,
attributeName,
results,
populateValue,
isCount
};
await morphToMany(input, ctx);
break;
}
case 'morphToOne':
{
const input = {
attribute,
attributeName,
results,
populateValue,
isCount
};
await morphToOne(input, ctx);
break;
}
}
}
};
export { applyPopulate as default };
//# sourceMappingURL=apply.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"apply.mjs","sources":["../../../../src/query/helpers/populate/apply.ts"],"sourcesContent":["import _ from 'lodash/fp';\n\nimport { fromRow } from '../transform';\nimport type { QueryBuilder } from '../../query-builder';\nimport type { Database } from '../../..';\nimport type { Meta } from '../../../metadata';\nimport { ID, RelationalAttribute, Relation } from '../../../types';\n\n// We must select the join column id, however whatever it is named will overwrite an attribute of the same name\n// Therefore, we will prefix with something unlikely to conflict with a user attribute\n// TODO: ...and completely restrict the strapi_ prefix for an attribute name in the future\nconst joinColPrefix = '__strapi' as const;\n\ntype Context = {\n db: Database;\n qb: QueryBuilder;\n uid: string;\n};\n\ntype Input<TRelationAttribute extends RelationalAttribute = RelationalAttribute> = {\n attribute: TRelationAttribute;\n attributeName: string;\n results: Row[];\n populateValue: {\n on?: Record<string, Record<string, unknown>>;\n } & Record<string, unknown>;\n\n isCount: boolean;\n};\n\ntype InputWithTarget<TRelationAttribute extends RelationalAttribute = RelationalAttribute> =\n Input<TRelationAttribute> & {\n targetMeta: Meta;\n };\n\ntype MorphIdMap = Record<string, Record<ID, Row[]>>;\n\ntype Row = Record<string, unknown>;\n\n/**\n * Populate oneToOne and manyToOne relation\n * @param {*} input\n * @param {*} ctx\n * @returns\n */\nconst XtoOne = async (\n input: InputWithTarget<Relation.OneToOne | Relation.ManyToOne>,\n ctx: Context\n) => {\n const { attribute, attributeName, results, populateValue, targetMeta, isCount } = input;\n const { db, qb } = ctx;\n\n const fromTargetRow = (rowOrRows: Row | Row[] | undefined) => fromRow(targetMeta, rowOrRows);\n\n if ('joinColumn' in attribute && attribute.joinColumn) {\n const { name: joinColumnName, referencedColumn: referencedColumnName } = attribute.joinColumn;\n\n const referencedValues = _.uniq(\n results.map((r) => r[joinColumnName]).filter((value) => !_.isNil(value))\n );\n\n if (_.isEmpty(referencedValues)) {\n results.forEach((result) => {\n result[attributeName] = null;\n });\n\n return;\n }\n\n const rows = await db.entityManager\n .createQueryBuilder(targetMeta.uid)\n .init(populateValue)\n .addSelect(`${qb.alias}.${referencedColumnName}`)\n .where({ [referencedColumnName]: referencedValues })\n .execute<Row[]>({ mapResults: false });\n\n const map = _.groupBy<Row[]>(referencedColumnName)(rows);\n\n results.forEach((result) => {\n result[attributeName] = fromTargetRow(_.first(map[result[joinColumnName] as string]));\n });\n\n return;\n }\n\n if ('joinTable' in attribute && attribute.joinTable) {\n const { joinTable } = attribute;\n\n const qb = db.entityManager.createQueryBuilder(targetMeta.uid);\n\n const { name: joinColumnName, referencedColumn: referencedColumnName } = joinTable.joinColumn;\n\n const alias = qb.getAlias();\n const joinColAlias = `${alias}.${joinColumnName}`;\n const joinColRenameAs = `${joinColPrefix}${joinColumnName}`;\n const joinColSelect = `${joinColAlias} as ${joinColRenameAs}`;\n\n const referencedValues = _.uniq(\n results.map((r) => r[referencedColumnName]).filter((value) => !_.isNil(value))\n );\n\n if (isCount) {\n if (_.isEmpty(referencedValues)) {\n results.forEach((result) => {\n result[attributeName] = { count: 0 };\n });\n return;\n }\n\n const rows = await qb\n .init(populateValue)\n .join({\n alias,\n referencedTable: joinTable.name,\n referencedColumn: joinTable.inverseJoinColumn.name,\n rootColumn: joinTable.inverseJoinColumn.referencedColumn,\n rootTable: qb.alias,\n on: joinTable.on,\n })\n .select([joinColAlias, qb.raw('count(*) AS count')])\n .where({ [joinColAlias]: referencedValues })\n .groupBy(joinColAlias)\n .execute<Array<{ count: number } & { [key: string]: string }>>({ mapResults: false });\n\n const map = rows.reduce(\n (map, row) => {\n map[row[joinColumnName]] = { count: Number(row.count) };\n return map;\n },\n {} as Record<string, { count: number }>\n );\n\n results.forEach((result) => {\n result[attributeName] = map[result[referencedColumnName] as string] || { count: 0 };\n });\n\n return;\n }\n\n if (_.isEmpty(referencedValues)) {\n results.forEach((result) => {\n result[attributeName] = null;\n });\n\n return;\n }\n\n const rows = await qb\n .init(populateValue)\n .join({\n alias,\n referencedTable: joinTable.name,\n referencedColumn: joinTable.inverseJoinColumn.name,\n rootColumn: joinTable.inverseJoinColumn.referencedColumn,\n rootTable: qb.alias,\nLine truncated
@@ -0,0 +1,4 @@
import applyPopulate from './apply';
import processPopulate from './process';
export { applyPopulate, processPopulate };
//# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/query/helpers/populate/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,SAAS,CAAC;AACpC,OAAO,eAAe,MAAM,WAAW,CAAC;AAExC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,24 @@
import type { QueryBuilder } from '../../query-builder';
import type { Database } from '../../..';
type Context = {
qb: QueryBuilder;
db: Database;
uid: string;
};
type PopulateMap = {
[key: string]: true | {
populate?: PopulateMap | true | string[];
};
};
/**
* Converts and prepares the query for populate
*
* @param {boolean|string[]|object} populate populate param
* @param {object} ctx query context
* @param {object} ctx.db database instance
* @param {object} ctx.qb query builder instance
* @param {string} ctx.uid model uid
*/
declare const processPopulate: (populate: unknown, ctx: Context) => PopulateMap | null;
export default processPopulate;
//# sourceMappingURL=process.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../../../src/query/helpers/populate/process.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAezC,KAAK,OAAO,GAAG;IACb,EAAE,EAAE,YAAY,CAAC;IACjB,EAAE,EAAE,QAAQ,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,CAAC,GAAG,EAAE,MAAM,GACR,IAAI,GACJ;QACE,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,MAAM,EAAE,CAAC;KAC1C,CAAC;CACP,CAAC;AAEF;;;;;;;;GAQG;AACH,QAAA,MAAM,eAAe,aAAc,OAAO,OAAO,OAAO,uBAwEvD,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -0,0 +1,92 @@
'use strict';
var _ = require('lodash/fp');
var types = require('../../../utils/types.js');
const getRootLevelPopulate = (meta)=>{
const populate = {};
for (const attributeName of Object.keys(meta.attributes)){
const attribute = meta.attributes[attributeName];
if (attribute.type === 'relation') {
populate[attributeName] = true;
}
}
return populate;
};
/**
* Converts and prepares the query for populate
*
* @param {boolean|string[]|object} populate populate param
* @param {object} ctx query context
* @param {object} ctx.db database instance
* @param {object} ctx.qb query builder instance
* @param {string} ctx.uid model uid
*/ const processPopulate = (populate, ctx)=>{
const { qb, db, uid } = ctx;
const meta = db.metadata.get(uid);
let populateMap = {};
if (populate === false || _.isNil(populate)) {
return null;
}
if (populate === true) {
populateMap = getRootLevelPopulate(meta);
} else if (Array.isArray(populate)) {
for (const key of populate){
const [root, ...rest] = key.split('.');
if (rest.length > 0) {
const subPopulate = rest.join('.');
if (populateMap[root]) {
const populateValue = populateMap[root];
if (populateValue === true) {
populateMap[root] = {
populate: [
subPopulate
]
};
} else {
populateValue.populate = [
subPopulate
].concat(populateValue.populate ?? []);
}
} else {
populateMap[root] = {
populate: [
subPopulate
]
};
}
} else {
populateMap[root] = populateMap[root] ? populateMap[root] : true;
}
}
} else {
populateMap = populate;
}
if (!_.isPlainObject(populateMap)) {
throw new Error('Populate must be an object');
}
const finalPopulate = {};
for (const key of Object.keys(populateMap)){
const attribute = meta.attributes[key];
if (!attribute) {
continue;
}
if (!types.isRelation(attribute.type)) {
continue;
}
// Make sure to query the join column value if needed,
// so that we can apply the populate later on
if ('joinColumn' in attribute && attribute.joinColumn) {
qb.addSelect(attribute.joinColumn.name);
}
// Make sure id is present for future populate queries
if (_.has('id', meta.attributes)) {
qb.addSelect('id');
}
finalPopulate[key] = populateMap[key];
}
return finalPopulate;
};
module.exports = processPopulate;
//# sourceMappingURL=process.js.map
@@ -0,0 +1 @@
{"version":3,"file":"process.js","sources":["../../../../src/query/helpers/populate/process.ts"],"sourcesContent":["import _ from 'lodash/fp';\n\nimport * as types from '../../../utils/types';\nimport type { Meta } from '../../../metadata';\nimport type { QueryBuilder } from '../../query-builder';\nimport type { Database } from '../../..';\n\nconst getRootLevelPopulate = (meta: Meta) => {\n const populate: PopulateMap = {};\n\n for (const attributeName of Object.keys(meta.attributes)) {\n const attribute = meta.attributes[attributeName];\n if (attribute.type === 'relation') {\n populate[attributeName] = true;\n }\n }\n\n return populate;\n};\n\ntype Context = {\n qb: QueryBuilder;\n db: Database;\n uid: string;\n};\n\ntype PopulateMap = {\n [key: string]:\n | true\n | {\n populate?: PopulateMap | true | string[];\n };\n};\n\n/**\n * Converts and prepares the query for populate\n *\n * @param {boolean|string[]|object} populate populate param\n * @param {object} ctx query context\n * @param {object} ctx.db database instance\n * @param {object} ctx.qb query builder instance\n * @param {string} ctx.uid model uid\n */\nconst processPopulate = (populate: unknown, ctx: Context) => {\n const { qb, db, uid } = ctx;\n const meta = db.metadata.get(uid);\n\n let populateMap: PopulateMap = {};\n\n if (populate === false || _.isNil(populate)) {\n return null;\n }\n\n if (populate === true) {\n populateMap = getRootLevelPopulate(meta);\n } else if (Array.isArray(populate)) {\n for (const key of populate) {\n const [root, ...rest] = key.split('.');\n\n if (rest.length > 0) {\n const subPopulate = rest.join('.');\n if (populateMap[root]) {\n const populateValue = populateMap[root];\n\n if (populateValue === true) {\n populateMap[root] = {\n populate: [subPopulate],\n };\n } else {\n populateValue.populate = [subPopulate].concat(populateValue.populate ?? []);\n }\n } else {\n populateMap[root] = {\n populate: [subPopulate],\n };\n }\n } else {\n populateMap[root] = populateMap[root] ? populateMap[root] : true;\n }\n }\n } else {\n populateMap = populate as PopulateMap;\n }\n\n if (!_.isPlainObject(populateMap)) {\n throw new Error('Populate must be an object');\n }\n\n const finalPopulate: PopulateMap = {};\n for (const key of Object.keys(populateMap)) {\n const attribute = meta.attributes[key];\n\n if (!attribute) {\n continue;\n }\n\n if (!types.isRelation(attribute.type)) {\n continue;\n }\n\n // Make sure to query the join column value if needed,\n // so that we can apply the populate later on\n if ('joinColumn' in attribute && attribute.joinColumn) {\n qb.addSelect(attribute.joinColumn.name);\n }\n\n // Make sure id is present for future populate queries\n if (_.has('id', meta.attributes)) {\n qb.addSelect('id');\n }\n\n finalPopulate[key] = populateMap[key];\n }\n\n return finalPopulate;\n};\n\nexport default processPopulate;\n"],"names":["getRootLevelPopulate","meta","populate","attributeName","Object","keys","attributes","attribute","type","processPopulate","ctx","qb","db","uid","metadata","get","populateMap","_","isNil","Array","isArray","key","root","rest","split","length","subPopulate","join","populateValue","concat","isPlainObject","Error","finalPopulate","types","joinColumn","addSelect","name","has"],"mappings":";;;;;AAOA,MAAMA,uBAAuB,CAACC,IAAAA,GAAAA;AAC5B,IAAA,MAAMC,WAAwB,EAAC;AAE/B,IAAA,KAAK,MAAMC,aAAiBC,IAAAA,MAAAA,CAAOC,IAAI,CAACJ,IAAAA,CAAKK,UAAU,CAAG,CAAA;AACxD,QAAA,MAAMC,SAAYN,GAAAA,IAAAA,CAAKK,UAAU,CAACH,aAAc,CAAA;QAChD,IAAII,SAAAA,CAAUC,IAAI,KAAK,UAAY,EAAA;YACjCN,QAAQ,CAACC,cAAc,GAAG,IAAA;AAC5B;AACF;IAEA,OAAOD,QAAAA;AACT,CAAA;AAgBA;;;;;;;;IASA,MAAMO,eAAkB,GAAA,CAACP,QAAmBQ,EAAAA,GAAAA,GAAAA;AAC1C,IAAA,MAAM,EAAEC,EAAE,EAAEC,EAAE,EAAEC,GAAG,EAAE,GAAGH,GAAAA;AACxB,IAAA,MAAMT,IAAOW,GAAAA,EAAAA,CAAGE,QAAQ,CAACC,GAAG,CAACF,GAAAA,CAAAA;AAE7B,IAAA,IAAIG,cAA2B,EAAC;AAEhC,IAAA,IAAId,QAAa,KAAA,KAAA,IAASe,CAAEC,CAAAA,KAAK,CAAChB,QAAW,CAAA,EAAA;QAC3C,OAAO,IAAA;AACT;AAEA,IAAA,IAAIA,aAAa,IAAM,EAAA;AACrBc,QAAAA,WAAAA,GAAchB,oBAAqBC,CAAAA,IAAAA,CAAAA;AACrC,KAAA,MAAO,IAAIkB,KAAAA,CAAMC,OAAO,CAAClB,QAAW,CAAA,EAAA;QAClC,KAAK,MAAMmB,OAAOnB,QAAU,CAAA;AAC1B,YAAA,MAAM,CAACoB,IAAM,EAAA,GAAGC,KAAK,GAAGF,GAAAA,CAAIG,KAAK,CAAC,GAAA,CAAA;YAElC,IAAID,IAAAA,CAAKE,MAAM,GAAG,CAAG,EAAA;gBACnB,MAAMC,WAAAA,GAAcH,IAAKI,CAAAA,IAAI,CAAC,GAAA,CAAA;gBAC9B,IAAIX,WAAW,CAACM,IAAAA,CAAK,EAAE;oBACrB,MAAMM,aAAAA,GAAgBZ,WAAW,CAACM,IAAK,CAAA;AAEvC,oBAAA,IAAIM,kBAAkB,IAAM,EAAA;wBAC1BZ,WAAW,CAACM,KAAK,GAAG;4BAClBpB,QAAU,EAAA;AAACwB,gCAAAA;AAAY;AACzB,yBAAA;qBACK,MAAA;AACLE,wBAAAA,aAAAA,CAAc1B,QAAQ,GAAG;AAACwB,4BAAAA;AAAY,yBAAA,CAACG,MAAM,CAACD,aAAc1B,CAAAA,QAAQ,IAAI,EAAE,CAAA;AAC5E;iBACK,MAAA;oBACLc,WAAW,CAACM,KAAK,GAAG;wBAClBpB,QAAU,EAAA;AAACwB,4BAAAA;AAAY;ALine truncated
@@ -0,0 +1,90 @@
import _ from 'lodash/fp';
import { isRelation } from '../../../utils/types.mjs';
const getRootLevelPopulate = (meta)=>{
const populate = {};
for (const attributeName of Object.keys(meta.attributes)){
const attribute = meta.attributes[attributeName];
if (attribute.type === 'relation') {
populate[attributeName] = true;
}
}
return populate;
};
/**
* Converts and prepares the query for populate
*
* @param {boolean|string[]|object} populate populate param
* @param {object} ctx query context
* @param {object} ctx.db database instance
* @param {object} ctx.qb query builder instance
* @param {string} ctx.uid model uid
*/ const processPopulate = (populate, ctx)=>{
const { qb, db, uid } = ctx;
const meta = db.metadata.get(uid);
let populateMap = {};
if (populate === false || _.isNil(populate)) {
return null;
}
if (populate === true) {
populateMap = getRootLevelPopulate(meta);
} else if (Array.isArray(populate)) {
for (const key of populate){
const [root, ...rest] = key.split('.');
if (rest.length > 0) {
const subPopulate = rest.join('.');
if (populateMap[root]) {
const populateValue = populateMap[root];
if (populateValue === true) {
populateMap[root] = {
populate: [
subPopulate
]
};
} else {
populateValue.populate = [
subPopulate
].concat(populateValue.populate ?? []);
}
} else {
populateMap[root] = {
populate: [
subPopulate
]
};
}
} else {
populateMap[root] = populateMap[root] ? populateMap[root] : true;
}
}
} else {
populateMap = populate;
}
if (!_.isPlainObject(populateMap)) {
throw new Error('Populate must be an object');
}
const finalPopulate = {};
for (const key of Object.keys(populateMap)){
const attribute = meta.attributes[key];
if (!attribute) {
continue;
}
if (!isRelation(attribute.type)) {
continue;
}
// Make sure to query the join column value if needed,
// so that we can apply the populate later on
if ('joinColumn' in attribute && attribute.joinColumn) {
qb.addSelect(attribute.joinColumn.name);
}
// Make sure id is present for future populate queries
if (_.has('id', meta.attributes)) {
qb.addSelect('id');
}
finalPopulate[key] = populateMap[key];
}
return finalPopulate;
};
export { processPopulate as default };
//# sourceMappingURL=process.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"process.mjs","sources":["../../../../src/query/helpers/populate/process.ts"],"sourcesContent":["import _ from 'lodash/fp';\n\nimport * as types from '../../../utils/types';\nimport type { Meta } from '../../../metadata';\nimport type { QueryBuilder } from '../../query-builder';\nimport type { Database } from '../../..';\n\nconst getRootLevelPopulate = (meta: Meta) => {\n const populate: PopulateMap = {};\n\n for (const attributeName of Object.keys(meta.attributes)) {\n const attribute = meta.attributes[attributeName];\n if (attribute.type === 'relation') {\n populate[attributeName] = true;\n }\n }\n\n return populate;\n};\n\ntype Context = {\n qb: QueryBuilder;\n db: Database;\n uid: string;\n};\n\ntype PopulateMap = {\n [key: string]:\n | true\n | {\n populate?: PopulateMap | true | string[];\n };\n};\n\n/**\n * Converts and prepares the query for populate\n *\n * @param {boolean|string[]|object} populate populate param\n * @param {object} ctx query context\n * @param {object} ctx.db database instance\n * @param {object} ctx.qb query builder instance\n * @param {string} ctx.uid model uid\n */\nconst processPopulate = (populate: unknown, ctx: Context) => {\n const { qb, db, uid } = ctx;\n const meta = db.metadata.get(uid);\n\n let populateMap: PopulateMap = {};\n\n if (populate === false || _.isNil(populate)) {\n return null;\n }\n\n if (populate === true) {\n populateMap = getRootLevelPopulate(meta);\n } else if (Array.isArray(populate)) {\n for (const key of populate) {\n const [root, ...rest] = key.split('.');\n\n if (rest.length > 0) {\n const subPopulate = rest.join('.');\n if (populateMap[root]) {\n const populateValue = populateMap[root];\n\n if (populateValue === true) {\n populateMap[root] = {\n populate: [subPopulate],\n };\n } else {\n populateValue.populate = [subPopulate].concat(populateValue.populate ?? []);\n }\n } else {\n populateMap[root] = {\n populate: [subPopulate],\n };\n }\n } else {\n populateMap[root] = populateMap[root] ? populateMap[root] : true;\n }\n }\n } else {\n populateMap = populate as PopulateMap;\n }\n\n if (!_.isPlainObject(populateMap)) {\n throw new Error('Populate must be an object');\n }\n\n const finalPopulate: PopulateMap = {};\n for (const key of Object.keys(populateMap)) {\n const attribute = meta.attributes[key];\n\n if (!attribute) {\n continue;\n }\n\n if (!types.isRelation(attribute.type)) {\n continue;\n }\n\n // Make sure to query the join column value if needed,\n // so that we can apply the populate later on\n if ('joinColumn' in attribute && attribute.joinColumn) {\n qb.addSelect(attribute.joinColumn.name);\n }\n\n // Make sure id is present for future populate queries\n if (_.has('id', meta.attributes)) {\n qb.addSelect('id');\n }\n\n finalPopulate[key] = populateMap[key];\n }\n\n return finalPopulate;\n};\n\nexport default processPopulate;\n"],"names":["getRootLevelPopulate","meta","populate","attributeName","Object","keys","attributes","attribute","type","processPopulate","ctx","qb","db","uid","metadata","get","populateMap","_","isNil","Array","isArray","key","root","rest","split","length","subPopulate","join","populateValue","concat","isPlainObject","Error","finalPopulate","types","joinColumn","addSelect","name","has"],"mappings":";;;AAOA,MAAMA,uBAAuB,CAACC,IAAAA,GAAAA;AAC5B,IAAA,MAAMC,WAAwB,EAAC;AAE/B,IAAA,KAAK,MAAMC,aAAiBC,IAAAA,MAAAA,CAAOC,IAAI,CAACJ,IAAAA,CAAKK,UAAU,CAAG,CAAA;AACxD,QAAA,MAAMC,SAAYN,GAAAA,IAAAA,CAAKK,UAAU,CAACH,aAAc,CAAA;QAChD,IAAII,SAAAA,CAAUC,IAAI,KAAK,UAAY,EAAA;YACjCN,QAAQ,CAACC,cAAc,GAAG,IAAA;AAC5B;AACF;IAEA,OAAOD,QAAAA;AACT,CAAA;AAgBA;;;;;;;;IASA,MAAMO,eAAkB,GAAA,CAACP,QAAmBQ,EAAAA,GAAAA,GAAAA;AAC1C,IAAA,MAAM,EAAEC,EAAE,EAAEC,EAAE,EAAEC,GAAG,EAAE,GAAGH,GAAAA;AACxB,IAAA,MAAMT,IAAOW,GAAAA,EAAAA,CAAGE,QAAQ,CAACC,GAAG,CAACF,GAAAA,CAAAA;AAE7B,IAAA,IAAIG,cAA2B,EAAC;AAEhC,IAAA,IAAId,QAAa,KAAA,KAAA,IAASe,CAAEC,CAAAA,KAAK,CAAChB,QAAW,CAAA,EAAA;QAC3C,OAAO,IAAA;AACT;AAEA,IAAA,IAAIA,aAAa,IAAM,EAAA;AACrBc,QAAAA,WAAAA,GAAchB,oBAAqBC,CAAAA,IAAAA,CAAAA;AACrC,KAAA,MAAO,IAAIkB,KAAAA,CAAMC,OAAO,CAAClB,QAAW,CAAA,EAAA;QAClC,KAAK,MAAMmB,OAAOnB,QAAU,CAAA;AAC1B,YAAA,MAAM,CAACoB,IAAM,EAAA,GAAGC,KAAK,GAAGF,GAAAA,CAAIG,KAAK,CAAC,GAAA,CAAA;YAElC,IAAID,IAAAA,CAAKE,MAAM,GAAG,CAAG,EAAA;gBACnB,MAAMC,WAAAA,GAAcH,IAAKI,CAAAA,IAAI,CAAC,GAAA,CAAA;gBAC9B,IAAIX,WAAW,CAACM,IAAAA,CAAK,EAAE;oBACrB,MAAMM,aAAAA,GAAgBZ,WAAW,CAACM,IAAK,CAAA;AAEvC,oBAAA,IAAIM,kBAAkB,IAAM,EAAA;wBAC1BZ,WAAW,CAACM,KAAK,GAAG;4BAClBpB,QAAU,EAAA;AAACwB,gCAAAA;AAAY;AACzB,yBAAA;qBACK,MAAA;AACLE,wBAAAA,aAAAA,CAAc1B,QAAQ,GAAG;AAACwB,4BAAAA;AAAY,yBAAA,CAACG,MAAM,CAACD,aAAc1B,CAAAA,QAAQ,IAAI,EAAE,CAAA;AAC5E;iBACK,MAAA;oBACLc,WAAW,CAACM,KAAK,GAAG;wBAClBpB,QAAU,EAAA;AAACwB,4BAAAA;AAAY;AALine truncated