### What?
Adds `populate` property to Local API and REST API operations that can
be used to specify `select` for a specific collection when it's
populated
```ts
const result = await payload.findByID({
populate: {
// type safe if you have generated types
posts: {
text: true,
},
},
collection: 'pages',
depth: 1,
id: aboutPage.id,
})
result.relatedPost // only has text and id properties
```
```ts
fetch('https://localhost:3000/api/pages?populate[posts][text]=true') // highlight-line
.then((res) => res.json())
.then((data) => console.log(data))
```
It also overrides
[`defaultPopulate`](https://github.com/payloadcms/payload/pull/8934)
Ensures `defaultPopulate` doesn't affect GraphQL.
### How?
Implements the property for all operations that have the `depth`
argument.
230 lines
6.5 KiB
TypeScript
230 lines
6.5 KiB
TypeScript
import type { Field, PayloadRequest, PopulateType } from 'payload'
|
|
|
|
import { fieldAffectsData, fieldHasSubFields, fieldIsArrayType, tabHasName } from 'payload/shared'
|
|
|
|
import { populate } from './populate.js'
|
|
import { recurseRichText } from './richTextRelationshipPromise.js'
|
|
|
|
type NestedRichTextFieldsArgs = {
|
|
currentDepth?: number
|
|
data: unknown
|
|
depth: number
|
|
draft: boolean
|
|
fields: Field[]
|
|
overrideAccess: boolean
|
|
populateArg?: PopulateType
|
|
populationPromises: Promise<void>[]
|
|
req: PayloadRequest
|
|
showHiddenFields: boolean
|
|
}
|
|
|
|
export const recurseNestedFields = ({
|
|
currentDepth = 0,
|
|
data,
|
|
depth,
|
|
draft,
|
|
fields,
|
|
overrideAccess = false,
|
|
populateArg,
|
|
populationPromises,
|
|
req,
|
|
showHiddenFields,
|
|
}: NestedRichTextFieldsArgs): void => {
|
|
fields.forEach((field) => {
|
|
if (field.type === 'relationship' || field.type === 'upload') {
|
|
if (field.type === 'relationship') {
|
|
if (field.hasMany && Array.isArray(data[field.name])) {
|
|
if (Array.isArray(field.relationTo)) {
|
|
data[field.name].forEach(({ relationTo, value }, i) => {
|
|
const collection = req.payload.collections[relationTo]
|
|
if (collection) {
|
|
populationPromises.push(
|
|
populate({
|
|
id: value,
|
|
collection,
|
|
currentDepth,
|
|
data: data[field.name],
|
|
depth,
|
|
draft,
|
|
field,
|
|
key: i,
|
|
overrideAccess,
|
|
req,
|
|
select:
|
|
populateArg?.[collection.config.slug] ?? collection.config.defaultPopulate,
|
|
showHiddenFields,
|
|
}),
|
|
)
|
|
}
|
|
})
|
|
} else {
|
|
data[field.name].forEach((id, i) => {
|
|
const collection = req.payload.collections[field.relationTo as string]
|
|
if (collection) {
|
|
populationPromises.push(
|
|
populate({
|
|
id,
|
|
collection,
|
|
currentDepth,
|
|
data: data[field.name],
|
|
depth,
|
|
draft,
|
|
field,
|
|
key: i,
|
|
overrideAccess,
|
|
req,
|
|
select:
|
|
populateArg?.[collection.config.slug] ?? collection.config.defaultPopulate,
|
|
showHiddenFields,
|
|
}),
|
|
)
|
|
}
|
|
})
|
|
}
|
|
} else if (
|
|
Array.isArray(field.relationTo) &&
|
|
data[field.name]?.value &&
|
|
data[field.name]?.relationTo
|
|
) {
|
|
if (!('hasMany' in field) || !field.hasMany) {
|
|
const collection = req.payload.collections[data[field.name].relationTo]
|
|
populationPromises.push(
|
|
populate({
|
|
id: data[field.name].value,
|
|
collection,
|
|
currentDepth,
|
|
data: data[field.name],
|
|
depth,
|
|
draft,
|
|
field,
|
|
key: 'value',
|
|
overrideAccess,
|
|
req,
|
|
select: populateArg?.[collection.config.slug] ?? collection.config.defaultPopulate,
|
|
showHiddenFields,
|
|
}),
|
|
)
|
|
}
|
|
}
|
|
}
|
|
if (typeof data[field.name] !== 'undefined' && typeof field.relationTo === 'string') {
|
|
const collection = req.payload.collections[field.relationTo]
|
|
populationPromises.push(
|
|
populate({
|
|
id: data[field.name],
|
|
collection,
|
|
currentDepth,
|
|
data,
|
|
depth,
|
|
draft,
|
|
field,
|
|
key: field.name,
|
|
overrideAccess,
|
|
req,
|
|
select: populateArg?.[collection.config.slug] ?? collection.config.defaultPopulate,
|
|
showHiddenFields,
|
|
}),
|
|
)
|
|
}
|
|
} else if (fieldHasSubFields(field) && !fieldIsArrayType(field)) {
|
|
if (fieldAffectsData(field) && typeof data[field.name] === 'object') {
|
|
recurseNestedFields({
|
|
currentDepth,
|
|
data: data[field.name],
|
|
depth,
|
|
draft,
|
|
fields: field.fields,
|
|
overrideAccess,
|
|
populateArg,
|
|
populationPromises,
|
|
req,
|
|
showHiddenFields,
|
|
})
|
|
} else {
|
|
recurseNestedFields({
|
|
currentDepth,
|
|
data,
|
|
depth,
|
|
draft,
|
|
fields: field.fields,
|
|
overrideAccess,
|
|
populateArg,
|
|
populationPromises,
|
|
req,
|
|
showHiddenFields,
|
|
})
|
|
}
|
|
} else if (field.type === 'tabs') {
|
|
field.tabs.forEach((tab) => {
|
|
recurseNestedFields({
|
|
currentDepth,
|
|
data: tabHasName(tab) ? data[tab.name] : data,
|
|
depth,
|
|
draft,
|
|
fields: tab.fields,
|
|
overrideAccess,
|
|
populateArg,
|
|
populationPromises,
|
|
req,
|
|
showHiddenFields,
|
|
})
|
|
})
|
|
} else if (Array.isArray(data[field.name])) {
|
|
if (field.type === 'blocks') {
|
|
data[field.name].forEach((row, i) => {
|
|
const block = field.blocks.find(({ slug }) => slug === row?.blockType)
|
|
if (block) {
|
|
recurseNestedFields({
|
|
currentDepth,
|
|
data: data[field.name][i],
|
|
depth,
|
|
draft,
|
|
fields: block.fields,
|
|
overrideAccess,
|
|
populateArg,
|
|
populationPromises,
|
|
req,
|
|
showHiddenFields,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
if (field.type === 'array') {
|
|
data[field.name].forEach((_, i) => {
|
|
recurseNestedFields({
|
|
currentDepth,
|
|
data: data[field.name][i],
|
|
depth,
|
|
draft,
|
|
fields: field.fields,
|
|
overrideAccess,
|
|
populateArg,
|
|
populationPromises,
|
|
req,
|
|
showHiddenFields,
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
if (field.type === 'richText' && Array.isArray(data[field.name])) {
|
|
data[field.name].forEach((node) => {
|
|
if (Array.isArray(node.children)) {
|
|
recurseRichText({
|
|
children: node.children,
|
|
currentDepth,
|
|
depth,
|
|
draft,
|
|
field,
|
|
overrideAccess,
|
|
populationPromises,
|
|
req,
|
|
showHiddenFields,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|