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
This commit is contained in:
@@ -34,7 +34,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bson-objectid": "2.0.4",
|
||||
"deepmerge": "4.3.1",
|
||||
"http-status": "1.6.2",
|
||||
"mongoose": "6.12.3",
|
||||
"mongoose-paginate-v2": "1.7.22",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { FilterQuery } from 'mongoose'
|
||||
import type { Field, Operator, Payload, Where } from 'payload'
|
||||
|
||||
import deepmerge from 'deepmerge'
|
||||
import { combineMerge } from 'payload'
|
||||
import { deepMergeWithCombinedArrays } from 'payload'
|
||||
import { validOperators } from 'payload/shared'
|
||||
|
||||
import { buildAndOrConditions } from './buildAndOrConditions.js'
|
||||
@@ -70,7 +69,7 @@ export async function parseParams({
|
||||
[searchParam.path]: searchParam.value,
|
||||
}
|
||||
} else if (typeof searchParam?.value === 'object') {
|
||||
result = deepmerge(result, searchParam.value, { arrayMerge: combineMerge })
|
||||
result = deepMergeWithCombinedArrays(result, searchParam.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { SanitizedCollectionConfig, TypeWithID } from '../collections/confi
|
||||
import type { SanitizedConfig } from '../config/types.js'
|
||||
import type { Field, FieldAffectingData, RichTextField, Validate } from '../fields/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../types/index.js'
|
||||
import type { WithServerSidePropsComponentProps } from './elements/WithServerSideProps.js'
|
||||
|
||||
export type RichTextFieldProps<Value extends object, AdapterProps, ExtraFieldProperties = {}> = {
|
||||
@@ -82,7 +82,7 @@ export type BeforeChangeRichTextHookArgs<
|
||||
/**
|
||||
* The original data with locales (not modified by any hooks). Only available in `beforeChange` and `beforeDuplicate` field hooks.
|
||||
*/
|
||||
docWithLocales?: Record<string, unknown>
|
||||
docWithLocales?: JsonObject
|
||||
|
||||
duplicate?: boolean
|
||||
|
||||
@@ -98,7 +98,7 @@ export type BeforeChangeRichTextHookArgs<
|
||||
/**
|
||||
* The original siblingData with locales (not modified by any hooks).
|
||||
*/
|
||||
siblingDocWithLocales?: Record<string, unknown>
|
||||
siblingDocWithLocales?: JsonObject
|
||||
|
||||
skipValidation?: boolean
|
||||
}
|
||||
@@ -216,7 +216,7 @@ type RichTextAdapterBase<
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDoc: JsonObject
|
||||
}) => void
|
||||
hooks?: RichTextHooks
|
||||
i18n?: Partial<GenericLanguages>
|
||||
|
||||
@@ -59,7 +59,7 @@ export const resetPasswordOperation = async (args: Arguments): Promise<Result> =
|
||||
collection: collectionConfig.slug,
|
||||
req,
|
||||
where: {
|
||||
resetPasswordExpiration: { greater_than: new Date() },
|
||||
resetPasswordExpiration: { greater_than: new Date().toISOString() },
|
||||
resetPasswordToken: { equals: data.token },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SanitizedCollectionConfig, TypeWithID } from '../../../collections/config/types.js'
|
||||
import type { Payload } from '../../../index.js'
|
||||
import type { JsonObject, Payload } from '../../../index.js'
|
||||
import type { PayloadRequest } from '../../../types/index.js'
|
||||
|
||||
type Args = {
|
||||
@@ -39,13 +39,13 @@ export const incrementLoginAttempts = async ({
|
||||
return
|
||||
}
|
||||
|
||||
const data: Record<string, unknown> = {
|
||||
const data: JsonObject = {
|
||||
loginAttempts: Number(doc.loginAttempts) + 1,
|
||||
}
|
||||
|
||||
// Lock the account if at max attempts and not already locked
|
||||
if (typeof doc.loginAttempts === 'number' && doc.loginAttempts + 1 >= maxLoginAttempts) {
|
||||
const lockUntil = new Date(Date.now() + lockTime)
|
||||
const lockUntil = new Date(Date.now() + lockTime).toISOString()
|
||||
data.lockUntil = lockUntil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { Payload } from '../../../index.js'
|
||||
import type { JsonObject, Payload } from '../../../index.js'
|
||||
import type { PayloadRequest } from '../../../types/index.js'
|
||||
|
||||
import { ValidationError } from '../../../errors/index.js'
|
||||
@@ -7,7 +7,7 @@ import { generatePasswordSaltHash } from './generatePasswordSaltHash.js'
|
||||
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig
|
||||
doc: Record<string, unknown>
|
||||
doc: JsonObject
|
||||
password: string
|
||||
payload: Payload
|
||||
req: PayloadRequest
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { LoginWithUsernameOptions } from '../../auth/types.js'
|
||||
import type { IncomingAuthType, LoginWithUsernameOptions } from '../../auth/types.js'
|
||||
import type { CollectionConfig } from './types.js'
|
||||
|
||||
import defaultAccess from '../../auth/defaultAccess.js'
|
||||
|
||||
export const defaults = {
|
||||
export const defaults: Partial<CollectionConfig> = {
|
||||
access: {
|
||||
create: defaultAccess,
|
||||
delete: defaultAccess,
|
||||
@@ -49,7 +50,7 @@ export const defaults = {
|
||||
versions: false,
|
||||
}
|
||||
|
||||
export const authDefaults = {
|
||||
export const authDefaults: IncomingAuthType = {
|
||||
cookies: {
|
||||
sameSite: 'Lax',
|
||||
secure: false,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import merge from 'deepmerge'
|
||||
|
||||
import type { Config, SanitizedConfig } from '../../config/types.js'
|
||||
import type { CollectionConfig, SanitizedCollectionConfig } from './types.js'
|
||||
|
||||
@@ -9,8 +7,8 @@ import { sanitizeFields } from '../../fields/config/sanitize.js'
|
||||
import { fieldAffectsData } from '../../fields/config/types.js'
|
||||
import mergeBaseFields from '../../fields/mergeBaseFields.js'
|
||||
import { getBaseUploadFields } from '../../uploads/getBaseFields.js'
|
||||
import { deepMergeWithReactComponents } from '../../utilities/deepMerge.js'
|
||||
import { formatLabels } from '../../utilities/formatLabels.js'
|
||||
import { isPlainObject } from '../../utilities/isPlainObject.js'
|
||||
import baseVersionFields from '../../versions/baseFields.js'
|
||||
import { versionDefaults } from '../../versions/defaults.js'
|
||||
import { authDefaults, defaults, loginWithUsernameDefaults } from './defaults.js'
|
||||
@@ -29,9 +27,7 @@ export const sanitizeCollection = async (
|
||||
// Make copy of collection config
|
||||
// /////////////////////////////////
|
||||
|
||||
const sanitized: CollectionConfig = merge(defaults, collection, {
|
||||
isMergeableObject: isPlainObject,
|
||||
})
|
||||
const sanitized: CollectionConfig = deepMergeWithReactComponents(defaults, collection)
|
||||
|
||||
// /////////////////////////////////
|
||||
// Sanitize fields
|
||||
@@ -141,9 +137,10 @@ export const sanitizeCollection = async (
|
||||
// sanitize fields for reserved names
|
||||
sanitizeAuthFields(sanitized.fields, sanitized)
|
||||
|
||||
sanitized.auth = merge(authDefaults, typeof sanitized.auth === 'object' ? sanitized.auth : {}, {
|
||||
isMergeableObject: isPlainObject,
|
||||
})
|
||||
sanitized.auth = deepMergeWithReactComponents(
|
||||
authDefaults,
|
||||
typeof sanitized.auth === 'object' ? sanitized.auth : {},
|
||||
)
|
||||
|
||||
if (!sanitized.auth.disableLocalStrategy && sanitized.auth.verify === true) {
|
||||
sanitized.auth.verify = {}
|
||||
@@ -157,12 +154,12 @@ export const sanitizeCollection = async (
|
||||
}
|
||||
|
||||
sanitized.auth.loginWithUsername = sanitized.auth.loginWithUsername
|
||||
? merge(
|
||||
loginWithUsernameDefaults,
|
||||
typeof sanitized.auth.loginWithUsername === 'boolean'
|
||||
? {
|
||||
...loginWithUsernameDefaults,
|
||||
...(typeof sanitized.auth.loginWithUsername === 'boolean'
|
||||
? {}
|
||||
: sanitized.auth.loginWithUsername,
|
||||
)
|
||||
: sanitized.auth.loginWithUsername),
|
||||
}
|
||||
: false
|
||||
|
||||
sanitized.fields = mergeBaseFields(sanitized.fields, getBaseAuthFields(sanitized.auth))
|
||||
|
||||
@@ -28,7 +28,12 @@ import type {
|
||||
} from '../../config/types.js'
|
||||
import type { DBIdentifierName } from '../../database/types.js'
|
||||
import type { Field } from '../../fields/config/types.js'
|
||||
import type { CollectionSlug, TypedAuthOperations, TypedCollection } from '../../index.js'
|
||||
import type {
|
||||
CollectionSlug,
|
||||
JsonObject,
|
||||
TypedAuthOperations,
|
||||
TypedCollection,
|
||||
} from '../../index.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../types/index.js'
|
||||
import type { SanitizedUploadConfig, UploadConfig } from '../../uploads/types.js'
|
||||
import type {
|
||||
@@ -41,7 +46,7 @@ export type DataFromCollectionSlug<TSlug extends CollectionSlug> = TypedCollecti
|
||||
export type AuthOperationsFromCollectionSlug<TSlug extends CollectionSlug> =
|
||||
TypedAuthOperations[TSlug]
|
||||
|
||||
export type RequiredDataFromCollection<TData extends Record<string, any>> = MarkOptional<
|
||||
export type RequiredDataFromCollection<TData extends JsonObject> = MarkOptional<
|
||||
TData,
|
||||
'createdAt' | 'id' | 'sizes' | 'updatedAt'
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { BatchLoadFn } from 'dataloader'
|
||||
|
||||
import DataLoader from 'dataloader'
|
||||
|
||||
import type { PayloadRequest } from '../types/index.js'
|
||||
import type { JsonValue, PayloadRequest } from '../types/index.js'
|
||||
import type { TypeWithID } from './config/types.js'
|
||||
|
||||
import { isValidID } from '../utilities/isValidID.js'
|
||||
@@ -119,7 +119,7 @@ const batchAndLoadDocs =
|
||||
showHiddenFields: Boolean(showHiddenFields),
|
||||
where: {
|
||||
id: {
|
||||
in: ids,
|
||||
in: ids as JsonValue,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from 'crypto'
|
||||
|
||||
import type { CollectionSlug } from '../../index.js'
|
||||
import type { CollectionSlug, JsonObject } from '../../index.js'
|
||||
import type { Document, PayloadRequest } from '../../types/index.js'
|
||||
import type {
|
||||
AfterChangeHook,
|
||||
@@ -184,7 +184,7 @@ export const createOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
const resultWithLocales = await beforeChange<Record<string, unknown>>({
|
||||
const resultWithLocales = await beforeChange<JsonObject>({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CollectionSlug } from '../../index.js'
|
||||
import type { CollectionSlug, JsonObject, TypeWithID } from '../../index.js'
|
||||
import type { PayloadRequest } from '../../types/index.js'
|
||||
import type { BeforeOperationHook, Collection, DataFromCollectionSlug } from '../config/types.js'
|
||||
|
||||
|
||||
@@ -194,7 +194,7 @@ export const duplicateOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await beforeChange<DataFromCollectionSlug<TSlug>>({
|
||||
result = await beforeChange({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
@@ -280,7 +280,7 @@ export const duplicateOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange<DataFromCollectionSlug<TSlug>>({
|
||||
result = await afterChange({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data: versionDoc,
|
||||
|
||||
@@ -263,7 +263,7 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
let result = await beforeChange<DataFromCollectionSlug<TSlug>>({
|
||||
let result = await beforeChange({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
@@ -349,7 +349,7 @@ export const updateOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange<DataFromCollectionSlug<TSlug>>({
|
||||
result = await afterChange({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
|
||||
@@ -236,7 +236,7 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
|
||||
// beforeChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
let result = await beforeChange<DataFromCollectionSlug<TSlug>>({
|
||||
let result = await beforeChange({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
@@ -341,7 +341,7 @@ export const updateByIDOperation = async <TSlug extends CollectionSlug>(
|
||||
// afterChange - Fields
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange<DataFromCollectionSlug<TSlug>>({
|
||||
result = await afterChange({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AcceptedLanguages } from '@payloadcms/translations'
|
||||
|
||||
import { en } from '@payloadcms/translations/languages/en'
|
||||
import merge from 'deepmerge'
|
||||
import { deepMergeSimple } from '@payloadcms/translations/utilities'
|
||||
|
||||
import type {
|
||||
Config,
|
||||
@@ -17,8 +17,7 @@ import { InvalidConfiguration } from '../errors/index.js'
|
||||
import { sanitizeGlobals } from '../globals/config/sanitize.js'
|
||||
import getPreferencesCollection from '../preferences/preferencesCollection.js'
|
||||
import checkDuplicateCollections from '../utilities/checkDuplicateCollections.js'
|
||||
import { deepMerge } from '../utilities/deepMerge.js'
|
||||
import { isPlainObject } from '../utilities/isPlainObject.js'
|
||||
import { deepMergeWithReactComponents } from '../utilities/deepMerge.js'
|
||||
import { defaults } from './defaults.js'
|
||||
|
||||
const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig> => {
|
||||
@@ -48,9 +47,7 @@ const sanitizeAdminConfig = (configToSanitize: Config): Partial<SanitizedConfig>
|
||||
}
|
||||
|
||||
export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedConfig> => {
|
||||
const configWithDefaults: Config = merge(defaults, incomingConfig, {
|
||||
isMergeableObject: isPlainObject,
|
||||
}) as Config
|
||||
const configWithDefaults: Config = deepMergeWithReactComponents(defaults, incomingConfig)
|
||||
|
||||
if (!configWithDefaults?.serverURL) {
|
||||
configWithDefaults.serverURL = ''
|
||||
@@ -163,7 +160,7 @@ export const sanitizeConfig = async (incomingConfig: Config): Promise<SanitizedC
|
||||
isRoot: true,
|
||||
})
|
||||
if (config.editor.i18n && Object.keys(config.editor.i18n).length >= 0) {
|
||||
config.i18n.translations = deepMerge(config.i18n.translations, config.editor.i18n)
|
||||
config.i18n.translations = deepMergeSimple(config.i18n.translations, config.editor.i18n)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,18 @@ export { formatFilesize } from '../uploads/formatFilesize.js'
|
||||
|
||||
export { isImage } from '../uploads/isImage.js'
|
||||
|
||||
export { deepCopyObject } from '../utilities/deepCopyObject.js'
|
||||
|
||||
export { deepMerge } from '../utilities/deepMerge.js'
|
||||
export {
|
||||
deepCopyObject,
|
||||
deepCopyObjectComplex,
|
||||
deepCopyObjectSimple,
|
||||
} from '../utilities/deepCopyObject.js'
|
||||
|
||||
export {
|
||||
deepMerge,
|
||||
deepMergeWithCombinedArrays,
|
||||
deepMergeWithReactComponents,
|
||||
deepMergeWithSourceArrays,
|
||||
} from '../utilities/deepMerge.js'
|
||||
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js'
|
||||
|
||||
export { getDataByPath } from '../utilities/getDataByPath.js'
|
||||
@@ -55,3 +63,5 @@ export { wait } from '../utilities/wait.js'
|
||||
export { default as wordBoundariesRegex } from '../utilities/wordBoundariesRegex.js'
|
||||
|
||||
export { versionDefaults } from '../versions/defaults.js'
|
||||
|
||||
export { deepMergeSimple } from '@payloadcms/translations/utilities'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { deepMergeSimple } from '@payloadcms/translations/utilities'
|
||||
|
||||
import type { CollectionConfig } from '../../collections/config/types.js'
|
||||
import type { Config, SanitizedConfig } from '../../config/types.js'
|
||||
import type { Field } from './types.js'
|
||||
@@ -9,7 +11,6 @@ import {
|
||||
InvalidFieldRelationship,
|
||||
MissingFieldType,
|
||||
} from '../../errors/index.js'
|
||||
import { deepMerge } from '../../utilities/deepMerge.js'
|
||||
import { formatLabels, toWords } from '../../utilities/formatLabels.js'
|
||||
import { baseBlockFields } from '../baseFields/baseBlockFields.js'
|
||||
import { baseIDField } from '../baseFields/baseIDField.js'
|
||||
@@ -175,7 +176,7 @@ export const sanitizeFields = async ({
|
||||
}
|
||||
|
||||
if (field.editor.i18n && Object.keys(field.editor.i18n).length >= 0) {
|
||||
config.i18n.translations = deepMerge(config.i18n.translations, field.editor.i18n)
|
||||
config.i18n.translations = deepMergeSimple(config.i18n.translations, field.editor.i18n)
|
||||
}
|
||||
}
|
||||
if (richTextSanitizationPromises) {
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
import type { PayloadRequest } from '../types/index.js'
|
||||
import type { JsonValue, PayloadRequest } from '../types/index.js'
|
||||
|
||||
import { deepCopyObject } from '../utilities/deepCopyObject.js'
|
||||
import { deepCopyObjectSimple } from '../utilities/deepCopyObject.js'
|
||||
|
||||
type Args = {
|
||||
defaultValue: unknown
|
||||
defaultValue: ((args: any) => JsonValue) | any
|
||||
locale: string | undefined
|
||||
user: PayloadRequest['user']
|
||||
value?: unknown
|
||||
value?: JsonValue
|
||||
}
|
||||
|
||||
const getValueWithDefault = ({ defaultValue, locale, user, value }: Args): unknown => {
|
||||
export const getDefaultValue = async ({
|
||||
defaultValue,
|
||||
locale,
|
||||
user,
|
||||
value,
|
||||
}: Args): Promise<JsonValue> => {
|
||||
if (typeof value !== 'undefined') {
|
||||
return value
|
||||
}
|
||||
|
||||
if (defaultValue && typeof defaultValue === 'function') {
|
||||
return defaultValue({ locale, user })
|
||||
return await defaultValue({ locale, user })
|
||||
}
|
||||
|
||||
if (typeof defaultValue === 'object') {
|
||||
return deepCopyObject(defaultValue)
|
||||
return deepCopyObjectSimple(defaultValue)
|
||||
}
|
||||
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export default getValueWithDefault
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
|
||||
import { deepCopyObject } from '../../../utilities/deepCopyObject.js'
|
||||
import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args<T> = {
|
||||
type Args<T extends JsonObject> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
/**
|
||||
* The data before hooks
|
||||
*/
|
||||
data: Record<string, unknown> | T
|
||||
data: T
|
||||
/**
|
||||
* The data after hooks
|
||||
*/
|
||||
doc: Record<string, unknown> | T
|
||||
doc: T
|
||||
global: SanitizedGlobalConfig | null
|
||||
operation: 'create' | 'update'
|
||||
previousDoc: Record<string, unknown> | T
|
||||
previousDoc: T
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ type Args<T> = {
|
||||
* This function is responsible for the following actions, in order:
|
||||
* - Execute field hooks
|
||||
*/
|
||||
export const afterChange = async <T extends Record<string, unknown>>({
|
||||
export const afterChange = async <T extends JsonObject>({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
@@ -36,7 +36,7 @@ export const afterChange = async <T extends Record<string, unknown>>({
|
||||
previousDoc,
|
||||
req,
|
||||
}: Args<T>): Promise<T> => {
|
||||
const doc = deepCopyObject(incomingDoc)
|
||||
const doc = deepCopyObjectSimple(incomingDoc)
|
||||
|
||||
await traverseFields({
|
||||
collection,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { RichTextAdapter } from '../../../admin/RichText.js'
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
@@ -12,8 +12,8 @@ import { traverseFields } from './traverseFields.js'
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
data: JsonObject
|
||||
doc: JsonObject
|
||||
field: Field | TabAsField
|
||||
global: SanitizedGlobalConfig | null
|
||||
operation: 'create' | 'update'
|
||||
@@ -25,11 +25,11 @@ type Args = {
|
||||
* The parent's schemaPath (path without indexes).
|
||||
*/
|
||||
parentSchemaPath: string[]
|
||||
previousDoc: Record<string, unknown>
|
||||
previousSiblingDoc: Record<string, unknown>
|
||||
previousDoc: JsonObject
|
||||
previousSiblingDoc: JsonObject
|
||||
req: PayloadRequest
|
||||
siblingData: Record<string, unknown>
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingData: JsonObject
|
||||
siblingDoc: JsonObject
|
||||
}
|
||||
|
||||
// This function is responsible for the following actions, in order:
|
||||
@@ -101,11 +101,11 @@ export const promise = async ({
|
||||
operation,
|
||||
path: fieldPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc[field.name] as Record<string, unknown>,
|
||||
previousSiblingDoc: previousDoc[field.name] as JsonObject,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: (siblingData?.[field.name] as Record<string, unknown>) || {},
|
||||
siblingDoc: siblingDoc[field.name] as Record<string, unknown>,
|
||||
siblingData: (siblingData?.[field.name] as JsonObject) || {},
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -128,11 +128,11 @@ export const promise = async ({
|
||||
operation,
|
||||
path: [...fieldPath, i],
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc?.[field.name]?.[i] || ({} as Record<string, unknown>),
|
||||
previousSiblingDoc: previousDoc?.[field.name]?.[i] || ({} as JsonObject),
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData?.[field.name]?.[i] || {},
|
||||
siblingDoc: { ...row } || {},
|
||||
siblingDoc: ({ ...(row as JsonObject) } as JsonObject) || {},
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -147,7 +147,9 @@ export const promise = async ({
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
const block = field.blocks.find((blockType) => blockType.slug === row.blockType)
|
||||
const block = field.blocks.find(
|
||||
(blockType) => blockType.slug === (row as JsonObject).blockType,
|
||||
)
|
||||
|
||||
if (block) {
|
||||
promises.push(
|
||||
@@ -161,12 +163,11 @@ export const promise = async ({
|
||||
operation,
|
||||
path: [...fieldPath, i],
|
||||
previousDoc,
|
||||
previousSiblingDoc:
|
||||
previousDoc?.[field.name]?.[i] || ({} as Record<string, unknown>),
|
||||
previousSiblingDoc: previousDoc?.[field.name]?.[i] || ({} as JsonObject),
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData?.[field.name]?.[i] || {},
|
||||
siblingDoc: { ...row } || {},
|
||||
siblingDoc: ({ ...(row as JsonObject) } as JsonObject) || {},
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -205,9 +206,9 @@ export const promise = async ({
|
||||
let tabPreviousSiblingDoc = siblingDoc
|
||||
|
||||
if (tabHasName(field)) {
|
||||
tabSiblingData = siblingData[field.name] as Record<string, unknown>
|
||||
tabSiblingDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
tabPreviousSiblingDoc = previousDoc[field.name] as Record<string, unknown>
|
||||
tabSiblingData = siblingData[field.name] as JsonObject
|
||||
tabSiblingDoc = siblingDoc[field.name] as JsonObject
|
||||
tabPreviousSiblingDoc = previousDoc[field.name] as JsonObject
|
||||
}
|
||||
|
||||
await traverseFields({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { promise } from './promise.js'
|
||||
@@ -8,18 +8,18 @@ import { promise } from './promise.js'
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
data: JsonObject
|
||||
doc: JsonObject
|
||||
fields: (Field | TabAsField)[]
|
||||
global: SanitizedGlobalConfig | null
|
||||
operation: 'create' | 'update'
|
||||
path: (number | string)[]
|
||||
previousDoc: Record<string, unknown>
|
||||
previousSiblingDoc: Record<string, unknown>
|
||||
previousDoc: JsonObject
|
||||
previousSiblingDoc: JsonObject
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
siblingData: Record<string, unknown>
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingData: JsonObject
|
||||
siblingDoc: JsonObject
|
||||
}
|
||||
|
||||
export const traverseFields = async ({
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
|
||||
import { deepCopyObject } from '../../../utilities/deepCopyObject.js'
|
||||
import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args = {
|
||||
type Args<T extends JsonObject> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
currentDepth?: number
|
||||
depth: number
|
||||
doc: Record<string, unknown>
|
||||
doc: T
|
||||
draft: boolean
|
||||
fallbackLocale: null | string
|
||||
findMany?: boolean
|
||||
@@ -32,7 +32,7 @@ type Args = {
|
||||
* - Populate relationships
|
||||
*/
|
||||
|
||||
export async function afterRead<T = any>(args: Args): Promise<T> {
|
||||
export async function afterRead<T extends JsonObject>(args: Args<T>): Promise<T> {
|
||||
const {
|
||||
collection,
|
||||
context,
|
||||
@@ -50,7 +50,7 @@ export async function afterRead<T = any>(args: Args): Promise<T> {
|
||||
showHiddenFields,
|
||||
} = args
|
||||
|
||||
const doc = deepCopyObject(incomingDoc)
|
||||
const doc = deepCopyObjectSimple(incomingDoc)
|
||||
const fieldPromises = []
|
||||
const populationPromises = []
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { RichTextAdapter } from '../../../admin/RichText.js'
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import getValueWithDefault from '../../getDefaultValue.js'
|
||||
import { getDefaultValue } from '../../getDefaultValue.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { relationshipPopulationPromise } from './relationshipPopulationPromise.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
@@ -16,7 +16,7 @@ type Args = {
|
||||
context: RequestContext
|
||||
currentDepth: number
|
||||
depth: number
|
||||
doc: Record<string, unknown>
|
||||
doc: JsonObject
|
||||
draft: boolean
|
||||
fallbackLocale: null | string
|
||||
field: Field | TabAsField
|
||||
@@ -40,7 +40,7 @@ type Args = {
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDoc: JsonObject
|
||||
triggerAccessControl?: boolean
|
||||
triggerHooks?: boolean
|
||||
}
|
||||
@@ -276,7 +276,7 @@ export const promise = async ({
|
||||
typeof siblingDoc[field.name] === 'undefined' &&
|
||||
typeof field.defaultValue !== 'undefined'
|
||||
) {
|
||||
siblingDoc[field.name] = await getValueWithDefault({
|
||||
siblingDoc[field.name] = await getDefaultValue({
|
||||
defaultValue: field.defaultValue,
|
||||
locale,
|
||||
user: req.user,
|
||||
@@ -304,7 +304,7 @@ export const promise = async ({
|
||||
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
let groupDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
let groupDoc = siblingDoc[field.name] as JsonObject
|
||||
if (typeof siblingDoc[field.name] !== 'object') groupDoc = {}
|
||||
|
||||
traverseFields({
|
||||
@@ -336,7 +336,7 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
case 'array': {
|
||||
const rows = siblingDoc[field.name]
|
||||
const rows = siblingDoc[field.name] as JsonObject
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
rows.forEach((row, i) => {
|
||||
@@ -389,7 +389,7 @@ export const promise = async ({
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
showHiddenFields,
|
||||
siblingDoc: row || {},
|
||||
siblingDoc: (row as JsonObject) || {},
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
@@ -407,7 +407,9 @@ export const promise = async ({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
rows.forEach((row, i) => {
|
||||
const block = field.blocks.find((blockType) => blockType.slug === row.blockType)
|
||||
const block = field.blocks.find(
|
||||
(blockType) => blockType.slug === (row as JsonObject).blockType,
|
||||
)
|
||||
|
||||
if (block) {
|
||||
traverseFields({
|
||||
@@ -430,7 +432,7 @@ export const promise = async ({
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
showHiddenFields,
|
||||
siblingDoc: row || {},
|
||||
siblingDoc: (row as JsonObject) || {},
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
@@ -440,7 +442,9 @@ export const promise = async ({
|
||||
Object.values(rows).forEach((localeRows) => {
|
||||
if (Array.isArray(localeRows)) {
|
||||
localeRows.forEach((row, i) => {
|
||||
const block = field.blocks.find((blockType) => blockType.slug === row.blockType)
|
||||
const block = field.blocks.find(
|
||||
(blockType) => blockType.slug === (row as JsonObject).blockType,
|
||||
)
|
||||
|
||||
if (block) {
|
||||
traverseFields({
|
||||
@@ -463,7 +467,7 @@ export const promise = async ({
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
showHiddenFields,
|
||||
siblingDoc: row || {},
|
||||
siblingDoc: (row as JsonObject) || {},
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
@@ -511,7 +515,7 @@ export const promise = async ({
|
||||
case 'tab': {
|
||||
let tabDoc = siblingDoc
|
||||
if (tabHasName(field)) {
|
||||
tabDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
tabDoc = siblingDoc[field.name] as JsonObject
|
||||
if (typeof siblingDoc[field.name] !== 'object') tabDoc = {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { promise } from './promise.js'
|
||||
@@ -10,7 +10,7 @@ type Args = {
|
||||
context: RequestContext
|
||||
currentDepth: number
|
||||
depth: number
|
||||
doc: Record<string, unknown>
|
||||
doc: JsonObject
|
||||
draft: boolean
|
||||
fallbackLocale: null | string
|
||||
/**
|
||||
@@ -28,7 +28,7 @@ type Args = {
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDoc: JsonObject
|
||||
triggerAccessControl?: boolean
|
||||
triggerHooks?: boolean
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export const cloneDataFromOriginalDoc = (originalDocData: unknown): unknown => {
|
||||
import type { JsonArray, JsonObject } from '../../../types/index.js'
|
||||
|
||||
export const cloneDataFromOriginalDoc = (
|
||||
originalDocData: JsonArray | JsonObject,
|
||||
): JsonArray | JsonObject => {
|
||||
if (Array.isArray(originalDocData)) {
|
||||
return originalDocData.map((row) => {
|
||||
if (typeof row === 'object' && row != null) {
|
||||
|
||||
@@ -4,11 +4,9 @@
|
||||
* this is an existing row, so it should be merged.
|
||||
* Otherwise, return an empty object.
|
||||
*/
|
||||
import type { JsonObject } from '../../../types/index.js'
|
||||
|
||||
export const getExistingRowDoc = (
|
||||
incomingRow: Record<string, unknown>,
|
||||
existingRows?: unknown,
|
||||
): Record<string, unknown> => {
|
||||
export const getExistingRowDoc = (incomingRow: JsonObject, existingRows?: unknown): JsonObject => {
|
||||
if (incomingRow.id && Array.isArray(existingRows)) {
|
||||
const matchedExistingRow = existingRows.find((existingRow) => {
|
||||
if (typeof existingRow === 'object' && 'id' in existingRow) {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { Operation, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, Operation, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
|
||||
import { ValidationError } from '../../../errors/index.js'
|
||||
import { deepCopyObject } from '../../../utilities/deepCopyObject.js'
|
||||
import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args<T> = {
|
||||
type Args<T extends JsonObject> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown> | T
|
||||
doc: Record<string, unknown> | T
|
||||
docWithLocales: Record<string, unknown>
|
||||
data: T
|
||||
doc: T
|
||||
docWithLocales: JsonObject
|
||||
duplicate?: boolean
|
||||
global: SanitizedGlobalConfig | null
|
||||
id?: number | string
|
||||
@@ -29,7 +29,7 @@ type Args<T> = {
|
||||
* - beforeDuplicate hooks (if duplicate)
|
||||
* - Unflatten locales. The input `data` is the normal document for one locale. The output result will become the document with locales.
|
||||
*/
|
||||
export const beforeChange = async <T extends Record<string, unknown>>({
|
||||
export const beforeChange = async <T extends JsonObject>({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
@@ -42,7 +42,7 @@ export const beforeChange = async <T extends Record<string, unknown>>({
|
||||
req,
|
||||
skipValidation,
|
||||
}: Args<T>): Promise<T> => {
|
||||
const data = deepCopyObject(incomingData)
|
||||
const data = deepCopyObjectSimple(incomingData)
|
||||
const mergeLocaleActions = []
|
||||
const errors: { field: string; message: string }[] = []
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import merge from 'deepmerge'
|
||||
|
||||
import type { RichTextAdapter } from '../../../admin/RichText.js'
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { Operation, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, Operation, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { Field, FieldHookArgs, TabAsField, ValidateOptions } from '../../config/types.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { beforeDuplicate } from './beforeDuplicate.js'
|
||||
@@ -16,9 +15,9 @@ import { traverseFields } from './traverseFields.js'
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
docWithLocales: Record<string, unknown>
|
||||
data: JsonObject
|
||||
doc: JsonObject
|
||||
docWithLocales: JsonObject
|
||||
duplicate: boolean
|
||||
errors: { field: string; message: string }[]
|
||||
field: Field | TabAsField
|
||||
@@ -35,9 +34,9 @@ type Args = {
|
||||
*/
|
||||
parentSchemaPath: string[]
|
||||
req: PayloadRequest
|
||||
siblingData: Record<string, unknown>
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDocWithLocales?: Record<string, unknown>
|
||||
siblingData: JsonObject
|
||||
siblingDoc: JsonObject
|
||||
siblingDocWithLocales?: JsonObject
|
||||
skipValidation: boolean
|
||||
}
|
||||
|
||||
@@ -137,12 +136,12 @@ export const promise = async ({
|
||||
const validationResult = await field.validate(valueToValidate, {
|
||||
...field,
|
||||
id,
|
||||
data: merge(doc, data, { arrayMerge: (_, source) => source }),
|
||||
data: deepMergeWithSourceArrays(doc, data),
|
||||
jsonError,
|
||||
operation,
|
||||
preferences: { fields: {} },
|
||||
req,
|
||||
siblingData: merge(siblingDoc, siblingData, { arrayMerge: (_, source) => source }),
|
||||
siblingData: deepMergeWithSourceArrays(siblingDoc, siblingData),
|
||||
} as ValidateOptions<any, any, { jsonError: object }>)
|
||||
|
||||
if (typeof validationResult === 'string') {
|
||||
@@ -173,7 +172,7 @@ export const promise = async ({
|
||||
if (localization && field.localized) {
|
||||
mergeLocaleActions.push(async () => {
|
||||
const localeData = await localization.localeCodes.reduce(
|
||||
async (localizedValuesPromise: Promise<Record<string, unknown>>, locale) => {
|
||||
async (localizedValuesPromise: Promise<JsonObject>, locale) => {
|
||||
const localizedValues = await localizedValuesPromise
|
||||
let fieldValue =
|
||||
locale === req.locale
|
||||
@@ -253,9 +252,9 @@ export const promise = async ({
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData[field.name] as Record<string, unknown>,
|
||||
siblingDoc: siblingDoc[field.name] as Record<string, unknown>,
|
||||
siblingDocWithLocales: siblingDocWithLocales[field.name] as Record<string, unknown>,
|
||||
siblingData: siblingData[field.name] as JsonObject,
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
siblingDocWithLocales: siblingDocWithLocales[field.name] as JsonObject,
|
||||
skipValidation: skipValidationFromHere,
|
||||
})
|
||||
|
||||
@@ -285,9 +284,12 @@ export const promise = async ({
|
||||
path: [...fieldPath, i],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: row,
|
||||
siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]),
|
||||
siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]),
|
||||
siblingData: row as JsonObject,
|
||||
siblingDoc: getExistingRowDoc(row as JsonObject, siblingDoc[field.name]),
|
||||
siblingDocWithLocales: getExistingRowDoc(
|
||||
row as JsonObject,
|
||||
siblingDocWithLocales[field.name],
|
||||
),
|
||||
skipValidation: skipValidationFromHere,
|
||||
}),
|
||||
)
|
||||
@@ -305,10 +307,13 @@ export const promise = async ({
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
const rowSiblingDoc = getExistingRowDoc(row, siblingDoc[field.name])
|
||||
const rowSiblingDocWithLocales = getExistingRowDoc(row, siblingDocWithLocales[field.name])
|
||||
const rowSiblingDoc = getExistingRowDoc(row as JsonObject, siblingDoc[field.name])
|
||||
const rowSiblingDocWithLocales = getExistingRowDoc(
|
||||
row as JsonObject,
|
||||
siblingDocWithLocales[field.name],
|
||||
)
|
||||
|
||||
const blockTypeToMatch = row.blockType || rowSiblingDoc.blockType
|
||||
const blockTypeToMatch = (row as JsonObject).blockType || rowSiblingDoc.blockType
|
||||
const block = field.blocks.find((blockType) => blockType.slug === blockTypeToMatch)
|
||||
|
||||
if (block) {
|
||||
@@ -329,7 +334,7 @@ export const promise = async ({
|
||||
path: [...fieldPath, i],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: row,
|
||||
siblingData: row as JsonObject,
|
||||
siblingDoc: rowSiblingDoc,
|
||||
siblingDocWithLocales: rowSiblingDocWithLocales,
|
||||
skipValidation: skipValidationFromHere,
|
||||
@@ -382,9 +387,9 @@ export const promise = async ({
|
||||
if (typeof siblingDocWithLocales[field.name] !== 'object')
|
||||
siblingDocWithLocales[field.name] = {}
|
||||
|
||||
tabSiblingData = siblingData[field.name] as Record<string, unknown>
|
||||
tabSiblingDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
tabSiblingDocWithLocales = siblingDocWithLocales[field.name] as Record<string, unknown>
|
||||
tabSiblingData = siblingData[field.name] as JsonObject
|
||||
tabSiblingDoc = siblingDoc[field.name] as JsonObject
|
||||
tabSiblingDocWithLocales = siblingDocWithLocales[field.name] as JsonObject
|
||||
}
|
||||
|
||||
await traverseFields({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { Operation, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, Operation, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { promise } from './promise.js'
|
||||
@@ -8,15 +8,15 @@ import { promise } from './promise.js'
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown>
|
||||
data: JsonObject
|
||||
/**
|
||||
* The original data (not modified by any hooks)
|
||||
*/
|
||||
doc: Record<string, unknown>
|
||||
doc: JsonObject
|
||||
/**
|
||||
* The original data with locales (not modified by any hooks)
|
||||
*/
|
||||
docWithLocales: Record<string, unknown>
|
||||
docWithLocales: JsonObject
|
||||
duplicate: boolean
|
||||
errors: { field: string; message: string }[]
|
||||
fields: (Field | TabAsField)[]
|
||||
@@ -27,15 +27,15 @@ type Args = {
|
||||
path: (number | string)[]
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
siblingData: Record<string, unknown>
|
||||
siblingData: JsonObject
|
||||
/**
|
||||
* The original siblingData (not modified by any hooks)
|
||||
*/
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDoc: JsonObject
|
||||
/**
|
||||
* The original siblingData with locales (not modified by any hooks)
|
||||
*/
|
||||
siblingDocWithLocales: Record<string, unknown>
|
||||
siblingDocWithLocales: JsonObject
|
||||
skipValidation?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
|
||||
import { deepCopyObject } from '../../../utilities/deepCopyObject.js'
|
||||
import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args<T> = {
|
||||
type Args<T extends JsonObject> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown> | T
|
||||
doc?: Record<string, unknown> | T
|
||||
data: T
|
||||
doc?: T
|
||||
duplicate?: boolean
|
||||
global: SanitizedGlobalConfig | null
|
||||
id?: number | string
|
||||
@@ -26,7 +26,7 @@ type Args<T> = {
|
||||
* - Merge original document data into incoming data
|
||||
* - Compute default values for undefined fields
|
||||
*/
|
||||
export const beforeValidate = async <T extends Record<string, unknown>>({
|
||||
export const beforeValidate = async <T extends JsonObject>({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
@@ -37,7 +37,7 @@ export const beforeValidate = async <T extends Record<string, unknown>>({
|
||||
overrideAccess,
|
||||
req,
|
||||
}: Args<T>): Promise<T> => {
|
||||
const data = deepCopyObject(incomingData)
|
||||
const data = deepCopyObjectSimple(incomingData)
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { RichTextAdapter } from '../../../admin/RichText.js'
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, JsonValue, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { fieldAffectsData, tabHasName, valueIsValueWithRelation } from '../../config/types.js'
|
||||
import getValueWithDefault from '../../getDefaultValue.js'
|
||||
import { getDefaultValue } from '../../getDefaultValue.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { cloneDataFromOriginalDoc } from '../beforeChange/cloneDataFromOriginalDoc.js'
|
||||
import { getExistingRowDoc } from '../beforeChange/getExistingRowDoc.js'
|
||||
@@ -28,11 +28,11 @@ type Args<T> = {
|
||||
parentPath: (number | string)[]
|
||||
parentSchemaPath: string[]
|
||||
req: PayloadRequest
|
||||
siblingData: Record<string, unknown>
|
||||
siblingData: JsonObject
|
||||
/**
|
||||
* The original siblingData (not modified by any hooks)
|
||||
*/
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDoc: JsonObject
|
||||
}
|
||||
|
||||
// This function is responsible for the following actions, in order:
|
||||
@@ -149,7 +149,7 @@ export const promise = async <T>({
|
||||
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((relatedDoc: { relationTo: string; value: unknown }, i) => {
|
||||
value.forEach((relatedDoc: { relationTo: string; value: JsonValue }, i) => {
|
||||
const relatedCollection = req.payload.config.collections.find(
|
||||
(collection) => collection.slug === relatedDoc.relationTo,
|
||||
)
|
||||
@@ -270,11 +270,11 @@ export const promise = async <T>({
|
||||
if (typeof siblingData[field.name] === 'undefined') {
|
||||
// If no incoming data, but existing document data is found, merge it in
|
||||
if (typeof siblingDoc[field.name] !== 'undefined') {
|
||||
siblingData[field.name] = cloneDataFromOriginalDoc(siblingDoc[field.name])
|
||||
siblingData[field.name] = cloneDataFromOriginalDoc(siblingDoc[field.name] as any)
|
||||
|
||||
// Otherwise compute default value
|
||||
} else if (typeof field.defaultValue !== 'undefined') {
|
||||
siblingData[field.name] = await getValueWithDefault({
|
||||
siblingData[field.name] = await getDefaultValue({
|
||||
defaultValue: field.defaultValue,
|
||||
locale: req.locale,
|
||||
user: req.user,
|
||||
@@ -306,8 +306,8 @@ export const promise = async <T>({
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: groupData,
|
||||
siblingDoc: groupDoc,
|
||||
siblingData: groupData as JsonObject,
|
||||
siblingDoc: groupDoc as JsonObject,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -333,8 +333,8 @@ export const promise = async <T>({
|
||||
path: [...fieldPath, i],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: row,
|
||||
siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]),
|
||||
siblingData: row as JsonObject,
|
||||
siblingDoc: getExistingRowDoc(row as JsonObject, siblingDoc[field.name]),
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -349,12 +349,12 @@ export const promise = async <T>({
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
const rowSiblingDoc = getExistingRowDoc(row, siblingDoc[field.name])
|
||||
const blockTypeToMatch = row.blockType || rowSiblingDoc.blockType
|
||||
const rowSiblingDoc = getExistingRowDoc(row as JsonObject, siblingDoc[field.name])
|
||||
const blockTypeToMatch = (row as JsonObject).blockType || rowSiblingDoc.blockType
|
||||
const block = field.blocks.find((blockType) => blockType.slug === blockTypeToMatch)
|
||||
|
||||
if (block) {
|
||||
row.blockType = blockTypeToMatch
|
||||
;(row as JsonObject).blockType = blockTypeToMatch
|
||||
|
||||
promises.push(
|
||||
traverseFields({
|
||||
@@ -370,7 +370,7 @@ export const promise = async <T>({
|
||||
path: [...fieldPath, i],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: row,
|
||||
siblingData: row as JsonObject,
|
||||
siblingDoc: rowSiblingDoc,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { JsonObject, PayloadRequest, RequestContext } from '../../../types/index.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { promise } from './promise.js'
|
||||
@@ -21,11 +21,11 @@ type Args<T> = {
|
||||
path: (number | string)[]
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
siblingData: Record<string, unknown>
|
||||
siblingData: JsonObject
|
||||
/**
|
||||
* The original siblingData (not modified by any hooks)
|
||||
*/
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDoc: JsonObject
|
||||
}
|
||||
|
||||
export const traverseFields = async <T>({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import merge from 'deepmerge'
|
||||
|
||||
import type { Field, FieldWithSubFields } from './config/types.js'
|
||||
|
||||
import { deepMergeWithReactComponents } from '../utilities/deepMerge.js'
|
||||
import { fieldAffectsData, fieldHasSubFields } from './config/types.js'
|
||||
|
||||
const mergeBaseFields = (fields: Field[], baseFields: Field[]): Field[] => {
|
||||
@@ -24,7 +23,7 @@ const mergeBaseFields = (fields: Field[], baseFields: Field[]): Field[] => {
|
||||
const matchCopy: Field = { ...match }
|
||||
mergedFields.splice(matchedIndex, 1)
|
||||
|
||||
const mergedField = merge<Field>(baseField, matchCopy)
|
||||
const mergedField = deepMergeWithReactComponents<Field>(baseField, matchCopy)
|
||||
|
||||
if (fieldHasSubFields(baseField) && fieldHasSubFields(matchCopy)) {
|
||||
;(mergedField as FieldWithSubFields).fields = mergeBaseFields(
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { AccessResult } from '../../config/types.js'
|
||||
import type { GeneratedTypes } from '../../index.js'
|
||||
import type { PayloadRequest, Where } from '../../types/index.js'
|
||||
import type { SanitizedGlobalConfig } from '../config/types.js'
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { combineQueries } from '../../database/combineQueries.js'
|
||||
import { Forbidden, NotFound } from '../../errors/index.js'
|
||||
import { afterRead } from '../../fields/hooks/afterRead/index.js'
|
||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { deepCopyObjectSimple } from '../../utilities/deepCopyObject.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
|
||||
@@ -78,7 +79,7 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
|
||||
}
|
||||
|
||||
// Clone the result - it may have come back memoized
|
||||
let result = JSON.parse(JSON.stringify(results[0]))
|
||||
let result: any = deepCopyObjectSimple(results[0])
|
||||
|
||||
// Patch globalType onto version doc
|
||||
result.version.globalType = globalConfig.slug
|
||||
|
||||
@@ -87,7 +87,7 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
|
||||
docs: await Promise.all(
|
||||
paginatedDocs.docs.map(async (data) => ({
|
||||
...data,
|
||||
version: await afterRead({
|
||||
version: await afterRead<T>({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
depth,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DeepPartial } from 'ts-essentials'
|
||||
|
||||
import type { GlobalSlug } from '../../index.js'
|
||||
import type { GlobalSlug, JsonObject } from '../../index.js'
|
||||
import type { PayloadRequest, Where } from '../../types/index.js'
|
||||
import type { DataFromGlobalSlug, SanitizedGlobalConfig } from '../config/types.js'
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 { deepCopyObjectSimple } from '../../index.js'
|
||||
import { commitTransaction } from '../../utilities/commitTransaction.js'
|
||||
import { initTransaction } from '../../utilities/initTransaction.js'
|
||||
import { killTransaction } from '../../utilities/killTransaction.js'
|
||||
@@ -81,10 +82,10 @@ export const updateOperation = async <TSlug extends GlobalSlug>(
|
||||
where: query,
|
||||
})
|
||||
|
||||
let globalJSON: Record<string, unknown> = {}
|
||||
let globalJSON: JsonObject = {}
|
||||
|
||||
if (global) {
|
||||
globalJSON = JSON.parse(JSON.stringify(global))
|
||||
globalJSON = deepCopyObjectSimple(global)
|
||||
|
||||
if (globalJSON._id) {
|
||||
delete globalJSON._id
|
||||
|
||||
@@ -50,6 +50,7 @@ import type { Options as FindGlobalVersionByIDOptions } from './globals/operatio
|
||||
import type { Options as FindGlobalVersionsOptions } from './globals/operations/local/findVersions.js'
|
||||
import type { Options as RestoreGlobalVersionOptions } from './globals/operations/local/restoreVersion.js'
|
||||
import type { Options as UpdateGlobalOptions } from './globals/operations/local/update.js'
|
||||
import type { JsonObject } from './types/index.js'
|
||||
import type { TypeWithVersion } from './versions/types.js'
|
||||
|
||||
import { decrypt, encrypt } from './auth/crypto.js'
|
||||
@@ -85,10 +86,10 @@ export interface GeneratedTypes {
|
||||
}
|
||||
}
|
||||
collectionsUntyped: {
|
||||
[slug: string]: Record<string, unknown> & TypeWithID
|
||||
[slug: string]: JsonObject & TypeWithID
|
||||
}
|
||||
globalsUntyped: {
|
||||
[slug: string]: Record<string, unknown>
|
||||
[slug: string]: JsonObject
|
||||
}
|
||||
localeUntyped: null | string
|
||||
userUntyped: User
|
||||
@@ -925,7 +926,7 @@ export type {
|
||||
ValidateOptions,
|
||||
ValueWithRelation,
|
||||
} from './fields/config/types.js'
|
||||
export { default as getDefaultValue } from './fields/getDefaultValue.js'
|
||||
export { getDefaultValue } from './fields/getDefaultValue.js'
|
||||
export { traverseFields as afterChangeTraverseFields } from './fields/hooks/afterChange/traverseFields.js'
|
||||
export { promise as afterReadPromise } from './fields/hooks/afterRead/promise.js'
|
||||
export { traverseFields as afterReadTraverseFields } from './fields/hooks/afterRead/traverseFields.js'
|
||||
@@ -964,7 +965,6 @@ export { getLocalI18n } from './translations/getLocalI18n.js'
|
||||
export * from './types/index.js'
|
||||
export { getFileByPath } from './uploads/getFileByPath.js'
|
||||
export type * from './uploads/types.js'
|
||||
export { combineMerge } from './utilities/combineMerge.js'
|
||||
export { commitTransaction } from './utilities/commitTransaction.js'
|
||||
export {
|
||||
configToJSONSchema,
|
||||
@@ -974,8 +974,17 @@ export {
|
||||
} from './utilities/configToJSONSchema.js'
|
||||
export { createArrayFromCommaDelineated } from './utilities/createArrayFromCommaDelineated.js'
|
||||
export { createLocalReq } from './utilities/createLocalReq.js'
|
||||
export { deepCopyObject } from './utilities/deepCopyObject.js'
|
||||
export { deepMerge } from './utilities/deepMerge.js'
|
||||
export {
|
||||
deepCopyObject,
|
||||
deepCopyObjectComplex,
|
||||
deepCopyObjectSimple,
|
||||
} from './utilities/deepCopyObject.js'
|
||||
export {
|
||||
deepMerge,
|
||||
deepMergeWithCombinedArrays,
|
||||
deepMergeWithReactComponents,
|
||||
deepMergeWithSourceArrays,
|
||||
} from './utilities/deepMerge.js'
|
||||
export { default as flattenTopLevelFields } from './utilities/flattenTopLevelFields.js'
|
||||
export { formatLabels, formatNames, toWords } from './utilities/formatLabels.js'
|
||||
export { getCollectionIDFieldTypes } from './utilities/getCollectionIDFieldTypes.js'
|
||||
@@ -995,6 +1004,7 @@ export { deleteCollectionVersions } from './versions/deleteCollectionVersions.js
|
||||
export { enforceMaxVersions } from './versions/enforceMaxVersions.js'
|
||||
export { getLatestCollectionVersion } from './versions/getLatestCollectionVersion.js'
|
||||
export { getLatestGlobalVersion } from './versions/getLatestGlobalVersion.js'
|
||||
export { getDependencies }
|
||||
export { saveVersion } from './versions/saveVersion.js'
|
||||
export { getDependencies }
|
||||
export type { TypeWithVersion } from './versions/types.js'
|
||||
export { deepMergeSimple } from '@payloadcms/translations/utilities'
|
||||
|
||||
@@ -68,7 +68,7 @@ type PayloadRequestData = {
|
||||
* 2. import { addDataAndFileToRequest } from '@payloadcms/next/utilities'
|
||||
* `await addDataAndFileToRequest(req)`
|
||||
* */
|
||||
data?: Record<string, unknown>
|
||||
data?: JsonObject
|
||||
/** The file on the request, same rules apply as the `data` property */
|
||||
file?: {
|
||||
data: Buffer
|
||||
@@ -89,8 +89,18 @@ export interface RequestContext {
|
||||
|
||||
export type Operator = (typeof validOperators)[number]
|
||||
|
||||
// Makes it so things like passing new Date() will error
|
||||
export type JsonValue = JsonArray | JsonObject | unknown //Date | JsonArray | JsonObject | boolean | null | number | string // TODO: Evaluate proper, strong type for this
|
||||
|
||||
export interface JsonArray extends Array<JsonValue> {}
|
||||
|
||||
export interface JsonObject {
|
||||
[key: string]: JsonValue
|
||||
}
|
||||
|
||||
export type WhereField = {
|
||||
[key in Operator]?: unknown
|
||||
// any json-serializable value
|
||||
[key in Operator]?: JsonValue
|
||||
}
|
||||
|
||||
export type Where = {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import merge from 'deepmerge'
|
||||
|
||||
export const combineMerge = (target, source, options) => {
|
||||
const destination = target.slice()
|
||||
|
||||
source.forEach((item, index) => {
|
||||
if (typeof destination[index] === 'undefined') {
|
||||
destination[index] = options.cloneUnlessOtherwiseSpecified(item, options)
|
||||
} else if (options.isMergeableObject(item)) {
|
||||
destination[index] = merge(target[index], item, options)
|
||||
} else if (target.indexOf(item) === -1) {
|
||||
destination.push(item)
|
||||
}
|
||||
})
|
||||
return destination
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
const convertArrayToObject = (arr, key) =>
|
||||
arr.reduce((obj, item) => {
|
||||
if (key) {
|
||||
obj[item[key]] = item
|
||||
return obj
|
||||
}
|
||||
|
||||
obj[item] = {}
|
||||
return obj
|
||||
}, {})
|
||||
|
||||
const convertObjectToArray = (arr) => Object.values(arr)
|
||||
|
||||
const convertArrayToHash = (arr, key) =>
|
||||
arr.reduce((obj, item, i) => {
|
||||
obj[item[key]] = i
|
||||
return obj
|
||||
}, {})
|
||||
|
||||
export { convertArrayToHash, convertArrayToObject, convertObjectToArray }
|
||||
@@ -1,23 +1,179 @@
|
||||
export const deepCopyObject = (inObject) => {
|
||||
if (inObject instanceof Date) return inObject
|
||||
import type { JsonValue } from '../types/index.js'
|
||||
|
||||
if (inObject instanceof Set) return new Set(inObject)
|
||||
/*
|
||||
Main deepCopyObject handling - from rfdc: https://github.com/davidmarkclements/rfdc/blob/master/index.js
|
||||
|
||||
if (inObject instanceof Map) return new Map(inObject)
|
||||
Copyright 2019 "David Mark Clements <david.mark.clements@gmail.com>"
|
||||
|
||||
if (typeof inObject !== 'object' || inObject === null) {
|
||||
return inObject // Return the value if inObject is not an object
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
|
||||
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
function copyBuffer(cur) {
|
||||
if (cur instanceof Buffer) {
|
||||
return Buffer.from(cur)
|
||||
}
|
||||
|
||||
// Create an array or object to hold the values
|
||||
const outObject = Array.isArray(inObject) ? [] : {}
|
||||
|
||||
Object.keys(inObject).forEach((key) => {
|
||||
const value = inObject[key]
|
||||
|
||||
// Recursively (deep) copy for nested objects, including arrays
|
||||
outObject[key] = typeof value === 'object' && value !== null ? deepCopyObject(value) : value
|
||||
})
|
||||
|
||||
return outObject
|
||||
return new cur.constructor(cur.buffer.slice(), cur.byteOffset, cur.length)
|
||||
}
|
||||
|
||||
const constructorHandlers = new Map()
|
||||
constructorHandlers.set(Date, (o) => new Date(o))
|
||||
constructorHandlers.set(Map, (o, fn) => new Map(cloneArray<any>(Array.from(o), fn)))
|
||||
constructorHandlers.set(Set, (o, fn) => new Set(cloneArray(Array.from(o), fn)))
|
||||
let handler = null
|
||||
|
||||
function cloneArray<T>(a: T, fn): T {
|
||||
const keys = Object.keys(a)
|
||||
const a2 = new Array(keys.length)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i]
|
||||
const cur = a[k]
|
||||
if (typeof cur !== 'object' || cur === null) {
|
||||
a2[k] = cur
|
||||
} else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) {
|
||||
a2[k] = handler(cur, fn)
|
||||
} else if (ArrayBuffer.isView(cur)) {
|
||||
a2[k] = copyBuffer(cur)
|
||||
} else {
|
||||
a2[k] = fn(cur)
|
||||
}
|
||||
}
|
||||
return a2 as T
|
||||
}
|
||||
|
||||
export const deepCopyObject = <T>(o: T): T => {
|
||||
if (typeof o !== 'object' || o === null) return o
|
||||
if (Array.isArray(o)) return cloneArray(o, deepCopyObject)
|
||||
if (o.constructor !== Object && (handler = constructorHandlers.get(o.constructor))) {
|
||||
return handler(o, deepCopyObject)
|
||||
}
|
||||
const o2 = {}
|
||||
for (const k in o) {
|
||||
if (Object.hasOwnProperty.call(o, k) === false) continue
|
||||
const cur = o[k]
|
||||
if (typeof cur !== 'object' || cur === null) {
|
||||
o2[k as string] = cur
|
||||
} else if (cur.constructor !== Object && (handler = constructorHandlers.get(cur.constructor))) {
|
||||
o2[k as string] = handler(cur, deepCopyObject)
|
||||
} else if (ArrayBuffer.isView(cur)) {
|
||||
o2[k as string] = copyBuffer(cur)
|
||||
} else {
|
||||
o2[k as string] = deepCopyObject(cur)
|
||||
}
|
||||
}
|
||||
return o2 as T
|
||||
}
|
||||
|
||||
/*
|
||||
Fast deepCopyObjectSimple handling - from fast-json-clone: https://github.com/rhysd/fast-json-clone
|
||||
|
||||
Benchmark: https://github.com/AlessioGr/fastest-deep-clone-json/blob/main/test/benchmark.js
|
||||
*/
|
||||
|
||||
/**
|
||||
* A deepCopyObject implementation which only works for JSON objects and arrays, and is faster than
|
||||
* JSON.parse(JSON.stringify(obj))
|
||||
*
|
||||
* @param value The JSON value to be cloned. There are two invariants. 1) It must not contain circles
|
||||
* as JSON does not allow it. This function will cause infinite loop for such values by
|
||||
* design. 2) It must contain JSON values only. Other values like `Date`, `Regexp`, `Map`,
|
||||
* `Set`, `Buffer`, ... are not allowed.
|
||||
* @returns The cloned JSON value.
|
||||
*/
|
||||
export function deepCopyObjectSimple<T extends JsonValue>(value: T): T {
|
||||
if (typeof value !== 'object' || value === null) {
|
||||
return value
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.map((e) =>
|
||||
typeof e !== 'object' || e === null ? e : deepCopyObjectSimple(e),
|
||||
) as T
|
||||
} else {
|
||||
if (value instanceof Date) return new Date(value) as unknown as T
|
||||
const ret: { [key: string]: T } = {}
|
||||
for (const k in value) {
|
||||
const v = value[k]
|
||||
ret[k] = typeof v !== 'object' || v === null ? v : (deepCopyObjectSimple(v as T) as any)
|
||||
}
|
||||
return ret as unknown as T
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A deepCopyObject implementation which is slower than deepCopyObject, but more correct.
|
||||
* Can be used if correctness is more important than speed. Supports circular dependencies
|
||||
*/
|
||||
export function deepCopyObjectComplex<T>(object: T, cache: WeakMap<any, any> = new WeakMap()): T {
|
||||
if (object === null) return null
|
||||
|
||||
if (cache.has(object)) {
|
||||
return cache.get(object)
|
||||
}
|
||||
|
||||
// Handle Date
|
||||
if (object instanceof Date) {
|
||||
return new Date(object.getTime()) as unknown as T
|
||||
}
|
||||
|
||||
// Handle RegExp
|
||||
if (object instanceof RegExp) {
|
||||
return new RegExp(object.source, object.flags) as unknown as T
|
||||
}
|
||||
|
||||
// Handle Map
|
||||
if (object instanceof Map) {
|
||||
const clonedMap = new Map()
|
||||
cache.set(object, clonedMap)
|
||||
for (const [key, value] of object.entries()) {
|
||||
clonedMap.set(key, deepCopyObjectComplex(value, cache))
|
||||
}
|
||||
return clonedMap as unknown as T
|
||||
}
|
||||
|
||||
// Handle Set
|
||||
if (object instanceof Set) {
|
||||
const clonedSet = new Set()
|
||||
cache.set(object, clonedSet)
|
||||
for (const value of object.values()) {
|
||||
clonedSet.add(deepCopyObjectComplex(value, cache))
|
||||
}
|
||||
return clonedSet as unknown as T
|
||||
}
|
||||
|
||||
// Handle Array and Object
|
||||
if (typeof object === 'object' && object !== null) {
|
||||
if ('$$typeof' in object && typeof object.$$typeof === 'symbol') {
|
||||
return object
|
||||
}
|
||||
|
||||
const clonedObject: any = Array.isArray(object)
|
||||
? []
|
||||
: Object.create(Object.getPrototypeOf(object))
|
||||
cache.set(object, clonedObject)
|
||||
|
||||
for (const key in object) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(object, key) ||
|
||||
Object.getOwnPropertySymbols(object).includes(key as any)
|
||||
) {
|
||||
clonedObject[key] = deepCopyObjectComplex(object[key], cache)
|
||||
}
|
||||
}
|
||||
|
||||
return clonedObject as T
|
||||
}
|
||||
|
||||
// Handle all other cases
|
||||
return object
|
||||
}
|
||||
|
||||
@@ -1,15 +1,46 @@
|
||||
export function deepMerge(obj1, obj2) {
|
||||
const output = { ...obj1 }
|
||||
import deepMerge from 'deepmerge'
|
||||
|
||||
for (const key in obj2) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj2, key)) {
|
||||
if (typeof obj2[key] === 'object' && !Array.isArray(obj2[key]) && obj1[key]) {
|
||||
output[key] = deepMerge(obj1[key], obj2[key])
|
||||
} else {
|
||||
output[key] = obj2[key]
|
||||
}
|
||||
import { isPlainObject } from './isPlainObject.js'
|
||||
|
||||
export { deepMerge }
|
||||
/**
|
||||
* Fully-featured deepMerge.
|
||||
*
|
||||
* Array handling: Arrays in the target object are combined with the source object's arrays.
|
||||
*/
|
||||
export function deepMergeWithCombinedArrays<T extends object>(obj1: object, obj2: object): T {
|
||||
return deepMerge<T>(obj1, obj2, {
|
||||
arrayMerge: (target, source, options) => {
|
||||
const destination = target.slice()
|
||||
|
||||
source.forEach((item, index) => {
|
||||
if (typeof destination[index] === 'undefined') {
|
||||
destination[index] = options.cloneUnlessOtherwiseSpecified(item, options)
|
||||
} else if (options.isMergeableObject(item)) {
|
||||
destination[index] = deepMerge(target[index], item, options)
|
||||
} else if (target.indexOf(item) === -1) {
|
||||
destination.push(item)
|
||||
}
|
||||
})
|
||||
return destination
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return output
|
||||
/**
|
||||
* Fully-featured deepMerge.
|
||||
*
|
||||
* Array handling: Arrays in the target object are replaced by the source object's arrays.
|
||||
*/
|
||||
export function deepMergeWithSourceArrays<T extends object>(obj1: object, obj2: object): T {
|
||||
return deepMerge<T>(obj1, obj2, { arrayMerge: (_, source) => source })
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully-featured deepMerge. Does not clone React components by default.
|
||||
*/
|
||||
export function deepMergeWithReactComponents<T extends object>(obj1: object, obj2: object): T {
|
||||
return deepMerge<T>(obj1, obj2, {
|
||||
isMergeableObject: isPlainObject,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export default (variable) =>
|
||||
getComputedStyle(document.documentElement).getPropertyValue(`--${variable}`)
|
||||
@@ -1,29 +1,11 @@
|
||||
function isObject(o: unknown): boolean {
|
||||
return Object.prototype.toString.call(o) === '[object Object]'
|
||||
}
|
||||
import { isReactComponentOrFunction } from './isReactComponent.js'
|
||||
|
||||
export function isPlainObject(o: unknown): boolean {
|
||||
export function isPlainObject(o: any): boolean {
|
||||
// Is this a React component?
|
||||
if (typeof o === 'object' && '$$typeof' in o && typeof o.$$typeof === 'symbol') {
|
||||
if (isReactComponentOrFunction(o)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isObject(o) === false) return false
|
||||
|
||||
// If has modified constructor
|
||||
const ctor = o.constructor
|
||||
if (ctor === undefined) return true
|
||||
|
||||
// If has modified prototype
|
||||
const prot = ctor.prototype
|
||||
if (isObject(prot) === false) return false
|
||||
|
||||
// If constructor does not have an Object-specific method
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (prot.hasOwnProperty('isPrototypeOf') === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Most likely a plain Object
|
||||
return true
|
||||
// from https://github.com/fastify/deepmerge/blob/master/index.js#L77
|
||||
return typeof o === 'object' && o !== null && !(o instanceof RegExp) && !(o instanceof Date)
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
const overwriteMerge = (_, sourceArray) => sourceArray
|
||||
|
||||
export default overwriteMerge
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { User } from '../../../auth/types.js'
|
||||
import type { Payload } from '../../../index.js'
|
||||
import type { PayloadRequest } from '../../../types/index.js'
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { PayloadRequest, Where } from '../../types/index.js'
|
||||
import { hasWhereAccessResult } from '../../auth/index.js'
|
||||
import { combineQueries } from '../../database/combineQueries.js'
|
||||
import { docHasTimestamps } from '../../types/index.js'
|
||||
import { deepCopyObjectSimple } from '../../utilities/deepCopyObject.js'
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
|
||||
import { appendVersionToQueryKey } from './appendVersionToQueryKey.js'
|
||||
|
||||
@@ -84,7 +85,7 @@ const replaceWithDraftIfAvailable = async <T extends TypeWithID>({
|
||||
return doc
|
||||
}
|
||||
|
||||
draft = JSON.parse(JSON.stringify(draft))
|
||||
draft = deepCopyObjectSimple(draft)
|
||||
draft = sanitizeInternalFields(draft)
|
||||
|
||||
// Patch globalType onto version doc
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { SanitizedCollectionConfig, TypeWithID } from '../collections/config/types.js'
|
||||
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
|
||||
import type { Payload } from '../index.js'
|
||||
import type { Payload } from '../index.js';
|
||||
import type { PayloadRequest } from '../types/index.js'
|
||||
|
||||
import { deepCopyObject } from '../utilities/deepCopyObject.js'
|
||||
import { deepCopyObjectSimple } from '../index.js'
|
||||
import sanitizeInternalFields from '../utilities/sanitizeInternalFields.js'
|
||||
import { enforceMaxVersions } from './enforceMaxVersions.js'
|
||||
|
||||
@@ -31,7 +31,7 @@ export const saveVersion = async ({
|
||||
let result
|
||||
let createNewVersion = true
|
||||
const now = new Date().toISOString()
|
||||
const versionData = deepCopyObject(doc)
|
||||
const versionData = deepCopyObjectSimple(doc)
|
||||
if (draft) versionData._status = 'draft'
|
||||
if (versionData._id) delete versionData._id
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"deepmerge": "^4.2.2",
|
||||
"escape-html": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Block, CollectionConfig, Field } from 'payload'
|
||||
|
||||
import merge from 'deepmerge'
|
||||
import { deepMergeWithSourceArrays } from 'payload'
|
||||
|
||||
import type { FieldConfig, FormBuilderPluginConfig } from '../../types.js'
|
||||
|
||||
@@ -84,9 +84,7 @@ export const generateFormCollection = (formConfig: FormBuilderPluginConfig): Col
|
||||
}
|
||||
|
||||
if (typeof block === 'object' && typeof fieldConfig === 'object') {
|
||||
return merge<FieldConfig>(block, fieldConfig, {
|
||||
arrayMerge: (_, sourceArray) => sourceArray,
|
||||
})
|
||||
return deepMergeWithSourceArrays(block, fieldConfig)
|
||||
}
|
||||
|
||||
if (typeof block === 'function') {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { CollectionAfterChangeHook, CollectionConfig, PayloadRequest } from 'payload'
|
||||
import type {
|
||||
CollectionAfterChangeHook,
|
||||
CollectionConfig,
|
||||
JsonObject,
|
||||
PayloadRequest,
|
||||
} from 'payload'
|
||||
|
||||
import type { NestedDocsPluginConfig } from '../types.js'
|
||||
|
||||
@@ -6,7 +11,7 @@ import { populateBreadcrumbs } from '../utilities/populateBreadcrumbs.js'
|
||||
|
||||
type ResaveArgs = {
|
||||
collection: CollectionConfig
|
||||
doc: Record<string, unknown>
|
||||
doc: JsonObject
|
||||
draft: boolean
|
||||
pluginConfig: NestedDocsPluginConfig
|
||||
req: PayloadRequest
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
"test": "echo \"Error: no tests specified\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"deepmerge": "4.3.1"
|
||||
"@payloadcms/ui": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { CollectionConfig, Field } from 'payload'
|
||||
|
||||
import deepMerge from 'deepmerge'
|
||||
|
||||
import type { SearchPluginConfig } from '../types.js'
|
||||
|
||||
import { LinkToDoc } from './ui/index.js'
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Config, Field, GroupField, TabsField, TextField } from 'payload'
|
||||
|
||||
import { addDataAndFileToRequest } from '@payloadcms/next/utilities'
|
||||
import { withMergedProps } from '@payloadcms/ui/shared'
|
||||
import { deepMerge } from 'payload/shared'
|
||||
import { deepMergeSimple } from 'payload/shared'
|
||||
|
||||
import type {
|
||||
GenerateDescription,
|
||||
@@ -298,7 +298,7 @@ export const seoPlugin =
|
||||
i18n: {
|
||||
...config.i18n,
|
||||
translations: {
|
||||
...deepMerge(translations, config.i18n?.translations),
|
||||
...deepMergeSimple(translations, config.i18n?.translations),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ export {
|
||||
sanitizeClientFeatures,
|
||||
} from '../../lexical/config/client/sanitize.js'
|
||||
export { CAN_USE_DOM } from '../../lexical/utils/canUseDOM.js'
|
||||
export { cloneDeep } from '../../lexical/utils/cloneDeep.js'
|
||||
export { getDOMRangeRect } from '../../lexical/utils/getDOMRangeRect.js'
|
||||
export { getSelectedNode } from '../../lexical/utils/getSelectedNode.js'
|
||||
export { isHTMLElement } from '../../lexical/utils/guard.js'
|
||||
|
||||
@@ -9,12 +9,14 @@ import type {
|
||||
NodeKey,
|
||||
Spread,
|
||||
} from 'lexical'
|
||||
import type { JsonObject } from 'payload'
|
||||
|
||||
import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode.js'
|
||||
import ObjectID from 'bson-objectid'
|
||||
import { deepCopyObjectSimple } from 'payload/shared'
|
||||
import React, { type JSX } from 'react'
|
||||
|
||||
export type BlockFields<TBlockFields extends object = Record<string, unknown>> = {
|
||||
export type BlockFields<TBlockFields extends JsonObject = JsonObject> = {
|
||||
/** Block form data */
|
||||
blockName: string
|
||||
blockType: string
|
||||
@@ -27,7 +29,7 @@ const BlockComponent = React.lazy(() =>
|
||||
})),
|
||||
)
|
||||
|
||||
export type SerializedBlockNode<TBlockFields extends object = Record<string, unknown>> = Spread<
|
||||
export type SerializedBlockNode<TBlockFields extends JsonObject = JsonObject> = Spread<
|
||||
{
|
||||
children?: never // required so that our typed editor state doesn't automatically add children
|
||||
fields: BlockFields<TBlockFields>
|
||||
@@ -117,7 +119,7 @@ export class BlockNode extends DecoratorBlockNode {
|
||||
}
|
||||
|
||||
setFields(fields: BlockFields): void {
|
||||
const fieldsCopy = JSON.parse(JSON.stringify(fields)) as BlockFields
|
||||
const fieldsCopy = deepCopyObjectSimple(fields)
|
||||
|
||||
const writable = this.getWritable()
|
||||
writable.__fields = fieldsCopy
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
|
||||
import ObjectID from 'bson-objectid'
|
||||
import { DecoratorNode } from 'lexical'
|
||||
import { deepCopyObjectSimple } from 'payload/shared'
|
||||
import React, { type JSX } from 'react'
|
||||
|
||||
export type InlineBlockFields = {
|
||||
@@ -112,7 +113,7 @@ export class InlineBlockNode extends DecoratorNode<React.ReactElement> {
|
||||
}
|
||||
|
||||
setFields(fields: InlineBlockFields): void {
|
||||
const fieldsCopy = JSON.parse(JSON.stringify(fields)) as InlineBlockFields
|
||||
const fieldsCopy = deepCopyObjectSimple(fields) as InlineBlockFields
|
||||
|
||||
const writable = this.getWritable()
|
||||
writable.__fields = fieldsCopy
|
||||
|
||||
@@ -99,12 +99,14 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
||||
if (!node) {
|
||||
return false
|
||||
}
|
||||
|
||||
node.setFields(fields as BlockFields)
|
||||
|
||||
setTargetNodeKey(null)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
const inlineBlockNode = $createInlineBlockNode(fields as BlockFields)
|
||||
$insertNodes([inlineBlockNode])
|
||||
if ($isRootOrShadowRoot(inlineBlockNode.getParentOrThrow())) {
|
||||
@@ -118,9 +120,10 @@ export const BlocksPlugin: PluginComponent<BlocksFeatureClientProps> = () => {
|
||||
editor.registerCommand(
|
||||
OPEN_INLINE_BLOCK_DRAWER_COMMAND,
|
||||
({ fields, nodeKey }) => {
|
||||
|
||||
setBlockFields((fields as BlockFields) ?? null)
|
||||
setTargetNodeKey(nodeKey ?? null)
|
||||
setBlockType((fields as BlockFields)?.blockType ?? ('' as any))
|
||||
setBlockType(fields?.blockType ?? ('' as any))
|
||||
|
||||
if (nodeKey) {
|
||||
toggleModal(drawerSlug)
|
||||
|
||||
@@ -91,7 +91,7 @@ export const LinkFeature = createServerFeature<
|
||||
// Thus, for tasks like validation, we do not want to pass it a text field in the schema which will never have data.
|
||||
// Otherwise, it will cause a validation error (field is required).
|
||||
const sanitizedFieldsWithoutText = deepCopyObject(sanitizedFields).filter(
|
||||
(field) => field.name !== 'text',
|
||||
(field) => !('name' in field) || field.name !== 'text',
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { SerializedElementNode, SerializedLexicalNode, Spread } from 'lexical'
|
||||
import type { JsonValue } from 'payload'
|
||||
|
||||
export type LinkFields = {
|
||||
// unknown, custom fields:
|
||||
[key: string]: unknown
|
||||
[key: string]: JsonValue
|
||||
doc: {
|
||||
relationTo: string
|
||||
value:
|
||||
| {
|
||||
// Actual doc data, populated in afterRead hook
|
||||
[key: string]: unknown
|
||||
[key: string]: JsonValue
|
||||
id: string
|
||||
}
|
||||
| string
|
||||
|
||||
@@ -7,7 +7,10 @@ import { getBaseFields } from '../../drawer/baseFields.js'
|
||||
*/
|
||||
export function transformExtraFields(
|
||||
customFieldSchema:
|
||||
| ((args: { config: SanitizedConfig; defaultFields: FieldAffectingData[] }) => Field[])
|
||||
| ((args: {
|
||||
config: SanitizedConfig
|
||||
defaultFields: FieldAffectingData[]
|
||||
}) => (Field | FieldAffectingData)[])
|
||||
| Field[],
|
||||
config: SanitizedConfig,
|
||||
enabledCollections?: CollectionSlug[],
|
||||
@@ -21,15 +24,15 @@ export function transformExtraFields(
|
||||
maxDepth,
|
||||
)
|
||||
|
||||
let fields: Field[]
|
||||
let fields: (Field | FieldAffectingData)[]
|
||||
|
||||
if (typeof customFieldSchema === 'function') {
|
||||
fields = customFieldSchema({ config, defaultFields: baseFields })
|
||||
} else if (Array.isArray(customFieldSchema)) {
|
||||
fields = customFieldSchema
|
||||
} else {
|
||||
fields = baseFields as Field[]
|
||||
fields = baseFields
|
||||
}
|
||||
|
||||
return fields
|
||||
return fields as Field[]
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
} from 'lexical'
|
||||
import type {
|
||||
Field,
|
||||
JsonObject,
|
||||
PayloadRequest,
|
||||
ReplaceAny,
|
||||
RequestContext,
|
||||
@@ -61,7 +62,7 @@ export type PopulationPromise<T extends SerializedLexicalNode = SerializedLexica
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDoc: JsonObject
|
||||
}) => void
|
||||
|
||||
export type NodeValidation<T extends SerializedLexicalNode = SerializedLexicalNode> = ({
|
||||
@@ -241,7 +242,7 @@ export type NodeWithHooks<T extends LexicalNode = any> = {
|
||||
getSubFieldsData?: (args: {
|
||||
node: ReturnType<ReplaceAny<T, LexicalNode>['exportJSON']>
|
||||
req: PayloadRequest
|
||||
}) => Record<string, unknown>
|
||||
}) => JsonObject
|
||||
/**
|
||||
* Allows you to run population logic when a node's data was requested from graphQL.
|
||||
* While `getSubFields` and `getSubFieldsData` automatically handle populating sub-fields (since they run hooks on them), those are only populated in the Rest API.
|
||||
@@ -397,7 +398,7 @@ export type SanitizedServerFeatures = {
|
||||
>
|
||||
getSubFieldsData?: Map<
|
||||
string,
|
||||
(args: { node: SerializedLexicalNode; req: PayloadRequest }) => Record<string, unknown>
|
||||
(args: { node: SerializedLexicalNode; req: PayloadRequest }) => JsonObject
|
||||
>
|
||||
graphQLPopulationPromises: Map<string, Array<PopulationPromise>>
|
||||
hooks?: {
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
NodeKey,
|
||||
Spread,
|
||||
} from 'lexical'
|
||||
import type { CollectionSlug } from 'payload'
|
||||
import type { CollectionSlug, JsonObject } from 'payload'
|
||||
import type { JSX } from 'react'
|
||||
|
||||
import { DecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode.js'
|
||||
@@ -21,10 +21,7 @@ const RawUploadComponent = React.lazy(() =>
|
||||
)
|
||||
|
||||
export type UploadData = {
|
||||
fields: {
|
||||
// unknown, custom fields:
|
||||
[key: string]: unknown
|
||||
}
|
||||
fields: JsonObject
|
||||
id: string
|
||||
relationTo: CollectionSlug
|
||||
value: number | string
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
afterReadTraverseFields,
|
||||
beforeChangeTraverseFields,
|
||||
beforeValidateTraverseFields,
|
||||
deepCopyObject,
|
||||
getDependencies,
|
||||
withNullableJSONSchemaType,
|
||||
} from 'payload'
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
sanitizeServerEditorConfig,
|
||||
sanitizeServerFeatures,
|
||||
} from './lexical/config/server/sanitize.js'
|
||||
import { cloneDeep } from './lexical/utils/cloneDeep.js'
|
||||
import { populateLexicalPopulationPromises } from './populateGraphQL/populateLexicalPopulationPromises.js'
|
||||
import { getGenerateComponentMap } from './utilities/generateComponentMap.js'
|
||||
import { getGenerateSchemaMap } from './utilities/generateSchemaMap.js'
|
||||
@@ -93,10 +93,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
defaultEditorConfig,
|
||||
config,
|
||||
)
|
||||
features = cloneDeep(defaultEditorFeatures)
|
||||
features = deepCopyObject(defaultEditorFeatures)
|
||||
}
|
||||
|
||||
finalSanitizedEditorConfig = cloneDeep(defaultSanitizedServerEditorConfig)
|
||||
finalSanitizedEditorConfig = deepCopyObject(defaultSanitizedServerEditorConfig)
|
||||
|
||||
resolvedFeatureMap = finalSanitizedEditorConfig.resolvedFeatureMap
|
||||
} else {
|
||||
@@ -109,12 +109,12 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
features =
|
||||
props.features && typeof props.features === 'function'
|
||||
? props.features({
|
||||
defaultFeatures: cloneDeep(defaultEditorFeatures),
|
||||
defaultFeatures: deepCopyObject(defaultEditorFeatures),
|
||||
rootFeatures: rootEditorFeatures,
|
||||
})
|
||||
: (props.features as FeatureProviderServer<unknown, unknown, unknown>[])
|
||||
if (!features) {
|
||||
features = cloneDeep(defaultEditorFeatures)
|
||||
features = deepCopyObject(defaultEditorFeatures)
|
||||
}
|
||||
|
||||
const lexical: LexicalEditorConfig = props.lexical
|
||||
@@ -463,8 +463,9 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
recurseNodeTree({
|
||||
nodeIDMap: originalNodeWithLocalesIDMap,
|
||||
nodes:
|
||||
(siblingDocWithLocales[field.name] as SerializedEditorState)?.root?.children ??
|
||||
[],
|
||||
|
||||
(siblingDocWithLocales[field.name] as unknown as SerializedEditorState)?.root
|
||||
?.children ?? [],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
export function cloneDeep<T>(object: T, cache: WeakMap<any, any> = new WeakMap()): T {
|
||||
if (object === null) return null
|
||||
|
||||
if (cache.has(object)) {
|
||||
return cache.get(object)
|
||||
}
|
||||
|
||||
// Handle Date
|
||||
if (object instanceof Date) {
|
||||
return new Date(object.getTime()) as unknown as T
|
||||
}
|
||||
|
||||
// Handle RegExp
|
||||
if (object instanceof RegExp) {
|
||||
return new RegExp(object.source, object.flags) as unknown as T
|
||||
}
|
||||
|
||||
// Handle Map
|
||||
if (object instanceof Map) {
|
||||
const clonedMap = new Map()
|
||||
cache.set(object, clonedMap)
|
||||
for (const [key, value] of object.entries()) {
|
||||
clonedMap.set(key, cloneDeep(value, cache))
|
||||
}
|
||||
return clonedMap as unknown as T
|
||||
}
|
||||
|
||||
// Handle Set
|
||||
if (object instanceof Set) {
|
||||
const clonedSet = new Set()
|
||||
cache.set(object, clonedSet)
|
||||
for (const value of object.values()) {
|
||||
clonedSet.add(cloneDeep(value, cache))
|
||||
}
|
||||
return clonedSet as unknown as T
|
||||
}
|
||||
|
||||
// Handle Array and Object
|
||||
if (typeof object === 'object' && object !== null) {
|
||||
if ('$$typeof' in object && typeof object.$$typeof === 'symbol') {
|
||||
return object
|
||||
}
|
||||
|
||||
const clonedObject: any = Array.isArray(object)
|
||||
? []
|
||||
: Object.create(Object.getPrototypeOf(object))
|
||||
cache.set(object, clonedObject)
|
||||
|
||||
for (const key in object) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(object, key) ||
|
||||
Object.getOwnPropertySymbols(object).includes(key as any)
|
||||
) {
|
||||
clonedObject[key] = cloneDeep(object[key], cache)
|
||||
}
|
||||
}
|
||||
|
||||
return clonedObject as T
|
||||
}
|
||||
|
||||
// Handle all other cases
|
||||
return object
|
||||
}
|
||||
@@ -59,6 +59,7 @@ export const populateLexicalPopulationPromises = ({
|
||||
}
|
||||
}
|
||||
},
|
||||
nodes: (siblingDoc[field?.name] as SerializedEditorState)?.root?.children ?? [],
|
||||
|
||||
nodes: (siblingDoc[field?.name] as unknown as SerializedEditorState)?.root?.children ?? [],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Field, PayloadRequest, RequestContext } from 'payload'
|
||||
import type { Field, JsonObject, PayloadRequest, RequestContext } from 'payload'
|
||||
|
||||
import { afterReadTraverseFields } from 'payload'
|
||||
|
||||
@@ -25,7 +25,7 @@ type NestedRichTextFieldsArgs = {
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
siblingDoc: Record<string, unknown>
|
||||
siblingDoc: JsonObject
|
||||
}
|
||||
|
||||
export const recursivelyPopulateFieldsForGraphQL = ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { Data, FormState } from 'payload'
|
||||
import type { Data, FormState, JsonObject } from 'payload'
|
||||
|
||||
import { Drawer } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
@@ -12,7 +12,7 @@ export type FieldsDrawerProps = {
|
||||
drawerSlug: string
|
||||
drawerTitle?: string
|
||||
featureKey: string
|
||||
handleDrawerSubmit: (fields: FormState, data: Record<string, unknown>) => void
|
||||
handleDrawerSubmit: (fields: FormState, data: JsonObject) => void
|
||||
schemaPathSuffix?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
"types": "./src/exports/all.ts",
|
||||
"default": "./src/exports/all.ts"
|
||||
},
|
||||
"./utilities": {
|
||||
"import": "./src/exports/utilities.ts",
|
||||
"types": "./src/exports/utilities.ts",
|
||||
"default": "./src/exports/utilities.ts"
|
||||
},
|
||||
"./languages/*": {
|
||||
"import": "./src/languages/*.ts",
|
||||
"types": "./src/languages/*.ts",
|
||||
@@ -61,6 +66,11 @@
|
||||
"types": "./dist/exports/all.d.ts",
|
||||
"default": "./dist/exports/all.js"
|
||||
},
|
||||
"./utilities": {
|
||||
"import": "./dist/exports/utilities.js",
|
||||
"types": "./dist/exports/utilities.d.ts",
|
||||
"default": "./dist/exports/utilities.js"
|
||||
},
|
||||
"./languages/*": {
|
||||
"import": "./dist/languages/*.js",
|
||||
"types": "./dist/languages/*.d.ts",
|
||||
|
||||
@@ -10,8 +10,7 @@ import type {
|
||||
GenericTranslationsObject,
|
||||
} from '../../src/types.js'
|
||||
|
||||
import { cloneDeep } from '../../src/utilities/cloneDeep.js'
|
||||
import { deepMerge } from '../../src/utilities/deepMerge.js'
|
||||
import { deepMergeSimple } from '../../src/utilities/deepMergeSimple.js'
|
||||
import { acceptedLanguages } from '../../src/utilities/languages.js'
|
||||
import { applyEslintFixes } from './applyEslintFixes.js'
|
||||
import { findMissingKeys } from './findMissingKeys.js'
|
||||
@@ -83,7 +82,7 @@ export async function translateObject(props: {
|
||||
dateFNSKey: string
|
||||
translations: GenericTranslationsObject
|
||||
}
|
||||
} = cloneDeep(allTranslationsObject)
|
||||
} = JSON.parse(JSON.stringify(allTranslationsObject))
|
||||
const allOnlyNewTranslatedTranslationsObject: GenericLanguages = {}
|
||||
|
||||
const translationPromises: Promise<void>[] = []
|
||||
@@ -160,7 +159,7 @@ export async function translateObject(props: {
|
||||
targetObj[keys[keys.length - 1]] = translated
|
||||
|
||||
allTranslatedTranslationsObject[targetLang].translations = sortKeys(
|
||||
deepMerge(
|
||||
deepMergeSimple(
|
||||
allTranslatedTranslationsObject[targetLang].translations,
|
||||
allOnlyNewTranslatedTranslationsObject[targetLang],
|
||||
),
|
||||
|
||||
1
packages/translations/src/exports/utilities.ts
Normal file
1
packages/translations/src/exports/utilities.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { deepMergeSimple } from '../utilities/deepMergeSimple.js'
|
||||
@@ -1,63 +0,0 @@
|
||||
export function cloneDeep<T>(object: T, cache: WeakMap<any, any> = new WeakMap()): T {
|
||||
if (object === null) return null
|
||||
|
||||
if (cache.has(object)) {
|
||||
return cache.get(object)
|
||||
}
|
||||
|
||||
// Handle Date
|
||||
if (object instanceof Date) {
|
||||
return new Date(object.getTime()) as unknown as T
|
||||
}
|
||||
|
||||
// Handle RegExp
|
||||
if (object instanceof RegExp) {
|
||||
return new RegExp(object.source, object.flags) as unknown as T
|
||||
}
|
||||
|
||||
// Handle Map
|
||||
if (object instanceof Map) {
|
||||
const clonedMap = new Map()
|
||||
cache.set(object, clonedMap)
|
||||
for (const [key, value] of object.entries()) {
|
||||
clonedMap.set(key, cloneDeep(value, cache))
|
||||
}
|
||||
return clonedMap as unknown as T
|
||||
}
|
||||
|
||||
// Handle Set
|
||||
if (object instanceof Set) {
|
||||
const clonedSet = new Set()
|
||||
cache.set(object, clonedSet)
|
||||
for (const value of object.values()) {
|
||||
clonedSet.add(cloneDeep(value, cache))
|
||||
}
|
||||
return clonedSet as unknown as T
|
||||
}
|
||||
|
||||
// Handle Array and Object
|
||||
if (typeof object === 'object' && object !== null) {
|
||||
if ('$$typeof' in object && typeof object.$$typeof === 'symbol') {
|
||||
return object
|
||||
}
|
||||
|
||||
const clonedObject: any = Array.isArray(object)
|
||||
? []
|
||||
: Object.create(Object.getPrototypeOf(object))
|
||||
cache.set(object, clonedObject)
|
||||
|
||||
for (const key in object) {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(object, key) ||
|
||||
Object.getOwnPropertySymbols(object).includes(key as any)
|
||||
) {
|
||||
clonedObject[key] = cloneDeep(object[key], cache)
|
||||
}
|
||||
}
|
||||
|
||||
return clonedObject as T
|
||||
}
|
||||
|
||||
// Handle all other cases
|
||||
return object
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* obj2 has priority over obj1
|
||||
*
|
||||
* Merges obj2 into obj1. Does not handle arrays
|
||||
*/
|
||||
export function deepMerge(obj1: any, obj2: any, doNotMergeInNulls = true) {
|
||||
const output = { ...obj1 }
|
||||
|
||||
for (const key in obj2) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj2, key)) {
|
||||
if (doNotMergeInNulls) {
|
||||
if (
|
||||
(obj2[key] === null || obj2[key] === undefined) &&
|
||||
obj1[key] !== null &&
|
||||
obj1[key] !== undefined
|
||||
) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj2[key] === 'object' && obj1[key]) {
|
||||
// Existing behavior for objects
|
||||
output[key] = deepMerge(obj1[key], obj2[key], doNotMergeInNulls)
|
||||
} else {
|
||||
// Direct assignment for values
|
||||
output[key] = obj2[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
25
packages/translations/src/utilities/deepMergeSimple.ts
Normal file
25
packages/translations/src/utilities/deepMergeSimple.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Very simple, but fast deepMerge implementation. Only deepMerges objects, not arrays and clones everything.
|
||||
* Do not use this if your object contains any complex objects like React Components, or if you would like to combine Arrays.
|
||||
* If you only have simple objects and need a fast deepMerge, this is the function for you.
|
||||
*
|
||||
* obj2 takes precedence over obj1 - thus if obj2 has a key that obj1 also has, obj2's value will be used.
|
||||
*
|
||||
* @param obj1 base object
|
||||
* @param obj2 object to merge "into" obj1
|
||||
*/
|
||||
export function deepMergeSimple<T = object>(obj1: object, obj2: object): T {
|
||||
const output = { ...obj1 }
|
||||
|
||||
for (const key in obj2) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj2, key)) {
|
||||
if (typeof obj2[key] === 'object' && !Array.isArray(obj2[key]) && obj1[key]) {
|
||||
output[key] = deepMergeSimple(obj1[key], obj2[key])
|
||||
} else {
|
||||
output[key] = obj2[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output as T
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
} from '../types.js'
|
||||
|
||||
import { importDateFNSLocale } from '../importDateFNSLocale.js'
|
||||
import { deepMerge } from './deepMerge.js'
|
||||
import { deepMergeSimple } from './deepMergeSimple.js'
|
||||
import { getTranslationsByContext } from './getTranslationsByContext.js'
|
||||
|
||||
/**
|
||||
@@ -143,7 +143,9 @@ export function t<
|
||||
|
||||
const initTFunction: InitTFunction = (args) => {
|
||||
const { config, language, translations } = args
|
||||
const mergedTranslations = deepMerge(translations, config?.translations?.[language] ?? {})
|
||||
const mergedTranslations = config?.translations?.[language]
|
||||
? deepMergeSimple<DefaultTranslationsObject>(translations, config?.translations?.[language])
|
||||
: translations
|
||||
|
||||
return {
|
||||
t: (key, vars) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { deepMerge, isReactServerComponentOrFunction, serverProps } from 'payload/shared'
|
||||
import { isReactServerComponentOrFunction, serverProps } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
@@ -37,7 +37,7 @@ export function withMergedProps<ToMergeIntoProps, CompleteReturnProps>({
|
||||
}
|
||||
// A wrapper around the args.Component to inject the args.toMergeArgs as props, which are merged with the passed props
|
||||
const MergedPropsComponent: React.FC<CompleteReturnProps> = (passedProps) => {
|
||||
const mergedProps = deepMerge(passedProps, toMergeIntoProps)
|
||||
const mergedProps = simpleMergeProps(passedProps, toMergeIntoProps) as CompleteReturnProps
|
||||
|
||||
if (sanitizeServerOnlyProps) {
|
||||
serverProps.forEach((prop) => {
|
||||
@@ -50,3 +50,7 @@ export function withMergedProps<ToMergeIntoProps, CompleteReturnProps>({
|
||||
|
||||
return MergedPropsComponent
|
||||
}
|
||||
|
||||
function simpleMergeProps(props, toMerge) {
|
||||
return { ...props, ...toMerge }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { FormField, FormState, Row } from 'payload'
|
||||
|
||||
import ObjectIdImport from 'bson-objectid'
|
||||
import { dequal } from 'dequal/lite' // lite: no need for Map and Set support
|
||||
import { deepCopyObject } from 'payload/shared'
|
||||
import { deepCopyObject, deepCopyObjectSimple } from 'payload/shared'
|
||||
|
||||
import type { FieldAction } from './types.js'
|
||||
|
||||
@@ -249,11 +249,14 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState {
|
||||
const { remainingFields, rows } = separateRows(path, state)
|
||||
const rowsMetadata = [...(state[path].rows || [])]
|
||||
|
||||
const duplicateRowMetadata = deepCopyObject(rowsMetadata[rowIndex])
|
||||
const duplicateRowMetadata = deepCopyObjectSimple(rowsMetadata[rowIndex])
|
||||
if (duplicateRowMetadata.id) duplicateRowMetadata.id = new ObjectId().toHexString()
|
||||
|
||||
const duplicateRowState = deepCopyObject(rows[rowIndex])
|
||||
if (duplicateRowState.id) duplicateRowState.id = new ObjectId().toHexString()
|
||||
if (duplicateRowState.id) {
|
||||
duplicateRowState.id.value = new ObjectId().toHexString()
|
||||
duplicateRowState.id.initialValue = new ObjectId().toHexString()
|
||||
}
|
||||
|
||||
// If there are subfields
|
||||
if (Object.keys(duplicateRowState).length > 0) {
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -258,9 +258,6 @@ importers:
|
||||
bson-objectid:
|
||||
specifier: 2.0.4
|
||||
version: 2.0.4
|
||||
deepmerge:
|
||||
specifier: 4.3.1
|
||||
version: 4.3.1
|
||||
http-status:
|
||||
specifier: 1.6.2
|
||||
version: 1.6.2
|
||||
@@ -868,9 +865,6 @@ importers:
|
||||
'@payloadcms/ui':
|
||||
specifier: workspace:*
|
||||
version: link:../ui
|
||||
deepmerge:
|
||||
specifier: ^4.2.2
|
||||
version: 4.3.1
|
||||
escape-html:
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
@@ -951,9 +945,6 @@ importers:
|
||||
'@payloadcms/ui':
|
||||
specifier: workspace:*
|
||||
version: link:../ui
|
||||
deepmerge:
|
||||
specifier: 4.3.1
|
||||
version: 4.3.1
|
||||
react:
|
||||
specifier: ^19.0.0-rc-6230622a1a-20240610
|
||||
version: 19.0.0-rc-fb9a90fa48-20240614
|
||||
|
||||
@@ -489,7 +489,7 @@ describe('Auth', () => {
|
||||
expect(lockedUser.docs[0].loginAttempts).toBe(2)
|
||||
expect(lockedUser.docs[0].lockUntil).toBeDefined()
|
||||
|
||||
const manuallyReleaseLock = new Date(Date.now() - 605 * 1000)
|
||||
const manuallyReleaseLock = new Date(Date.now() - 605 * 1000).toISOString()
|
||||
const userLockElapsed = await payload.update({
|
||||
collection: slug,
|
||||
data: {
|
||||
@@ -503,7 +503,7 @@ describe('Auth', () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(userLockElapsed.docs[0].lockUntil).toEqual(manuallyReleaseLock.toISOString())
|
||||
expect(userLockElapsed.docs[0].lockUntil).toEqual(manuallyReleaseLock)
|
||||
|
||||
// login
|
||||
await restClient.POST(`/${slug}/login`, {
|
||||
|
||||
@@ -286,7 +286,7 @@ describe('Localization', () => {
|
||||
const post = await payload.create({
|
||||
collection: localizedSortSlug,
|
||||
data: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
title: `EN ${i}`,
|
||||
},
|
||||
locale: englishLocale,
|
||||
@@ -296,7 +296,7 @@ describe('Localization', () => {
|
||||
id: post.id,
|
||||
collection: localizedSortSlug,
|
||||
data: {
|
||||
date: new Date(),
|
||||
date: new Date().toISOString(),
|
||||
title: `ES ${i}`,
|
||||
},
|
||||
locale: spanishLocale,
|
||||
|
||||
Reference in New Issue
Block a user