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:
@@ -240,8 +240,8 @@ export default buildConfig({
|
|||||||
// highlight-start
|
// highlight-start
|
||||||
cors: {
|
cors: {
|
||||||
origins: ['http://localhost:3000'],
|
origins: ['http://localhost:3000'],
|
||||||
headers: ['x-custom-header']
|
headers: ['x-custom-header'],
|
||||||
}
|
},
|
||||||
// highlight-end
|
// highlight-end
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ const config = buildConfig({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'collection2',
|
slug: 'collection2',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
@@ -365,7 +365,7 @@ const config = buildConfig({
|
|||||||
blocks: ['TextBlock'],
|
blocks: ['TextBlock'],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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:
|
- To install the [Postgres Adapter](../database/postgres), run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm i @payloadcms/db-postgres
|
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
|
#### 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
|
```plaintext
|
||||||
app/
|
app/
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ export const validateUseAsTitle = (config: CollectionConfig) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (useAsTitleField && fieldIsVirtual(useAsTitleField)) {
|
if (useAsTitleField && 'virtual' in useAsTitleField && useAsTitleField.virtual === true) {
|
||||||
throw new InvalidConfiguration(
|
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) {
|
if (!useAsTitleField) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { buildVersionCollectionFields } from '../../versions/buildCollectionFiel
|
|||||||
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
|
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
|
||||||
import { getQueryDraftsSelect } from '../../versions/drafts/getQueryDraftsSelect.js'
|
import { getQueryDraftsSelect } from '../../versions/drafts/getQueryDraftsSelect.js'
|
||||||
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
|
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
|
||||||
|
import { sanitizeSortQuery } from './utilities/sanitizeSortQuery.js'
|
||||||
import { buildAfterOperation } from './utils.js'
|
import { buildAfterOperation } from './utils.js'
|
||||||
|
|
||||||
export type Arguments = {
|
export type Arguments = {
|
||||||
@@ -96,7 +97,7 @@ export const findOperation = async <
|
|||||||
req,
|
req,
|
||||||
select: incomingSelect,
|
select: incomingSelect,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
sort,
|
sort: incomingSort,
|
||||||
where,
|
where,
|
||||||
} = args
|
} = args
|
||||||
|
|
||||||
@@ -143,6 +144,11 @@ export const findOperation = async <
|
|||||||
|
|
||||||
let fullWhere = combineQueries(where, accessResult)
|
let fullWhere = combineQueries(where, accessResult)
|
||||||
|
|
||||||
|
const sort = sanitizeSortQuery({
|
||||||
|
fields: collection.config.flattenedFields,
|
||||||
|
sort: incomingSort,
|
||||||
|
})
|
||||||
|
|
||||||
const sanitizedJoins = await sanitizeJoinQuery({
|
const sanitizedJoins = await sanitizeJoinQuery({
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
joins,
|
joins,
|
||||||
@@ -170,7 +176,10 @@ export const findOperation = async <
|
|||||||
pagination: usePagination,
|
pagination: usePagination,
|
||||||
req,
|
req,
|
||||||
select: getQueryDraftsSelect({ select }),
|
select: getQueryDraftsSelect({ select }),
|
||||||
sort: getQueryDraftsSort({ collectionConfig, sort }),
|
sort: getQueryDraftsSort({
|
||||||
|
collectionConfig,
|
||||||
|
sort,
|
||||||
|
}),
|
||||||
where: fullWhere,
|
where: fullWhere,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { sanitizeSelect } from '../../utilities/sanitizeSelect.js'
|
|||||||
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
|
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
|
||||||
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
|
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
|
||||||
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
|
import { getQueryDraftsSort } from '../../versions/drafts/getQueryDraftsSort.js'
|
||||||
|
import { sanitizeSortQuery } from './utilities/sanitizeSortQuery.js'
|
||||||
import { updateDocument } from './utilities/update.js'
|
import { updateDocument } from './utilities/update.js'
|
||||||
import { buildAfterOperation } from './utils.js'
|
import { buildAfterOperation } from './utils.js'
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ export const updateOperation = async <
|
|||||||
req,
|
req,
|
||||||
select: incomingSelect,
|
select: incomingSelect,
|
||||||
showHiddenFields,
|
showHiddenFields,
|
||||||
sort,
|
sort: incomingSort,
|
||||||
where,
|
where,
|
||||||
} = args
|
} = args
|
||||||
|
|
||||||
@@ -136,6 +137,11 @@ export const updateOperation = async <
|
|||||||
|
|
||||||
const fullWhere = combineQueries(where, accessResult)
|
const fullWhere = combineQueries(where, accessResult)
|
||||||
|
|
||||||
|
const sort = sanitizeSortQuery({
|
||||||
|
fields: collection.config.flattenedFields,
|
||||||
|
sort: incomingSort,
|
||||||
|
})
|
||||||
|
|
||||||
let docs
|
let docs
|
||||||
|
|
||||||
if (collectionConfig.versions?.drafts && shouldSaveDraft) {
|
if (collectionConfig.versions?.drafts && shouldSaveDraft) {
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
}
|
||||||
@@ -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({
|
export async function validateQueryPaths({
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
errors = [],
|
errors = [],
|
||||||
@@ -61,17 +45,47 @@ export async function validateQueryPaths({
|
|||||||
const fields = versionFields || (globalConfig || collectionConfig).flattenedFields
|
const fields = versionFields || (globalConfig || collectionConfig).flattenedFields
|
||||||
|
|
||||||
if (typeof where === 'object') {
|
if (typeof where === 'object') {
|
||||||
const whereFields = flattenWhere(where)
|
|
||||||
// We need to determine if the whereKey is an AND, OR, or a schema path
|
// We need to determine if the whereKey is an AND, OR, or a schema path
|
||||||
const promises = []
|
const promises = []
|
||||||
for (const constraint of whereFields) {
|
for (const path in where) {
|
||||||
for (const path in constraint) {
|
const constraint = where[path]
|
||||||
for (const operator in constraint[path]) {
|
|
||||||
const val = constraint[path][operator]
|
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)) {
|
if (validOperatorSet.has(operator as Operator)) {
|
||||||
promises.push(
|
promises.push(
|
||||||
validateSearchParam({
|
validateSearchParam({
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
|
constraint: where as WhereField,
|
||||||
errors,
|
errors,
|
||||||
fields,
|
fields,
|
||||||
globalConfig,
|
globalConfig,
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
|
import type { SanitizedCollectionConfig } from '../../collections/config/types.js'
|
||||||
import type { FlattenedField } from '../../fields/config/types.js'
|
import type { FlattenedField } from '../../fields/config/types.js'
|
||||||
import type { SanitizedGlobalConfig } from '../../globals/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 type { EntityPolicies, PathToQuery } from './types.js'
|
||||||
|
|
||||||
import { fieldAffectsData, fieldIsVirtual } from '../../fields/config/types.js'
|
import { fieldAffectsData, fieldIsVirtual } from '../../fields/config/types.js'
|
||||||
import { getEntityPolicies } from '../../utilities/getEntityPolicies.js'
|
import { getEntityPolicies } from '../../utilities/getEntityPolicies.js'
|
||||||
|
import { getFieldByPath } from '../../utilities/getFieldByPath.js'
|
||||||
import isolateObjectProperty from '../../utilities/isolateObjectProperty.js'
|
import isolateObjectProperty from '../../utilities/isolateObjectProperty.js'
|
||||||
import { getLocalizedPaths } from '../getLocalizedPaths.js'
|
import { getLocalizedPaths } from '../getLocalizedPaths.js'
|
||||||
import { validateQueryPaths } from './validateQueryPaths.js'
|
import { validateQueryPaths } from './validateQueryPaths.js'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
collectionConfig?: SanitizedCollectionConfig
|
collectionConfig?: SanitizedCollectionConfig
|
||||||
|
constraint: WhereField
|
||||||
errors: { path: string }[]
|
errors: { path: string }[]
|
||||||
fields: FlattenedField[]
|
fields: FlattenedField[]
|
||||||
globalConfig?: SanitizedGlobalConfig
|
globalConfig?: SanitizedGlobalConfig
|
||||||
@@ -32,6 +34,7 @@ type Args = {
|
|||||||
*/
|
*/
|
||||||
export async function validateSearchParam({
|
export async function validateSearchParam({
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
|
constraint,
|
||||||
errors,
|
errors,
|
||||||
fields,
|
fields,
|
||||||
globalConfig,
|
globalConfig,
|
||||||
@@ -100,8 +103,13 @@ export async function validateSearchParam({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldIsVirtual(field)) {
|
if ('virtual' in field && field.virtual) {
|
||||||
errors.push({ path })
|
if (field.virtual === true) {
|
||||||
|
errors.push({ path })
|
||||||
|
} else {
|
||||||
|
constraint[`${field.virtual}`] = constraint[path]
|
||||||
|
delete constraint[path]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (polymorphicJoin && path === 'relationTo') {
|
if (polymorphicJoin && path === 'relationTo') {
|
||||||
|
|||||||
@@ -514,9 +514,9 @@ export interface FieldBase {
|
|||||||
/**
|
/**
|
||||||
* Pass `true` to disable field in the DB
|
* 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):
|
* 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 {
|
export interface FieldBaseClient {
|
||||||
@@ -1955,7 +1955,7 @@ export function fieldShouldBeLocalized({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fieldIsVirtual(field: Field | Tab): boolean {
|
export function fieldIsVirtual(field: Field | Tab): boolean {
|
||||||
return 'virtual' in field && field.virtual
|
return 'virtual' in field && Boolean(field.virtual)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HookName =
|
export type HookName =
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import type { RichTextAdapter } from '../../../admin/RichText.js'
|
import type { RichTextAdapter } from '../../../admin/RichText.js'
|
||||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
import type { SanitizedCollectionConfig } from '../../../collections/config/types.js'
|
||||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
import type { SanitizedGlobalConfig } from '../../../globals/config/types.js'
|
||||||
import type { RequestContext } from '../../../index.js'
|
|
||||||
import type {
|
import type {
|
||||||
JsonObject,
|
JsonObject,
|
||||||
PayloadRequest,
|
PayloadRequest,
|
||||||
@@ -13,6 +12,7 @@ import type {
|
|||||||
import type { Block, Field, TabAsField } from '../../config/types.js'
|
import type { Block, Field, TabAsField } from '../../config/types.js'
|
||||||
|
|
||||||
import { MissingEditorProp } from '../../../errors/index.js'
|
import { MissingEditorProp } from '../../../errors/index.js'
|
||||||
|
import { type RequestContext } from '../../../index.js'
|
||||||
import { getBlockSelect } from '../../../utilities/getBlockSelect.js'
|
import { getBlockSelect } from '../../../utilities/getBlockSelect.js'
|
||||||
import { stripUnselectedFields } from '../../../utilities/stripUnselectedFields.js'
|
import { stripUnselectedFields } from '../../../utilities/stripUnselectedFields.js'
|
||||||
import { fieldAffectsData, fieldShouldBeLocalized, tabHasName } from '../../config/types.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 { getFieldPathsModified as getFieldPaths } from '../../getFieldPaths.js'
|
||||||
import { relationshipPopulationPromise } from './relationshipPopulationPromise.js'
|
import { relationshipPopulationPromise } from './relationshipPopulationPromise.js'
|
||||||
import { traverseFields } from './traverseFields.js'
|
import { traverseFields } from './traverseFields.js'
|
||||||
|
import { virtualFieldPopulationPromise } from './virtualFieldPopulationPromise.js'
|
||||||
|
|
||||||
type Args = {
|
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
|
// Execute access control
|
||||||
let allowDefaultValue = true
|
let allowDefaultValue = true
|
||||||
if (triggerAccessControl && field.access && field.access.read) {
|
if (triggerAccessControl && field.access && field.access.read) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,4 +25,4 @@ export const es: GenericTranslationsObject = {
|
|||||||
tooLong: 'Demasiado largo',
|
tooLong: 'Demasiado largo',
|
||||||
tooShort: 'Demasiado corto',
|
tooShort: 'Demasiado corto',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ export default buildConfigWithDefaults({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
collections: [
|
collections: [
|
||||||
|
{
|
||||||
|
slug: 'categories',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'title',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: postsSlug,
|
slug: postsSlug,
|
||||||
fields: [
|
fields: [
|
||||||
@@ -43,6 +52,17 @@ export default buildConfigWithDefaults({
|
|||||||
name: 'title',
|
name: 'title',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
|
// access: { read: () => false },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'categories',
|
||||||
|
name: 'category',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'localized',
|
||||||
|
type: 'text',
|
||||||
|
localized: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'text',
|
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,
|
slug: fieldsPersistanceSlug,
|
||||||
fields: [
|
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: {
|
localization: {
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
migrateRelationshipsV2_V3,
|
migrateRelationshipsV2_V3,
|
||||||
migrateVersionsV1_V2,
|
migrateVersionsV1_V2,
|
||||||
} from '@payloadcms/db-mongodb/migration-utils'
|
} from '@payloadcms/db-mongodb/migration-utils'
|
||||||
|
import { objectToFrontmatter } from '@payloadcms/richtext-lexical'
|
||||||
import { randomUUID } from 'crypto'
|
import { randomUUID } from 'crypto'
|
||||||
import { type Table } from 'drizzle-orm'
|
import { type Table } from 'drizzle-orm'
|
||||||
import * as drizzlePg from 'drizzle-orm/pg-core'
|
import * as drizzlePg from 'drizzle-orm/pg-core'
|
||||||
@@ -1977,6 +1978,132 @@ describe('database', () => {
|
|||||||
expect(res.textWithinRow).toBeUndefined()
|
expect(res.textWithinRow).toBeUndefined()
|
||||||
expect(res.textWithinTabs).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 () => {
|
it('should convert numbers to text', async () => {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export interface Config {
|
|||||||
};
|
};
|
||||||
blocks: {};
|
blocks: {};
|
||||||
collections: {
|
collections: {
|
||||||
|
categories: Category;
|
||||||
posts: Post;
|
posts: Post;
|
||||||
'error-on-unnamed-fields': ErrorOnUnnamedField;
|
'error-on-unnamed-fields': ErrorOnUnnamedField;
|
||||||
'default-values': DefaultValue;
|
'default-values': DefaultValue;
|
||||||
@@ -75,6 +76,7 @@ export interface Config {
|
|||||||
'pg-migrations': PgMigration;
|
'pg-migrations': PgMigration;
|
||||||
'custom-schema': CustomSchema;
|
'custom-schema': CustomSchema;
|
||||||
places: Place;
|
places: Place;
|
||||||
|
'virtual-relations': VirtualRelation;
|
||||||
'fields-persistance': FieldsPersistance;
|
'fields-persistance': FieldsPersistance;
|
||||||
'custom-ids': CustomId;
|
'custom-ids': CustomId;
|
||||||
'fake-custom-ids': FakeCustomId;
|
'fake-custom-ids': FakeCustomId;
|
||||||
@@ -88,6 +90,7 @@ export interface Config {
|
|||||||
};
|
};
|
||||||
collectionsJoins: {};
|
collectionsJoins: {};
|
||||||
collectionsSelect: {
|
collectionsSelect: {
|
||||||
|
categories: CategoriesSelect<false> | CategoriesSelect<true>;
|
||||||
posts: PostsSelect<false> | PostsSelect<true>;
|
posts: PostsSelect<false> | PostsSelect<true>;
|
||||||
'error-on-unnamed-fields': ErrorOnUnnamedFieldsSelect<false> | ErrorOnUnnamedFieldsSelect<true>;
|
'error-on-unnamed-fields': ErrorOnUnnamedFieldsSelect<false> | ErrorOnUnnamedFieldsSelect<true>;
|
||||||
'default-values': DefaultValuesSelect<false> | DefaultValuesSelect<true>;
|
'default-values': DefaultValuesSelect<false> | DefaultValuesSelect<true>;
|
||||||
@@ -96,6 +99,7 @@ export interface Config {
|
|||||||
'pg-migrations': PgMigrationsSelect<false> | PgMigrationsSelect<true>;
|
'pg-migrations': PgMigrationsSelect<false> | PgMigrationsSelect<true>;
|
||||||
'custom-schema': CustomSchemaSelect<false> | CustomSchemaSelect<true>;
|
'custom-schema': CustomSchemaSelect<false> | CustomSchemaSelect<true>;
|
||||||
places: PlacesSelect<false> | PlacesSelect<true>;
|
places: PlacesSelect<false> | PlacesSelect<true>;
|
||||||
|
'virtual-relations': VirtualRelationsSelect<false> | VirtualRelationsSelect<true>;
|
||||||
'fields-persistance': FieldsPersistanceSelect<false> | FieldsPersistanceSelect<true>;
|
'fields-persistance': FieldsPersistanceSelect<false> | FieldsPersistanceSelect<true>;
|
||||||
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
'custom-ids': CustomIdsSelect<false> | CustomIdsSelect<true>;
|
||||||
'fake-custom-ids': FakeCustomIdsSelect<false> | FakeCustomIdsSelect<true>;
|
'fake-custom-ids': FakeCustomIdsSelect<false> | FakeCustomIdsSelect<true>;
|
||||||
@@ -114,11 +118,13 @@ export interface Config {
|
|||||||
global: Global;
|
global: Global;
|
||||||
'global-2': Global2;
|
'global-2': Global2;
|
||||||
'global-3': Global3;
|
'global-3': Global3;
|
||||||
|
'virtual-relation-global': VirtualRelationGlobal;
|
||||||
};
|
};
|
||||||
globalsSelect: {
|
globalsSelect: {
|
||||||
global: GlobalSelect<false> | GlobalSelect<true>;
|
global: GlobalSelect<false> | GlobalSelect<true>;
|
||||||
'global-2': Global2Select<false> | Global2Select<true>;
|
'global-2': Global2Select<false> | Global2Select<true>;
|
||||||
'global-3': Global3Select<false> | Global3Select<true>;
|
'global-3': Global3Select<false> | Global3Select<true>;
|
||||||
|
'virtual-relation-global': VirtualRelationGlobalSelect<false> | VirtualRelationGlobalSelect<true>;
|
||||||
};
|
};
|
||||||
locale: 'en' | 'es';
|
locale: 'en' | 'es';
|
||||||
user: User & {
|
user: User & {
|
||||||
@@ -147,6 +153,16 @@ export interface UserAuthOperations {
|
|||||||
password: string;
|
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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "posts".
|
* via the `definition` "posts".
|
||||||
@@ -154,6 +170,9 @@ export interface UserAuthOperations {
|
|||||||
export interface Post {
|
export interface Post {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
category?: (string | null) | Category;
|
||||||
|
localized?: string | null;
|
||||||
|
text?: string | null;
|
||||||
number?: number | null;
|
number?: number | null;
|
||||||
D1?: {
|
D1?: {
|
||||||
D2?: {
|
D2?: {
|
||||||
@@ -346,6 +365,20 @@ export interface Place {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: 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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "fields-persistance".
|
* via the `definition` "fields-persistance".
|
||||||
@@ -465,6 +498,10 @@ export interface User {
|
|||||||
export interface PayloadLockedDocument {
|
export interface PayloadLockedDocument {
|
||||||
id: string;
|
id: string;
|
||||||
document?:
|
document?:
|
||||||
|
| ({
|
||||||
|
relationTo: 'categories';
|
||||||
|
value: string | Category;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'posts';
|
relationTo: 'posts';
|
||||||
value: string | Post;
|
value: string | Post;
|
||||||
@@ -497,6 +534,10 @@ export interface PayloadLockedDocument {
|
|||||||
relationTo: 'places';
|
relationTo: 'places';
|
||||||
value: string | Place;
|
value: string | Place;
|
||||||
} | null)
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'virtual-relations';
|
||||||
|
value: string | VirtualRelation;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'fields-persistance';
|
relationTo: 'fields-persistance';
|
||||||
value: string | FieldsPersistance;
|
value: string | FieldsPersistance;
|
||||||
@@ -567,12 +608,24 @@ export interface PayloadMigration {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: 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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "posts_select".
|
* via the `definition` "posts_select".
|
||||||
*/
|
*/
|
||||||
export interface PostsSelect<T extends boolean = true> {
|
export interface PostsSelect<T extends boolean = true> {
|
||||||
title?: T;
|
title?: T;
|
||||||
|
category?: T;
|
||||||
|
localized?: T;
|
||||||
|
text?: T;
|
||||||
number?: T;
|
number?: T;
|
||||||
D1?:
|
D1?:
|
||||||
| T
|
| T
|
||||||
@@ -747,6 +800,19 @@ export interface PlacesSelect<T extends boolean = true> {
|
|||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: 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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "fields-persistance_select".
|
* via the `definition` "fields-persistance_select".
|
||||||
@@ -917,6 +983,17 @@ export interface Global3 {
|
|||||||
updatedAt?: string | null;
|
updatedAt?: string | null;
|
||||||
createdAt?: 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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "global_select".
|
* via the `definition` "global_select".
|
||||||
@@ -947,6 +1024,17 @@ export interface Global3Select<T extends boolean = true> {
|
|||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
globalType?: 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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "auth".
|
* via the `definition` "auth".
|
||||||
|
|||||||
Reference in New Issue
Block a user