diff --git a/docs/configuration/overview.mdx b/docs/configuration/overview.mdx index 6b1b28c9fa..25b1d9c83f 100644 --- a/docs/configuration/overview.mdx +++ b/docs/configuration/overview.mdx @@ -240,8 +240,8 @@ export default buildConfig({ // highlight-start cors: { origins: ['http://localhost:3000'], - headers: ['x-custom-header'] - } + headers: ['x-custom-header'], + }, // highlight-end }) ``` diff --git a/docs/fields/blocks.mdx b/docs/fields/blocks.mdx index edb5e51e86..aa1b860c2c 100644 --- a/docs/fields/blocks.mdx +++ b/docs/fields/blocks.mdx @@ -352,7 +352,7 @@ const config = buildConfig({ }, ], }, - { + { slug: 'collection2', fields: [ { @@ -365,7 +365,7 @@ const config = buildConfig({ blocks: ['TextBlock'], }), ], - }) + }), }, ], }, diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index d42d3e7ca4..2f507862a6 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -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]() on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this: ```plaintext app/ diff --git a/packages/payload/src/collections/config/useAsTitle.ts b/packages/payload/src/collections/config/useAsTitle.ts index a57aa32e17..c4633f94f7 100644 --- a/packages/payload/src/collections/config/useAsTitle.ts +++ b/packages/payload/src/collections/config/useAsTitle.ts @@ -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) { diff --git a/packages/payload/src/collections/operations/find.ts b/packages/payload/src/collections/operations/find.ts index 48810c7285..f0c9117f00 100644 --- a/packages/payload/src/collections/operations/find.ts +++ b/packages/payload/src/collections/operations/find.ts @@ -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 { diff --git a/packages/payload/src/collections/operations/update.ts b/packages/payload/src/collections/operations/update.ts index f64ca7f47d..ab2e2308fa 100644 --- a/packages/payload/src/collections/operations/update.ts +++ b/packages/payload/src/collections/operations/update.ts @@ -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) { diff --git a/packages/payload/src/collections/operations/utilities/sanitizeSortQuery.ts b/packages/payload/src/collections/operations/utilities/sanitizeSortQuery.ts new file mode 100644 index 0000000000..692d13d318 --- /dev/null +++ b/packages/payload/src/collections/operations/utilities/sanitizeSortQuery.ts @@ -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 }) +} diff --git a/packages/payload/src/database/queryValidation/validateQueryPaths.ts b/packages/payload/src/database/queryValidation/validateQueryPaths.ts index 563b90b051..c6af5ccf23 100644 --- a/packages/payload/src/database/queryValidation/validateQueryPaths.ts +++ b/packages/payload/src/database/queryValidation/validateQueryPaths.ts @@ -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, diff --git a/packages/payload/src/database/queryValidation/validateSearchParams.ts b/packages/payload/src/database/queryValidation/validateSearchParams.ts index 9ec7fde56d..125df0336a 100644 --- a/packages/payload/src/database/queryValidation/validateSearchParams.ts +++ b/packages/payload/src/database/queryValidation/validateSearchParams.ts @@ -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') { diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index 835c91da5e..3050e6250c 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -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 = diff --git a/packages/payload/src/fields/hooks/afterRead/promise.ts b/packages/payload/src/fields/hooks/afterRead/promise.ts index fd2b0d1443..20612d465c 100644 --- a/packages/payload/src/fields/hooks/afterRead/promise.ts +++ b/packages/payload/src/fields/hooks/afterRead/promise.ts @@ -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) { diff --git a/packages/payload/src/fields/hooks/afterRead/virtualFieldPopulationPromise.ts b/packages/payload/src/fields/hooks/afterRead/virtualFieldPopulationPromise.ts new file mode 100644 index 0000000000..4c8eb758d1 --- /dev/null +++ b/packages/payload/src/fields/hooks/afterRead/virtualFieldPopulationPromise.ts @@ -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 +}): Promise => { + 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, + }) + } +} diff --git a/packages/plugin-seo/src/translations/es.ts b/packages/plugin-seo/src/translations/es.ts index fe0cc4c852..014f03ba1b 100644 --- a/packages/plugin-seo/src/translations/es.ts +++ b/packages/plugin-seo/src/translations/es.ts @@ -25,4 +25,4 @@ export const es: GenericTranslationsObject = { tooLong: 'Demasiado largo', tooShort: 'Demasiado corto', }, -} \ No newline at end of file +} diff --git a/test/database/config.ts b/test/database/config.ts index f9f8be424d..d1656c347a 100644 --- a/test/database/config.ts +++ b/test/database/config.ts @@ -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', diff --git a/test/database/int.spec.ts b/test/database/int.spec.ts index 2cb385f815..a4c3210a9a 100644 --- a/test/database/int.spec.ts +++ b/test/database/int.spec.ts @@ -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 () => { diff --git a/test/database/payload-types.ts b/test/database/payload-types.ts index f2692eaaa8..f29cf028fc 100644 --- a/test/database/payload-types.ts +++ b/test/database/payload-types.ts @@ -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 | CategoriesSelect; posts: PostsSelect | PostsSelect; 'error-on-unnamed-fields': ErrorOnUnnamedFieldsSelect | ErrorOnUnnamedFieldsSelect; 'default-values': DefaultValuesSelect | DefaultValuesSelect; @@ -96,6 +99,7 @@ export interface Config { 'pg-migrations': PgMigrationsSelect | PgMigrationsSelect; 'custom-schema': CustomSchemaSelect | CustomSchemaSelect; places: PlacesSelect | PlacesSelect; + 'virtual-relations': VirtualRelationsSelect | VirtualRelationsSelect; 'fields-persistance': FieldsPersistanceSelect | FieldsPersistanceSelect; 'custom-ids': CustomIdsSelect | CustomIdsSelect; 'fake-custom-ids': FakeCustomIdsSelect | FakeCustomIdsSelect; @@ -114,11 +118,13 @@ export interface Config { global: Global; 'global-2': Global2; 'global-3': Global3; + 'virtual-relation-global': VirtualRelationGlobal; }; globalsSelect: { global: GlobalSelect | GlobalSelect; 'global-2': Global2Select | Global2Select; 'global-3': Global3Select | Global3Select; + 'virtual-relation-global': VirtualRelationGlobalSelect | VirtualRelationGlobalSelect; }; 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 { + title?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "posts_select". */ export interface PostsSelect { title?: T; + category?: T; + localized?: T; + text?: T; number?: T; D1?: | T @@ -747,6 +800,19 @@ export interface PlacesSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "virtual-relations_select". + */ +export interface VirtualRelationsSelect { + 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 { createdAt?: T; globalType?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "virtual-relation-global_select". + */ +export interface VirtualRelationGlobalSelect { + postTitle?: T; + post?: T; + updatedAt?: T; + createdAt?: T; + globalType?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "auth".