feat(graphql): graphQL custom field complexity and validationRules (#9955)
### What?
Adds the ability to set custom validation rules on the root `graphQL`
config property and the ability to define custom complexity on
relationship, join and upload type fields.
### Why?
**Validation Rules**
These give you the option to add your own validation rules. For example,
you may want to prevent introspection queries in production. You can now
do that with the following:
```ts
import { GraphQL } from '@payloadcms/graphql/types'
import { buildConfig } from 'payload'
export default buildConfig({
// ...
graphQL: {
validationRules: (args) => [
NoProductionIntrospection
]
},
// ...
})
const NoProductionIntrospection: GraphQL.ValidationRule = (context) => ({
Field(node) {
if (process.env.NODE_ENV === 'production') {
if (node.name.value === '__schema' || node.name.value === '__type') {
context.reportError(
new GraphQL.GraphQLError(
'GraphQL introspection is not allowed, but the query contained __schema or __type',
{ nodes: [node] }
)
);
}
}
}
})
```
**Custom field complexity**
You can now increase the complexity of a field, this will help users
from running queries that are too expensive. A higher number will make
the `maxComplexity` trigger sooner.
```ts
const fieldWithComplexity = {
name: 'authors',
type: 'relationship',
relationship: 'authors',
graphQL: {
complexity: 100, // highlight-line
}
}
```
This commit is contained in:
@@ -136,6 +136,7 @@ powerful Admin UI.
|
|||||||
| **`admin`** | Admin-specific configuration. [More details](#admin-config-options). |
|
| **`admin`** | Admin-specific configuration. [More details](#admin-config-options). |
|
||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
||||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
|
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
|
||||||
|
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
|
||||||
|
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export const MyRelationshipField: Field = {
|
|||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||||
|
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
|
||||||
|
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export const MyUploadField: Field = {
|
|||||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||||
|
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
|
||||||
|
|
||||||
_\* An asterisk denotes that a property is required._
|
_\* An asterisk denotes that a property is required._
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ At the top of your Payload Config you can define all the options to manage Graph
|
|||||||
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
|
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
|
||||||
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
|
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
|
||||||
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
|
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
|
||||||
|
| `validationRules` | A function that takes the ExecutionArgs and returns an array of ValidationRules. |
|
||||||
|
|
||||||
## Collections
|
## Collections
|
||||||
|
|
||||||
@@ -124,6 +125,55 @@ You can even log in using the `login[collection-singular-label-here]` mutation t
|
|||||||
see a ton of detail about how GraphQL operates within Payload.
|
see a ton of detail about how GraphQL operates within Payload.
|
||||||
</Banner>
|
</Banner>
|
||||||
|
|
||||||
|
## Custom Validation Rules
|
||||||
|
|
||||||
|
You can add custom validation rules to your GraphQL API by defining a `validationRules` function in your Payload Config. This function should return an array of [Validation Rules](https://graphql.org/graphql-js/validation/#validation-rules) that will be applied to all incoming queries and mutations.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { GraphQL } from '@payloadcms/graphql/types'
|
||||||
|
import { buildConfig } from 'payload'
|
||||||
|
|
||||||
|
export default buildConfig({
|
||||||
|
// ...
|
||||||
|
graphQL: {
|
||||||
|
validationRules: (args) => [
|
||||||
|
NoProductionIntrospection
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
|
||||||
|
const NoProductionIntrospection: GraphQL.ValidationRule = (context) => ({
|
||||||
|
Field(node) {
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
if (node.name.value === '__schema' || node.name.value === '__type') {
|
||||||
|
context.reportError(
|
||||||
|
new GraphQL.GraphQLError(
|
||||||
|
'GraphQL introspection is not allowed, but the query contained __schema or __type',
|
||||||
|
{ nodes: [node] }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Query complexity limits
|
## Query complexity limits
|
||||||
|
|
||||||
Payload comes with a built-in query complexity limiter to prevent bad people from trying to slow down your server by running massive queries. To learn more, [click here](/docs/production/preventing-abuse#limiting-graphql-complexity).
|
Payload comes with a built-in query complexity limiter to prevent bad people from trying to slow down your server by running massive queries. To learn more, [click here](/docs/production/preventing-abuse#limiting-graphql-complexity).
|
||||||
|
|
||||||
|
## Field complexity
|
||||||
|
|
||||||
|
You can define custom complexity for `relationship`, `upload` and `join` type fields. This is useful if you want to assign a higher complexity to a field that is more expensive to resolve. This can help prevent users from running queries that are too complex.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const fieldWithComplexity = {
|
||||||
|
name: 'authors',
|
||||||
|
type: 'relationship',
|
||||||
|
relationship: 'authors',
|
||||||
|
graphQL: {
|
||||||
|
complexity: 100, // highlight-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -98,14 +98,12 @@ export function configToSchema(config: SanitizedConfig): {
|
|||||||
const query = new GraphQL.GraphQLObjectType(graphqlResult.Query)
|
const query = new GraphQL.GraphQLObjectType(graphqlResult.Query)
|
||||||
const mutation = new GraphQL.GraphQLObjectType(graphqlResult.Mutation)
|
const mutation = new GraphQL.GraphQLObjectType(graphqlResult.Mutation)
|
||||||
|
|
||||||
const schemaToCreate = {
|
const schema = new GraphQL.GraphQLSchema({
|
||||||
mutation,
|
mutation,
|
||||||
query,
|
query,
|
||||||
}
|
})
|
||||||
|
|
||||||
const schema = new GraphQL.GraphQLSchema(schemaToCreate)
|
const validationRules = (args): GraphQL.ValidationRule[] => [
|
||||||
|
|
||||||
const validationRules = (args) => [
|
|
||||||
createComplexityRule({
|
createComplexityRule({
|
||||||
estimators: [
|
estimators: [
|
||||||
fieldExtensionsEstimator(),
|
fieldExtensionsEstimator(),
|
||||||
@@ -115,6 +113,9 @@ export function configToSchema(config: SanitizedConfig): {
|
|||||||
variables: args.variableValues,
|
variables: args.variableValues,
|
||||||
// onComplete: (complexity) => { console.log('Query Complexity:', complexity); },
|
// onComplete: (complexity) => { console.log('Query Complexity:', complexity); },
|
||||||
}),
|
}),
|
||||||
|
...(typeof config?.graphQL?.validationRules === 'function'
|
||||||
|
? config.graphQL.validationRules(args)
|
||||||
|
: []),
|
||||||
]
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -245,7 +245,10 @@ export function buildObjectType({
|
|||||||
type: graphqlResult.collections[field.collection].graphQL.whereInputType,
|
type: graphqlResult.collections[field.collection].graphQL.whereInputType,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extensions: { complexity: 10 },
|
extensions: {
|
||||||
|
complexity:
|
||||||
|
typeof field?.graphQL?.complexity === 'number' ? field.graphQL.complexity : 10,
|
||||||
|
},
|
||||||
async resolve(parent, args, context: Context) {
|
async resolve(parent, args, context: Context) {
|
||||||
const { collection } = field
|
const { collection } = field
|
||||||
const { limit, sort, where } = args
|
const { limit, sort, where } = args
|
||||||
@@ -416,7 +419,10 @@ export function buildObjectType({
|
|||||||
forceNullable,
|
forceNullable,
|
||||||
),
|
),
|
||||||
args: relationshipArgs,
|
args: relationshipArgs,
|
||||||
extensions: { complexity: 10 },
|
extensions: {
|
||||||
|
complexity:
|
||||||
|
typeof field?.graphQL?.complexity === 'number' ? field.graphQL.complexity : 10,
|
||||||
|
},
|
||||||
async resolve(parent, args, context: Context) {
|
async resolve(parent, args, context: Context) {
|
||||||
const value = parent[field.name]
|
const value = parent[field.name]
|
||||||
const locale = args.locale || context.req.locale
|
const locale = args.locale || context.req.locale
|
||||||
@@ -768,7 +774,10 @@ export function buildObjectType({
|
|||||||
forceNullable,
|
forceNullable,
|
||||||
),
|
),
|
||||||
args: relationshipArgs,
|
args: relationshipArgs,
|
||||||
extensions: { complexity: 10 },
|
extensions: {
|
||||||
|
complexity:
|
||||||
|
typeof field?.graphQL?.complexity === 'number' ? field.graphQL.complexity : 10,
|
||||||
|
},
|
||||||
async resolve(parent, args, context: Context) {
|
async resolve(parent, args, context: Context) {
|
||||||
const value = parent[field.name]
|
const value = parent[field.name]
|
||||||
const locale = args.locale || context.req.locale
|
const locale = args.locale || context.req.locale
|
||||||
|
|||||||
@@ -950,6 +950,12 @@ export type Config = {
|
|||||||
* Filepath to write the generated schema to
|
* Filepath to write the generated schema to
|
||||||
*/
|
*/
|
||||||
schemaOutputFile?: string
|
schemaOutputFile?: string
|
||||||
|
/**
|
||||||
|
* Function that returns an array of validation rules to apply to the GraphQL schema
|
||||||
|
*
|
||||||
|
* @see https://payloadcms.com/docs/graphql/overview#custom-validation-rules
|
||||||
|
*/
|
||||||
|
validationRules?: (args: GraphQL.ExecutionArgs) => GraphQL.ValidationRule[]
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Tap into Payload-wide hooks.
|
* Tap into Payload-wide hooks.
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export type ServerOnlyFieldProperties =
|
|||||||
| 'editor' // This is a `richText` only property
|
| 'editor' // This is a `richText` only property
|
||||||
| 'enumName' // can be a function
|
| 'enumName' // can be a function
|
||||||
| 'filterOptions' // This is a `relationship` and `upload` only property
|
| 'filterOptions' // This is a `relationship` and `upload` only property
|
||||||
|
| 'graphQL'
|
||||||
| 'label'
|
| 'label'
|
||||||
| 'typescriptSchema'
|
| 'typescriptSchema'
|
||||||
| 'validate'
|
| 'validate'
|
||||||
@@ -53,6 +54,7 @@ const serverOnlyFieldProperties: Partial<ServerOnlyFieldProperties>[] = [
|
|||||||
'typescriptSchema',
|
'typescriptSchema',
|
||||||
'dbName', // can be a function
|
'dbName', // can be a function
|
||||||
'enumName', // can be a function
|
'enumName', // can be a function
|
||||||
|
'graphQL', // client does not need graphQL
|
||||||
// the following props are handled separately (see below):
|
// the following props are handled separately (see below):
|
||||||
// `label`
|
// `label`
|
||||||
// `fields`
|
// `fields`
|
||||||
|
|||||||
@@ -370,6 +370,17 @@ export type OptionObject = {
|
|||||||
|
|
||||||
export type Option = OptionObject | string
|
export type Option = OptionObject | string
|
||||||
|
|
||||||
|
export type FieldGraphQLType = {
|
||||||
|
graphQL?: {
|
||||||
|
/**
|
||||||
|
* Complexity for the query. This is used to limit the complexity of the join query.
|
||||||
|
*
|
||||||
|
* @default 10
|
||||||
|
*/
|
||||||
|
complexity?: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface FieldBase {
|
export interface FieldBase {
|
||||||
/**
|
/**
|
||||||
* Do not set this property manually. This is set to true during sanitization, to avoid
|
* Do not set this property manually. This is set to true during sanitization, to avoid
|
||||||
@@ -844,6 +855,7 @@ type SharedUploadProperties = {
|
|||||||
validate?: UploadFieldSingleValidation
|
validate?: UploadFieldSingleValidation
|
||||||
}
|
}
|
||||||
) &
|
) &
|
||||||
|
FieldGraphQLType &
|
||||||
Omit<FieldBase, 'validate'>
|
Omit<FieldBase, 'validate'>
|
||||||
|
|
||||||
type SharedUploadPropertiesClient = FieldBaseClient &
|
type SharedUploadPropertiesClient = FieldBaseClient &
|
||||||
@@ -1023,6 +1035,7 @@ type SharedRelationshipProperties = {
|
|||||||
validate?: RelationshipFieldSingleValidation
|
validate?: RelationshipFieldSingleValidation
|
||||||
}
|
}
|
||||||
) &
|
) &
|
||||||
|
FieldGraphQLType &
|
||||||
Omit<FieldBase, 'validate'>
|
Omit<FieldBase, 'validate'>
|
||||||
|
|
||||||
type SharedRelationshipPropertiesClient = FieldBaseClient &
|
type SharedRelationshipPropertiesClient = FieldBaseClient &
|
||||||
@@ -1405,7 +1418,8 @@ export type JoinField = {
|
|||||||
type: 'join'
|
type: 'join'
|
||||||
validate?: never
|
validate?: never
|
||||||
where?: Where
|
where?: Where
|
||||||
} & FieldBase
|
} & FieldBase &
|
||||||
|
FieldGraphQLType
|
||||||
|
|
||||||
export type JoinFieldClient = {
|
export type JoinFieldClient = {
|
||||||
admin?: AdminClient & Pick<JoinField['admin'], 'allowCreate' | 'disableBulkEdit' | 'readOnly'>
|
admin?: AdminClient & Pick<JoinField['admin'], 'allowCreate' | 'disableBulkEdit' | 'readOnly'>
|
||||||
|
|||||||
67
test/graphql/config.ts
Normal file
67
test/graphql/config.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { GraphQL } from '@payloadcms/graphql/types'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||||
|
import { devUser } from '../credentials.js'
|
||||||
|
|
||||||
|
const filename = fileURLToPath(import.meta.url)
|
||||||
|
const dirname = path.dirname(filename)
|
||||||
|
|
||||||
|
export default buildConfigWithDefaults({
|
||||||
|
// ...extend config here
|
||||||
|
collections: [
|
||||||
|
{
|
||||||
|
slug: 'posts',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
label: 'Title',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'relationship',
|
||||||
|
relationTo: 'posts',
|
||||||
|
name: 'relationToSelf',
|
||||||
|
graphQL: {
|
||||||
|
complexity: 801,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
admin: {
|
||||||
|
importMap: {
|
||||||
|
baseDir: path.resolve(dirname),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onInit: async (payload) => {
|
||||||
|
await payload.create({
|
||||||
|
collection: 'users',
|
||||||
|
data: {
|
||||||
|
email: devUser.email,
|
||||||
|
password: devUser.password,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||||
|
},
|
||||||
|
graphQL: {
|
||||||
|
maxComplexity: 800,
|
||||||
|
validationRules: () => [NoIntrospection],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const NoIntrospection: GraphQL.ValidationRule = (context) => ({
|
||||||
|
Field(node) {
|
||||||
|
if (node.name.value === '__schema' || node.name.value === '__type') {
|
||||||
|
context.reportError(
|
||||||
|
new GraphQL.GraphQLError(
|
||||||
|
'GraphQL introspection is not allowed, but the query contained __schema or __type',
|
||||||
|
{ nodes: [node] },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
19
test/graphql/eslint.config.js
Normal file
19
test/graphql/eslint.config.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { rootParserOptions } from '../../eslint.config.js'
|
||||||
|
import testEslintConfig from '../eslint.config.js'
|
||||||
|
|
||||||
|
/** @typedef {import('eslint').Linter.Config} Config */
|
||||||
|
|
||||||
|
/** @type {Config[]} */
|
||||||
|
export const index = [
|
||||||
|
...testEslintConfig,
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
...rootParserOptions,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default index
|
||||||
84
test/graphql/int.spec.ts
Normal file
84
test/graphql/int.spec.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import type { Payload } from 'payload'
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
|
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
|
||||||
|
|
||||||
|
import { idToString } from '../helpers/idToString.js'
|
||||||
|
import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||||
|
|
||||||
|
let payload: Payload
|
||||||
|
let restClient: NextRESTClient
|
||||||
|
|
||||||
|
const filename = fileURLToPath(import.meta.url)
|
||||||
|
const dirname = path.dirname(filename)
|
||||||
|
|
||||||
|
describe('graphql', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
;({ payload, restClient } = await initPayloadInt(dirname))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
if (typeof payload.db.destroy === 'function') {
|
||||||
|
await payload.db.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('graphql', () => {
|
||||||
|
it('should not be able to query introspection', async () => {
|
||||||
|
const query = `query {
|
||||||
|
__schema {
|
||||||
|
queryType {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const response = await restClient
|
||||||
|
.GRAPHQL_POST({
|
||||||
|
body: JSON.stringify({ query }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
|
||||||
|
expect(response.errors[0].message).toMatch(
|
||||||
|
'GraphQL introspection is not allowed, but the query contained __schema or __type',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respect maxComplexity', async () => {
|
||||||
|
const post = await payload.create({
|
||||||
|
collection: 'posts',
|
||||||
|
data: {
|
||||||
|
title: 'example post',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await payload.update({
|
||||||
|
collection: 'posts',
|
||||||
|
id: post.id,
|
||||||
|
data: {
|
||||||
|
relatedToSelf: post.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const query = `query {
|
||||||
|
Post(id: ${idToString(post.id, payload)}) {
|
||||||
|
title
|
||||||
|
relationToSelf {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const response = await restClient
|
||||||
|
.GRAPHQL_POST({
|
||||||
|
body: JSON.stringify({ query }),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
|
||||||
|
expect(response.errors[0].message).toMatch(
|
||||||
|
'The query exceeds the maximum complexity of 800. Actual complexity is 804',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
214
test/graphql/payload-types.ts
Normal file
214
test/graphql/payload-types.ts
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* This file was automatically generated by Payload.
|
||||||
|
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
|
||||||
|
* and re-run `payload generate:types` to regenerate this file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
auth: {
|
||||||
|
users: UserAuthOperations;
|
||||||
|
};
|
||||||
|
collections: {
|
||||||
|
posts: Post;
|
||||||
|
users: User;
|
||||||
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
|
'payload-preferences': PayloadPreference;
|
||||||
|
'payload-migrations': PayloadMigration;
|
||||||
|
};
|
||||||
|
collectionsJoins: {};
|
||||||
|
collectionsSelect: {
|
||||||
|
posts: PostsSelect<false> | PostsSelect<true>;
|
||||||
|
users: UsersSelect<false> | UsersSelect<true>;
|
||||||
|
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||||
|
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||||
|
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||||
|
};
|
||||||
|
db: {
|
||||||
|
defaultIDType: string;
|
||||||
|
};
|
||||||
|
globals: {};
|
||||||
|
globalsSelect: {};
|
||||||
|
locale: null;
|
||||||
|
user: User & {
|
||||||
|
collection: 'users';
|
||||||
|
};
|
||||||
|
jobs: {
|
||||||
|
tasks: unknown;
|
||||||
|
workflows: unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export interface UserAuthOperations {
|
||||||
|
forgotPassword: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
login: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
registerFirstUser: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
unlock: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "posts".
|
||||||
|
*/
|
||||||
|
export interface Post {
|
||||||
|
id: string;
|
||||||
|
title?: string | null;
|
||||||
|
relationToSelf?: (string | null) | Post;
|
||||||
|
updatedAt: 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
|
||||||
|
* via the `definition` "payload-locked-documents".
|
||||||
|
*/
|
||||||
|
export interface PayloadLockedDocument {
|
||||||
|
id: string;
|
||||||
|
document?:
|
||||||
|
| ({
|
||||||
|
relationTo: 'posts';
|
||||||
|
value: string | Post;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'users';
|
||||||
|
value: string | User;
|
||||||
|
} | null);
|
||||||
|
globalSlug?: string | null;
|
||||||
|
user: {
|
||||||
|
relationTo: 'users';
|
||||||
|
value: string | User;
|
||||||
|
};
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-preferences".
|
||||||
|
*/
|
||||||
|
export interface PayloadPreference {
|
||||||
|
id: string;
|
||||||
|
user: {
|
||||||
|
relationTo: 'users';
|
||||||
|
value: string | User;
|
||||||
|
};
|
||||||
|
key?: string | null;
|
||||||
|
value?:
|
||||||
|
| {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
| unknown[]
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-migrations".
|
||||||
|
*/
|
||||||
|
export interface PayloadMigration {
|
||||||
|
id: string;
|
||||||
|
name?: string | null;
|
||||||
|
batch?: number | null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "posts_select".
|
||||||
|
*/
|
||||||
|
export interface PostsSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
relationToSelf?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "users_select".
|
||||||
|
*/
|
||||||
|
export interface UsersSelect<T extends boolean = true> {
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
email?: T;
|
||||||
|
resetPasswordToken?: T;
|
||||||
|
resetPasswordExpiration?: T;
|
||||||
|
salt?: T;
|
||||||
|
hash?: T;
|
||||||
|
loginAttempts?: T;
|
||||||
|
lockUntil?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-locked-documents_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
|
||||||
|
document?: T;
|
||||||
|
globalSlug?: T;
|
||||||
|
user?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-preferences_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadPreferencesSelect<T extends boolean = true> {
|
||||||
|
user?: T;
|
||||||
|
key?: T;
|
||||||
|
value?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "payload-migrations_select".
|
||||||
|
*/
|
||||||
|
export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||||
|
name?: T;
|
||||||
|
batch?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "auth".
|
||||||
|
*/
|
||||||
|
export interface Auth {
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
declare module 'payload' {
|
||||||
|
// @ts-ignore
|
||||||
|
export interface GeneratedTypes extends Config {}
|
||||||
|
}
|
||||||
1799
test/graphql/schema.graphql
Normal file
1799
test/graphql/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
13
test/graphql/tsconfig.eslint.json
Normal file
13
test/graphql/tsconfig.eslint.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
// extend your base config to share compilerOptions, etc
|
||||||
|
//"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
// ensure that nobody can accidentally use this config for a build
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
// whatever paths you intend to lint
|
||||||
|
"./**/*.ts",
|
||||||
|
"./**/*.tsx"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
test/graphql/tsconfig.json
Normal file
3
test/graphql/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user