diff --git a/packages/payload/src/fields/config/sanitizeJoinField.ts b/packages/payload/src/fields/config/sanitizeJoinField.ts index 133b244922..89e57dfd8e 100644 --- a/packages/payload/src/fields/config/sanitizeJoinField.ts +++ b/packages/payload/src/fields/config/sanitizeJoinField.ts @@ -4,6 +4,8 @@ import type { Config, SanitizedConfig } from '../../config/types.js' import { APIError } from '../../errors/index.js' import { InvalidFieldJoin } from '../../errors/InvalidFieldJoin.js' +import { flattenAllFields } from '../../utilities/flattenAllFields.js' +import { getFieldByPath } from '../../utilities/getFieldByPath.js' import { traverseFields } from '../../utilities/traverseFields.js' import { fieldShouldBeLocalized, @@ -74,86 +76,40 @@ export const sanitizeJoinField = ({ if (!joinCollection) { throw new InvalidFieldJoin(field) } - let joinRelationship: RelationshipField | UploadField - const pathSegments = field.on.split('.') // Split the schema path into segments - let currentSegmentIndex = 0 - - let localized = false - // Traverse fields and match based on the schema path - traverseFields({ - callback: ({ field, next, parentIsLocalized }) => { - if (!('name' in field) || !field.name) { - return - } - const currentSegment = pathSegments[currentSegmentIndex] - // match field on path segments - if ('name' in field && field.name === currentSegment) { - if (fieldShouldBeLocalized({ field, parentIsLocalized })) { - localized = true - const fieldIndex = currentSegmentIndex - - join.getForeignPath = ({ locale }) => { - return pathSegments.reduce((acc, segment, index) => { - let result = `${acc}${segment}` - - if (index === fieldIndex) { - result = `${result}.${locale}` - } - - if (index !== pathSegments.length - 1) { - result = `${result}.` - } - - return result - }, '') - } - } - - // Check if this is the last segment in the path - if ( - (currentSegmentIndex === pathSegments.length - 1 && - 'type' in field && - field.type === 'relationship') || - field.type === 'upload' - ) { - joinRelationship = field // Return the matched field - next() - return true - } else { - // Move to the next path segment and continue traversal - currentSegmentIndex++ - } - } else { - // skip fields in non-matching path segments - next() - return - } - }, - config: config as unknown as SanitizedConfig, - fields: joinCollection.fields, - parentIsLocalized: false, + const relationshipField = getFieldByPath({ + fields: flattenAllFields({ cache: true, fields: joinCollection.fields }), + path: field.on, }) - if (!joinRelationship) { + if ( + !relationshipField || + (relationshipField.field.type !== 'relationship' && relationshipField.field.type !== 'upload') + ) { throw new InvalidFieldJoin(join.field) } - if (!joinRelationship.index && !joinRelationship.unique) { - joinRelationship.index = true + if (relationshipField.pathHasLocalized) { + join.getForeignPath = ({ locale }) => { + return relationshipField.localizedPath.replace('', locale) + } + } + + if (!relationshipField.field.index && !relationshipField.field.unique) { + relationshipField.field.index = true } if (validateOnly) { return } - join.targetField = joinRelationship + join.targetField = relationshipField.field // override the join field localized property to use whatever the relationship field has // or if it's nested to a localized array / blocks / tabs / group - field.localized = localized + field.localized = relationshipField.field.localized // override the join field hasMany property to use whatever the relationship field has - field.hasMany = joinRelationship.hasMany + field.hasMany = relationshipField.field.hasMany // @ts-expect-error converting JoinField to FlattenedJoinField to track targetField field.targetField = join.targetField diff --git a/test/joins/collections/Categories.ts b/test/joins/collections/Categories.ts index b01f93e934..a87a9084d3 100644 --- a/test/joins/collections/Categories.ts +++ b/test/joins/collections/Categories.ts @@ -162,6 +162,12 @@ export const Categories: CollectionConfig = { isFiltered: { not_equals: true }, }, }, + { + name: 'inTab', + type: 'join', + collection: postsSlug, + on: 'tab.category', + }, { name: 'joinWithError', type: 'join', diff --git a/test/joins/collections/Posts.ts b/test/joins/collections/Posts.ts index eaa1cf2b53..7fc65e4a73 100644 --- a/test/joins/collections/Posts.ts +++ b/test/joins/collections/Posts.ts @@ -142,5 +142,34 @@ export const Posts: CollectionConfig = { }, ], }, + { + type: 'tabs', + tabs: [ + { + name: 'first', + fields: [ + { + type: 'text', + name: 'tabText', + }, + ], + }, + { + name: 'tab', + fields: [ + { + type: 'row', + fields: [ + { + name: 'category', + type: 'relationship', + relationTo: categoriesSlug, + }, + ], + }, + ], + }, + ], + }, ], } diff --git a/test/joins/payload-types.ts b/test/joins/payload-types.ts index 9d5d5ed63b..5f290e335b 100644 --- a/test/joins/payload-types.ts +++ b/test/joins/payload-types.ts @@ -113,6 +113,7 @@ export interface Config { localizedPolymorphic: 'posts'; localizedPolymorphics: 'posts'; filtered: 'posts'; + inTab: 'posts'; joinWithError: 'posts'; hiddenPosts: 'hidden-posts'; singulars: 'singular'; @@ -316,6 +317,12 @@ export interface Post { blockType: 'block'; }[] | null; + first?: { + tabText?: string | null; + }; + tab?: { + category?: (string | null) | Category; + }; updatedAt: string; createdAt: string; } @@ -429,6 +436,11 @@ export interface Category { hasNextPage?: boolean; totalDocs?: number; }; + inTab?: { + docs?: (string | Post)[]; + hasNextPage?: boolean; + totalDocs?: number; + }; joinWithError?: { docs?: (string | Post)[]; hasNextPage?: boolean; @@ -935,6 +947,16 @@ export interface PostsSelect { blockName?: T; }; }; + first?: + | T + | { + tabText?: T; + }; + tab?: + | T + | { + category?: T; + }; updatedAt?: T; createdAt?: T; } @@ -963,6 +985,7 @@ export interface CategoriesSelect { localizedPolymorphics?: T; singulars?: T; filtered?: T; + inTab?: T; joinWithError?: T; enableErrorOnJoin?: T; updatedAt?: T;