Skip to content

Commit c45fbb9

Browse files
authored
feat!: 700% faster deepCopyObject, refactor deep merging and deep copying, type improvements (#7272)
**BREAKING:** - The `deepMerge` exported from payload now handles more complex data and is slower. The old, simple deepMerge is now exported as `deepMergeSimple` - `combineMerge` is no longer exported. You can use `deepMergeWithCombinedArrays` instead - The behavior of the exported `deepCopyObject` and `isPlainObject` may be different and more reliable, as the underlying algorithm has changed
1 parent 2c16c60 commit c45fbb9

File tree

82 files changed

+591
-536
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+591
-536
lines changed

packages/db-mongodb/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
},
3535
"dependencies": {
3636
"bson-objectid": "2.0.4",
37-
"deepmerge": "4.3.1",
3837
"http-status": "1.6.2",
3938
"mongoose": "6.12.3",
4039
"mongoose-paginate-v2": "1.7.22",

packages/db-mongodb/src/queries/parseParams.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type { FilterQuery } from 'mongoose'
22
import type { Field, Operator, Payload, Where } from 'payload'
33

4-
import deepmerge from 'deepmerge'
5-
import { combineMerge } from 'payload'
4+
import { deepMergeWithCombinedArrays } from 'payload'
65
import { validOperators } from 'payload/shared'
76

87
import { buildAndOrConditions } from './buildAndOrConditions.js'
@@ -70,7 +69,7 @@ export async function parseParams({
7069
[searchParam.path]: searchParam.value,
7170
}
7271
} else if (typeof searchParam?.value === 'object') {
73-
result = deepmerge(result, searchParam.value, { arrayMerge: combineMerge })
72+
result = deepMergeWithCombinedArrays(result, searchParam.value)
7473
}
7574
}
7675
}

packages/payload/src/admin/RichText.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { SanitizedCollectionConfig, TypeWithID } from '../collections/confi
66
import type { SanitizedConfig } from '../config/types.js'
77
import type { Field, FieldAffectingData, RichTextField, Validate } from '../fields/config/types.js'
88
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
9-
import type { PayloadRequest, RequestContext } from '../types/index.js'
9+
import type { JsonObject, PayloadRequest, RequestContext } from '../types/index.js'
1010
import type { WithServerSidePropsComponentProps } from './elements/WithServerSideProps.js'
1111

1212
export type RichTextFieldProps<Value extends object, AdapterProps, ExtraFieldProperties = {}> = {
@@ -82,7 +82,7 @@ export type BeforeChangeRichTextHookArgs<
8282
/**
8383
* The original data with locales (not modified by any hooks). Only available in `beforeChange` and `beforeDuplicate` field hooks.
8484
*/
85-
docWithLocales?: Record<string, unknown>
85+
docWithLocales?: JsonObject
8686

8787
duplicate?: boolean
8888

@@ -98,7 +98,7 @@ export type BeforeChangeRichTextHookArgs<
9898
/**
9999
* The original siblingData with locales (not modified by any hooks).
100100
*/
101-
siblingDocWithLocales?: Record<string, unknown>
101+
siblingDocWithLocales?: JsonObject
102102

103103
skipValidation?: boolean
104104
}
@@ -216,7 +216,7 @@ type RichTextAdapterBase<
216216
populationPromises: Promise<void>[]
217217
req: PayloadRequest
218218
showHiddenFields: boolean
219-
siblingDoc: Record<string, unknown>
219+
siblingDoc: JsonObject
220220
}) => void
221221
hooks?: RichTextHooks
222222
i18n?: Partial<GenericLanguages>

packages/payload/src/auth/operations/resetPassword.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const resetPasswordOperation = async (args: Arguments): Promise<Result> =
5959
collection: collectionConfig.slug,
6060
req,
6161
where: {
62-
resetPasswordExpiration: { greater_than: new Date() },
62+
resetPasswordExpiration: { greater_than: new Date().toISOString() },
6363
resetPasswordToken: { equals: data.token },
6464
},
6565
})

packages/payload/src/auth/strategies/local/incrementLoginAttempts.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { SanitizedCollectionConfig, TypeWithID } from '../../../collections/config/types.js'
2-
import type { Payload } from '../../../index.js'
2+
import type { JsonObject, Payload } from '../../../index.js'
33
import type { PayloadRequest } from '../../../types/index.js'
44

55
type Args = {
@@ -39,13 +39,13 @@ export const incrementLoginAttempts = async ({
3939
return
4040
}
4141

42-
const data: Record<string, unknown> = {
42+
const data: JsonObject = {
4343
loginAttempts: Number(doc.loginAttempts) + 1,
4444
}
4545

4646
// Lock the account if at max attempts and not already locked
4747
if (typeof doc.loginAttempts === 'number' && doc.loginAttempts + 1 >= maxLoginAttempts) {
48-
const lockUntil = new Date(Date.now() + lockTime)
48+
const lockUntil = new Date(Date.now() + lockTime).toISOString()
4949
data.lockUntil = lockUntil
5050
}
5151

packages/payload/src/auth/strategies/local/register.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
2-
import type { Payload } from '../../../index.js'
2+
import type { JsonObject, Payload } from '../../../index.js'
33
import type { PayloadRequest } from '../../../types/index.js'
44

55
import { ValidationError } from '../../../errors/index.js'
66
import { generatePasswordSaltHash } from './generatePasswordSaltHash.js'
77

88
type Args = {
99
collection: SanitizedCollectionConfig
10-
doc: Record<string, unknown>
10+
doc: JsonObject
1111
password: string
1212
payload: Payload
1313
req: PayloadRequest

packages/payload/src/collections/config/defaults.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import type { LoginWithUsernameOptions } from '../../auth/types.js'
1+
import type { IncomingAuthType, LoginWithUsernameOptions } from '../../auth/types.js'
2+
import type { CollectionConfig } from './types.js'
23

34
import defaultAccess from '../../auth/defaultAccess.js'
45

5-
export const defaults = {
6+
export const defaults: Partial<CollectionConfig> = {
67
access: {
78
create: defaultAccess,
89
delete: defaultAccess,
@@ -49,7 +50,7 @@ export const defaults = {
4950
versions: false,
5051
}
5152

52-
export const authDefaults = {
53+
export const authDefaults: IncomingAuthType = {
5354
cookies: {
5455
sameSite: 'Lax',
5556
secure: false,

packages/payload/src/collections/config/sanitize.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import merge from 'deepmerge'
2-
31
import type { Config, SanitizedConfig } from '../../config/types.js'
42
import type { CollectionConfig, SanitizedCollectionConfig } from './types.js'
53

@@ -9,8 +7,8 @@ import { sanitizeFields } from '../../fields/config/sanitize.js'
97
import { fieldAffectsData } from '../../fields/config/types.js'
108
import mergeBaseFields from '../../fields/mergeBaseFields.js'
119
import { getBaseUploadFields } from '../../uploads/getBaseFields.js'
10+
import { deepMergeWithReactComponents } from '../../utilities/deepMerge.js'
1211
import { formatLabels } from '../../utilities/formatLabels.js'
13-
import { isPlainObject } from '../../utilities/isPlainObject.js'
1412
import baseVersionFields from '../../versions/baseFields.js'
1513
import { versionDefaults } from '../../versions/defaults.js'
1614
import { authDefaults, defaults, loginWithUsernameDefaults } from './defaults.js'
@@ -29,9 +27,7 @@ export const sanitizeCollection = async (
2927
// Make copy of collection config
3028
// /////////////////////////////////
3129

32-
const sanitized: CollectionConfig = merge(defaults, collection, {
33-
isMergeableObject: isPlainObject,
34-
})
30+
const sanitized: CollectionConfig = deepMergeWithReactComponents(defaults, collection)
3531

3632
// /////////////////////////////////
3733
// Sanitize fields
@@ -141,9 +137,10 @@ export const sanitizeCollection = async (
141137
// sanitize fields for reserved names
142138
sanitizeAuthFields(sanitized.fields, sanitized)
143139

144-
sanitized.auth = merge(authDefaults, typeof sanitized.auth === 'object' ? sanitized.auth : {}, {
145-
isMergeableObject: isPlainObject,
146-
})
140+
sanitized.auth = deepMergeWithReactComponents(
141+
authDefaults,
142+
typeof sanitized.auth === 'object' ? sanitized.auth : {},
143+
)
147144

148145
if (!sanitized.auth.disableLocalStrategy && sanitized.auth.verify === true) {
149146
sanitized.auth.verify = {}
@@ -157,12 +154,12 @@ export const sanitizeCollection = async (
157154
}
158155

159156
sanitized.auth.loginWithUsername = sanitized.auth.loginWithUsername
160-
? merge(
161-
loginWithUsernameDefaults,
162-
typeof sanitized.auth.loginWithUsername === 'boolean'
157+
? {
158+
...loginWithUsernameDefaults,
159+
...(typeof sanitized.auth.loginWithUsername === 'boolean'
163160
? {}
164-
: sanitized.auth.loginWithUsername,
165-
)
161+
: sanitized.auth.loginWithUsername),
162+
}
166163
: false
167164

168165
sanitized.fields = mergeBaseFields(sanitized.fields, getBaseAuthFields(sanitized.auth))

packages/payload/src/collections/config/types.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ import type {
2828
} from '../../config/types.js'
2929
import type { DBIdentifierName } from '../../database/types.js'
3030
import type { Field } from '../../fields/config/types.js'
31-
import type { CollectionSlug, TypedAuthOperations, TypedCollection } from '../../index.js'
31+
import type {
32+
CollectionSlug,
33+
JsonObject,
34+
TypedAuthOperations,
35+
TypedCollection,
36+
} from '../../index.js'
3237
import type { PayloadRequest, RequestContext } from '../../types/index.js'
3338
import type { SanitizedUploadConfig, UploadConfig } from '../../uploads/types.js'
3439
import type {
@@ -41,7 +46,7 @@ export type DataFromCollectionSlug<TSlug extends CollectionSlug> = TypedCollecti
4146
export type AuthOperationsFromCollectionSlug<TSlug extends CollectionSlug> =
4247
TypedAuthOperations[TSlug]
4348

44-
export type RequiredDataFromCollection<TData extends Record<string, any>> = MarkOptional<
49+
export type RequiredDataFromCollection<TData extends JsonObject> = MarkOptional<
4550
TData,
4651
'createdAt' | 'id' | 'sizes' | 'updatedAt'
4752
>

packages/payload/src/collections/dataloader.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { BatchLoadFn } from 'dataloader'
22

33
import DataLoader from 'dataloader'
44

5-
import type { PayloadRequest } from '../types/index.js'
5+
import type { JsonValue, PayloadRequest } from '../types/index.js'
66
import type { TypeWithID } from './config/types.js'
77

88
import { isValidID } from '../utilities/isValidID.js'
@@ -119,7 +119,7 @@ const batchAndLoadDocs =
119119
showHiddenFields: Boolean(showHiddenFields),
120120
where: {
121121
id: {
122-
in: ids,
122+
in: ids as JsonValue,
123123
},
124124
},
125125
})

0 commit comments

Comments
 (0)