perf: reduce document data deepCopying in field hooks (#10610)

A lot of this deepCopying was just not necessary. This removes the
deepCopying from all field hook operations where I think it's 100% safe.
It does not remove all deepCopying, especially in areas where the input
data was deep copied, and that data pre-modification is then used after
the field hooks have run.

In these cases, further execution of the hook might be intentionally
expecting the unmodified version of that input data
This commit is contained in:
Alessio Gravili
2025-01-16 09:43:46 -07:00
committed by GitHub
parent fafe37e8b8
commit 116fd9919e
9 changed files with 20 additions and 23 deletions

View File

@@ -1,7 +1,6 @@
import type { DeepPartial } from 'ts-essentials'
import type { Args } from '../../../fields/hooks/beforeChange/index.js'
import type { AccessResult, CollectionSlug, FileToSave, SanitizedConfig } from '../../../index.js'
import type {
Payload,
PayloadRequest,
@@ -22,6 +21,13 @@ import { afterChange } from '../../../fields/hooks/afterChange/index.js'
import { afterRead } from '../../../fields/hooks/afterRead/index.js'
import { beforeChange } from '../../../fields/hooks/beforeChange/index.js'
import { beforeValidate } from '../../../fields/hooks/beforeValidate/index.js'
import {
type AccessResult,
type CollectionSlug,
deepCopyObjectSimple,
type FileToSave,
type SanitizedConfig,
} from '../../../index.js'
import { deleteAssociatedFiles } from '../../../uploads/deleteAssociatedFiles.js'
import { uploadFiles } from '../../../uploads/uploadFiles.js'
import { checkDocumentLockStatus } from '../../../utilities/checkDocumentLockStatus.js'
@@ -110,7 +116,7 @@ export const updateDocument = async <
collection: collectionConfig,
context: req.context,
depth: 0,
doc: docWithLocales,
doc: deepCopyObjectSimple(docWithLocales),
draft: draftArg,
fallbackLocale: id ? null : fallbackLocale,
global: null,

View File

@@ -9,6 +9,7 @@ import { Forbidden } from '../errors/Forbidden.js'
import { NotFound } from '../errors/NotFound.js'
import { afterRead } from '../fields/hooks/afterRead/index.js'
import { beforeDuplicate } from '../fields/hooks/beforeDuplicate/index.js'
import { deepCopyObjectSimple } from '../utilities/deepCopyObject.js'
import { getLatestCollectionVersion } from '../versions/getLatestCollectionVersion.js'
type GetDuplicateDocumentArgs = {
@@ -92,7 +93,7 @@ export const getDuplicateDocumentData = async ({
collection: collectionConfig,
context: req.context,
depth: 0,
doc: duplicatedFromDocWithLocales,
doc: deepCopyObjectSimple(duplicatedFromDocWithLocales),
draft: draftArg,
fallbackLocale: null,
global: null,

View File

@@ -3,7 +3,6 @@ import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
import type { RequestContext } from '../../../index.js'
import type { JsonObject, PayloadRequest } from '../../../types/index.js'
import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js'
import { traverseFields } from './traverseFields.js'
type Args<T extends JsonObject> = {
@@ -37,13 +36,11 @@ export const afterChange = async <T extends JsonObject>({
previousDoc,
req,
}: Args<T>): Promise<T> => {
const doc = deepCopyObjectSimple(incomingDoc)
await traverseFields({
collection,
context,
data,
doc,
doc: incomingDoc,
fields: collection?.fields || global?.fields,
global,
operation,
@@ -53,8 +50,8 @@ export const afterChange = async <T extends JsonObject>({
req,
schemaPath: [],
siblingData: data,
siblingDoc: doc,
siblingDoc: incomingDoc,
})
return doc
return incomingDoc
}

View File

@@ -56,7 +56,6 @@ export async function afterRead<T extends JsonObject>(args: Args<T>): Promise<T>
showHiddenFields,
} = args
const doc = deepCopyObjectSimple(incomingDoc)
const fieldPromises = []
const populationPromises = []
@@ -75,7 +74,7 @@ export async function afterRead<T extends JsonObject>(args: Args<T>): Promise<T>
context,
currentDepth,
depth,
doc,
doc: incomingDoc,
draft,
fallbackLocale,
fieldPromises,
@@ -93,11 +92,11 @@ export async function afterRead<T extends JsonObject>(args: Args<T>): Promise<T>
select,
selectMode: select ? getSelectMode(select) : undefined,
showHiddenFields,
siblingDoc: doc,
siblingDoc: incomingDoc,
})
await Promise.all(fieldPromises)
await Promise.all(populationPromises)
return doc
return incomingDoc
}

View File

@@ -28,20 +28,18 @@ export const beforeDuplicate = async <T extends JsonObject>({
overrideAccess,
req,
}: Args<T>): Promise<T> => {
const newDoc = deepCopyObjectSimple(doc)
await traverseFields({
id,
collection,
context,
doc: newDoc,
doc,
fields: collection?.fields,
overrideAccess,
path: [],
req,
schemaPath: [],
siblingDoc: newDoc,
siblingDoc: doc,
})
return newDoc
return doc
}

View File

@@ -39,7 +39,6 @@ export const beforeValidate = async <T extends JsonObject>({
req,
}: Args<T>): Promise<T> => {
const data = deepCopyObjectSimple(incomingData)
await traverseFields({
id,
collection,

View File

@@ -127,7 +127,7 @@ export const updateOperation = async <
collection: null,
context: req.context,
depth: 0,
doc: globalJSON,
doc: deepCopyObjectSimple(globalJSON),
draft: draftArg,
fallbackLocale,
global: globalConfig,

View File

@@ -98,7 +98,6 @@ const replaceWithDraftIfAvailable = async <T extends TypeWithID>({
return doc
}
draft = deepCopyObjectSimple(draft)
draft = sanitizeInternalFields(draft)
// Patch globalType onto version doc

View File

@@ -1,8 +1,6 @@
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
import type { Document, Payload, PayloadRequest, Where } from '../types/index.js'
import { docHasTimestamps } from '../types/index.js'
type Args = {
config: SanitizedGlobalConfig
locale?: string