-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
index.ts
90 lines (87 loc) · 3.29 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import { getNamedType, isLeafType } from 'graphql'
import {
type BaseListTypeInfo,
type BaseItem,
type CommonFieldConfig,
type FieldTypeFunc,
fieldType,
type KeystoneContext,
type ListGraphQLTypes,
getGqlNames,
} from '../../../types'
import { graphql } from '../../..'
type VirtualFieldGraphQLField<
Item extends BaseItem,
Context extends KeystoneContext
> = graphql.Field<Item, any, graphql.OutputType, string, Context>
export type VirtualFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
field:
| VirtualFieldGraphQLField<ListTypeInfo['item'], KeystoneContext<ListTypeInfo['all']>>
| ((
lists: Record<string, ListGraphQLTypes>
) => VirtualFieldGraphQLField<ListTypeInfo['item'], KeystoneContext<ListTypeInfo['all']>>)
unreferencedConcreteInterfaceImplementations?: readonly graphql.ObjectType<any>[]
ui?: {
/**
* Defines what the Admin UI should fetch from this field, it's interpolated into a query like this:
* ```graphql
* query {
* item(where: { id: "..." }) {
* field${ui.query}
* }
* }
* ```
*
* This is only needed when you your field returns a GraphQL type other than a scalar(String and etc.)
* or an enum or you need to provide arguments to the field.
*/
query?: string
}
}
export const virtual =
<ListTypeInfo extends BaseListTypeInfo>({
field,
...config
}: VirtualFieldConfig<ListTypeInfo>): FieldTypeFunc<ListTypeInfo> =>
meta => {
const usableField = typeof field === 'function' ? field(meta.lists) : field
const namedType = getNamedType(usableField.type.graphQLType)
const hasRequiredArgs =
usableField.args &&
Object.values(
usableField.args as Record<string, graphql.Arg<graphql.InputType, boolean>>
).some(x => x.type.kind === 'non-null' && x.defaultValue === undefined)
if (
(!isLeafType(namedType) || hasRequiredArgs) &&
!config.ui?.query &&
(config.ui?.itemView?.fieldMode !== 'hidden' || config.ui?.listView?.fieldMode !== 'hidden')
) {
throw new Error(
`The virtual field at ${meta.listKey}.${meta.fieldKey} requires a selection for the Admin UI but ui.query is unspecified and ui.listView.fieldMode and ui.itemView.fieldMode are not both set to 'hidden'.\n` +
`Either set ui.query with what the Admin UI should fetch or hide the field from the Admin UI by setting ui.listView.fieldMode and ui.itemView.fieldMode to 'hidden'.\n` +
`When setting ui.query, it is interpolated into a GraphQL query like this:\n` +
`query {\n` +
` ${
getGqlNames({ listKey: meta.listKey, pluralGraphQLName: '' }).itemQueryName
}(where: { id: "..." }) {\n` +
` ${meta.fieldKey}\${ui.query}\n` +
` }\n` +
`}`
)
}
return fieldType({
kind: 'none',
})({
...config,
output: graphql.field({
...(usableField as any),
resolve ({ item }, ...args) {
return usableField.resolve!(item as any, ...args)
},
}),
__ksTelemetryFieldTypeName: '@keystone-6/virtual',
views: '@keystone-6/core/fields/types/virtual/views',
getAdminMeta: () => ({ query: config.ui?.query || '' }),
})
}