feat: queriable / sortable / useAsTitle virtual fields linked with a relationship field (#11805)

This PR adds an ability to specify a virtual field in this way
```js
{
  slug: 'posts',
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
  ],
},
{
  slug: 'virtual-relations',
  fields: [
    {
      name: 'postTitle',
      type: 'text',
      virtual: 'post.title',
    },
    {
      name: 'post',
      type: 'relationship',
      relationTo: 'posts',
    },
  ],
},
```

Then, every time you query `virtual-relations`, `postTitle` will be
automatically populated (even if using `depth: 0`) on the db level. This
field also, unlike `virtual: true` is available for querying / sorting /
`useAsTitle`.

Also, the field can be deeply nested to 2 or more relationships, for
example:
```
{
  name: 'postCategoryTitle',
  type: 'text',
  virtual: 'post.category.title',
},
```

Where the current collection has `post` - a relationship to `posts`, the
collection `posts` has `category` that's a relationship to `categories`
and finally `categories` has `title`.
This commit is contained in:
Sasha
2025-04-16 22:46:18 +03:00
committed by GitHub
parent c877b1ad43
commit 1c99f46e4f
16 changed files with 568 additions and 39 deletions

View File

@@ -240,8 +240,8 @@ export default buildConfig({
// highlight-start
cors: {
origins: ['http://localhost:3000'],
headers: ['x-custom-header']
}
headers: ['x-custom-header'],
},
// highlight-end
})
```

View File

@@ -352,7 +352,7 @@ const config = buildConfig({
},
],
},
{
{
slug: 'collection2',
fields: [
{
@@ -365,7 +365,7 @@ const config = buildConfig({
blocks: ['TextBlock'],
}),
],
})
}),
},
],
},

View File

@@ -63,6 +63,7 @@ To install a Database Adapter, you can run **one** of the following commands:
```
- To install the [Postgres Adapter](../database/postgres), run:
```bash
pnpm i @payloadcms/db-postgres
```
@@ -80,7 +81,7 @@ To install a Database Adapter, you can run **one** of the following commands:
#### 2. Copy Payload files into your Next.js app folder
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
```plaintext
app/

View File

@@ -33,9 +33,9 @@ export const validateUseAsTitle = (config: CollectionConfig) => {
}
}
} else {
if (useAsTitleField && fieldIsVirtual(useAsTitleField)) {
if (useAsTitleField && 'virtual' in useAsTitleField && useAsTitleField.virtual === true) {
throw new InvalidConfiguration(
`The field "${config.admin.useAsTitle}" specified in "admin.useAsTitle" in the collection "${config.slug}" is virtual. A virtual field cannot be used as the title.`,
`The field "${config.admin.useAsTitle}" specified in "admin.useAsTitle" in the collection "${config.slug}" is virtual. A virtual field can be used as the title only when linked to a relationship field.`,
)
}
if (!useAsTitleField) {

View File

@@ -28,6 +28,7 @@ import { buildVersionCollectionFields } from '../../versions/buildCollectionFiel
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
import { getQueryDraftsSelect } from '../../versions/drafts/getQueryDraftsSelect.js'
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
import { sanitizeSortQuery } from './utilities/sanitizeSortQuery.js'
import { buildAfterOperation } from './utils.js'
export type Arguments = {
@@ -96,7 +97,7 @@ export const findOperation = async <
req,
select: incomingSelect,
showHiddenFields,
sort,
sort: incomingSort,
where,
} = args
@@ -143,6 +144,11 @@ export const findOperation = async <
let fullWhere = combineQueries(where, accessResult)
const sort = sanitizeSortQuery({
fields: collection.config.flattenedFields,
sort: incomingSort,
})
const sanitizedJoins = await sanitizeJoinQuery({
collectionConfig,
joins,
@@ -170,7 +176,10 @@ export const findOperation = async <
pagination: usePagination,
req,
select: getQueryDraftsSelect({ select }),
sort: getQueryDraftsSort({ collectionConfig, sort }),
sort: getQueryDraftsSort({
collectionConfig,
sort,
}),
where: fullWhere,
})
} else {

View File

@@ -27,6 +27,7 @@ import { sanitizeSelect } from '../../utilities/sanitizeSelect.js'
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
import { sanitizeSortQuery } from './utilities/sanitizeSortQuery.js'
import { updateDocument } from './utilities/update.js'
import { buildAfterOperation } from './utils.js'
@@ -103,7 +104,7 @@ export const updateOperation = async <
req,
select: incomingSelect,
showHiddenFields,
sort,
sort: incomingSort,
where,
} = args
@@ -136,6 +137,11 @@ export const updateOperation = async <
const fullWhere = combineQueries(where, accessResult)
const sort = sanitizeSortQuery({
fields: collection.config.flattenedFields,
sort: incomingSort,
})
let docs
if (collectionConfig.versions?.drafts && shouldSaveDraft) {

View File

@@ -0,0 +1,51 @@
import type { FlattenedField } from '../../../fields/config/types.js'
const sanitizeSort = ({ fields, sort }: { fields: FlattenedField[]; sort: string }): string => {
let sortProperty = sort
let desc = false
if (sort.indexOf('-') === 0) {
desc = true
sortProperty = sortProperty.substring(1)
}
const segments = sortProperty.split('.')
for (const segment of segments) {
const field = fields.find((each) => each.name === segment)
if (!field) {
return sort
}
if ('fields' in field) {
fields = field.flattenedFields
continue
}
if ('virtual' in field && typeof field.virtual === 'string') {
return `${desc ? '-' : ''}${field.virtual}`
}
}
return sort
}
/**
* Sanitizes the sort parameter, for example virtual fields linked to relationships are replaced with the full path.
*/
export const sanitizeSortQuery = ({
fields,
sort,
}: {
fields: FlattenedField[]
sort?: string | string[]
}): string | string[] | undefined => {
if (!sort) {
return undefined
}
if (Array.isArray(sort)) {
return sort.map((sort) => sanitizeSort({ fields, sort }))
}
return sanitizeSort({ fields, sort })
}

View File

@@ -28,22 +28,6 @@ type Args = {
}
)
const flattenWhere = (query: Where): WhereField[] => {
const flattenedConstraints: WhereField[] = []
for (const [key, val] of Object.entries(query)) {
if ((key === 'and' || key === 'or') && Array.isArray(val)) {
for (const subVal of val) {
flattenedConstraints.push(...flattenWhere(subVal))
}
} else {
flattenedConstraints.push({ [key]: val })
}
}
return flattenedConstraints
}
export async function validateQueryPaths({
collectionConfig,
errors = [],
@@ -61,17 +45,47 @@ export async function validateQueryPaths({
const fields = versionFields || (globalConfig || collectionConfig).flattenedFields
if (typeof where === 'object') {
const whereFields = flattenWhere(where)
// We need to determine if the whereKey is an AND, OR, or a schema path
const promises = []
for (const constraint of whereFields) {
for (const path in constraint) {
for (const operator in constraint[path]) {
const val = constraint[path][operator]
for (const path in where) {
const constraint = where[path]
if ((path === 'and' || path === 'or') && Array.isArray(constraint)) {
for (const item of constraint) {
if (collectionConfig) {
promises.push(
validateQueryPaths({
collectionConfig,
errors,
overrideAccess,
policies,
req,
versionFields,
where: item,
}),
)
} else {
promises.push(
validateQueryPaths({
errors,
globalConfig,
overrideAccess,
policies,
req,
versionFields,
where: item,
}),
)
}
}
} else if (!Array.isArray(constraint)) {
for (const operator in constraint) {
const val = constraint[operator]
if (validOperatorSet.has(operator as Operator)) {
promises.push(
validateSearchParam({
collectionConfig,
constraint: where as WhereField,
errors,
fields,
globalConfig,

View File

@@ -2,17 +2,19 @@
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
import type { FlattenedField } from '../../fields/config/types.js'
import type { SanitizedGlobalConfig } from '../../globals/config/types.js'
import type { PayloadRequest } from '../../types/index.js'
import type { PayloadRequest, WhereField } from '../../types/index.js'
import type { EntityPolicies, PathToQuery } from './types.js'
import { fieldAffectsData, fieldIsVirtual } from '../../fields/config/types.js'
import { getEntityPolicies } from '../../utilities/getEntityPolicies.js'
import { getFieldByPath } from '../../utilities/getFieldByPath.js'
import isolateObjectProperty from '../../utilities/isolateObjectProperty.js'
import { getLocalizedPaths } from '../getLocalizedPaths.js'
import { validateQueryPaths } from './validateQueryPaths.js'
type Args = {
collectionConfig?: SanitizedCollectionConfig
constraint: WhereField
errors: { path: string }[]
fields: FlattenedField[]
globalConfig?: SanitizedGlobalConfig
@@ -32,6 +34,7 @@ type Args = {
*/
export async function validateSearchParam({
collectionConfig,
constraint,
errors,
fields,
globalConfig,
@@ -100,8 +103,13 @@ export async function validateSearchParam({
return
}
if (fieldIsVirtual(field)) {
errors.push({ path })
if ('virtual' in field && field.virtual) {
if (field.virtual === true) {
errors.push({ path })
} else {
constraint[`${field.virtual}`] = constraint[path]
delete constraint[path]
}
}
if (polymorphicJoin && path === 'relationTo') {

View File

@@ -514,9 +514,9 @@ export interface FieldBase {
/**
* Pass `true` to disable field in the DB
* for [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges):
* A virtual field cannot be used in `admin.useAsTitle`
* A virtual field can be used in `admin.useAsTitle` only when linked to a relationship.
*/
virtual?: boolean
virtual?: boolean | string
}
export interface FieldBaseClient {
@@ -1955,7 +1955,7 @@ export function fieldShouldBeLocalized({
}
export function fieldIsVirtual(field: Field | Tab): boolean {
return 'virtual' in field && field.virtual
return 'virtual' in field && Boolean(field.virtual)
}
export type HookName =

View File

@@ -2,7 +2,6 @@
import type { RichTextAdapter } from '../../../admin/RichText.js'
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
import type { RequestContext } from '../../../index.js'
import type {
JsonObject,
PayloadRequest,
@@ -13,6 +12,7 @@ import type {
import type { Block, Field, TabAsField } from '../../config/types.js'
import { MissingEditorProp } from '../../../errors/index.js'
import { type RequestContext } from '../../../index.js'
import { getBlockSelect } from '../../../utilities/getBlockSelect.js'
import { stripUnselectedFields } from '../../../utilities/stripUnselectedFields.js'
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.js'
@@ -20,6 +20,7 @@ import { getDefaultValue } from '../../getDefaultValue.js'
import { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
import { relationshipPopulationPromise } from './relationshipPopulationPromise.js'
import { traverseFields } from './traverseFields.js'
import { virtualFieldPopulationPromise } from './virtualFieldPopulationPromise.js'
type Args = {
/**
@@ -306,6 +307,24 @@ export const promise = async ({
}
}
if ('virtual' in field && typeof field.virtual === 'string') {
populationPromises.push(
virtualFieldPopulationPromise({
name: field.name,
draft,
fallbackLocale,
fields: (collection || global).flattenedFields,
locale,
overrideAccess,
ref: doc,
req,
segments: field.virtual.split('.'),
showHiddenFields,
siblingDoc,
}),
)
}
// Execute access control
let allowDefaultValue = true
if (triggerAccessControl && field.access && field.access.read) {

View File

@@ -0,0 +1,144 @@
import type { PayloadRequest } from '../../../types/index.js'
import type { FlattenedField } from '../../config/types.js'
import { createDataloaderCacheKey } from '../../../collections/dataloader.js'
export const virtualFieldPopulationPromise = async ({
name,
draft,
fallbackLocale,
fields,
locale,
overrideAccess,
ref,
req,
segments,
showHiddenFields,
siblingDoc,
}: {
draft: boolean
fallbackLocale: string
fields: FlattenedField[]
locale: string
name: string
overrideAccess: boolean
ref: any
req: PayloadRequest
segments: string[]
showHiddenFields: boolean
siblingDoc: Record<string, unknown>
}): Promise<void> => {
const currentSegment = segments.shift()
if (!currentSegment) {
return
}
const currentValue = ref[currentSegment]
if (typeof currentValue === 'undefined') {
return
}
// Final step
if (segments.length === 0) {
siblingDoc[name] = currentValue
return
}
const currentField = fields.find((each) => each.name === currentSegment)
if (!currentField) {
return
}
if (currentField.type === 'group' || currentField.type === 'tab') {
if (!currentValue || typeof currentValue !== 'object') {
return
}
return virtualFieldPopulationPromise({
name,
draft,
fallbackLocale,
fields: currentField.flattenedFields,
locale,
overrideAccess,
ref: currentValue,
req,
segments,
showHiddenFields,
siblingDoc,
})
}
if (
(currentField.type === 'relationship' || currentField.type === 'upload') &&
typeof currentField.relationTo === 'string' &&
!currentField.hasMany
) {
let docID: number | string
if (typeof currentValue === 'object' && currentValue) {
docID = currentValue.id
} else {
docID = currentValue
}
if (typeof docID !== 'string' && typeof docID !== 'number') {
return
}
const select = {}
let currentSelectRef: any = select
const currentFields = req.payload.collections[currentField.relationTo].config.flattenedFields
for (let i = 0; i < segments.length; i++) {
const field = currentFields.find((each) => each.name === segments[i])
const shouldBreak =
i === segments.length - 1 || field?.type === 'relationship' || field?.type === 'upload'
currentSelectRef[segments[i]] = shouldBreak ? true : {}
currentSelectRef = currentSelectRef[segments[i]]
if (shouldBreak) {
break
}
}
const populatedDoc = await req.payloadDataLoader.load(
createDataloaderCacheKey({
collectionSlug: currentField.relationTo,
currentDepth: 0,
depth: 0,
docID,
draft,
fallbackLocale,
locale,
overrideAccess,
select,
showHiddenFields,
transactionID: req.transactionID as number,
}),
)
if (!populatedDoc) {
return
}
return virtualFieldPopulationPromise({
name,
draft,
fallbackLocale,
fields: req.payload.collections[currentField.relationTo].config.flattenedFields,
locale,
overrideAccess,
ref: populatedDoc,
req,
segments,
showHiddenFields,
siblingDoc,
})
}
}

View File

@@ -36,6 +36,15 @@ export default buildConfigWithDefaults({
},
},
collections: [
{
slug: 'categories',
fields: [
{
type: 'text',
name: 'title',
},
],
},
{
slug: postsSlug,
fields: [
@@ -43,6 +52,17 @@ export default buildConfigWithDefaults({
name: 'title',
type: 'text',
required: true,
// access: { read: () => false },
},
{
type: 'relationship',
relationTo: 'categories',
name: 'category',
},
{
name: 'localized',
type: 'text',
localized: true,
},
{
name: 'text',
@@ -437,6 +457,33 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: 'virtual-relations',
admin: { useAsTitle: 'postTitle' },
fields: [
{
name: 'postTitle',
type: 'text',
virtual: 'post.title',
},
{
name: 'postCategoryTitle',
type: 'text',
virtual: 'post.category.title',
},
{
name: 'postLocalized',
type: 'text',
virtual: 'post.localized',
},
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
},
],
versions: { drafts: true },
},
{
slug: fieldsPersistanceSlug,
fields: [
@@ -662,6 +709,21 @@ export default buildConfigWithDefaults({
},
],
},
{
slug: 'virtual-relation-global',
fields: [
{
type: 'text',
name: 'postTitle',
virtual: 'post.title',
},
{
type: 'relationship',
name: 'post',
relationTo: 'posts',
},
],
},
],
localization: {
defaultLocale: 'en',

View File

@@ -7,6 +7,7 @@ import {
migrateRelationshipsV2_V3,
migrateVersionsV1_V2,
} from '@payloadcms/db-mongodb/migration-utils'
import { objectToFrontmatter } from '@payloadcms/richtext-lexical'
import { randomUUID } from 'crypto'
import { type Table } from 'drizzle-orm'
import * as drizzlePg from 'drizzle-orm/pg-core'
@@ -1977,6 +1978,132 @@ describe('database', () => {
expect(res.textWithinRow).toBeUndefined()
expect(res.textWithinTabs).toBeUndefined()
})
it('should allow virtual field with reference', async () => {
const post = await payload.create({ collection: 'posts', data: { title: 'my-title' } })
const { id } = await payload.create({
collection: 'virtual-relations',
depth: 0,
data: { post: post.id },
})
const doc = await payload.findByID({ collection: 'virtual-relations', depth: 0, id })
expect(doc.postTitle).toBe('my-title')
const draft = await payload.find({
collection: 'virtual-relations',
depth: 0,
where: { id: { equals: id } },
draft: true,
})
expect(draft.docs[0]?.postTitle).toBe('my-title')
})
it('should allow virtual field with reference localized', async () => {
const post = await payload.create({
collection: 'posts',
data: { title: 'my-title', localized: 'localized en' },
})
await payload.update({
collection: 'posts',
id: post.id,
locale: 'es',
data: { localized: 'localized es' },
})
const { id } = await payload.create({
collection: 'virtual-relations',
depth: 0,
data: { post: post.id },
})
let doc = await payload.findByID({ collection: 'virtual-relations', depth: 0, id })
expect(doc.postLocalized).toBe('localized en')
doc = await payload.findByID({ collection: 'virtual-relations', depth: 0, id, locale: 'es' })
expect(doc.postLocalized).toBe('localized es')
})
it('should allow to query by a virtual field with reference', async () => {
await payload.delete({ collection: 'posts', where: {} })
await payload.delete({ collection: 'virtual-relations', where: {} })
const post_1 = await payload.create({ collection: 'posts', data: { title: 'Dan' } })
const post_2 = await payload.create({ collection: 'posts', data: { title: 'Mr.Dan' } })
const doc_1 = await payload.create({
collection: 'virtual-relations',
depth: 0,
data: { post: post_1.id },
})
const doc_2 = await payload.create({
collection: 'virtual-relations',
depth: 0,
data: { post: post_2.id },
})
const { docs: ascDocs } = await payload.find({
collection: 'virtual-relations',
sort: 'postTitle',
depth: 0,
})
expect(ascDocs[0]?.id).toBe(doc_1.id)
expect(ascDocs[1]?.id).toBe(doc_2.id)
const { docs: descDocs } = await payload.find({
collection: 'virtual-relations',
sort: '-postTitle',
depth: 0,
})
expect(descDocs[1]?.id).toBe(doc_1.id)
expect(descDocs[0]?.id).toBe(doc_2.id)
})
it.todo('should allow to sort by a virtual field with reference')
it('should allow virtual field 2x deep', async () => {
const category = await payload.create({
collection: 'categories',
data: { title: '1-category' },
})
const post = await payload.create({
collection: 'posts',
data: { title: '1-post', category: category.id },
})
const doc = await payload.create({ collection: 'virtual-relations', data: { post: post.id } })
expect(doc.postCategoryTitle).toBe('1-category')
})
it('should allow to query by virtual field 2x deep', async () => {
const category = await payload.create({
collection: 'categories',
data: { title: '2-category' },
})
const post = await payload.create({
collection: 'posts',
data: { title: '2-post', category: category.id },
})
const doc = await payload.create({ collection: 'virtual-relations', data: { post: post.id } })
const found = await payload.find({
collection: 'virtual-relations',
where: { postCategoryTitle: { equals: '2-category' } },
})
expect(found.docs).toHaveLength(1)
expect(found.docs[0].id).toBe(doc.id)
})
it('should allow referenced virtual field in globals', async () => {
const post = await payload.create({ collection: 'posts', data: { title: 'post' } })
const globalData = await payload.updateGlobal({
slug: 'virtual-relation-global',
data: { post: post.id },
depth: 0,
})
expect(globalData.postTitle).toBe('post')
})
})
it('should convert numbers to text', async () => {

View File

@@ -67,6 +67,7 @@ export interface Config {
};
blocks: {};
collections: {
categories: Category;
posts: Post;
'error-on-unnamed-fields': ErrorOnUnnamedField;
'default-values': DefaultValue;
@@ -75,6 +76,7 @@ export interface Config {
'pg-migrations': PgMigration;
'custom-schema': CustomSchema;
places: Place;
'virtual-relations': VirtualRelation;
'fields-persistance': FieldsPersistance;
'custom-ids': CustomId;
'fake-custom-ids': FakeCustomId;
@@ -88,6 +90,7 @@ export interface Config {
};
collectionsJoins: {};
collectionsSelect: {
categories: CategoriesSelect<false> | CategoriesSelect<true>;
posts: PostsSelect<false> | PostsSelect<true>;
'error-on-unnamed-fields': ErrorOnUnnamedFieldsSelect<false> | ErrorOnUnnamedFieldsSelect<true>;
'default-values': DefaultValuesSelect<false> | DefaultValuesSelect<true>;
@@ -96,6 +99,7 @@ export interface Config {
'pg-migrations': PgMigrationsSelect<false> | PgMigrationsSelect<true>;
'custom-schema': CustomSchemaSelect<false> | CustomSchemaSelect<true>;
places: PlacesSelect<false> | PlacesSelect<true>;
'virtual-relations': VirtualRelationsSelect<false> | VirtualRelationsSelect<true>;
'fields-persistance': FieldsPersistanceSelect<false> | FieldsPersistanceSelect<true>;
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
'fake-custom-ids': FakeCustomIdsSelect<false> | FakeCustomIdsSelect<true>;
@@ -114,11 +118,13 @@ export interface Config {
global: Global;
'global-2': Global2;
'global-3': Global3;
'virtual-relation-global': VirtualRelationGlobal;
};
globalsSelect: {
global: GlobalSelect<false> | GlobalSelect<true>;
'global-2': Global2Select<false> | Global2Select<true>;
'global-3': Global3Select<false> | Global3Select<true>;
'virtual-relation-global': VirtualRelationGlobalSelect<false> | VirtualRelationGlobalSelect<true>;
};
locale: 'en' | 'es';
user: User & {
@@ -147,6 +153,16 @@ export interface UserAuthOperations {
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "categories".
*/
export interface Category {
id: string;
title?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts".
@@ -154,6 +170,9 @@ export interface UserAuthOperations {
export interface Post {
id: string;
title: string;
category?: (string | null) | Category;
localized?: string | null;
text?: string | null;
number?: number | null;
D1?: {
D2?: {
@@ -346,6 +365,20 @@ export interface Place {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "virtual-relations".
*/
export interface VirtualRelation {
id: string;
postTitle?: string | null;
postCategoryTitle?: string | null;
postLocalized?: string | null;
post?: (string | null) | Post;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "fields-persistance".
@@ -465,6 +498,10 @@ export interface User {
export interface PayloadLockedDocument {
id: string;
document?:
| ({
relationTo: 'categories';
value: string | Category;
} | null)
| ({
relationTo: 'posts';
value: string | Post;
@@ -497,6 +534,10 @@ export interface PayloadLockedDocument {
relationTo: 'places';
value: string | Place;
} | null)
| ({
relationTo: 'virtual-relations';
value: string | VirtualRelation;
} | null)
| ({
relationTo: 'fields-persistance';
value: string | FieldsPersistance;
@@ -567,12 +608,24 @@ export interface PayloadMigration {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "categories_select".
*/
export interface CategoriesSelect<T extends boolean = true> {
title?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts_select".
*/
export interface PostsSelect<T extends boolean = true> {
title?: T;
category?: T;
localized?: T;
text?: T;
number?: T;
D1?:
| T
@@ -747,6 +800,19 @@ export interface PlacesSelect<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "virtual-relations_select".
*/
export interface VirtualRelationsSelect<T extends boolean = true> {
postTitle?: T;
postCategoryTitle?: T;
postLocalized?: T;
post?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "fields-persistance_select".
@@ -917,6 +983,17 @@ export interface Global3 {
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "virtual-relation-global".
*/
export interface VirtualRelationGlobal {
id: string;
postTitle?: string | null;
post?: (string | null) | Post;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "global_select".
@@ -947,6 +1024,17 @@ export interface Global3Select<T extends boolean = true> {
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "virtual-relation-global_select".
*/
export interface VirtualRelationGlobalSelect<T extends boolean = true> {
postTitle?: T;
post?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".