diff --git a/docs/fields/join.mdx b/docs/fields/join.mdx index 8ee3eea79..151567e9b 100644 --- a/docs/fields/join.mdx +++ b/docs/fields/join.mdx @@ -6,8 +6,10 @@ desc: The Join field provides the ability to work on related documents. Learn ho keywords: join, relationship, junction, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs --- -The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can edit and view collections -having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is stored on the collection with a Join +The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can +edit and view collections +having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is +stored on the collection with a Join field. Instead, the Admin UI surfaces the related documents for a better editing experience and is surfaced by Payload's APIs. @@ -19,10 +21,10 @@ The Join field is useful in scenarios including: - Displaying where a document or upload is used in other documents For the Join field to work, you must have an existing [relationship](./relationship) or [upload](./upload) field in the @@ -111,9 +113,11 @@ related docs from a new pseudo-junction collection called `categories_posts`. No third junction collection, and can be surfaced on both Posts and Categories. But, importantly, you could add additional "context" fields to this shared junction collection. -For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add custom "context" fields like `featured` or `spotlight`, +For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add +custom "context" fields like `featured` or `spotlight`, which would allow you to store additional information directly on relationships. -The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a powerful Admin UI. +The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a +powerful Admin UI. ## Config Options @@ -126,11 +130,11 @@ The `join` field gives you complete control over any type of relational architec | **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. | | **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). | | **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). | -| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. | -| **`required`** | Require this field to have a value. | +| **`defaultLimit`** | The number of documents to return. Set to 0 to return all related documents. | +| **`defaultSort`** | The field name used to specify the order the joined documents are returned. | | **`admin`** | Admin-specific configuration. | -| **`custom`** | Extension point for adding custom data (e.g. for plugins) | -| **`typescriptSchema`** | Override field type generation with providing a JSON schema | +| **`custom`** | Extension point for adding custom data (e.g. for plugins). | +| **`typescriptSchema`** | Override field type generation with providing a JSON schema. | _\* An asterisk denotes that a property is required._ @@ -150,12 +154,12 @@ object with: { "id": "66e3431a3f23e684075aaeb9", // other fields... - "category": "66e3431a3f23e684075aae9c", - }, + "category": "66e3431a3f23e684075aae9c" + } // { ... } ], "hasNextPage": false - }, + } // other fields... } ``` @@ -213,7 +217,8 @@ You can specify as many `joins` parameters as needed for the same or different j ### GraphQL -The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each join field in your query. +The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each +join field in your query. Example: @@ -226,9 +231,9 @@ query { limit: 5 where: { author: { - equals: "66e3431a3f23e684075aaeb9" - } + equals: "66e3431a3f23e684075aaeb9" } + } ) { docs { title diff --git a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts index 332e09d7c..2ff570a25 100644 --- a/packages/db-mongodb/src/utilities/buildJoinAggregation.ts +++ b/packages/db-mongodb/src/utilities/buildJoinAggregation.ts @@ -57,8 +57,8 @@ export const buildJoinAggregation = async ({ const joinModel = adapter.collections[join.field.collection] const { - limit: limitJoin = 10, - sort: sortJoin, + limit: limitJoin = join.field.defaultLimit ?? 10, + sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort, where: whereJoin, } = joins?.[join.schemaPath] || {} @@ -66,7 +66,7 @@ export const buildJoinAggregation = async ({ config: adapter.payload.config, fields: adapter.payload.collections[slug].config.fields, locale, - sort: sortJoin || collectionConfig.defaultSort, + sort: sortJoin, timestamps: true, }) const sortProperty = Object.keys(sort)[0] diff --git a/packages/drizzle/src/find/traverseFields.ts b/packages/drizzle/src/find/traverseFields.ts index 8f6440957..558341179 100644 --- a/packages/drizzle/src/find/traverseFields.ts +++ b/packages/drizzle/src/find/traverseFields.ts @@ -238,8 +238,8 @@ export const traverseFields = ({ } const { - limit: limitArg = 10, - sort, + limit: limitArg = field.defaultLimit ?? 10, + sort = field.defaultSort, where, } = joinQuery[`${path.replaceAll('_', '.')}${field.name}`] || {} let limit = limitArg @@ -285,7 +285,9 @@ export const traverseFields = ({ let columnReferenceToCurrentID: string if (versions) { - columnReferenceToCurrentID = `${topLevelTableName.replace('_', '').replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id` + columnReferenceToCurrentID = `${topLevelTableName + .replace('_', '') + .replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id` } else { columnReferenceToCurrentID = `${topLevelTableName}_id` } diff --git a/packages/drizzle/src/transform/read/traverseFields.ts b/packages/drizzle/src/transform/read/traverseFields.ts index 864eeb82c..4f7feacda 100644 --- a/packages/drizzle/src/transform/read/traverseFields.ts +++ b/packages/drizzle/src/transform/read/traverseFields.ts @@ -420,7 +420,8 @@ export const traverseFields = >({ } if (field.type === 'join') { - const { limit = 10 } = joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {} + const { limit = field.defaultLimit ?? 10 } = + joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {} // raw hasMany results from SQLite if (typeof fieldData === 'string') { diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index ad71c526a..4e7dd6c57 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -121,6 +121,7 @@ import type { JSONFieldValidation, PointFieldValidation, RadioFieldValidation, + Sort, TextareaFieldValidation, } from '../../index.js' import type { DocumentPreferences } from '../../preferences/types.js' @@ -1452,6 +1453,8 @@ export type JoinField = { * The slug of the collection to relate with. */ collection: CollectionSlug + defaultLimit?: number + defaultSort?: Sort defaultValue?: never /** * This does not need to be set and will be overridden by the relationship field's hasMany property. diff --git a/test/joins/collections/Categories.ts b/test/joins/collections/Categories.ts index 22bf85110..6604c13ed 100644 --- a/test/joins/collections/Categories.ts +++ b/test/joins/collections/Categories.ts @@ -47,7 +47,10 @@ export const Categories: CollectionConfig = { label: 'Related Posts', type: 'join', collection: postsSlug, + defaultSort: '-title', + defaultLimit: 5, on: 'category', + maxDepth: 1, }, { name: 'hasManyPosts', diff --git a/test/joins/int.spec.ts b/test/joins/int.spec.ts index d700379dd..4fea55828 100644 --- a/test/joins/int.spec.ts +++ b/test/joins/int.spec.ts @@ -105,15 +105,6 @@ describe('Joins Field', () => { }, collection: 'categories', }) - // const sortCategoryWithPosts = await payload.findByID({ - // id: category.id, - // joins: { - // 'group.relatedPosts': { - // sort: 'title', - // }, - // }, - // collection: 'categories', - // }) expect(categoryWithPosts.group.relatedPosts.docs).toHaveLength(10) expect(categoryWithPosts.group.relatedPosts.docs[0]).toHaveProperty('id') @@ -125,11 +116,12 @@ describe('Joins Field', () => { const { docs } = await payload.find({ limit: 1, collection: 'posts', + depth: 2, }) expect(docs[0].category.id).toBeDefined() expect(docs[0].category.name).toBeDefined() - expect(docs[0].category.relatedPosts.docs).toHaveLength(10) + expect(docs[0].category.relatedPosts.docs).toHaveLength(5) // uses defaultLimit }) it('should populate relationships in joins with camelCase names', async () => {