From 0dc2066cb135d419de85f469f5c37efaa736ce3e Mon Sep 17 00:00:00 2001 From: Santiago Persico Date: Tue, 21 Dec 2021 23:39:28 -0300 Subject: [PATCH 1/4] reduce computational complexity of serializer --- src/serializers/serializer.ts | 56 ++++++++++++++++------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/serializers/serializer.ts b/src/serializers/serializer.ts index 7097bc3..2a7edc2 100644 --- a/src/serializers/serializer.ts +++ b/src/serializers/serializer.ts @@ -49,11 +49,8 @@ export default class JsonApiSerializer implements IJsonApiSerializer { .reduce((relationAttributes, relName) => { const key = schemaRelationships[relName].foreignKeyName || this.relationshipToColumn(relName, primaryKey); const value = (op.data?.relationships[relName].data).id; - - return { - ...relationAttributes, - [key]: value, - }; + relationAttributes[key] = value; + return relationAttributes; }, op.data.attributes); return op; } @@ -77,23 +74,22 @@ export default class JsonApiSerializer implements IJsonApiSerializer { this.relationshipToColumn(relName, schemaRelationships[relName].type().schema.primaryKeyName), })); - data.relationships = relationshipsFound.reduce( - (relationships, relationship) => ({ - ...relationships, - [relationship.name]: { - id: data.attributes[relationship.key], - type: schemaRelationships[relationship.name].type().type, - }, - }), - Object.entries(data.relationships as EagerLoadedData).reduce( - (includedDirectRelationships, [relName, relData]: [string, ResourceRelationshipDescriptor]) => ({ - ...includedDirectRelationships, - [relName]: relData.direct, - }), - {}, - ), + const eagerlyLoadedRelationships = Object.entries(data.relationships as EagerLoadedData).reduce( + (includedDirectRelationships, [relName, relData]: [string, ResourceRelationshipDescriptor]) => { + includedDirectRelationships[relName] = relData.direct; + return includedDirectRelationships; + }, + {}, ); + data.relationships = relationshipsFound.reduce((relationships, relationship) => { + relationships[relationship.name] = { + id: data.attributes[relationship.key], + type: schemaRelationships[relationship.name].type().type, + }; + return relationships; + }, eagerlyLoadedRelationships); + Object.keys(data.relationships) .filter((relName) => data.relationships[relName] && schemaRelationships[relName]) .forEach((relName) => { @@ -122,11 +118,12 @@ export default class JsonApiSerializer implements IJsonApiSerializer { ), ); - data.attributes = Object.keys(data.attributes) - .map((attribute) => ({ + data.attributes = Object.assign( + {}, + ...Object.keys(data.attributes).map((attribute) => ({ [this.columnToAttribute(attribute)]: data.attributes[attribute], - })) - .reduce((keyValues, keyValue) => ({ ...keyValues, ...keyValue }), {}); + })), + ); return data; } @@ -167,7 +164,7 @@ export default class JsonApiSerializer implements IJsonApiSerializer { } const schemaRelationships = resourceType.schema.relationships; - const includedData: (Resource | undefined)[] = []; + let includedData: (Resource | undefined)[] = []; Object.keys(data.relationships) .filter((relationshipName) => data.relationships[relationshipName]) @@ -180,9 +177,8 @@ export default class JsonApiSerializer implements IJsonApiSerializer { const { direct: directResources = [], nested: nestedResources = [] } = resources; const relatedResourceClass = schemaRelationships[relationshipName].type(); const pkName = relatedResourceClass.schema.primaryKeyName || DEFAULT_PRIMARY_KEY; - - includedData.push( - ...directResources.map((resource) => { + includedData = includedData.concat( + directResources.map((resource) => { if (resource[pkName]) { return { ...this.serializeResource( @@ -205,8 +201,8 @@ export default class JsonApiSerializer implements IJsonApiSerializer { ); if (nestedResources) { - includedData.push( - ...Object.entries(nestedResources).map(([subRelationName, nestedRelationData]) => { + includedData = includedData.concat( + Object.entries(nestedResources).map(([subRelationName, nestedRelationData]) => { const subResourceClass = relatedResourceClass.schema.relationships[subRelationName].type(); const subPkName = subResourceClass.schema.primaryKeyName || DEFAULT_PRIMARY_KEY; return nestedRelationData.map((resource: Resource) => { From 09089ab7cb92329dbb58114648230561b8040d4f Mon Sep 17 00:00:00 2001 From: Santiago Persico Date: Tue, 21 Dec 2021 23:40:28 -0300 Subject: [PATCH 2/4] reduce computational complexity of processors --- src/processors/knex-processor.ts | 33 +++++++++++++-------------- src/processors/operation-processor.ts | 2 +- src/processors/session-processor.ts | 10 ++++---- src/processors/user-processor.ts | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/processors/knex-processor.ts b/src/processors/knex-processor.ts index 43fb741..fa22c80 100644 --- a/src/processors/knex-processor.ts +++ b/src/processors/knex-processor.ts @@ -64,15 +64,12 @@ const parseOperationIncludedRelationships = ( const nestedRelationships = includes .filter((include) => include.length > 1) - .reduce( - (acumRelationships, [nestedOrigin, nestedRelationshipName]) => ({ - ...acumRelationships, - [nestedOrigin]: { - [nestedRelationshipName]: relationships[nestedOrigin].type().schema.relationships[nestedRelationshipName], - }, - }), - {}, - ); + .reduce((acumRelationships, [nestedOrigin, nestedRelationshipName]) => { + acumRelationships[nestedOrigin] = { + [nestedRelationshipName]: relationships[nestedOrigin].type().schema.relationships[nestedRelationshipName], + }; + return acumRelationships; + }, {}); return { relationships, nestedRelationships }; }; @@ -197,11 +194,12 @@ export default class KnexProcessor extends Operation const primaryKey = this.resourceClass.schema.primaryKeyName || DEFAULT_PRIMARY_KEY; const filters = params ? { [primaryKey]: id, ...(params.filter || {}) } : { [primaryKey]: id }; - const dataToUpdate = Object.keys(data.attributes) - .map((attribute) => ({ + const dataToUpdate = Object.assign( + {}, + ...Object.keys(data.attributes).map((attribute) => ({ [this.appInstance.app.serializer.attributeToColumn(attribute)]: data.attributes[attribute], - })) - .reduce((keyValues, keyValue) => ({ ...keyValues, ...keyValue }), {}); + })), + ); const updated = await this.getQuery() .where((queryBuilder) => this.filtersToKnex(queryBuilder, filters)) @@ -222,11 +220,12 @@ export default class KnexProcessor extends Operation const primaryKeyName = this.resourceClass.schema.primaryKeyName || DEFAULT_PRIMARY_KEY; const data = op.data as Resource; - const dataToInsert = Object.keys(data.attributes) - .map((attribute) => ({ + const dataToInsert = Object.assign( + {}, + ...Object.keys(data.attributes).map((attribute) => ({ [this.appInstance.app.serializer.attributeToColumn(attribute)]: data.attributes[attribute], - })) - .reduce((keyValues, keyValue) => ({ ...keyValues, ...keyValue }), {}); + })), + ); if (data.id) { dataToInsert[primaryKeyName] = data.id; diff --git a/src/processors/operation-processor.ts b/src/processors/operation-processor.ts index 31a7eb9..dd7ba93 100644 --- a/src/processors/operation-processor.ts +++ b/src/processors/operation-processor.ts @@ -81,7 +81,7 @@ export default class OperationProcessor { } const value = directRelations[includedRelationResource]; const computed = await resourceProcessor.getComputedProperties(op, relationResourceClass, value, {}); - directRelations[includedRelationResource] = { ...value, ...computed }; + Object.assign(directRelations[includedRelationResource], computed); } return directRelations; } diff --git a/src/processors/session-processor.ts b/src/processors/session-processor.ts index 49aee1d..535b6cd 100644 --- a/src/processors/session-processor.ts +++ b/src/processors/session-processor.ts @@ -19,10 +19,12 @@ export default class SessionProcessor extends KnexProcessor { - const fields = Object.keys(op.data?.attributes as { [key: string]: Function }) - .filter((attribute) => this.resourceClass.schema.attributes[attribute] !== Password) - .map((attribute) => ({ [attribute]: op.data?.attributes[attribute] })) - .reduce((attributes, attribute) => ({ ...attributes, ...attribute }), {}); + const fields = Object.assign( + {}, + ...Object.keys(op.data?.attributes as { [key: string]: Function }) + .filter((attribute) => this.resourceClass.schema.attributes[attribute] !== Password) + .map((attribute) => ({ [attribute]: op.data?.attributes[attribute] })), + ); if (Object.keys(fields).length === 0) { throw jsonApiErrors.InvalidData(); diff --git a/src/processors/user-processor.ts b/src/processors/user-processor.ts index 9c534af..ae3f47c 100644 --- a/src/processors/user-processor.ts +++ b/src/processors/user-processor.ts @@ -1,5 +1,5 @@ import Authorize from "../decorators/authorize"; -import { Operation, HasId, DEFAULT_PRIMARY_KEY } from "../types"; +import { Operation, HasId } from "../types"; import User from "../resources/user"; import KnexProcessor from "./knex-processor"; import Password from "../attribute-types/password"; From 5b397bd7fb5ba760357c8302b31cf736242117eb Mon Sep 17 00:00:00 2001 From: Santiago Persico Date: Tue, 21 Dec 2021 23:40:39 -0300 Subject: [PATCH 3/4] reduce computational complexity of pick utility --- src/utils/pick.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/pick.ts b/src/utils/pick.ts index 992d57e..0459e4a 100644 --- a/src/utils/pick.ts +++ b/src/utils/pick.ts @@ -1,7 +1,9 @@ const pick = , T = Record>(object: R, list: string[] = []): T => { return list.reduce((acc, key) => { const hasProperty = key in object; - return hasProperty ? { ...acc, [key]: object[key] } : acc; + if (!hasProperty) return acc; + acc[key] = object[key]; + return acc; }, {}) as T; }; From 2a460c3119bda91794974108605a654ed1ff0562 Mon Sep 17 00:00:00 2001 From: Santiago Persico Date: Tue, 21 Dec 2021 23:46:28 -0300 Subject: [PATCH 4/4] missing removed spread reduce anti pattern removal --- src/processors/user-processor.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/processors/user-processor.ts b/src/processors/user-processor.ts index ae3f47c..3134d63 100644 --- a/src/processors/user-processor.ts +++ b/src/processors/user-processor.ts @@ -18,10 +18,12 @@ export default class UserProcessor extends KnexProcessor { } async add(op: Operation): Promise { - const fields = Object.keys(op.data?.attributes as { [key: string]: Function }) - .filter((attribute) => this.resourceClass.schema.attributes[attribute] !== Password) - .map((attribute) => ({ [attribute]: op.data?.attributes[attribute] })) - .reduce((attributes, attribute) => ({ ...attributes, ...attribute }), {}); + const fields = Object.assign( + {}, + ...Object.keys(op.data?.attributes as { [key: string]: Function }) + .filter((attribute) => this.resourceClass.schema.attributes[attribute] !== Password) + .map((attribute) => ({ [attribute]: op.data?.attributes[attribute] })), + ); const id = await this.generateId();