Skip to content

Commit f0edbb7

Browse files
authored
feat: join field defaultLimit and defaultSort (#8908)
### What? Allow specifying the defaultSort and defaultLimit to use for populating a join field ### Why? It is much easier to set defaults rather than be forced to always call the join query using the query pattern ("?joins[categories][limit]=0"). ### How? See docs and type changes
1 parent 3605da1 commit f0edbb7

File tree

7 files changed

+41
-35
lines changed

7 files changed

+41
-35
lines changed

docs/fields/join.mdx

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ desc: The Join field provides the ability to work on related documents. Learn ho
66
keywords: join, relationship, junction, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
77
---
88

9-
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
10-
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
9+
The Join Field is used to make Relationship and Upload fields available in the opposite direction. With a Join you can
10+
edit and view collections
11+
having reference to a specific collection document. The field itself acts as a virtual field, in that no new data is
12+
stored on the collection with a Join
1113
field. Instead, the Admin UI surfaces the related documents for a better editing experience and is surfaced by Payload's
1214
APIs.
1315

@@ -19,10 +21,10 @@ The Join field is useful in scenarios including:
1921
- Displaying where a document or upload is used in other documents
2022

2123
<LightDarkImage
22-
srcLight="https://payloadcms.com/images/docs/fields/join.png"
23-
srcDark="https://payloadcms.com/images/docs/fields/join-dark.png"
24-
alt="Shows Join field in the Payload Admin Panel"
25-
caption="Admin Panel screenshot of Join field"
24+
srcLight="https://payloadcms.com/images/docs/fields/join.png"
25+
srcDark="https://payloadcms.com/images/docs/fields/join-dark.png"
26+
alt="Shows Join field in the Payload Admin Panel"
27+
caption="Admin Panel screenshot of Join field"
2628
/>
2729

2830
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
111113
third junction collection, and can be surfaced on both Posts and Categories. But, importantly, you could add
112114
additional "context" fields to this shared junction collection.
113115

114-
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`,
116+
For example, on this `categories_posts` collection, in addition to having the `category` and `post` fields, we could add
117+
custom "context" fields like `featured` or `spotlight`,
115118
which would allow you to store additional information directly on relationships.
116-
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a powerful Admin UI.
119+
The `join` field gives you complete control over any type of relational architecture in Payload, all wrapped up in a
120+
powerful Admin UI.
117121

118122
## Config Options
119123

@@ -126,11 +130,11 @@ The `join` field gives you complete control over any type of relational architec
126130
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
127131
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
128132
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
129-
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
130-
| **`required`** | Require this field to have a value. |
133+
| **`defaultLimit`** | The number of documents to return. Set to 0 to return all related documents. |
134+
| **`defaultSort`** | The field name used to specify the order the joined documents are returned. |
131135
| **`admin`** | Admin-specific configuration. |
132-
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
133-
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
136+
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
137+
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
134138

135139
_\* An asterisk denotes that a property is required._
136140

@@ -150,12 +154,12 @@ object with:
150154
{
151155
"id": "66e3431a3f23e684075aaeb9",
152156
// other fields...
153-
"category": "66e3431a3f23e684075aae9c",
154-
},
157+
"category": "66e3431a3f23e684075aae9c"
158+
}
155159
// { ... }
156160
],
157161
"hasNextPage": false
158-
},
162+
}
159163
// other fields...
160164
}
161165
```
@@ -213,7 +217,8 @@ You can specify as many `joins` parameters as needed for the same or different j
213217

214218
### GraphQL
215219

216-
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.
220+
The GraphQL API supports the same query options as the local and REST APIs. You can specify the query options for each
221+
join field in your query.
217222

218223
Example:
219224

@@ -226,9 +231,9 @@ query {
226231
limit: 5
227232
where: {
228233
author: {
229-
equals: "66e3431a3f23e684075aaeb9"
230-
}
234+
equals: "66e3431a3f23e684075aaeb9"
231235
}
236+
}
232237
) {
233238
docs {
234239
title

packages/db-mongodb/src/utilities/buildJoinAggregation.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,16 @@ export const buildJoinAggregation = async ({
5757
const joinModel = adapter.collections[join.field.collection]
5858

5959
const {
60-
limit: limitJoin = 10,
61-
sort: sortJoin,
60+
limit: limitJoin = join.field.defaultLimit ?? 10,
61+
sort: sortJoin = join.field.defaultSort || collectionConfig.defaultSort,
6262
where: whereJoin,
6363
} = joins?.[join.schemaPath] || {}
6464

6565
const sort = buildSortParam({
6666
config: adapter.payload.config,
6767
fields: adapter.payload.collections[slug].config.fields,
6868
locale,
69-
sort: sortJoin || collectionConfig.defaultSort,
69+
sort: sortJoin,
7070
timestamps: true,
7171
})
7272
const sortProperty = Object.keys(sort)[0]

packages/drizzle/src/find/traverseFields.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ export const traverseFields = ({
238238
}
239239

240240
const {
241-
limit: limitArg = 10,
242-
sort,
241+
limit: limitArg = field.defaultLimit ?? 10,
242+
sort = field.defaultSort,
243243
where,
244244
} = joinQuery[`${path.replaceAll('_', '.')}${field.name}`] || {}
245245
let limit = limitArg
@@ -285,7 +285,9 @@ export const traverseFields = ({
285285
let columnReferenceToCurrentID: string
286286

287287
if (versions) {
288-
columnReferenceToCurrentID = `${topLevelTableName.replace('_', '').replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id`
288+
columnReferenceToCurrentID = `${topLevelTableName
289+
.replace('_', '')
290+
.replace(new RegExp(`${adapter.versionsSuffix}$`), '')}_id`
289291
} else {
290292
columnReferenceToCurrentID = `${topLevelTableName}_id`
291293
}

packages/drizzle/src/transform/read/traverseFields.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
420420
}
421421

422422
if (field.type === 'join') {
423-
const { limit = 10 } = joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {}
423+
const { limit = field.defaultLimit ?? 10 } =
424+
joinQuery?.[`${fieldPrefix.replaceAll('_', '.')}${field.name}`] || {}
424425

425426
// raw hasMany results from SQLite
426427
if (typeof fieldData === 'string') {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ import type {
121121
JSONFieldValidation,
122122
PointFieldValidation,
123123
RadioFieldValidation,
124+
Sort,
124125
TextareaFieldValidation,
125126
} from '../../index.js'
126127
import type { DocumentPreferences } from '../../preferences/types.js'
@@ -1452,6 +1453,8 @@ export type JoinField = {
14521453
* The slug of the collection to relate with.
14531454
*/
14541455
collection: CollectionSlug
1456+
defaultLimit?: number
1457+
defaultSort?: Sort
14551458
defaultValue?: never
14561459
/**
14571460
* This does not need to be set and will be overridden by the relationship field's hasMany property.

test/joins/collections/Categories.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ export const Categories: CollectionConfig = {
4747
label: 'Related Posts',
4848
type: 'join',
4949
collection: postsSlug,
50+
defaultSort: '-title',
51+
defaultLimit: 5,
5052
on: 'category',
53+
maxDepth: 1,
5154
},
5255
{
5356
name: 'hasManyPosts',

test/joins/int.spec.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,6 @@ describe('Joins Field', () => {
105105
},
106106
collection: 'categories',
107107
})
108-
// const sortCategoryWithPosts = await payload.findByID({
109-
// id: category.id,
110-
// joins: {
111-
// 'group.relatedPosts': {
112-
// sort: 'title',
113-
// },
114-
// },
115-
// collection: 'categories',
116-
// })
117108

118109
expect(categoryWithPosts.group.relatedPosts.docs).toHaveLength(10)
119110
expect(categoryWithPosts.group.relatedPosts.docs[0]).toHaveProperty('id')
@@ -125,11 +116,12 @@ describe('Joins Field', () => {
125116
const { docs } = await payload.find({
126117
limit: 1,
127118
collection: 'posts',
119+
depth: 2,
128120
})
129121

130122
expect(docs[0].category.id).toBeDefined()
131123
expect(docs[0].category.name).toBeDefined()
132-
expect(docs[0].category.relatedPosts.docs).toHaveLength(10)
124+
expect(docs[0].category.relatedPosts.docs).toHaveLength(5) // uses defaultLimit
133125
})
134126

135127
it('should populate relationships in joins with camelCase names', async () => {

0 commit comments

Comments
 (0)