feat: join field with polymorphic relationships (#9990)

### What?
The join field had a limitation imposed that prevents it from targeting
polymorphic relationship fields. With this change we can support any
relationship fields.

### Why?
Improves the functionality of join field.

### How?
Extended the database adapters and removed the config sanitization that
would throw an error when polymorphic relationships were used.

Fixes #
This commit is contained in:
Dan Ribbens
2024-12-19 17:34:52 -05:00
committed by GitHub
parent 07be617963
commit d03658de01
19 changed files with 330 additions and 40 deletions

View File

@@ -81,6 +81,11 @@ export const buildJoinAggregation = async ({
}) })
} }
let polymorphicSuffix = ''
if (Array.isArray(join.targetField.relationTo)) {
polymorphicSuffix = '.value'
}
if (adapter.payload.config.localization && locale === 'all') { if (adapter.payload.config.localization && locale === 'all') {
adapter.payload.config.localization.localeCodes.forEach((code) => { adapter.payload.config.localization.localeCodes.forEach((code) => {
const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${code}` const as = `${versions ? `version.${join.joinPath}` : join.joinPath}${code}`
@@ -89,7 +94,7 @@ export const buildJoinAggregation = async ({
{ {
$lookup: { $lookup: {
as: `${as}.docs`, as: `${as}.docs`,
foreignField: `${join.field.on}${code}`, foreignField: `${join.field.on}${code}${polymorphicSuffix}`,
from: adapter.collections[slug].collection.name, from: adapter.collections[slug].collection.name,
localField: versions ? 'parent' : '_id', localField: versions ? 'parent' : '_id',
pipeline, pipeline,
@@ -130,7 +135,7 @@ export const buildJoinAggregation = async ({
{ {
$lookup: { $lookup: {
as: `${as}.docs`, as: `${as}.docs`,
foreignField: `${join.field.on}${localeSuffix}`, foreignField: `${join.field.on}${localeSuffix}${polymorphicSuffix}`,
from: adapter.collections[slug].collection.name, from: adapter.collections[slug].collection.name,
localField: versions ? 'parent' : '_id', localField: versions ? 'parent' : '_id',
pipeline, pipeline,

View File

@@ -17,6 +17,7 @@ export const find: Find = async function find(
return findMany({ return findMany({
adapter: this, adapter: this,
collectionSlug: collectionConfig.slug,
fields: collectionConfig.flattenedFields, fields: collectionConfig.flattenedFields,
joins, joins,
limit, limit,

View File

@@ -9,6 +9,7 @@ import { traverseFields } from './traverseFields.js'
type BuildFindQueryArgs = { type BuildFindQueryArgs = {
adapter: DrizzleAdapter adapter: DrizzleAdapter
collectionSlug?: string
depth: number depth: number
fields: FlattenedField[] fields: FlattenedField[]
joinQuery?: JoinQuery joinQuery?: JoinQuery
@@ -32,6 +33,7 @@ export type Result = {
// a collection field structure // a collection field structure
export const buildFindManyArgs = ({ export const buildFindManyArgs = ({
adapter, adapter,
collectionSlug,
depth, depth,
fields, fields,
joinQuery, joinQuery,
@@ -74,6 +76,7 @@ export const buildFindManyArgs = ({
traverseFields({ traverseFields({
_locales, _locales,
adapter, adapter,
collectionSlug,
currentArgs: result, currentArgs: result,
currentTableName: tableName, currentTableName: tableName,
depth, depth,

View File

@@ -13,6 +13,7 @@ import { buildFindManyArgs } from './buildFindManyArgs.js'
type Args = { type Args = {
adapter: DrizzleAdapter adapter: DrizzleAdapter
collectionSlug?: string
fields: FlattenedField[] fields: FlattenedField[]
tableName: string tableName: string
versions?: boolean versions?: boolean
@@ -20,6 +21,7 @@ type Args = {
export const findMany = async function find({ export const findMany = async function find({
adapter, adapter,
collectionSlug,
fields, fields,
joins: joinQuery, joins: joinQuery,
limit: limitArg, limit: limitArg,
@@ -70,6 +72,7 @@ export const findMany = async function find({
const findManyArgs = buildFindManyArgs({ const findManyArgs = buildFindManyArgs({
adapter, adapter,
collectionSlug,
depth: 0, depth: 0,
fields, fields,
joinQuery, joinQuery,

View File

@@ -16,6 +16,7 @@ import { chainMethods } from './chainMethods.js'
type TraverseFieldArgs = { type TraverseFieldArgs = {
_locales: Result _locales: Result
adapter: DrizzleAdapter adapter: DrizzleAdapter
collectionSlug?: string
currentArgs: Result currentArgs: Result
currentTableName: string currentTableName: string
depth?: number depth?: number
@@ -42,6 +43,7 @@ type TraverseFieldArgs = {
export const traverseFields = ({ export const traverseFields = ({
_locales, _locales,
adapter, adapter,
collectionSlug,
currentArgs, currentArgs,
currentTableName, currentTableName,
depth, depth,
@@ -292,6 +294,7 @@ export const traverseFields = ({
traverseFields({ traverseFields({
_locales, _locales,
adapter, adapter,
collectionSlug,
currentArgs, currentArgs,
currentTableName, currentTableName,
depth, depth,
@@ -357,13 +360,26 @@ export const traverseFields = ({
? adapter.tables[currentTableName].parent ? adapter.tables[currentTableName].parent
: adapter.tables[currentTableName].id : adapter.tables[currentTableName].id
let joinQueryWhere: Where = { let joinQueryWhere: Where
[field.on]: {
equals: rawConstraint(currentIDColumn), if (Array.isArray(field.targetField.relationTo)) {
}, joinQueryWhere = {
[field.on]: {
equals: {
relationTo: collectionSlug,
value: rawConstraint(currentIDColumn),
},
},
}
} else {
joinQueryWhere = {
[field.on]: {
equals: rawConstraint(currentIDColumn),
},
}
} }
if (where) { if (where && Object.keys(where).length) {
joinQueryWhere = { joinQueryWhere = {
and: [joinQueryWhere, where], and: [joinQueryWhere, where],
} }

View File

@@ -16,6 +16,7 @@ export async function findOne<T extends TypeWithID>(
const { docs } = await findMany({ const { docs } = await findMany({
adapter: this, adapter: this,
collectionSlug: collection,
fields: collectionConfig.flattenedFields, fields: collectionConfig.flattenedFields,
joins, joins,
limit: 1, limit: 1,

View File

@@ -142,6 +142,12 @@ export const sanitizeQueryValue = ({
collection: adapter.payload.collections[val.relationTo], collection: adapter.payload.collections[val.relationTo],
}) })
if (isRawConstraint(val.value)) {
return {
operator,
value: val.value.value,
}
}
return { return {
operator, operator,
value: idType === 'number' ? Number(val.value) : String(val.value), value: idType === 'number' ? Number(val.value) : String(val.value),

View File

@@ -21,6 +21,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
const result = await findMany({ const result = await findMany({
adapter: this, adapter: this,
collectionSlug: collection,
fields, fields,
joins, joins,
limit, limit,

View File

@@ -8,6 +8,7 @@ import type {
ClientField, ClientField,
Field, Field,
FieldBase, FieldBase,
JoinFieldClient,
LabelsClient, LabelsClient,
RadioFieldClient, RadioFieldClient,
RowFieldClient, RowFieldClient,
@@ -229,6 +230,16 @@ export const createClientField = ({
break break
} }
case 'join': {
const field = clientField as JoinFieldClient
field.targetField = {
relationTo: field.targetField.relationTo,
}
break
}
case 'radio': case 'radio':
// falls through // falls through
case 'select': { case 'select': {

View File

@@ -1,6 +1,6 @@
import type { SanitizedJoin, SanitizedJoins } from '../../collections/config/types.js' import type { SanitizedJoin, SanitizedJoins } from '../../collections/config/types.js'
import type { Config } from '../../config/types.js' import type { Config } from '../../config/types.js'
import type { JoinField, RelationshipField, UploadField } from './types.js' import type { FlattenedJoinField, JoinField, RelationshipField, UploadField } from './types.js'
import { APIError } from '../../errors/index.js' import { APIError } from '../../errors/index.js'
import { InvalidFieldJoin } from '../../errors/InvalidFieldJoin.js' import { InvalidFieldJoin } from '../../errors/InvalidFieldJoin.js'
@@ -12,7 +12,7 @@ export const sanitizeJoinField = ({
joins, joins,
}: { }: {
config: Config config: Config
field: JoinField field: FlattenedJoinField | JoinField
joinPath?: string joinPath?: string
joins?: SanitizedJoins joins?: SanitizedJoins
}) => { }) => {
@@ -74,9 +74,6 @@ export const sanitizeJoinField = ({
if (!joinRelationship) { if (!joinRelationship) {
throw new InvalidFieldJoin(join.field) throw new InvalidFieldJoin(join.field)
} }
if (Array.isArray(joinRelationship.relationTo)) {
throw new APIError('Join fields cannot be used with polymorphic relationships.')
}
join.targetField = joinRelationship join.targetField = joinRelationship
@@ -85,6 +82,9 @@ export const sanitizeJoinField = ({
// override the join field hasMany property to use whatever the relationship field has // override the join field hasMany property to use whatever the relationship field has
field.hasMany = joinRelationship.hasMany field.hasMany = joinRelationship.hasMany
// @ts-expect-error converting JoinField to FlattenedJoinField to track targetField
field.targetField = join.targetField
if (!joins[field.collection]) { if (!joins[field.collection]) {
joins[field.collection] = [join] joins[field.collection] = [join]
} else { } else {

View File

@@ -1425,7 +1425,7 @@ export type JoinField = {
export type JoinFieldClient = { export type JoinFieldClient = {
admin?: AdminClient & admin?: AdminClient &
Pick<JoinField['admin'], 'allowCreate' | 'defaultColumns' | 'disableBulkEdit' | 'readOnly'> Pick<JoinField['admin'], 'allowCreate' | 'defaultColumns' | 'disableBulkEdit' | 'readOnly'>
} & FieldBaseClient & } & { targetField: Pick<RelationshipFieldClient, 'relationTo'> } & FieldBaseClient &
Pick< Pick<
JoinField, JoinField,
'collection' | 'defaultLimit' | 'defaultSort' | 'index' | 'maxDepth' | 'on' | 'type' | 'where' 'collection' | 'defaultLimit' | 'defaultSort' | 'index' | 'maxDepth' | 'on' | 'type' | 'where'
@@ -1451,6 +1451,10 @@ export type FlattenedTabAsField = {
flattenedFields: FlattenedField[] flattenedFields: FlattenedField[]
} & MarkRequired<TabAsField, 'name'> } & MarkRequired<TabAsField, 'name'>
export type FlattenedJoinField = {
targetField: RelationshipField | UploadField
} & JoinField
export type FlattenedField = export type FlattenedField =
| CheckboxField | CheckboxField
| CodeField | CodeField
@@ -1459,8 +1463,8 @@ export type FlattenedField =
| FlattenedArrayField | FlattenedArrayField
| FlattenedBlocksField | FlattenedBlocksField
| FlattenedGroupField | FlattenedGroupField
| FlattenedJoinField
| FlattenedTabAsField | FlattenedTabAsField
| JoinField
| JSONField | JSONField
| NumberField | NumberField
| PointField | PointField

View File

@@ -1,4 +1,4 @@
import type { Field, FlattenedField } from '../fields/config/types.js' import type { Field, FlattenedField, FlattenedJoinField } from '../fields/config/types.js'
import { tabHasName } from '../fields/config/types.js' import { tabHasName } from '../fields/config/types.js'
@@ -36,6 +36,11 @@ export const flattenAllFields = ({ fields }: { fields: Field[] }): FlattenedFiel
break break
} }
case 'join': {
result.push(field as FlattenedJoinField)
break
}
case 'tabs': { case 'tabs': {
for (const tab of field.tabs) { for (const tab of field.tabs) {
if (!tabHasName(tab)) { if (!tabHasName(tab)) {

View File

@@ -61,7 +61,6 @@ export const RelationshipTable: React.FC<RelationshipTableComponentProps> = (pro
relationTo, relationTo,
} = props } = props
const [Table, setTable] = useState<React.ReactNode>(null) const [Table, setTable] = useState<React.ReactNode>(null)
const { getEntityConfig } = useConfig() const { getEntityConfig } = useConfig()
const { permissions } = useAuth() const { permissions } = useAuth()

View File

@@ -29,10 +29,12 @@ const ObjectId = (ObjectIdImport.default ||
* Recursively builds the default data for joined collection * Recursively builds the default data for joined collection
*/ */
const getInitialDrawerData = ({ const getInitialDrawerData = ({
collectionSlug,
docID, docID,
fields, fields,
segments, segments,
}: { }: {
collectionSlug: string
docID: number | string docID: number | string
fields: ClientField[] fields: ClientField[]
segments: string[] segments: string[]
@@ -48,9 +50,15 @@ const getInitialDrawerData = ({
} }
if (field.type === 'relationship' || field.type === 'upload') { if (field.type === 'relationship' || field.type === 'upload') {
let value: { relationTo: string; value: number | string } | number | string = docID
if (Array.isArray(field.relationTo)) {
value = {
relationTo: collectionSlug,
value: docID,
}
}
return { return {
// TODO: Handle polymorphic https://github.com/payloadcms/payload/pull/9990 [field.name]: field.hasMany ? [value] : value,
[field.name]: field.hasMany ? [docID] : docID,
} }
} }
@@ -58,12 +66,18 @@ const getInitialDrawerData = ({
if (field.type === 'tab' || field.type === 'group') { if (field.type === 'tab' || field.type === 'group') {
return { return {
[field.name]: getInitialDrawerData({ docID, fields: field.fields, segments: nextSegments }), [field.name]: getInitialDrawerData({
collectionSlug,
docID,
fields: field.fields,
segments: nextSegments,
}),
} }
} }
if (field.type === 'array') { if (field.type === 'array') {
const initialData = getInitialDrawerData({ const initialData = getInitialDrawerData({
collectionSlug,
docID, docID,
fields: field.fields, fields: field.fields,
segments: nextSegments, segments: nextSegments,
@@ -79,6 +93,7 @@ const getInitialDrawerData = ({
if (field.type === 'blocks') { if (field.type === 'blocks') {
for (const block of field.blocks) { for (const block of field.blocks) {
const blockInitialData = getInitialDrawerData({ const blockInitialData = getInitialDrawerData({
collectionSlug,
docID, docID,
fields: block.fields, fields: block.fields,
segments: nextSegments, segments: nextSegments,
@@ -110,7 +125,7 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
path, path,
} = props } = props
const { id: docID } = useDocumentInfo() const { id: docID, docConfig } = useDocumentInfo()
const { const {
config: { collections }, config: { collections },
@@ -126,9 +141,18 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
return null return null
} }
let value: { relationTo: string; value: number | string } | number | string = docID
if (Array.isArray(field.targetField.relationTo)) {
value = {
relationTo: docConfig.slug,
value,
}
}
const where = { const where = {
[on]: { [on]: {
equals: docID, equals: value,
}, },
} }
@@ -139,17 +163,18 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
} }
return where return where
}, [docID, on, field.where]) }, [docID, field.targetField.relationTo, field.where, on, docConfig.slug])
const initialDrawerData = useMemo(() => { const initialDrawerData = useMemo(() => {
const relatedCollection = collections.find((collection) => collection.slug === field.collection) const relatedCollection = collections.find((collection) => collection.slug === field.collection)
return getInitialDrawerData({ return getInitialDrawerData({
collectionSlug: docConfig.slug,
docID, docID,
fields: relatedCollection.fields, fields: relatedCollection.fields,
segments: field.on.split('.'), segments: field.on.split('.'),
}) })
}, [collections, field.on, docID, field.collection]) }, [collections, field.on, field.collection, docConfig.slug, docID])
return ( return (
<div <div

View File

@@ -115,6 +115,30 @@ export const Categories: CollectionConfig = {
collection: 'posts', collection: 'posts',
on: 'blocks.category', on: 'blocks.category',
}, },
{
name: 'polymorphic',
type: 'join',
collection: postsSlug,
on: 'polymorphic',
},
{
name: 'polymorphics',
type: 'join',
collection: postsSlug,
on: 'polymorphics',
},
{
name: 'localizedPolymorphic',
type: 'join',
collection: postsSlug,
on: 'localizedPolymorphic',
},
{
name: 'localizedPolymorphics',
type: 'join',
collection: postsSlug,
on: 'localizedPolymorphics',
},
{ {
name: 'singulars', name: 'singulars',
type: 'join', type: 'join',

View File

@@ -53,6 +53,30 @@ export const Posts: CollectionConfig = {
hasMany: true, hasMany: true,
localized: true, localized: true,
}, },
{
name: 'polymorphic',
type: 'relationship',
relationTo: ['categories', 'users'],
},
{
name: 'polymorphics',
type: 'relationship',
relationTo: ['categories', 'users'],
hasMany: true,
},
{
name: 'localizedPolymorphic',
type: 'relationship',
relationTo: ['categories', 'users'],
localized: true,
},
{
name: 'localizedPolymorphics',
type: 'relationship',
relationTo: ['categories', 'users'],
hasMany: true,
localized: true,
},
{ {
name: 'group', name: 'group',
type: 'group', type: 'group',

View File

@@ -291,6 +291,67 @@ test.describe('Join Field', () => {
await expect(joinField.locator('tbody .row-1')).toContainText('Test Post 1 Updated') await expect(joinField.locator('tbody .row-1')).toContainText('Test Post 1 Updated')
}) })
test('should create join collection from polymorphic relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-polymorphic.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-polymorphic')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic, hasMany relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-polymorphics.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-polymorphics')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic localized relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-localizedPolymorphic.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-localizedPolymorphic')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should create join collection from polymorphic, hasMany, localized relationships', async () => {
await page.goto(categoriesURL.edit(categoryID))
const joinField = page.locator('#field-localizedPolymorphics.field-type.join')
await expect(joinField).toBeVisible()
await joinField.locator('.relationship-table__add-new').click()
const drawer = page.locator('[id^=doc-drawer_posts_1_]')
await expect(drawer).toBeVisible()
const titleField = drawer.locator('#field-title')
await expect(titleField).toBeVisible()
await titleField.fill('Test polymorphic Post')
await expect(drawer.locator('#field-localizedPolymorphics')).toContainText('example')
await drawer.locator('button[id="action-save"]').click()
await expect(drawer).toBeHidden()
await expect(joinField.locator('tbody .row-1')).toContainText('Test polymorphic Post')
})
test('should render empty relationship table when creating new document', async () => { test('should render empty relationship table when creating new document', async () => {
await page.goto(categoriesURL.create) await page.goto(categoriesURL.create)
const joinField = page.locator('#field-relatedPosts.field-type.join') const joinField = page.locator('#field-relatedPosts.field-type.join')

View File

@@ -90,6 +90,26 @@ describe('Joins Field', () => {
upload: uploadedImage, upload: uploadedImage,
categories, categories,
categoriesLocalized: categories, categoriesLocalized: categories,
polymorphic: {
relationTo: 'categories',
value: category.id,
},
polymorphics: [
{
relationTo: 'categories',
value: category.id,
},
],
localizedPolymorphic: {
relationTo: 'categories',
value: category.id,
},
localizedPolymorphics: [
{
relationTo: 'categories',
value: category.id,
},
],
group: { group: {
category: category.id, category: category.id,
camelCaseCategory: category.id, camelCaseCategory: category.id,
@@ -216,6 +236,17 @@ describe('Joins Field', () => {
expect(docs[0].upload.relatedPosts.docs).toHaveLength(10) expect(docs[0].upload.relatedPosts.docs).toHaveLength(10)
}) })
it('should join on polymorphic relationships', async () => {
const categoryWithPosts = await payload.findByID({
collection: categoriesSlug,
id: category.id,
})
expect(categoryWithPosts.polymorphic.docs[0]).toHaveProperty('id')
expect(categoryWithPosts.polymorphics.docs[0]).toHaveProperty('id')
expect(categoryWithPosts.localizedPolymorphic.docs[0]).toHaveProperty('id')
expect(categoryWithPosts.localizedPolymorphics.docs[0]).toHaveProperty('id')
})
it('should filter joins using where query', async () => { it('should filter joins using where query', async () => {
const categoryWithPosts = await payload.findByID({ const categoryWithPosts = await payload.findByID({
id: category.id, id: category.id,

View File

@@ -38,6 +38,10 @@ export interface Config {
'group.camelCasePosts': 'posts'; 'group.camelCasePosts': 'posts';
arrayPosts: 'posts'; arrayPosts: 'posts';
blocksPosts: 'posts'; blocksPosts: 'posts';
polymorphic: 'posts';
polymorphics: 'posts';
localizedPolymorphic: 'posts';
localizedPolymorphics: 'posts';
filtered: 'posts'; filtered: 'posts';
hiddenPosts: 'hidden-posts'; hiddenPosts: 'hidden-posts';
singulars: 'singular'; singulars: 'singular';
@@ -123,6 +127,48 @@ export interface Post {
category?: (string | null) | Category; category?: (string | null) | Category;
categories?: (string | Category)[] | null; categories?: (string | Category)[] | null;
categoriesLocalized?: (string | Category)[] | null; categoriesLocalized?: (string | Category)[] | null;
polymorphic?:
| ({
relationTo: 'categories';
value: string | Category;
} | null)
| ({
relationTo: 'users';
value: string | User;
} | null);
polymorphics?:
| (
| {
relationTo: 'categories';
value: string | Category;
}
| {
relationTo: 'users';
value: string | User;
}
)[]
| null;
localizedPolymorphic?:
| ({
relationTo: 'categories';
value: string | Category;
} | null)
| ({
relationTo: 'users';
value: string | User;
} | null);
localizedPolymorphics?:
| (
| {
relationTo: 'categories';
value: string | Category;
}
| {
relationTo: 'users';
value: string | User;
}
)[]
| null;
group?: { group?: {
category?: (string | null) | Category; category?: (string | null) | Category;
camelCaseCategory?: (string | null) | Category; camelCaseCategory?: (string | null) | Category;
@@ -207,6 +253,22 @@ export interface Category {
docs?: (string | Post)[] | null; docs?: (string | Post)[] | null;
hasNextPage?: boolean | null; hasNextPage?: boolean | null;
} | null; } | null;
polymorphic?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
polymorphics?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
localizedPolymorphic?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
localizedPolymorphics?: {
docs?: (string | Post)[] | null;
hasNextPage?: boolean | null;
} | null;
singulars?: { singulars?: {
docs?: (string | Singular)[] | null; docs?: (string | Singular)[] | null;
hasNextPage?: boolean | null; hasNextPage?: boolean | null;
@@ -239,6 +301,23 @@ export interface Singular {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "versions". * via the `definition` "versions".
@@ -347,23 +426,6 @@ export interface RestrictedPost {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents". * via the `definition` "payload-locked-documents".
@@ -481,6 +543,10 @@ export interface PostsSelect<T extends boolean = true> {
category?: T; category?: T;
categories?: T; categories?: T;
categoriesLocalized?: T; categoriesLocalized?: T;
polymorphic?: T;
polymorphics?: T;
localizedPolymorphic?: T;
localizedPolymorphics?: T;
group?: group?:
| T | T
| { | {
@@ -525,6 +591,10 @@ export interface CategoriesSelect<T extends boolean = true> {
}; };
arrayPosts?: T; arrayPosts?: T;
blocksPosts?: T; blocksPosts?: T;
polymorphic?: T;
polymorphics?: T;
localizedPolymorphic?: T;
localizedPolymorphics?: T;
singulars?: T; singulars?: T;
filtered?: T; filtered?: T;
updatedAt?: T; updatedAt?: T;