fix: field paths within hooks (#10638)
Field paths within hooks are not correct. For example, an unnamed tab containing a group field and nested text field should have the path: - `myGroupField.myTextField` However, within hooks that path is formatted as: - `_index-1.myGroupField.myTextField` The leading index shown above should not exist, as this field is considered top-level since it is located within an unnamed tab. This discrepancy is only evident through the APIs themselves, such as when creating a request with invalid data and reading the validation errors in the response. Form state contains proper field paths, which is ultimately why this issue was never caught. This is because within the admin panel we merge the API response with the current form state, obscuring the underlying issue. This becomes especially obvious in #10580, where we no longer initialize validation errors within form state until the form has been submitted, and instead rely solely on the API response for the initial error state. Here's comprehensive example of how field paths _should_ be formatted: ``` { // ... fields: [ { // path: 'topLevelNamedField' // schemaPath: 'topLevelNamedField' // indexPath: '' name: 'topLevelNamedField', type: 'text', }, { // path: 'array' // schemaPath: 'array' // indexPath: '' name: 'array', type: 'array', fields: [ { // path: 'array.[n].fieldWithinArray' // schemaPath: 'array.fieldWithinArray' // indexPath: '' name: 'fieldWithinArray', type: 'text', }, { // path: 'array.[n].nestedArray' // schemaPath: 'array.nestedArray' // indexPath: '' name: 'nestedArray', type: 'array', fields: [ { // path: 'array.[n].nestedArray.[n].fieldWithinNestedArray' // schemaPath: 'array.nestedArray.fieldWithinNestedArray' // indexPath: '' name: 'fieldWithinNestedArray', type: 'text', }, ], }, { // path: 'array.[n]._index-2' // schemaPath: 'array._index-2' // indexPath: '2' type: 'row', fields: [ { // path: 'array.[n].fieldWithinRowWithinArray' // schemaPath: 'array._index-2.fieldWithinRowWithinArray' // indexPath: '' name: 'fieldWithinRowWithinArray', type: 'text', }, ], }, ], }, { // path: '_index-2' // schemaPath: '_index-2' // indexPath: '2' type: 'row', fields: [ { // path: 'fieldWithinRow' // schemaPath: '_index-2.fieldWithinRow' // indexPath: '' name: 'fieldWithinRow', type: 'text', }, ], }, { // path: '_index-3' // schemaPath: '_index-3' // indexPath: '3' type: 'tabs', tabs: [ { // path: '_index-3-0' // schemaPath: '_index-3-0' // indexPath: '3-0' label: 'Unnamed Tab', fields: [ { // path: 'fieldWithinUnnamedTab' // schemaPath: '_index-3-0.fieldWithinUnnamedTab' // indexPath: '' name: 'fieldWithinUnnamedTab', type: 'text', }, { // path: '_index-3-0-1' // schemaPath: '_index-3-0-1' // indexPath: '3-0-1' type: 'tabs', tabs: [ { // path: '_index-3-0-1-0' // schemaPath: '_index-3-0-1-0' // indexPath: '3-0-1-0' label: 'Nested Unnamed Tab', fields: [ { // path: 'fieldWithinNestedUnnamedTab' // schemaPath: '_index-3-0-1-0.fieldWithinNestedUnnamedTab' // indexPath: '' name: 'fieldWithinNestedUnnamedTab', type: 'text', }, ], }, ], }, ], }, { // path: 'namedTab' // schemaPath: '_index-3.namedTab' // indexPath: '' label: 'Named Tab', name: 'namedTab', fields: [ { // path: 'namedTab.fieldWithinNamedTab' // schemaPath: '_index-3.namedTab.fieldWithinNamedTab' // indexPath: '' name: 'fieldWithinNamedTab', type: 'text', }, ], }, ], }, ] } ```
This commit is contained in:
@@ -116,7 +116,7 @@ export type BaseRichTextHookArgs<
|
||||
field: FieldAffectingData
|
||||
/** The global which the field belongs to. If the field belongs to a collection, this will be null. */
|
||||
global: null | SanitizedGlobalConfig
|
||||
|
||||
indexPath: number[]
|
||||
/** The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. */
|
||||
originalDoc?: TData
|
||||
/**
|
||||
|
||||
@@ -239,7 +239,7 @@ export const findOperation = async <
|
||||
doc._isLocked = !!lockedDoc
|
||||
doc._userEditing = lockedDoc ? lockedDoc?.user?.value : null
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (_err) {
|
||||
for (const doc of result.docs) {
|
||||
doc._isLocked = false
|
||||
doc._userEditing = null
|
||||
|
||||
@@ -166,6 +166,7 @@ export type FieldHookArgs<TData extends TypeWithID = any, TValue = any, TSibling
|
||||
findMany?: boolean
|
||||
/** The global which the field belongs to. If the field belongs to a collection, this will be null. */
|
||||
global: null | SanitizedGlobalConfig
|
||||
indexPath: number[]
|
||||
/** A string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations. */
|
||||
operation?: 'create' | 'delete' | 'read' | 'update'
|
||||
/** The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. */
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { ClientField, Field, TabAsField, TabAsFieldClient } from './config/types.js'
|
||||
import type { ClientField, Field, Tab, TabAsFieldClient } from './config/types.js'
|
||||
|
||||
type Args = {
|
||||
field: ClientField | Field | TabAsField | TabAsFieldClient
|
||||
field: ClientField | Field | Tab | TabAsFieldClient
|
||||
index: number
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
}
|
||||
|
||||
type Result = {
|
||||
type FieldPaths = {
|
||||
/**
|
||||
* A string of '-' separated indexes representing where
|
||||
* to find this field in a given field schema array.
|
||||
@@ -16,11 +16,11 @@ type Result = {
|
||||
*/
|
||||
indexPath: string
|
||||
/**
|
||||
* Path for this field specifically.
|
||||
* Path for this field relative to its position in the data.
|
||||
*/
|
||||
path: string
|
||||
/**
|
||||
* Schema path for this field specifically.
|
||||
* Path for this field relative to its position in the schema.
|
||||
*/
|
||||
schemaPath: string
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export function getFieldPaths({
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
}: Args): Result {
|
||||
}: Args): FieldPaths {
|
||||
if ('name' in field) {
|
||||
return {
|
||||
indexPath: `${parentIndexPath ? parentIndexPath + '-' : ''}${index}`,
|
||||
@@ -48,3 +48,37 @@ export function getFieldPaths({
|
||||
schemaPath: `${parentSchemaPath ? parentSchemaPath + '.' : ''}${indexSuffix}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function getFieldPathsModified({
|
||||
field,
|
||||
index,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
}: Args): FieldPaths {
|
||||
const parentPathSegments = parentPath.split('.')
|
||||
|
||||
const parentIsUnnamed = parentPathSegments[parentPathSegments.length - 1].startsWith('_index-')
|
||||
|
||||
const parentWithoutIndex = parentIsUnnamed
|
||||
? parentPathSegments.slice(0, -1).join('.')
|
||||
: parentPath
|
||||
|
||||
const parentPathToUse = parentIsUnnamed ? parentWithoutIndex : parentPath
|
||||
|
||||
if ('name' in field) {
|
||||
return {
|
||||
indexPath: '',
|
||||
path: `${parentPathToUse ? parentPathToUse + '.' : ''}${field.name}`,
|
||||
schemaPath: `${parentSchemaPath ? parentSchemaPath + '.' : ''}${field.name}`,
|
||||
}
|
||||
}
|
||||
|
||||
const indexSuffix = `_index-${`${parentIndexPath ? parentIndexPath + '-' : ''}${index}`}`
|
||||
|
||||
return {
|
||||
indexPath: `${parentIndexPath ? parentIndexPath + '-' : ''}${index}`,
|
||||
path: `${parentPathToUse ? parentPathToUse + '.' : ''}${indexSuffix}`,
|
||||
schemaPath: `${!parentIsUnnamed && parentSchemaPath ? parentSchemaPath + '.' : ''}${indexSuffix}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,12 @@ export const afterChange = async <T extends JsonObject>({
|
||||
fields: collection?.fields || global?.fields,
|
||||
global,
|
||||
operation,
|
||||
path: [],
|
||||
parentIndexPath: '',
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc,
|
||||
req,
|
||||
schemaPath: [],
|
||||
siblingData: data,
|
||||
siblingDoc: incomingDoc,
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args = {
|
||||
@@ -19,14 +19,9 @@ type Args = {
|
||||
fieldIndex: number
|
||||
global: null | SanitizedGlobalConfig
|
||||
operation: 'create' | 'update'
|
||||
/**
|
||||
* The parent's path
|
||||
*/
|
||||
parentPath: (number | string)[]
|
||||
/**
|
||||
* The parent's schemaPath (path without indexes).
|
||||
*/
|
||||
parentSchemaPath: string[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
previousDoc: JsonObject
|
||||
previousSiblingDoc: JsonObject
|
||||
req: PayloadRequest
|
||||
@@ -46,6 +41,7 @@ export const promise = async ({
|
||||
fieldIndex,
|
||||
global,
|
||||
operation,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
previousDoc,
|
||||
@@ -54,15 +50,17 @@ export const promise = async ({
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
}: Args): Promise<void> => {
|
||||
const { path: _fieldPath, schemaPath: _fieldSchemaPath } = getFieldPaths({
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
parentIndexPath: '', // Doesn't matter, as unnamed fields do not affect data, and hooks are only run on fields that affect data
|
||||
parentPath: parentPath.join('.'),
|
||||
parentSchemaPath: parentSchemaPath.join('.'),
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
})
|
||||
const fieldPath = _fieldPath ? _fieldPath.split('.') : []
|
||||
const fieldSchemaPath = _fieldSchemaPath ? _fieldSchemaPath.split('.') : []
|
||||
|
||||
const pathSegments = path ? path.split('.') : []
|
||||
const schemaPathSegments = schemaPath ? schemaPath.split('.') : []
|
||||
const indexPathSegments = indexPath ? indexPath.split('-').filter(Boolean)?.map(Number) : []
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
// Execute hooks
|
||||
@@ -76,14 +74,15 @@ export const promise = async ({
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
previousValue: previousDoc[field.name],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
value: siblingDoc[field.name],
|
||||
})
|
||||
@@ -102,7 +101,7 @@ export const promise = async ({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
rows.forEach((row, rowIndex) => {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
collection,
|
||||
@@ -112,18 +111,20 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc?.[field.name]?.[i] || ({} as JsonObject),
|
||||
previousSiblingDoc: previousDoc?.[field.name]?.[rowIndex] || ({} as JsonObject),
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData?.[field.name]?.[i] || {},
|
||||
siblingData: siblingData?.[field.name]?.[rowIndex] || {},
|
||||
siblingDoc: row ? { ...row } : {},
|
||||
}),
|
||||
)
|
||||
})
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -132,7 +133,8 @@ export const promise = async ({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const block = field.blocks.find(
|
||||
(blockType) => blockType.slug === (row as JsonObject).blockType,
|
||||
)
|
||||
@@ -147,17 +149,19 @@ export const promise = async ({
|
||||
fields: block.fields,
|
||||
global,
|
||||
operation,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc?.[field.name]?.[i] || ({} as JsonObject),
|
||||
previousSiblingDoc: previousDoc?.[field.name]?.[rowIndex] || ({} as JsonObject),
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData?.[field.name]?.[i] || {},
|
||||
siblingData: siblingData?.[field.name]?.[rowIndex] || {},
|
||||
siblingDoc: row ? { ...row } : {},
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
@@ -174,17 +178,19 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: { ...previousSiblingDoc },
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData || {},
|
||||
siblingDoc: { ...siblingDoc },
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
await traverseFields({
|
||||
collection,
|
||||
@@ -194,11 +200,12 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
parentIndexPath: '',
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc[field.name] as JsonObject,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: (siblingData?.[field.name] as JsonObject) || {},
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
})
|
||||
@@ -210,6 +217,7 @@ export const promise = async ({
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
@@ -226,14 +234,15 @@ export const promise = async ({
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
previousValue: previousDoc[field.name],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
value: siblingDoc[field.name],
|
||||
})
|
||||
@@ -251,7 +260,9 @@ export const promise = async ({
|
||||
let tabSiblingDoc = siblingDoc
|
||||
let tabPreviousSiblingDoc = siblingDoc
|
||||
|
||||
if (tabHasName(field)) {
|
||||
const isNamedTab = tabHasName(field)
|
||||
|
||||
if (isNamedTab) {
|
||||
tabSiblingData = (siblingData[field.name] as JsonObject) ?? {}
|
||||
tabSiblingDoc = (siblingDoc[field.name] as JsonObject) ?? {}
|
||||
tabPreviousSiblingDoc = (previousDoc[field.name] as JsonObject) ?? {}
|
||||
@@ -265,11 +276,12 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
parentIndexPath: isNamedTab ? '' : indexPath,
|
||||
parentPath: isNamedTab ? path : parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: tabPreviousSiblingDoc,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: tabSiblingData,
|
||||
siblingDoc: tabSiblingDoc,
|
||||
})
|
||||
@@ -286,14 +298,16 @@ export const promise = async ({
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
global,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc: { ...previousSiblingDoc },
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData || {},
|
||||
siblingDoc: { ...siblingDoc },
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@@ -14,11 +14,12 @@ type Args = {
|
||||
fields: (Field | TabAsField)[]
|
||||
global: null | SanitizedGlobalConfig
|
||||
operation: 'create' | 'update'
|
||||
path: (number | string)[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
previousDoc: JsonObject
|
||||
previousSiblingDoc: JsonObject
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
siblingData: JsonObject
|
||||
siblingDoc: JsonObject
|
||||
}
|
||||
@@ -31,11 +32,12 @@ export const traverseFields = async ({
|
||||
fields,
|
||||
global,
|
||||
operation,
|
||||
path,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
}: Args): Promise<void> => {
|
||||
@@ -52,8 +54,9 @@ export const traverseFields = async ({
|
||||
fieldIndex,
|
||||
global,
|
||||
operation,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
req,
|
||||
|
||||
@@ -83,11 +83,12 @@ export async function afterRead<T extends JsonObject>(args: Args<T>): Promise<T>
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: [],
|
||||
parentIndexPath: '',
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: [],
|
||||
select,
|
||||
selectMode: select ? getSelectMode(select) : undefined,
|
||||
showHiddenFields,
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { Field, TabAsField } from '../../config/types.js'
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import { getDefaultValue } from '../../getDefaultValue.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { relationshipPopulationPromise } from './relationshipPopulationPromise.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
@@ -37,14 +37,9 @@ type Args = {
|
||||
global: null | SanitizedGlobalConfig
|
||||
locale: null | string
|
||||
overrideAccess: boolean
|
||||
/**
|
||||
* The parent's path.
|
||||
*/
|
||||
parentPath: (number | string)[]
|
||||
/**
|
||||
* The parent's schemaPath (path without indexes).
|
||||
*/
|
||||
parentSchemaPath: string[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
populate?: PopulateType
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
@@ -80,6 +75,7 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
populate,
|
||||
@@ -92,15 +88,17 @@ export const promise = async ({
|
||||
triggerAccessControl = true,
|
||||
triggerHooks = true,
|
||||
}: Args): Promise<void> => {
|
||||
const { path: _fieldPath, schemaPath: _fieldSchemaPath } = getFieldPaths({
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
parentIndexPath: '', // Doesn't matter, as unnamed fields do not affect data, and hooks are only run on fields that affect data
|
||||
parentPath: parentPath.join('.'),
|
||||
parentSchemaPath: parentSchemaPath.join('.'),
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
})
|
||||
const fieldPath = _fieldPath ? _fieldPath.split('.') : []
|
||||
const fieldSchemaPath = _fieldSchemaPath ? _fieldSchemaPath.split('.') : []
|
||||
|
||||
const pathSegments = path ? path.split('.') : []
|
||||
const schemaPathSegments = schemaPath ? schemaPath.split('.') : []
|
||||
const indexPathSegments = indexPath ? indexPath.split('-').filter(Boolean)?.map(Number) : []
|
||||
|
||||
if (
|
||||
fieldAffectsData(field) &&
|
||||
@@ -111,6 +109,7 @@ export const promise = async ({
|
||||
delete siblingDoc[field.name]
|
||||
}
|
||||
|
||||
// Strip unselected fields
|
||||
if (fieldAffectsData(field) && select && selectMode) {
|
||||
if (selectMode === 'include') {
|
||||
if (!select[field.name]) {
|
||||
@@ -246,12 +245,13 @@ export const promise = async ({
|
||||
field,
|
||||
findMany,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
operation: 'read',
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
showHiddenFields,
|
||||
siblingData: siblingDoc,
|
||||
value,
|
||||
@@ -275,12 +275,13 @@ export const promise = async ({
|
||||
field,
|
||||
findMany,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
operation: 'read',
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
showHiddenFields,
|
||||
siblingData: siblingDoc,
|
||||
value: siblingDoc[field.name],
|
||||
@@ -361,7 +362,7 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
rows.forEach((row, i) => {
|
||||
rows.forEach((row, rowIndex) => {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
@@ -377,11 +378,12 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select: typeof arraySelect === 'object' ? arraySelect : undefined,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
@@ -393,7 +395,7 @@ export const promise = async ({
|
||||
} else if (!shouldHoistLocalizedValue && typeof rows === 'object' && rows !== null) {
|
||||
Object.values(rows).forEach((localeRows) => {
|
||||
if (Array.isArray(localeRows)) {
|
||||
localeRows.forEach((row, i) => {
|
||||
localeRows.forEach((row, rowIndex) => {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
@@ -409,11 +411,12 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
showHiddenFields,
|
||||
siblingDoc: (row as JsonObject) || {},
|
||||
triggerAccessControl,
|
||||
@@ -434,7 +437,7 @@ export const promise = async ({
|
||||
let blocksSelect = select?.[field.name]
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
rows.forEach((row, i) => {
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const block = field.blocks.find(
|
||||
(blockType) => blockType.slug === (row as JsonObject).blockType,
|
||||
)
|
||||
@@ -487,11 +490,12 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select: typeof blockSelect === 'object' ? blockSelect : undefined,
|
||||
selectMode: blockSelectMode,
|
||||
showHiddenFields,
|
||||
@@ -504,7 +508,7 @@ export const promise = async ({
|
||||
} else if (!shouldHoistLocalizedValue && typeof rows === 'object' && rows !== null) {
|
||||
Object.values(rows).forEach((localeRows) => {
|
||||
if (Array.isArray(localeRows)) {
|
||||
localeRows.forEach((row, i) => {
|
||||
localeRows.forEach((row, rowIndex) => {
|
||||
const block = field.blocks.find(
|
||||
(blockType) => blockType.slug === (row as JsonObject).blockType,
|
||||
)
|
||||
@@ -525,11 +529,12 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
showHiddenFields,
|
||||
siblingDoc: (row as JsonObject) || {},
|
||||
triggerAccessControl,
|
||||
@@ -563,11 +568,12 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
@@ -578,8 +584,10 @@ export const promise = async ({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
let groupDoc = siblingDoc[field.name] as JsonObject
|
||||
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
groupDoc = {}
|
||||
}
|
||||
@@ -601,11 +609,12 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: '',
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select: typeof groupSelect === 'object' ? groupSelect : undefined,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
@@ -621,6 +630,7 @@ export const promise = async ({
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
@@ -652,15 +662,16 @@ export const promise = async ({
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
locale,
|
||||
operation: 'read',
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
showHiddenFields,
|
||||
siblingData: siblingDoc,
|
||||
triggerAccessControl,
|
||||
@@ -689,15 +700,16 @@ export const promise = async ({
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
locale,
|
||||
operation: 'read',
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
showHiddenFields,
|
||||
siblingData: siblingDoc,
|
||||
triggerAccessControl,
|
||||
@@ -717,8 +729,12 @@ export const promise = async ({
|
||||
case 'tab': {
|
||||
let tabDoc = siblingDoc
|
||||
let tabSelect: SelectType | undefined
|
||||
if (tabHasName(field)) {
|
||||
|
||||
const isNamedTab = tabHasName(field)
|
||||
|
||||
if (isNamedTab) {
|
||||
tabDoc = siblingDoc[field.name] as JsonObject
|
||||
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
tabDoc = {}
|
||||
}
|
||||
@@ -745,11 +761,12 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: isNamedTab ? '' : indexPath,
|
||||
parentPath: isNamedTab ? path : parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select: tabSelect,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
@@ -777,11 +794,12 @@ export const promise = async ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
select,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
@@ -789,9 +807,9 @@ export const promise = async ({
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
break
|
||||
}
|
||||
|
||||
@@ -30,11 +30,12 @@ type Args = {
|
||||
global: null | SanitizedGlobalConfig
|
||||
locale: null | string
|
||||
overrideAccess: boolean
|
||||
path: (number | string)[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
populate?: PopulateType
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
select?: SelectType
|
||||
selectMode?: SelectMode
|
||||
showHiddenFields: boolean
|
||||
@@ -58,11 +59,12 @@ export const traverseFields = ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
path,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
schemaPath,
|
||||
select,
|
||||
selectMode,
|
||||
showHiddenFields,
|
||||
@@ -88,8 +90,9 @@ export const traverseFields = ({
|
||||
global,
|
||||
locale,
|
||||
overrideAccess,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
populate,
|
||||
populationPromises,
|
||||
req,
|
||||
|
||||
@@ -28,6 +28,7 @@ export type Args<T extends JsonObject> = {
|
||||
* - Transform data for storage
|
||||
* - 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 JsonObject>({
|
||||
id,
|
||||
collection,
|
||||
@@ -56,9 +57,10 @@ export const beforeChange = async <T extends JsonObject>({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: [],
|
||||
parentIndexPath: '',
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
req,
|
||||
schemaPath: [],
|
||||
siblingData: data,
|
||||
siblingDoc: doc,
|
||||
siblingDocWithLocales: docWithLocales,
|
||||
|
||||
@@ -4,14 +4,14 @@ import type { ValidationFieldError } from '../../../errors/index.js'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||
import type { RequestContext } from '../../../index.js'
|
||||
import type { JsonObject, Operation, PayloadRequest } from '../../../types/index.js'
|
||||
import type { BaseValidateOptions, Field, TabAsField } from '../../config/types.js'
|
||||
import type { Field, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { deepMergeWithSourceArrays } from '../../../utilities/deepMerge.js'
|
||||
import { getFormattedLabel } from '../../../utilities/getFormattedLabel.js'
|
||||
import { getLabelFromPath } from '../../../utilities/getLabelFromPath.js'
|
||||
import { getTranslatedLabel } from '../../../utilities/getTranslatedLabel.js'
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { getExistingRowDoc } from './getExistingRowDoc.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
@@ -23,23 +23,14 @@ type Args = {
|
||||
docWithLocales: JsonObject
|
||||
errors: ValidationFieldError[]
|
||||
field: Field | TabAsField
|
||||
/**
|
||||
* The index of the field as it appears in the parent's fields array. This is used to construct the field path / schemaPath
|
||||
* for unnamed fields like rows and collapsibles.
|
||||
*/
|
||||
fieldIndex: number
|
||||
global: null | SanitizedGlobalConfig
|
||||
id?: number | string
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
operation: Operation
|
||||
/**
|
||||
* The parent's path.
|
||||
*/
|
||||
parentPath: (number | string)[]
|
||||
/**
|
||||
* The parent's schemaPath (path without indexes).
|
||||
*/
|
||||
parentSchemaPath: string[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
siblingData: JsonObject
|
||||
siblingDoc: JsonObject
|
||||
@@ -68,6 +59,7 @@ export const promise = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
@@ -76,6 +68,14 @@ export const promise = async ({
|
||||
siblingDocWithLocales,
|
||||
skipValidation,
|
||||
}: Args): Promise<void> => {
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
})
|
||||
|
||||
const passesCondition = field.admin?.condition
|
||||
? Boolean(field.admin.condition(data, siblingData, { user: req.user }))
|
||||
: true
|
||||
@@ -84,15 +84,9 @@ export const promise = async ({
|
||||
const defaultLocale = localization ? localization?.defaultLocale : 'en'
|
||||
const operationLocale = req.locale || defaultLocale
|
||||
|
||||
const { path: _fieldPath, schemaPath: _fieldSchemaPath } = getFieldPaths({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
parentIndexPath: '', // Doesn't matter, as unnamed fields do not affect data, and hooks are only run on fields that affect data
|
||||
parentPath: parentPath.join('.'),
|
||||
parentSchemaPath: parentSchemaPath.join('.'),
|
||||
})
|
||||
const fieldPath = _fieldPath ? _fieldPath.split('.') : []
|
||||
const fieldSchemaPath = _fieldSchemaPath ? _fieldSchemaPath.split('.') : []
|
||||
const pathSegments = path ? path.split('.') : []
|
||||
const schemaPathSegments = schemaPath ? schemaPath.split('.') : []
|
||||
const indexPathSegments = indexPath ? indexPath.split('-').filter(Boolean)?.map(Number) : []
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
// skip validation if the field is localized and the incoming data is null
|
||||
@@ -113,13 +107,14 @@ export const promise = async ({
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name],
|
||||
req,
|
||||
schemaPath: parentSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
siblingDocWithLocales,
|
||||
value: siblingData[field.name],
|
||||
@@ -163,16 +158,17 @@ export const promise = async ({
|
||||
|
||||
if (typeof validationResult === 'string') {
|
||||
const label = getTranslatedLabel(field?.label || field?.name, req.i18n)
|
||||
const parentPathSegments = parentPath ? parentPath.split('.') : []
|
||||
|
||||
const fieldLabel =
|
||||
Array.isArray(parentPath) && parentPath.length > 0
|
||||
? getFormattedLabel([...parentPath, label])
|
||||
Array.isArray(parentPathSegments) && parentPathSegments.length > 0
|
||||
? getLabelFromPath(parentPathSegments.concat(label))
|
||||
: label
|
||||
|
||||
errors.push({
|
||||
label: fieldLabel,
|
||||
message: validationResult,
|
||||
path: fieldPath.join('.'),
|
||||
path,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -216,7 +212,8 @@ export const promise = async ({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
@@ -230,9 +227,10 @@ export const promise = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: row as JsonObject,
|
||||
siblingDoc: getExistingRowDoc(row as JsonObject, siblingDoc[field.name]),
|
||||
siblingDocWithLocales: getExistingRowDoc(
|
||||
@@ -254,8 +252,10 @@ export const promise = async ({
|
||||
const rows = siblingData[field.name]
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const rowSiblingDoc = getExistingRowDoc(row as JsonObject, siblingDoc[field.name])
|
||||
|
||||
const rowSiblingDocWithLocales = getExistingRowDoc(
|
||||
row as JsonObject,
|
||||
siblingDocWithLocales ? siblingDocWithLocales[field.name] : {},
|
||||
@@ -278,9 +278,10 @@ export const promise = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: row as JsonObject,
|
||||
siblingDoc: rowSiblingDoc,
|
||||
siblingDocWithLocales: rowSiblingDocWithLocales,
|
||||
@@ -310,9 +311,10 @@ export const promise = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingDocWithLocales,
|
||||
@@ -326,9 +328,11 @@ export const promise = async ({
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
|
||||
if (typeof siblingDocWithLocales[field.name] !== 'object') {
|
||||
siblingDocWithLocales[field.name] = {}
|
||||
}
|
||||
@@ -345,9 +349,10 @@ export const promise = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
parentIndexPath: '',
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: siblingData[field.name] as JsonObject,
|
||||
siblingDoc: siblingDoc[field.name] as JsonObject,
|
||||
siblingDocWithLocales: siblingDocWithLocales[field.name] as JsonObject,
|
||||
@@ -356,6 +361,7 @@ export const promise = async ({
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'point': {
|
||||
// Transform point data for storage
|
||||
if (
|
||||
@@ -379,6 +385,7 @@ export const promise = async ({
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
@@ -397,14 +404,15 @@ export const promise = async ({
|
||||
errors,
|
||||
field,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name],
|
||||
req,
|
||||
schemaPath: parentSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
siblingDocWithLocales,
|
||||
skipValidation,
|
||||
@@ -425,13 +433,17 @@ export const promise = async ({
|
||||
let tabSiblingDoc = siblingDoc
|
||||
let tabSiblingDocWithLocales = siblingDocWithLocales
|
||||
|
||||
if (tabHasName(field)) {
|
||||
const isNamedTab = tabHasName(field)
|
||||
|
||||
if (isNamedTab) {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
|
||||
if (typeof siblingDocWithLocales[field.name] !== 'object') {
|
||||
siblingDocWithLocales[field.name] = {}
|
||||
}
|
||||
@@ -453,9 +465,10 @@ export const promise = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
parentIndexPath: isNamedTab ? '' : indexPath,
|
||||
parentPath: isNamedTab ? path : parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: tabSiblingData,
|
||||
siblingDoc: tabSiblingDoc,
|
||||
siblingDocWithLocales: tabSiblingDocWithLocales,
|
||||
@@ -478,9 +491,10 @@ export const promise = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingDocWithLocales,
|
||||
|
||||
@@ -25,9 +25,10 @@ type Args = {
|
||||
id?: number | string
|
||||
mergeLocaleActions: (() => Promise<void>)[]
|
||||
operation: Operation
|
||||
path: (number | string)[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
siblingData: JsonObject
|
||||
/**
|
||||
* The original siblingData (not modified by any hooks)
|
||||
@@ -60,9 +61,10 @@ export const traverseFields = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
siblingDocWithLocales,
|
||||
@@ -85,8 +87,9 @@ export const traverseFields = async ({
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { SanitizedCollectionConfig } from '../../../collections/config/type
|
||||
import type { RequestContext } from '../../../index.js'
|
||||
import type { JsonObject, PayloadRequest } from '../../../types/index.js'
|
||||
|
||||
import { deepCopyObjectSimple } from '../../../utilities/deepCopyObject.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
type Args<T extends JsonObject> = {
|
||||
@@ -35,9 +34,10 @@ export const beforeDuplicate = async <T extends JsonObject>({
|
||||
doc,
|
||||
fields: collection?.fields,
|
||||
overrideAccess,
|
||||
path: [],
|
||||
parentIndexPath: '',
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
req,
|
||||
schemaPath: [],
|
||||
siblingDoc: doc,
|
||||
})
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { RequestContext } from '../../../index.js'
|
||||
import type { JsonObject, PayloadRequest } from '../../../types/index.js'
|
||||
import type { Field, FieldHookArgs, TabAsField } from '../../config/types.js'
|
||||
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { fieldAffectsData } from '../../config/types.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { runBeforeDuplicateHooks } from './runHook.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
|
||||
@@ -16,8 +16,9 @@ type Args<T> = {
|
||||
fieldIndex: number
|
||||
id?: number | string
|
||||
overrideAccess: boolean
|
||||
parentPath: (number | string)[]
|
||||
parentSchemaPath: string[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
siblingDoc: JsonObject
|
||||
}
|
||||
@@ -30,40 +31,25 @@ export const promise = async <T>({
|
||||
field,
|
||||
fieldIndex,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
siblingDoc,
|
||||
}: Args<T>): Promise<void> => {
|
||||
const { localization } = req.payload.config
|
||||
|
||||
const { path: _fieldPath, schemaPath: _fieldSchemaPath } = getFieldPaths({
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
parentIndexPath: '', // Doesn't matter, as unnamed fields do not affect data, and hooks are only run on fields that affect data
|
||||
parentPath: parentPath.join('.'),
|
||||
parentSchemaPath: parentSchemaPath.join('.'),
|
||||
})
|
||||
const fieldPath = _fieldPath ? _fieldPath.split('.') : []
|
||||
const fieldSchemaPath = _fieldSchemaPath ? _fieldSchemaPath.split('.') : []
|
||||
|
||||
// Handle unnamed tabs
|
||||
if (field.type === 'tab' && !tabHasName(field)) {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
const { localization } = req.payload.config
|
||||
|
||||
const pathSegments = path ? path.split('.') : []
|
||||
const schemaPathSegments = schemaPath ? schemaPath.split('.') : []
|
||||
const indexPathSegments = indexPath ? indexPath.split('-').filter(Boolean)?.map(Number) : []
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
let fieldData = siblingDoc?.[field.name]
|
||||
@@ -82,11 +68,12 @@ export const promise = async <T>({
|
||||
data: doc,
|
||||
field,
|
||||
global: undefined,
|
||||
path: fieldPath,
|
||||
indexPath: indexPathSegments,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name]?.[locale],
|
||||
req,
|
||||
schemaPath: parentSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData: siblingDoc,
|
||||
siblingDocWithLocales: siblingDoc,
|
||||
value: siblingDoc[field.name]?.[locale],
|
||||
@@ -114,11 +101,12 @@ export const promise = async <T>({
|
||||
data: doc,
|
||||
field,
|
||||
global: undefined,
|
||||
path: fieldPath,
|
||||
indexPath: indexPathSegments,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name],
|
||||
req,
|
||||
schemaPath: parentSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData: siblingDoc,
|
||||
siblingDocWithLocales: siblingDoc,
|
||||
value: siblingDoc[field.name],
|
||||
@@ -150,7 +138,8 @@ export const promise = async <T>({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
@@ -159,22 +148,26 @@ export const promise = async <T>({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: row,
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const rows = fieldData[locale]
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const blockTypeToMatch = row.blockType
|
||||
|
||||
const block = field.blocks.find(
|
||||
@@ -189,9 +182,10 @@ export const promise = async <T>({
|
||||
doc,
|
||||
fields: block.fields,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: row,
|
||||
}),
|
||||
)
|
||||
@@ -201,7 +195,6 @@ export const promise = async <T>({
|
||||
}
|
||||
|
||||
case 'group':
|
||||
|
||||
case 'tab': {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
@@ -211,9 +204,10 @@ export const promise = async <T>({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: fieldSchemaPath,
|
||||
parentIndexPath: '',
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: fieldData[locale],
|
||||
}),
|
||||
)
|
||||
@@ -235,7 +229,8 @@ export const promise = async <T>({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
@@ -244,23 +239,28 @@ export const promise = async <T>({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: row,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'blocks': {
|
||||
const rows = siblingDoc[field.name]
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
const blockTypeToMatch = row.blockType
|
||||
const block = field.blocks.find((blockType) => blockType.slug === blockTypeToMatch)
|
||||
|
||||
@@ -275,28 +275,28 @@ export const promise = async <T>({
|
||||
doc,
|
||||
fields: block.fields,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: row,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'group':
|
||||
|
||||
case 'tab': {
|
||||
case 'group': {
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
|
||||
const groupDoc = siblingDoc[field.name] as Record<string, unknown>
|
||||
const groupDoc = siblingDoc[field.name] as JsonObject
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
@@ -305,10 +305,35 @@ export const promise = async <T>({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: '',
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc: groupDoc as JsonObject,
|
||||
siblingDoc: groupDoc,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'tab': {
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
|
||||
const tabDoc = siblingDoc[field.name] as JsonObject
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: '',
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
siblingDoc: tabDoc,
|
||||
})
|
||||
|
||||
break
|
||||
@@ -327,9 +352,31 @@ export const promise = async <T>({
|
||||
doc,
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// Unnamed Tab
|
||||
// @ts-expect-error `fieldAffectsData` inferred return type doesn't account for TabAsField
|
||||
case 'tab': {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
doc,
|
||||
// @ts-expect-error `fieldAffectsData` inferred return type doesn't account for TabAsField
|
||||
fields: field.fields,
|
||||
overrideAccess,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
@@ -344,9 +391,10 @@ export const promise = async <T>({
|
||||
doc,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ type Args<T> = {
|
||||
fields: (Field | TabAsField)[]
|
||||
id?: number | string
|
||||
overrideAccess: boolean
|
||||
path: (number | string)[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
siblingDoc: JsonObject
|
||||
}
|
||||
|
||||
@@ -25,12 +26,14 @@ export const traverseFields = async <T>({
|
||||
doc,
|
||||
fields,
|
||||
overrideAccess,
|
||||
path,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
schemaPath,
|
||||
siblingDoc,
|
||||
}: Args<T>): Promise<void> => {
|
||||
const promises = []
|
||||
|
||||
fields.forEach((field, fieldIndex) => {
|
||||
promises.push(
|
||||
promise({
|
||||
@@ -41,8 +44,9 @@ export const traverseFields = async <T>({
|
||||
field,
|
||||
fieldIndex,
|
||||
overrideAccess,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
siblingDoc,
|
||||
}),
|
||||
|
||||
@@ -47,9 +47,10 @@ export const beforeValidate = async <T extends JsonObject>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: [],
|
||||
parentIndexPath: '',
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
req,
|
||||
schemaPath: [],
|
||||
siblingData: incomingData,
|
||||
siblingDoc: doc,
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { Field, TabAsField } from '../../config/types.js'
|
||||
import { MissingEditorProp } from '../../../errors/index.js'
|
||||
import { fieldAffectsData, tabHasName, valueIsValueWithRelation } from '../../config/types.js'
|
||||
import { getDefaultValue } from '../../getDefaultValue.js'
|
||||
import { getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||
import { cloneDataFromOriginalDoc } from '../beforeChange/cloneDataFromOriginalDoc.js'
|
||||
import { getExistingRowDoc } from '../beforeChange/getExistingRowDoc.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
@@ -27,8 +27,9 @@ type Args<T> = {
|
||||
id?: number | string
|
||||
operation: 'create' | 'update'
|
||||
overrideAccess: boolean
|
||||
parentPath: (number | string)[]
|
||||
parentSchemaPath: string[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
siblingData: JsonObject
|
||||
/**
|
||||
@@ -55,21 +56,24 @@ export const promise = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
}: Args<T>): Promise<void> => {
|
||||
const { path: _fieldPath, schemaPath: _fieldSchemaPath } = getFieldPaths({
|
||||
const { indexPath, path, schemaPath } = getFieldPaths({
|
||||
field,
|
||||
index: fieldIndex,
|
||||
parentIndexPath: '', // Doesn't matter, as unnamed fields do not affect data, and hooks are only run on fields that affect data
|
||||
parentPath: parentPath.join('.'),
|
||||
parentSchemaPath: parentSchemaPath.join('.'),
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
})
|
||||
const fieldPath = _fieldPath ? _fieldPath.split('.') : []
|
||||
const fieldSchemaPath = _fieldSchemaPath ? _fieldSchemaPath.split('.') : []
|
||||
|
||||
const pathSegments = path ? path.split('.') : []
|
||||
const schemaPathSegments = schemaPath ? schemaPath.split('.') : []
|
||||
const indexPathSegments = indexPath ? indexPath.split('-').filter(Boolean)?.map(Number) : []
|
||||
|
||||
if (fieldAffectsData(field)) {
|
||||
if (field.name === 'id') {
|
||||
@@ -271,14 +275,15 @@ export const promise = async <T>({
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingDoc[field.name],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
@@ -325,7 +330,8 @@ export const promise = async <T>({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
@@ -337,14 +343,16 @@ export const promise = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: row as JsonObject,
|
||||
siblingDoc: getExistingRowDoc(row as JsonObject, siblingDoc[field.name]),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
break
|
||||
@@ -355,7 +363,8 @@ export const promise = async <T>({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
|
||||
rows.forEach((row, rowIndex) => {
|
||||
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)
|
||||
@@ -374,15 +383,17 @@ export const promise = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: [...fieldPath, i],
|
||||
parentIndexPath: '',
|
||||
parentPath: path + '.' + rowIndex,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: row as JsonObject,
|
||||
siblingDoc: rowSiblingDoc,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
@@ -401,19 +412,22 @@ export const promise = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'group': {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
@@ -431,9 +445,10 @@ export const promise = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: '',
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: groupData as JsonObject,
|
||||
siblingDoc: groupDoc as JsonObject,
|
||||
})
|
||||
@@ -445,6 +460,7 @@ export const promise = async <T>({
|
||||
if (!field?.editor) {
|
||||
throw new MissingEditorProp(field) // while we allow disabling editor functionality, you should not have any richText fields defined if you do not have an editor
|
||||
}
|
||||
|
||||
if (typeof field?.editor === 'function') {
|
||||
throw new Error('Attempted to access unsanitized rich text editor.')
|
||||
}
|
||||
@@ -461,14 +477,15 @@ export const promise = async <T>({
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
indexPath: indexPathSegments,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
path: pathSegments,
|
||||
previousSiblingDoc: siblingDoc,
|
||||
previousValue: siblingData[field.name],
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
schemaPath: schemaPathSegments,
|
||||
siblingData,
|
||||
value: siblingData[field.name],
|
||||
})
|
||||
@@ -484,10 +501,14 @@ export const promise = async <T>({
|
||||
case 'tab': {
|
||||
let tabSiblingData
|
||||
let tabSiblingDoc
|
||||
if (tabHasName(field)) {
|
||||
|
||||
const isNamedTab = tabHasName(field)
|
||||
|
||||
if (isNamedTab) {
|
||||
if (typeof siblingData[field.name] !== 'object') {
|
||||
siblingData[field.name] = {}
|
||||
}
|
||||
|
||||
if (typeof siblingDoc[field.name] !== 'object') {
|
||||
siblingDoc[field.name] = {}
|
||||
}
|
||||
@@ -509,9 +530,10 @@ export const promise = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: isNamedTab ? '' : indexPath,
|
||||
parentPath: isNamedTab ? path : parentPath,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData: tabSiblingData,
|
||||
siblingDoc: tabSiblingDoc,
|
||||
})
|
||||
@@ -530,9 +552,10 @@ export const promise = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path: fieldPath,
|
||||
parentIndexPath: indexPath,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
req,
|
||||
schemaPath: fieldSchemaPath,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
@@ -19,9 +19,10 @@ type Args<T> = {
|
||||
id?: number | string
|
||||
operation: 'create' | 'update'
|
||||
overrideAccess: boolean
|
||||
path: (number | string)[]
|
||||
parentIndexPath: string
|
||||
parentPath: string
|
||||
parentSchemaPath: string
|
||||
req: PayloadRequest
|
||||
schemaPath: string[]
|
||||
siblingData: JsonObject
|
||||
/**
|
||||
* The original siblingData (not modified by any hooks)
|
||||
@@ -39,13 +40,15 @@ export const traverseFields = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
path,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
}: Args<T>): Promise<void> => {
|
||||
const promises = []
|
||||
|
||||
fields.forEach((field, fieldIndex) => {
|
||||
promises.push(
|
||||
promise({
|
||||
@@ -59,13 +62,15 @@ export const traverseFields = async <T>({
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
parentPath: path,
|
||||
parentSchemaPath: schemaPath,
|
||||
parentIndexPath,
|
||||
parentPath,
|
||||
parentSchemaPath,
|
||||
req,
|
||||
siblingData,
|
||||
siblingDoc,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
@@ -1152,6 +1152,7 @@ export {
|
||||
type ServerOnlyFieldProperties,
|
||||
} from './fields/config/client.js'
|
||||
export { sanitizeFields } from './fields/config/sanitize.js'
|
||||
|
||||
export type {
|
||||
AdminClient,
|
||||
ArrayField,
|
||||
@@ -1428,7 +1429,6 @@ 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 { saveVersion } from './versions/saveVersion.js'
|
||||
export type { SchedulePublishTaskInput } from './versions/schedule/types.js'
|
||||
export type { TypeWithVersion } from './versions/types.js'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const getFormattedLabel = (path: (number | string)[]): string => {
|
||||
export const getLabelFromPath = (path: (number | string)[]): string => {
|
||||
return path
|
||||
.filter((pathSegment) => !(typeof pathSegment === 'string' && pathSegment.includes('_index')))
|
||||
.reduce<string[]>((acc, part) => {
|
||||
@@ -190,6 +190,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
context: _context,
|
||||
data,
|
||||
global,
|
||||
indexPath,
|
||||
operation,
|
||||
originalDoc,
|
||||
path,
|
||||
@@ -198,6 +199,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
req,
|
||||
schemaPath,
|
||||
} = args
|
||||
|
||||
let { value } = args
|
||||
if (finalSanitizedEditorConfig?.features?.hooks?.afterChange?.length) {
|
||||
for (const hook of finalSanitizedEditorConfig.features.hooks.afterChange) {
|
||||
@@ -292,11 +294,12 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
fields: subFields,
|
||||
global,
|
||||
operation,
|
||||
path,
|
||||
parentIndexPath: indexPath.join('-'),
|
||||
parentPath: path.join('.'),
|
||||
parentSchemaPath: schemaPath.join('.'),
|
||||
previousDoc,
|
||||
previousSiblingDoc: { ...nodePreviousSiblingDoc },
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData: nodeSiblingData || {},
|
||||
siblingDoc: { ...nodeSiblingDoc },
|
||||
})
|
||||
@@ -322,6 +325,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
indexPath,
|
||||
locale,
|
||||
originalDoc,
|
||||
overrideAccess,
|
||||
@@ -334,6 +338,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
triggerAccessControl,
|
||||
triggerHooks,
|
||||
} = args
|
||||
|
||||
let { value } = args
|
||||
|
||||
if (finalSanitizedEditorConfig?.features?.hooks?.afterRead?.length) {
|
||||
@@ -408,11 +413,12 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
global,
|
||||
locale: locale!,
|
||||
overrideAccess: overrideAccess!,
|
||||
path,
|
||||
parentIndexPath: indexPath.join('-'),
|
||||
parentPath: path.join('.'),
|
||||
parentSchemaPath: schemaPath.join('.'),
|
||||
populate,
|
||||
populationPromises: populationPromises!,
|
||||
req,
|
||||
schemaPath,
|
||||
showHiddenFields: showHiddenFields!,
|
||||
siblingDoc: nodeSliblingData,
|
||||
triggerAccessControl,
|
||||
@@ -435,6 +441,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
errors,
|
||||
field,
|
||||
global,
|
||||
indexPath,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
originalDoc,
|
||||
@@ -446,6 +453,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
siblingDocWithLocales,
|
||||
skipValidation,
|
||||
} = args
|
||||
|
||||
let { value } = args
|
||||
|
||||
if (finalSanitizedEditorConfig?.features?.hooks?.beforeChange?.length) {
|
||||
@@ -566,9 +574,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
global,
|
||||
mergeLocaleActions: mergeLocaleActions!,
|
||||
operation: operation!,
|
||||
path,
|
||||
parentIndexPath: indexPath.join('-'),
|
||||
parentPath: path.join('.'),
|
||||
parentSchemaPath: schemaPath.join('.'),
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData: nodeSiblingData,
|
||||
siblingDoc: nodePreviousSiblingDoc,
|
||||
siblingDocWithLocales: nodeSiblingDocWithLocales ?? {},
|
||||
@@ -620,6 +629,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
context,
|
||||
data,
|
||||
global,
|
||||
indexPath,
|
||||
operation,
|
||||
originalDoc,
|
||||
overrideAccess,
|
||||
@@ -628,6 +638,7 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
req,
|
||||
schemaPath,
|
||||
} = args
|
||||
|
||||
let { value } = args
|
||||
if (finalSanitizedEditorConfig?.features?.hooks?.beforeValidate?.length) {
|
||||
for (const hook of finalSanitizedEditorConfig.features.hooks.beforeValidate) {
|
||||
@@ -755,9 +766,10 @@ export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapte
|
||||
global,
|
||||
operation,
|
||||
overrideAccess: overrideAccess!,
|
||||
path,
|
||||
parentIndexPath: indexPath.join('-'),
|
||||
parentPath: path.join('.'),
|
||||
parentSchemaPath: schemaPath.join('.'),
|
||||
req,
|
||||
schemaPath,
|
||||
siblingData: nodeSiblingData,
|
||||
siblingDoc: nodeSiblingDoc,
|
||||
})
|
||||
|
||||
@@ -59,10 +59,11 @@ export const recursivelyPopulateFieldsForGraphQL = ({
|
||||
global: null, // Pass from core? This is only needed for hooks, so we can leave this null for now
|
||||
locale: req.locale!,
|
||||
overrideAccess,
|
||||
path: [],
|
||||
parentIndexPath: '',
|
||||
parentPath: '',
|
||||
parentSchemaPath: '',
|
||||
populationPromises, // This is not the same as populationPromises passed into this recurseNestedFields. These are just promises resolved at the very end.
|
||||
req,
|
||||
schemaPath: [],
|
||||
showHiddenFields,
|
||||
siblingDoc,
|
||||
triggerHooks: false,
|
||||
|
||||
@@ -365,7 +365,6 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
}
|
||||
|
||||
const parentPath = path + '.' + i
|
||||
const rowSchemaPath = schemaPath + '.' + block.slug
|
||||
|
||||
if (block) {
|
||||
row.id = row?.id || new ObjectId().toHexString()
|
||||
@@ -435,7 +434,7 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
parentIndexPath: '',
|
||||
parentPassesCondition: passesCondition,
|
||||
parentPath,
|
||||
parentSchemaPath: rowSchemaPath,
|
||||
parentSchemaPath: schemaPath + '.' + block.slug,
|
||||
permissions:
|
||||
fieldPermissions === true
|
||||
? fieldPermissions
|
||||
@@ -741,10 +740,10 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
|
||||
includeSchema,
|
||||
omitParents,
|
||||
operation,
|
||||
parentIndexPath: tabHasName(tab) ? '' : tabIndexPath,
|
||||
parentIndexPath: isNamedTab ? '' : tabIndexPath,
|
||||
parentPassesCondition: passesCondition,
|
||||
parentPath: tabHasName(tab) ? tabPath : parentPath,
|
||||
parentSchemaPath: tabHasName(tab) ? tabSchemaPath : parentSchemaPath,
|
||||
parentPath: isNamedTab ? tabPath : parentPath,
|
||||
parentSchemaPath: isNamedTab ? tabSchemaPath : parentSchemaPath,
|
||||
permissions: childPermissions,
|
||||
preferences,
|
||||
previousFormState,
|
||||
|
||||
@@ -131,8 +131,11 @@ export const traverseFields = ({
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'tabs':
|
||||
field.tabs.map((tab, tabIndex) => {
|
||||
const isNamedTab = tabHasName(tab)
|
||||
|
||||
const { indexPath: tabIndexPath, schemaPath: tabSchemaPath } = getFieldPaths({
|
||||
field: {
|
||||
...tab,
|
||||
@@ -151,8 +154,8 @@ export const traverseFields = ({
|
||||
config,
|
||||
fields: tab.fields,
|
||||
i18n,
|
||||
parentIndexPath: tabHasName(tab) ? '' : tabIndexPath,
|
||||
parentSchemaPath: tabHasName(tab) ? tabSchemaPath : parentSchemaPath,
|
||||
parentIndexPath: isNamedTab ? '' : tabIndexPath,
|
||||
parentSchemaPath: isNamedTab ? tabSchemaPath : parentSchemaPath,
|
||||
payload,
|
||||
schemaMap,
|
||||
})
|
||||
|
||||
@@ -98,6 +98,8 @@ export const traverseFields = ({
|
||||
|
||||
case 'tabs':
|
||||
field.tabs.map((tab, tabIndex) => {
|
||||
const isNamedTab = tabHasName(tab)
|
||||
|
||||
const { indexPath: tabIndexPath, schemaPath: tabSchemaPath } = getFieldPaths({
|
||||
field: {
|
||||
...tab,
|
||||
@@ -115,8 +117,8 @@ export const traverseFields = ({
|
||||
config,
|
||||
fields: tab.fields,
|
||||
i18n,
|
||||
parentIndexPath: tabHasName(tab) ? '' : tabIndexPath,
|
||||
parentSchemaPath: tabHasName(tab) ? tabSchemaPath : parentSchemaPath,
|
||||
parentIndexPath: isNamedTab ? '' : tabIndexPath,
|
||||
parentSchemaPath: isNamedTab ? tabSchemaPath : parentSchemaPath,
|
||||
schemaMap,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ export interface UserAuthOperations {
|
||||
export interface Post {
|
||||
id: string;
|
||||
title?: string | null;
|
||||
richText?: {
|
||||
content?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
@@ -217,7 +217,7 @@ export interface PayloadMigration {
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
richText?: T;
|
||||
content?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
@@ -340,23 +340,6 @@ export interface MenuSelect<T extends boolean = true> {
|
||||
createdAt?: T;
|
||||
globalType?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "ContactBlock".
|
||||
*/
|
||||
export interface ContactBlock {
|
||||
/**
|
||||
* ...
|
||||
*/
|
||||
first: string;
|
||||
/**
|
||||
* ...
|
||||
*/
|
||||
two: string;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'contact';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "auth".
|
||||
|
||||
@@ -8,7 +8,7 @@ import { v4 as uuid } from 'uuid'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { devUser } from '../credentials.js'
|
||||
import { postsSlug } from './shared.js'
|
||||
import { errorOnUnnamedFieldsSlug, postsSlug } from './shared.js'
|
||||
|
||||
const defaultValueField: TextField = {
|
||||
name: 'defaultValue',
|
||||
@@ -156,6 +156,32 @@ export default buildConfigWithDefaults({
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
slug: errorOnUnnamedFieldsSlug,
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'UnnamedTab',
|
||||
fields: [
|
||||
{
|
||||
name: 'groupWithinUnnamedTab',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: 'default-values',
|
||||
fields: [
|
||||
|
||||
@@ -26,7 +26,7 @@ import { devUser } from '../credentials.js'
|
||||
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
import { isMongoose } from '../helpers/isMongoose.js'
|
||||
import removeFiles from '../helpers/removeFiles.js'
|
||||
import { postsSlug } from './shared.js'
|
||||
import { errorOnUnnamedFieldsSlug, postsSlug } from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -895,7 +895,7 @@ describe('database', () => {
|
||||
await expect(errorMessage).toBe('The following field is invalid: Title')
|
||||
})
|
||||
|
||||
it('should return proper deeply nested field validation errors', async () => {
|
||||
it('should return validation errors in response', async () => {
|
||||
try {
|
||||
await payload.create({
|
||||
collection: postsSlug,
|
||||
@@ -921,6 +921,22 @@ describe('database', () => {
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should return validation errors with proper field paths for unnamed fields', async () => {
|
||||
try {
|
||||
await payload.create({
|
||||
collection: errorOnUnnamedFieldsSlug,
|
||||
data: {
|
||||
groupWithinUnnamedTab: {
|
||||
// @ts-expect-error
|
||||
text: undefined,
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (e: any) {
|
||||
expect(e.data?.errors?.[0]?.path).toBe('groupWithinUnnamedTab.text')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('defaultValue', () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface Config {
|
||||
};
|
||||
collections: {
|
||||
posts: Post;
|
||||
'error-on-unnamed-fields': ErrorOnUnnamedField;
|
||||
'default-values': DefaultValue;
|
||||
'relation-a': RelationA;
|
||||
'relation-b': RelationB;
|
||||
@@ -30,6 +31,7 @@ export interface Config {
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
posts: PostsSelect<false> | PostsSelect<true>;
|
||||
'error-on-unnamed-fields': ErrorOnUnnamedFieldsSelect<false> | ErrorOnUnnamedFieldsSelect<true>;
|
||||
'default-values': DefaultValuesSelect<false> | DefaultValuesSelect<true>;
|
||||
'relation-a': RelationASelect<false> | RelationASelect<true>;
|
||||
'relation-b': RelationBSelect<false> | RelationBSelect<true>;
|
||||
@@ -114,6 +116,18 @@ export interface Post {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "error-on-unnamed-fields".
|
||||
*/
|
||||
export interface ErrorOnUnnamedField {
|
||||
id: string;
|
||||
groupWithinUnnamedTab: {
|
||||
text: string;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "default-values".
|
||||
@@ -355,6 +369,10 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'error-on-unnamed-fields';
|
||||
value: string | ErrorOnUnnamedField;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'default-values';
|
||||
value: string | DefaultValue;
|
||||
@@ -482,6 +500,19 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "error-on-unnamed-fields_select".
|
||||
*/
|
||||
export interface ErrorOnUnnamedFieldsSelect<T extends boolean = true> {
|
||||
groupWithinUnnamedTab?:
|
||||
| T
|
||||
| {
|
||||
text?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "default-values_select".
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export const postsSlug = 'posts'
|
||||
export const errorOnUnnamedFieldsSlug = 'error-on-unnamed-fields'
|
||||
|
||||
@@ -47,6 +47,8 @@ const TabsFields: CollectionConfig = {
|
||||
type: 'array',
|
||||
required: true,
|
||||
fields: [
|
||||
// path: 'array.n.text'
|
||||
// schemaPath: '_index-1-0.array.text'
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
|
||||
@@ -30,6 +30,7 @@ import { clearAndSeedEverything } from './seed.js'
|
||||
import {
|
||||
arrayFieldsSlug,
|
||||
blockFieldsSlug,
|
||||
collapsibleFieldsSlug,
|
||||
groupFieldsSlug,
|
||||
relationshipFieldsSlug,
|
||||
tabsFieldsSlug,
|
||||
@@ -487,7 +488,7 @@ describe('Fields', () => {
|
||||
})
|
||||
|
||||
describe('rows', () => {
|
||||
it('show proper validation error message on text field within row field', async () => {
|
||||
it('should show proper validation error message on text field within row field', async () => {
|
||||
await expect(async () =>
|
||||
payload.create({
|
||||
collection: 'row-fields',
|
||||
@@ -1677,7 +1678,7 @@ describe('Fields', () => {
|
||||
expect(res.id).toBe(doc.id)
|
||||
})
|
||||
|
||||
it('show proper validation error on text field in nested array', async () => {
|
||||
it('should show proper validation error on text field in nested array', async () => {
|
||||
await expect(async () =>
|
||||
payload.create({
|
||||
collection,
|
||||
@@ -1697,7 +1698,7 @@ describe('Fields', () => {
|
||||
).rejects.toThrow('The following field is invalid: Items 1 > SubArray 1 > Second text field')
|
||||
})
|
||||
|
||||
it('show proper validation error on text field in row field in nested array', async () => {
|
||||
it('should show proper validation error on text field in row field in nested array', async () => {
|
||||
await expect(async () =>
|
||||
payload.create({
|
||||
collection,
|
||||
@@ -2506,10 +2507,10 @@ describe('Fields', () => {
|
||||
})
|
||||
|
||||
describe('collapsible', () => {
|
||||
it('show proper validation error message for fields nested in collapsible', async () => {
|
||||
it('should show proper validation error message for fields nested in collapsible', async () => {
|
||||
await expect(async () =>
|
||||
payload.create({
|
||||
collection: 'collapsible-fields',
|
||||
collection: collapsibleFieldsSlug,
|
||||
data: {
|
||||
text: 'required',
|
||||
group: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { beforeValidateSlug } from '../../collectionSlugs.js'
|
||||
import { beforeValidateSlug } from '../../shared.js'
|
||||
|
||||
export const BeforeValidateCollection: CollectionConfig = {
|
||||
slug: beforeValidateSlug,
|
||||
|
||||
@@ -18,7 +18,6 @@ export const DataHooks: CollectionConfig = {
|
||||
return args
|
||||
},
|
||||
],
|
||||
|
||||
beforeChange: [
|
||||
({ context, data, collection }) => {
|
||||
context['collection_beforeChange_collection'] = JSON.stringify(collection)
|
||||
@@ -69,7 +68,6 @@ export const DataHooks: CollectionConfig = {
|
||||
return value
|
||||
},
|
||||
],
|
||||
|
||||
afterRead: [
|
||||
({ collection, field, context }) => {
|
||||
return (
|
||||
|
||||
214
test/hooks/collections/FieldPaths/index.ts
Normal file
214
test/hooks/collections/FieldPaths/index.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import type { CollectionConfig, Field, FieldHook, FieldHookArgs } from 'payload'
|
||||
|
||||
import { fieldPathsSlug } from '../../shared.js'
|
||||
|
||||
const attachPathsToDoc = (
|
||||
label: string,
|
||||
{ value, path, schemaPath, indexPath, data }: FieldHookArgs,
|
||||
): any => {
|
||||
if (!data) {
|
||||
data = {}
|
||||
}
|
||||
|
||||
// attach values to data for `beforeRead` and `beforeChange` hooks
|
||||
data[`${label}_FieldPaths`] = {
|
||||
path,
|
||||
schemaPath,
|
||||
indexPath,
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
const attachHooks = (
|
||||
fieldIdentifier: string,
|
||||
): {
|
||||
afterRead: FieldHook[]
|
||||
beforeChange: FieldHook[]
|
||||
beforeDuplicate: FieldHook[]
|
||||
beforeValidate: FieldHook[]
|
||||
} => ({
|
||||
beforeValidate: [(args) => attachPathsToDoc(`${fieldIdentifier}_beforeValidate`, args)],
|
||||
beforeChange: [(args) => attachPathsToDoc(`${fieldIdentifier}_beforeChange`, args)],
|
||||
afterRead: [(args) => attachPathsToDoc(`${fieldIdentifier}_afterRead`, args)],
|
||||
beforeDuplicate: [(args) => attachPathsToDoc(`${fieldIdentifier}_beforeDuplicate`, args)],
|
||||
})
|
||||
|
||||
const createFields = (fieldIdentifiers: string[]): Field[] =>
|
||||
fieldIdentifiers.reduce((acc, fieldIdentifier) => {
|
||||
return [
|
||||
...acc,
|
||||
{
|
||||
name: `${fieldIdentifier}_beforeValidate_FieldPaths`,
|
||||
type: 'json',
|
||||
},
|
||||
{
|
||||
name: `${fieldIdentifier}_beforeChange_FieldPaths`,
|
||||
type: 'json',
|
||||
},
|
||||
{
|
||||
name: `${fieldIdentifier}_afterRead_FieldPaths`,
|
||||
type: 'json',
|
||||
},
|
||||
{
|
||||
name: `${fieldIdentifier}_beforeDuplicate_FieldPaths`,
|
||||
type: 'json',
|
||||
},
|
||||
]
|
||||
}, [] as Field[])
|
||||
|
||||
export const FieldPaths: CollectionConfig = {
|
||||
slug: fieldPathsSlug,
|
||||
fields: [
|
||||
{
|
||||
// path: 'topLevelNamedField'
|
||||
// schemaPath: 'topLevelNamedField'
|
||||
// indexPath: ''
|
||||
name: 'topLevelNamedField',
|
||||
type: 'text',
|
||||
hooks: attachHooks('topLevelNamedField'),
|
||||
},
|
||||
{
|
||||
// path: 'array'
|
||||
// schemaPath: 'array'
|
||||
// indexPath: ''
|
||||
name: 'array',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
// path: 'array.[n].fieldWithinArray'
|
||||
// schemaPath: 'array.fieldWithinArray'
|
||||
// indexPath: ''
|
||||
name: 'fieldWithinArray',
|
||||
type: 'text',
|
||||
hooks: attachHooks('fieldWithinArray'),
|
||||
},
|
||||
{
|
||||
// path: 'array.[n].nestedArray'
|
||||
// schemaPath: 'array.nestedArray'
|
||||
// indexPath: ''
|
||||
name: 'nestedArray',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
// path: 'array.[n].nestedArray.[n].fieldWithinNestedArray'
|
||||
// schemaPath: 'array.nestedArray.fieldWithinNestedArray'
|
||||
// indexPath: ''
|
||||
name: 'fieldWithinNestedArray',
|
||||
type: 'text',
|
||||
hooks: attachHooks('fieldWithinNestedArray'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// path: 'array.[n]._index-2'
|
||||
// schemaPath: 'array._index-2'
|
||||
// indexPath: ''
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
// path: 'array.[n].fieldWithinRowWithinArray'
|
||||
// schemaPath: 'array._index-2.fieldWithinRowWithinArray'
|
||||
// indexPath: ''
|
||||
name: 'fieldWithinRowWithinArray',
|
||||
type: 'text',
|
||||
hooks: attachHooks('fieldWithinRowWithinArray'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// path: '_index-2'
|
||||
// schemaPath: '_index-2'
|
||||
// indexPath: '2'
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
// path: 'fieldWithinRow'
|
||||
// schemaPath: '_index-2.fieldWithinRow'
|
||||
// indexPath: ''
|
||||
name: 'fieldWithinRow',
|
||||
type: 'text',
|
||||
hooks: attachHooks('fieldWithinRow'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// path: '_index-3'
|
||||
// schemaPath: '_index-3'
|
||||
// indexPath: '3'
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
// path: '_index-3-0'
|
||||
// schemaPath: '_index-3-0'
|
||||
// indexPath: '3-0'
|
||||
label: 'Unnamed Tab',
|
||||
fields: [
|
||||
{
|
||||
// path: 'fieldWithinUnnamedTab'
|
||||
// schemaPath: '_index-3-0.fieldWithinUnnamedTab'
|
||||
// indexPath: ''
|
||||
name: 'fieldWithinUnnamedTab',
|
||||
type: 'text',
|
||||
hooks: attachHooks('fieldWithinUnnamedTab'),
|
||||
},
|
||||
{
|
||||
// path: '_index-3-0-1'
|
||||
// schemaPath: '_index-3-0-1'
|
||||
// indexPath: '3-0-1'
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
// path: '_index-3-0-1-0'
|
||||
// schemaPath: '_index-3-0-1-0'
|
||||
// indexPath: '3-0-1-0'
|
||||
label: 'Nested Unnamed Tab',
|
||||
fields: [
|
||||
{
|
||||
// path: 'fieldWithinNestedUnnamedTab'
|
||||
// schemaPath: '_index-3-0-1-0.fieldWithinNestedUnnamedTab'
|
||||
// indexPath: ''
|
||||
name: 'fieldWithinNestedUnnamedTab',
|
||||
type: 'text',
|
||||
hooks: attachHooks('fieldWithinNestedUnnamedTab'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// path: 'namedTab'
|
||||
// schemaPath: '_index-3.namedTab'
|
||||
// indexPath: ''
|
||||
label: 'Named Tab',
|
||||
name: 'namedTab',
|
||||
fields: [
|
||||
{
|
||||
// path: 'namedTab.fieldWithinNamedTab'
|
||||
// schemaPath: '_index-3.namedTab.fieldWithinNamedTab'
|
||||
// indexPath: ''
|
||||
name: 'fieldWithinNamedTab',
|
||||
type: 'text',
|
||||
hooks: attachHooks('fieldWithinNamedTab'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// create fields for the hooks to save data to
|
||||
...createFields([
|
||||
'topLevelNamedField',
|
||||
'fieldWithinArray',
|
||||
'fieldWithinNestedArray',
|
||||
'fieldWithinRowWithinArray',
|
||||
'fieldWithinRow',
|
||||
'fieldWithinUnnamedTab',
|
||||
'fieldWithinNestedUnnamedTab',
|
||||
'fieldWithinNamedTab',
|
||||
]),
|
||||
],
|
||||
}
|
||||
@@ -13,12 +13,14 @@ import { BeforeValidateCollection } from './collections/BeforeValidate/index.js'
|
||||
import ChainingHooks from './collections/ChainingHooks/index.js'
|
||||
import ContextHooks from './collections/ContextHooks/index.js'
|
||||
import { DataHooks } from './collections/Data/index.js'
|
||||
import { FieldPaths } from './collections/FieldPaths/index.js'
|
||||
import Hooks, { hooksSlug } from './collections/Hook/index.js'
|
||||
import NestedAfterReadHooks from './collections/NestedAfterReadHooks/index.js'
|
||||
import Relations from './collections/Relations/index.js'
|
||||
import TransformHooks from './collections/Transform/index.js'
|
||||
import Users, { seedHooksUsers } from './collections/Users/index.js'
|
||||
import { DataHooksGlobal } from './globals/Data/index.js'
|
||||
|
||||
export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
|
||||
admin: {
|
||||
importMap: {
|
||||
@@ -37,6 +39,7 @@ export const HooksConfig: Promise<SanitizedConfig> = buildConfigWithDefaults({
|
||||
Relations,
|
||||
Users,
|
||||
DataHooks,
|
||||
FieldPaths,
|
||||
],
|
||||
globals: [DataHooksGlobal],
|
||||
endpoints: [
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ensureCompilationIsDone, initPageConsoleErrorCatch, saveDocAndAssert }
|
||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||
import { beforeValidateSlug } from './collectionSlugs.js'
|
||||
import { beforeValidateSlug } from './shared.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
import { relationsSlug } from './collections/Relations/index.js'
|
||||
import { transformSlug } from './collections/Transform/index.js'
|
||||
import { hooksUsersSlug } from './collections/Users/index.js'
|
||||
import { beforeValidateSlug } from './collectionSlugs.js'
|
||||
import { beforeValidateSlug, fieldPathsSlug } from './shared.js'
|
||||
import { HooksConfig } from './config.js'
|
||||
import { dataHooksGlobalSlug } from './globals/Data/index.js'
|
||||
|
||||
@@ -517,6 +517,101 @@ describe('Hooks', () => {
|
||||
|
||||
expect(doc.field_globalAndField).toStrictEqual(globalAndFieldString + globalAndFieldString)
|
||||
})
|
||||
|
||||
it('should pass correct field paths through field hooks', async () => {
|
||||
const formatExpectedFieldPaths = (
|
||||
fieldIdentifier: string,
|
||||
{
|
||||
path,
|
||||
schemaPath,
|
||||
}: {
|
||||
path: string[]
|
||||
schemaPath: string[]
|
||||
},
|
||||
) => ({
|
||||
[`${fieldIdentifier}_beforeValidate_FieldPaths`]: {
|
||||
path,
|
||||
schemaPath,
|
||||
},
|
||||
[`${fieldIdentifier}_beforeChange_FieldPaths`]: {
|
||||
path,
|
||||
schemaPath,
|
||||
},
|
||||
[`${fieldIdentifier}_afterRead_FieldPaths`]: {
|
||||
path,
|
||||
schemaPath,
|
||||
},
|
||||
[`${fieldIdentifier}_beforeDuplicate_FieldPaths`]: {
|
||||
path,
|
||||
schemaPath,
|
||||
},
|
||||
})
|
||||
|
||||
const originalDoc = await payload.create({
|
||||
collection: fieldPathsSlug,
|
||||
data: {
|
||||
topLevelNamedField: 'Test',
|
||||
array: [
|
||||
{
|
||||
fieldWithinArray: 'Test',
|
||||
nestedArray: [
|
||||
{
|
||||
fieldWithinNestedArray: 'Test',
|
||||
fieldWithinNestedRow: 'Test',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
fieldWithinRow: 'Test',
|
||||
fieldWithinUnnamedTab: 'Test',
|
||||
namedTab: {
|
||||
fieldWithinNamedTab: 'Test',
|
||||
},
|
||||
fieldWithinNestedUnnamedTab: 'Test',
|
||||
},
|
||||
})
|
||||
|
||||
// duplicate the doc to ensure that the beforeDuplicate hook is run
|
||||
const doc = await payload.duplicate({
|
||||
id: originalDoc.id,
|
||||
collection: fieldPathsSlug,
|
||||
})
|
||||
|
||||
expect(doc).toMatchObject({
|
||||
...formatExpectedFieldPaths('topLevelNamedField', {
|
||||
path: ['topLevelNamedField'],
|
||||
schemaPath: ['topLevelNamedField'],
|
||||
}),
|
||||
...formatExpectedFieldPaths('fieldWithinArray', {
|
||||
path: ['array', '0', 'fieldWithinArray'],
|
||||
schemaPath: ['array', 'fieldWithinArray'],
|
||||
}),
|
||||
...formatExpectedFieldPaths('fieldWithinNestedArray', {
|
||||
path: ['array', '0', 'nestedArray', '0', 'fieldWithinNestedArray'],
|
||||
schemaPath: ['array', 'nestedArray', 'fieldWithinNestedArray'],
|
||||
}),
|
||||
...formatExpectedFieldPaths('fieldWithinRowWithinArray', {
|
||||
path: ['array', '0', 'fieldWithinRowWithinArray'],
|
||||
schemaPath: ['array', '_index-2', 'fieldWithinRowWithinArray'],
|
||||
}),
|
||||
...formatExpectedFieldPaths('fieldWithinRow', {
|
||||
path: ['fieldWithinRow'],
|
||||
schemaPath: ['_index-2', 'fieldWithinRow'],
|
||||
}),
|
||||
...formatExpectedFieldPaths('fieldWithinUnnamedTab', {
|
||||
path: ['fieldWithinUnnamedTab'],
|
||||
schemaPath: ['_index-3-0', 'fieldWithinUnnamedTab'],
|
||||
}),
|
||||
...formatExpectedFieldPaths('fieldWithinNestedUnnamedTab', {
|
||||
path: ['fieldWithinNestedUnnamedTab'],
|
||||
schemaPath: ['_index-3-0-1-0', 'fieldWithinNestedUnnamedTab'],
|
||||
}),
|
||||
...formatExpectedFieldPaths('fieldWithinNamedTab', {
|
||||
path: ['namedTab', 'fieldWithinNamedTab'],
|
||||
schemaPath: ['_index-3', 'namedTab', 'fieldWithinNamedTab'],
|
||||
}),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('config level after error hook', () => {
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface Config {
|
||||
relations: Relation;
|
||||
'hooks-users': HooksUser;
|
||||
'data-hooks': DataHook;
|
||||
'field-paths': FieldPath;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
@@ -39,6 +40,7 @@ export interface Config {
|
||||
relations: RelationsSelect<false> | RelationsSelect<true>;
|
||||
'hooks-users': HooksUsersSelect<false> | HooksUsersSelect<true>;
|
||||
'data-hooks': DataHooksSelect<false> | DataHooksSelect<true>;
|
||||
'field-paths': FieldPathsSelect<false> | FieldPathsSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
@@ -236,6 +238,286 @@ export interface DataHook {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "field-paths".
|
||||
*/
|
||||
export interface FieldPath {
|
||||
id: string;
|
||||
topLevelNamedField?: string | null;
|
||||
array?:
|
||||
| {
|
||||
fieldWithinArray?: string | null;
|
||||
nestedArray?:
|
||||
| {
|
||||
fieldWithinNestedArray?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
fieldWithinRow?: string | null;
|
||||
fieldWithinUnnamedTab?: string | null;
|
||||
fieldWithinNestedUnnamedTab?: string | null;
|
||||
namedTab?: {
|
||||
fieldWithinNamedTab?: string | null;
|
||||
};
|
||||
topLevelNamedField_beforeValidate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
topLevelNamedField_beforeChange_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
topLevelNamedField_afterRead_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
topLevelNamedField_beforeDuplicate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinArray_beforeValidate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinArray_beforeChange_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinArray_afterRead_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinArray_beforeDuplicate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNestedArray_beforeValidate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNestedArray_beforeChange_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNestedArray_afterRead_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNestedArray_beforeDuplicate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinRow_beforeValidate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinRow_beforeChange_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinRow_afterRead_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinRow_beforeDuplicate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinUnnamedTab_beforeValidate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinUnnamedTab_beforeChange_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinUnnamedTab_afterRead_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinUnnamedTab_beforeDuplicate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNestedUnnamedTab_beforeValidate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNestedUnnamedTab_beforeChange_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNestedUnnamedTab_afterRead_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNestedUnnamedTab_beforeDuplicate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNamedTab_beforeValidate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNamedTab_beforeChange_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNamedTab_afterRead_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
fieldWithinNamedTab_beforeDuplicate_FieldPaths?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents".
|
||||
@@ -286,6 +568,10 @@ export interface PayloadLockedDocument {
|
||||
| ({
|
||||
relationTo: 'data-hooks';
|
||||
value: string | DataHook;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'field-paths';
|
||||
value: string | FieldPath;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
@@ -470,6 +756,63 @@ export interface DataHooksSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "field-paths_select".
|
||||
*/
|
||||
export interface FieldPathsSelect<T extends boolean = true> {
|
||||
topLevelNamedField?: T;
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
fieldWithinArray?: T;
|
||||
nestedArray?:
|
||||
| T
|
||||
| {
|
||||
fieldWithinNestedArray?: T;
|
||||
id?: T;
|
||||
};
|
||||
id?: T;
|
||||
};
|
||||
fieldWithinRow?: T;
|
||||
fieldWithinUnnamedTab?: T;
|
||||
fieldWithinNestedUnnamedTab?: T;
|
||||
namedTab?:
|
||||
| T
|
||||
| {
|
||||
fieldWithinNamedTab?: T;
|
||||
};
|
||||
topLevelNamedField_beforeValidate_FieldPaths?: T;
|
||||
topLevelNamedField_beforeChange_FieldPaths?: T;
|
||||
topLevelNamedField_afterRead_FieldPaths?: T;
|
||||
topLevelNamedField_beforeDuplicate_FieldPaths?: T;
|
||||
fieldWithinArray_beforeValidate_FieldPaths?: T;
|
||||
fieldWithinArray_beforeChange_FieldPaths?: T;
|
||||
fieldWithinArray_afterRead_FieldPaths?: T;
|
||||
fieldWithinArray_beforeDuplicate_FieldPaths?: T;
|
||||
fieldWithinNestedArray_beforeValidate_FieldPaths?: T;
|
||||
fieldWithinNestedArray_beforeChange_FieldPaths?: T;
|
||||
fieldWithinNestedArray_afterRead_FieldPaths?: T;
|
||||
fieldWithinNestedArray_beforeDuplicate_FieldPaths?: T;
|
||||
fieldWithinRow_beforeValidate_FieldPaths?: T;
|
||||
fieldWithinRow_beforeChange_FieldPaths?: T;
|
||||
fieldWithinRow_afterRead_FieldPaths?: T;
|
||||
fieldWithinRow_beforeDuplicate_FieldPaths?: T;
|
||||
fieldWithinUnnamedTab_beforeValidate_FieldPaths?: T;
|
||||
fieldWithinUnnamedTab_beforeChange_FieldPaths?: T;
|
||||
fieldWithinUnnamedTab_afterRead_FieldPaths?: T;
|
||||
fieldWithinUnnamedTab_beforeDuplicate_FieldPaths?: T;
|
||||
fieldWithinNestedUnnamedTab_beforeValidate_FieldPaths?: T;
|
||||
fieldWithinNestedUnnamedTab_beforeChange_FieldPaths?: T;
|
||||
fieldWithinNestedUnnamedTab_afterRead_FieldPaths?: T;
|
||||
fieldWithinNestedUnnamedTab_beforeDuplicate_FieldPaths?: T;
|
||||
fieldWithinNamedTab_beforeValidate_FieldPaths?: T;
|
||||
fieldWithinNamedTab_beforeChange_FieldPaths?: T;
|
||||
fieldWithinNamedTab_afterRead_FieldPaths?: T;
|
||||
fieldWithinNamedTab_beforeDuplicate_FieldPaths?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "payload-locked-documents_select".
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export const beforeValidateSlug = 'before-validate'
|
||||
export const fieldPathsSlug = 'field-paths'
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": ["./test/admin/config.ts"],
|
||||
"@payload-config": ["./test/hooks/config.ts"],
|
||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||
"@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user