Perf optimizations (#16117)

Co-authored-by: Marc-Roig <marc12info@gmail.com>
This commit is contained in:
Alexandre BODIN 2023-03-21 18:37:21 +01:00 committed by GitHub
parent 011967acca
commit 942e646d28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 106 additions and 59 deletions

View File

@ -22,7 +22,6 @@ const createCollectionTypeController = ({ contentType }) => {
const sanitizedQuery = await this.sanitizeQuery(ctx);
const { results, pagination } = await strapi.service(uid).find(sanitizedQuery);
const sanitizedResults = await this.sanitizeOutput(results, ctx);
return this.transformResponse(sanitizedResults, { pagination });
},

View File

@ -18,7 +18,7 @@ const createController = ({ contentType }) => {
return transformResponse(data, meta, { contentType });
},
sanitizeOutput(data, ctx) {
async sanitizeOutput(data, ctx) {
const auth = getAuthFromKoaContext(ctx);
return sanitize.contentAPI.output(data, contentType, { auth });

View File

@ -6,9 +6,8 @@ const { curry, curryN } = require('lodash/fp');
function pipeAsync(...methods) {
return async (data) => {
let res = data;
for (const method of methods) {
res = await method(res);
for (let i = 0; i < methods.length; i += 1) {
res = await methods[i](res);
}
return res;

View File

@ -37,12 +37,16 @@ const createContentAPISanitizers = () => {
return pipeAsync(...transforms)(data);
};
const sanitizeOuput = (data, schema, { auth } = {}) => {
const sanitizeOutput = async (data, schema, { auth } = {}) => {
if (isArray(data)) {
return Promise.all(data.map((entry) => sanitizeOuput(entry, schema, { auth })));
const res = new Array(data.length);
for (let i = 0; i < data.length; i += 1) {
res[i] = await sanitizeOutput(data[i], schema, { auth });
}
return res;
}
const transforms = [sanitizers.defaultSanitizeOutput(schema)];
const transforms = [(data) => sanitizers.defaultSanitizeOutput(schema, data)];
if (auth) {
transforms.push(traverseEntity(visitors.removeRestrictedRelations(auth), { schema }));
@ -122,7 +126,7 @@ const createContentAPISanitizers = () => {
return {
input: sanitizeInput,
output: sanitizeOuput,
output: sanitizeOutput,
query: sanitizeQuery,
filters: sanitizeFilters,
sort: sanitizeSort,

View File

@ -20,17 +20,20 @@ const {
removeMorphToRelations,
} = require('./visitors');
const sanitizePasswords = curry((schema, entity) => {
const sanitizePasswords = (schema) => async (entity) => {
return traverseEntity(removePassword, { schema }, entity);
});
};
const sanitizePrivates = curry((schema, entity) => {
return traverseEntity(removePrivate, { schema }, entity);
});
const defaultSanitizeOutput = curry((schema, entity) => {
return pipeAsync(sanitizePrivates(schema), sanitizePasswords(schema))(entity);
});
const defaultSanitizeOutput = async (schema, entity) => {
return traverseEntity(
(...args) => {
removePassword(...args);
removePrivate(...args);
},
{ schema },
entity
);
};
const defaultSanitizeFilters = curry((schema, filters) => {
return pipeAsync(
@ -140,7 +143,6 @@ const defaultSanitizePopulate = curry((schema, populate) => {
module.exports = {
sanitizePasswords,
sanitizePrivates,
defaultSanitizeOutput,
defaultSanitizeFilters,
defaultSanitizeSort,

View File

@ -1,6 +1,42 @@
'use strict';
const { cloneDeep, isObject, isArray, isNil, curry } = require('lodash/fp');
const { clone, isObject, isArray, isNil, curry } = require('lodash/fp');
const traverseMorphRelationTarget = async (visitor, path, entry) => {
const targetSchema = strapi.getModel(entry.__type);
const traverseOptions = { schema: targetSchema, path };
return traverseEntity(visitor, traverseOptions, entry);
};
const traverseRelationTarget = (schema) => async (visitor, path, entry) => {
const traverseOptions = { schema, path };
return traverseEntity(visitor, traverseOptions, entry);
};
const traverseMediaTarget = async (visitor, path, entry) => {
const targetSchemaUID = 'plugin::upload.file';
const targetSchema = strapi.getModel(targetSchemaUID);
const traverseOptions = { schema: targetSchema, path };
return traverseEntity(visitor, traverseOptions, entry);
};
const traverseComponent = async (visitor, path, schema, entry) => {
const traverseOptions = { schema, path };
return traverseEntity(visitor, traverseOptions, entry);
};
const visitDynamicZoneEntry = async (visitor, path, entry) => {
const targetSchema = strapi.getModel(entry.__component);
const traverseOptions = { schema: targetSchema, path };
return traverseEntity(visitor, traverseOptions, entry);
};
const traverseEntity = async (visitor, options, entity) => {
const { path = { raw: null, attribute: null }, schema } = options;
@ -11,9 +47,13 @@ const traverseEntity = async (visitor, options, entity) => {
}
// Don't mutate the original entity object
const copy = cloneDeep(entity);
// only clone at 1st level as the next level will get clone when traversed
const copy = clone(entity);
const visitorUtils = createVisitorUtils({ data: copy });
for (const key of Object.keys(copy)) {
const keys = Object.keys(copy);
for (let i = 0; i < keys.length; i += 1) {
const key = keys[i];
// Retrieve the attribute definition associated to the key from the schema
const attribute = schema.attributes[key];
@ -32,7 +72,6 @@ const traverseEntity = async (visitor, options, entity) => {
// Visit the current attribute
const visitorOptions = { data: copy, schema, key, value: copy[key], attribute, path: newPath };
const visitorUtils = createVisitorUtils({ data: copy });
await visitor(visitorOptions, visitorUtils);
@ -52,58 +91,62 @@ const traverseEntity = async (visitor, options, entity) => {
if (isRelation) {
const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');
const traverseTarget = (entry) => {
// Handle polymorphic relationships
const targetSchemaUID = isMorphRelation ? entry.__type : attribute.target;
const targetSchema = strapi.getModel(targetSchemaUID);
const method = isMorphRelation
? traverseMorphRelationTarget
: traverseRelationTarget(strapi.getModel(attribute.target));
const traverseOptions = { schema: targetSchema, path: newPath };
if (isArray(value)) {
const res = new Array(value.length);
for (let i = 0; i < value.length; i += 1) {
res[i] = await method(visitor, newPath, value[i]);
}
copy[key] = res;
} else {
copy[key] = await method(visitor, newPath, value);
}
return traverseEntity(visitor, traverseOptions, entry);
};
// need to update copy
copy[key] = isArray(value)
? await Promise.all(value.map(traverseTarget))
: await traverseTarget(value);
continue;
}
if (isMedia) {
const traverseTarget = (entry) => {
const targetSchemaUID = 'plugin::upload.file';
const targetSchema = strapi.getModel(targetSchemaUID);
const traverseOptions = { schema: targetSchema, path: newPath };
return traverseEntity(visitor, traverseOptions, entry);
};
// need to update copy
copy[key] = isArray(value)
? await Promise.all(value.map(traverseTarget))
: await traverseTarget(value);
if (isArray(value)) {
const res = new Array(value.length);
for (let i = 0; i < value.length; i += 1) {
res[i] = await traverseMediaTarget(visitor, newPath, value[i]);
}
copy[key] = res;
} else {
copy[key] = await traverseMediaTarget(visitor, newPath, value);
}
continue;
}
if (isComponent) {
const targetSchema = strapi.getModel(attribute.component);
const traverseOptions = { schema: targetSchema, path: newPath };
const traverseComponent = (entry) => traverseEntity(visitor, traverseOptions, entry);
if (isArray(value)) {
const res = new Array(value.length);
for (let i = 0; i < value.length; i += 1) {
res[i] = await traverseComponent(visitor, newPath, targetSchema, value[i]);
}
copy[key] = res;
} else {
copy[key] = await traverseComponent(visitor, newPath, targetSchema, value);
}
copy[key] = isArray(value)
? await Promise.all(value.map(traverseComponent))
: await traverseComponent(value);
continue;
}
if (isDynamicZone && isArray(value)) {
const visitDynamicZoneEntry = (entry) => {
const targetSchema = strapi.getModel(entry.__component);
const traverseOptions = { schema: targetSchema, path: newPath };
const res = new Array(value.length);
for (let i = 0; i < value.length; i += 1) {
res[i] = await visitDynamicZoneEntry(visitor, newPath, value[i]);
}
copy[key] = res;
return traverseEntity(visitor, traverseOptions, entry);
};
copy[key] = await Promise.all(value.map(visitDynamicZoneEntry));
continue;
}
}