@nixxie-cms/core 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/README.md +2 -2
  2. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.cjs.js +4 -4
  3. package/admin-ui/components/dist/nixxie-cms-core-admin-ui-components.esm.js +4 -4
  4. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.cjs.js +2 -2
  5. package/admin-ui/context/dist/nixxie-cms-core-admin-ui-context.esm.js +2 -2
  6. package/context/dist/nixxie-cms-core-context.cjs.js +2 -2
  7. package/context/dist/nixxie-cms-core-context.esm.js +2 -2
  8. package/dist/{CreateItemDialog-33335548.esm.js → CreateItemDialog-7008b050.esm.js} +1 -1
  9. package/dist/{CreateItemDialog-56cf59b7.cjs.js → CreateItemDialog-a0cab315.cjs.js} +1 -1
  10. package/dist/{PageContainer-7db73317.esm.js → PageContainer-5ae731cc.esm.js} +25 -18
  11. package/dist/{PageContainer-27c27f10.cjs.js → PageContainer-abd7159f.cjs.js} +25 -18
  12. package/dist/{admin-meta-graphql-6f7f5331.esm.js → admin-meta-graphql-0e6e606e.esm.js} +1 -1
  13. package/dist/{admin-meta-graphql-c8f926e9.cjs.js → admin-meta-graphql-306c224a.cjs.js} +1 -1
  14. package/dist/{context-3132c3ed.esm.js → context-af9957ed.esm.js} +2 -2
  15. package/dist/{context-e7a45152.cjs.js → context-b5204629.cjs.js} +2 -2
  16. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  17. package/dist/declarations/src/admin-ui/components/PageContainer.d.ts.map +1 -1
  18. package/dist/declarations/src/helpers.d.ts.map +1 -1
  19. package/dist/declarations/src/index.d.ts +1 -0
  20. package/dist/declarations/src/index.d.ts.map +1 -1
  21. package/dist/declarations/src/internal-unstable/admin-ui/id-field-view.d.ts.map +1 -0
  22. package/dist/declarations/src/internal-unstable/admin-ui/pages/App/index.d.ts.map +1 -0
  23. package/dist/declarations/src/internal-unstable/admin-ui/pages/CreateItemPage/index.d.ts.map +1 -0
  24. package/dist/declarations/src/internal-unstable/admin-ui/pages/HomePage/index.d.ts.map +1 -0
  25. package/dist/declarations/src/internal-unstable/admin-ui/pages/ItemPage/index.d.ts.map +1 -0
  26. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -0
  27. package/dist/declarations/src/internal-unstable/admin-ui/pages/NoAccessPage/index.d.ts.map +1 -0
  28. package/dist/declarations/src/internal-unstable/artifacts.d.ts.map +1 -0
  29. package/dist/declarations/src/lib/core/initialise-lists.d.ts +1 -1
  30. package/dist/declarations/src/schema.d.ts.map +1 -1
  31. package/dist/declarations/src/types/config/index.d.ts +60 -1
  32. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  33. package/dist/declarations/src/types/config/lists.d.ts +4 -4
  34. package/dist/declarations/src/types/context.d.ts +150 -0
  35. package/dist/declarations/src/types/context.d.ts.map +1 -1
  36. package/dist/declarations/src/types/next-fields.d.ts +1 -1
  37. package/dist/{express-e9ed9a7d.cjs.js → express-455ae20c.cjs.js} +1 -1
  38. package/dist/{express-6743b918.esm.js → express-7559ca2d.esm.js} +1 -1
  39. package/dist/{index-ac01583b.cjs.js → index-89635494.cjs.js} +4 -4
  40. package/dist/{index-24b78415.esm.js → index-baa799e0.esm.js} +4 -4
  41. package/dist/nixxie-cms-core.cjs.js +104 -77
  42. package/dist/nixxie-cms-core.esm.js +104 -77
  43. package/dist/{non-null-graphql-5315718c.esm.js → non-null-graphql-a84ed64d.esm.js} +1 -1
  44. package/dist/{non-null-graphql-17b83ddc.cjs.js → non-null-graphql-add6bb3d.cjs.js} +1 -1
  45. package/dist/{resolve-hooks-66fe8a8e.cjs.js → resolve-hooks-165a9ce2.cjs.js} +1 -1
  46. package/dist/{resolve-hooks-17aafd37.esm.js → resolve-hooks-6813a045.esm.js} +2 -2
  47. package/dist/{system-dfec2f0a.esm.js → system-03e49e4f.esm.js} +8 -4
  48. package/dist/{system-48c5f6df.cjs.js → system-a321642d.cjs.js} +8 -4
  49. package/dist/{useFilter-0b5a1ee6.esm.js → useFilter-9b6db1f9.esm.js} +1 -1
  50. package/dist/{useFilter-1a4e6900.cjs.js → useFilter-acc9d413.cjs.js} +1 -1
  51. package/fields/dist/nixxie-cms-core-fields.cjs.js +16 -16
  52. package/fields/dist/nixxie-cms-core-fields.esm.js +17 -17
  53. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +3 -3
  54. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +3 -3
  55. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.cjs.js +1 -1
  56. package/fields/types/bytes/views/dist/nixxie-cms-core-fields-types-bytes-views.esm.js +1 -1
  57. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +3 -3
  58. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +3 -3
  59. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.cjs.js +4 -4
  60. package/fields/types/relationship/views/dist/nixxie-cms-core-fields-types-relationship-views.esm.js +4 -4
  61. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.cjs.js +1 -1
  62. package/fields/types/select/views/dist/nixxie-cms-core-fields-types-select-views.esm.js +1 -1
  63. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.cjs.js +1 -1
  64. package/fields/types/text/views/dist/nixxie-cms-core-fields-types-text-views.esm.js +1 -1
  65. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.d.ts +2 -0
  66. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.cjs.js +244 -0
  67. package/internal-unstable/admin-ui/id-field-view/dist/nixxie-cms-core-internal-unstable-admin-ui-id-field-view.esm.js +235 -0
  68. package/internal-unstable/admin-ui/id-field-view/package.json +4 -0
  69. package/internal-unstable/admin-ui/next-config/package.json +4 -0
  70. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.d.ts +2 -0
  71. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.cjs.js +59 -0
  72. package/internal-unstable/admin-ui/pages/App/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-App.esm.js +55 -0
  73. package/internal-unstable/admin-ui/pages/App/package.json +4 -0
  74. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.d.ts +2 -0
  75. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.cjs.js +116 -0
  76. package/internal-unstable/admin-ui/pages/CreateItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-CreateItemPage.esm.js +112 -0
  77. package/internal-unstable/admin-ui/pages/CreateItemPage/package.json +4 -0
  78. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.d.ts +2 -0
  79. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.cjs.js +336 -0
  80. package/internal-unstable/admin-ui/pages/HomePage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-HomePage.esm.js +332 -0
  81. package/internal-unstable/admin-ui/pages/HomePage/package.json +4 -0
  82. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.d.ts +2 -0
  83. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.cjs.js +463 -0
  84. package/internal-unstable/admin-ui/pages/ItemPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ItemPage.esm.js +455 -0
  85. package/internal-unstable/admin-ui/pages/ItemPage/package.json +4 -0
  86. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.d.ts +2 -0
  87. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.cjs.js +1195 -0
  88. package/internal-unstable/admin-ui/pages/ListPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-ListPage.esm.js +1187 -0
  89. package/internal-unstable/admin-ui/pages/ListPage/package.json +4 -0
  90. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.d.ts +2 -0
  91. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.cjs.js +40 -0
  92. package/internal-unstable/admin-ui/pages/NoAccessPage/dist/nixxie-cms-core-internal-unstable-admin-ui-pages-NoAccessPage.esm.js +35 -0
  93. package/internal-unstable/admin-ui/pages/NoAccessPage/package.json +4 -0
  94. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.d.ts +2 -0
  95. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +51 -0
  96. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +38 -0
  97. package/internal-unstable/artifacts/package.json +4 -0
  98. package/package.json +44 -44
  99. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +44 -15
  100. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +44 -15
  101. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +3 -3
  102. package/scripts/dist/nixxie-cms-core-scripts.esm.js +3 -3
  103. package/src/admin-ui/admin-meta-graphql.ts +168 -168
  104. package/src/admin-ui/components/CommandPalette.tsx +433 -431
  105. package/src/admin-ui/components/Navigation.tsx +389 -385
  106. package/src/admin-ui/components/PageContainer.tsx +311 -310
  107. package/src/admin-ui/components/WelcomeDialog.tsx +1 -1
  108. package/src/admin-ui/context.tsx +338 -338
  109. package/src/admin-ui/templates/app.ts +60 -60
  110. package/src/admin-ui/templates/create-item.ts +5 -5
  111. package/src/admin-ui/templates/home.ts +2 -2
  112. package/src/admin-ui/templates/item.tsx +5 -5
  113. package/src/admin-ui/templates/list.tsx +5 -5
  114. package/src/admin-ui/templates/next-config.ts +29 -0
  115. package/src/admin-ui/templates/no-access.ts +7 -7
  116. package/src/fields/types/bigInt/index.ts +181 -181
  117. package/src/fields/types/bytes/index.ts +275 -275
  118. package/src/fields/types/calendarDay/index.ts +194 -194
  119. package/src/fields/types/checkbox/index.ts +76 -76
  120. package/src/fields/types/decimal/index.ts +182 -182
  121. package/src/fields/types/file/index.ts +168 -168
  122. package/src/fields/types/float/index.ts +133 -133
  123. package/src/fields/types/image/index.ts +244 -244
  124. package/src/fields/types/integer/index.ts +156 -156
  125. package/src/fields/types/json/index.ts +77 -77
  126. package/src/fields/types/multiselect/index.ts +212 -212
  127. package/src/fields/types/password/index.ts +241 -241
  128. package/src/fields/types/relationship/index.ts +381 -381
  129. package/src/fields/types/relationship/views/RelationshipTable.tsx +190 -190
  130. package/src/fields/types/select/index.ts +226 -226
  131. package/src/fields/types/text/index.ts +207 -207
  132. package/src/fields/types/timestamp/index.ts +116 -116
  133. package/src/fields/types/virtual/index.ts +108 -108
  134. package/src/helpers.ts +342 -316
  135. package/src/index.ts +4 -0
  136. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.tsx +167 -167
  137. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.tsx +22 -22
  138. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.tsx +71 -71
  139. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.tsx +333 -333
  140. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/common.tsx +358 -358
  141. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.tsx +483 -483
  142. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/FilterAdd.tsx +221 -221
  143. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/PaginationControls.tsx +170 -170
  144. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/Tag.tsx +72 -72
  145. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.tsx +1006 -1006
  146. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.tsx +24 -24
  147. package/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.ts +5 -5
  148. package/src/lib/context/createContext.ts +165 -161
  149. package/src/lib/core/initialise-lists.ts +1097 -1097
  150. package/src/lib/id-field.ts +214 -214
  151. package/src/lib/telemetry.ts +342 -342
  152. package/src/schema.ts +237 -233
  153. package/src/scripts/telemetry.ts +1 -1
  154. package/src/types/config/index.ts +400 -333
  155. package/src/types/config/lists.ts +4 -4
  156. package/src/types/context.ts +700 -530
  157. package/src/types/next-fields.ts +499 -499
  158. package/src/types/telemetry.ts +51 -51
  159. package/tests/telemetry.test.ts +361 -361
  160. package/CHANGELOG.md +0 -3158
  161. package/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view/package.json +0 -4
  162. package/___internal-do-not-use-will-break-in-patch/admin-ui/next-config/package.json +0 -4
  163. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/package.json +0 -4
  164. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/package.json +0 -4
  165. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/package.json +0 -4
  166. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/package.json +0 -4
  167. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/package.json +0 -4
  168. package/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/package.json +0 -4
  169. package/___internal-do-not-use-will-break-in-patch/artifacts/package.json +0 -4
  170. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.d.ts.map +0 -1
  171. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/App/index.d.ts.map +0 -1
  172. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/CreateItemPage/index.d.ts.map +0 -1
  173. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/HomePage/index.d.ts.map +0 -1
  174. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ItemPage/index.d.ts.map +0 -1
  175. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/ListPage/index.d.ts.map +0 -1
  176. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/admin-ui/pages/NoAccessPage/index.d.ts.map +0 -1
  177. package/dist/declarations/src/___internal-do-not-use-will-break-in-patch/artifacts.d.ts.map +0 -1
  178. /package/dist/{common-1a350e11.cjs.js → common-5933f758.cjs.js} +0 -0
  179. /package/dist/{common-29fc82e6.esm.js → common-ea5c441a.esm.js} +0 -0
  180. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/id-field-view.d.ts +0 -0
  181. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/App/index.d.ts +0 -0
  182. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/CreateItemPage/index.d.ts +0 -0
  183. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/HomePage/index.d.ts +0 -0
  184. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ItemPage/index.d.ts +0 -0
  185. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/ListPage/index.d.ts +0 -0
  186. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/admin-ui/pages/NoAccessPage/index.d.ts +0 -0
  187. /package/dist/declarations/src/{___internal-do-not-use-will-break-in-patch → internal-unstable}/artifacts.d.ts +0 -0
@@ -1,1097 +1,1097 @@
1
- import type { CacheHint } from '@apollo/cache-control-types'
2
- import { GInputObjectType, type GArg, type GInputType } from '@graphql-ts/schema'
3
- import { GNonNull } from '@graphql-ts/schema'
4
- import {
5
- GraphQLList,
6
- GraphQLNonNull,
7
- GraphQLString,
8
- isInputObjectType,
9
- type GraphQLType,
10
- } from 'graphql'
11
-
12
- import { g } from '../..'
13
- import { expandVoidHooks } from '../../fields/resolve-hooks'
14
- import { humanize } from '../../lib/utils'
15
- import type { GroupInfo } from '../../schema'
16
- import type {
17
- ActionMeta,
18
- BaseFieldTypeInfo,
19
- BaseItem,
20
- BaseListTypeInfo,
21
- CacheHintArgs,
22
- FieldTypeFunc,
23
- FindManyArgs,
24
- GraphQLTypesForList,
25
- NixxieConfig,
26
- ListGraphQLTypes,
27
- ListHooks,
28
- MaybeFieldFunction,
29
- NextFieldType,
30
- } from '../../types'
31
- import { QueryMode } from '../../types'
32
- import type { FieldHooks, ResolvedFieldHooks, ResolvedListHooks } from '../../types/config/hooks'
33
- import type {
34
- BaseActions,
35
- MaybeBooleanItemFunctionWithFilter,
36
- MaybeBooleanSessionFunctionWithFilter,
37
- MaybeItemActionFunctionWithFilter,
38
- MaybeItemFieldFunction,
39
- MaybeItemFieldFunctionWithFilter,
40
- MaybeSessionFunction,
41
- MaybeSessionFunctionWithFilter,
42
- } from '../../types/config/lists'
43
- import { type GraphQLNames, __getNames } from '../../types/utils'
44
- import {
45
- type ResolvedActionAccessControl,
46
- type ResolvedFieldAccessControl,
47
- type ResolvedListAccessControl,
48
- parseFieldAccessControl,
49
- parseListAccessControl,
50
- } from './access-control'
51
- import { assertFieldsValid } from './field-assertions'
52
- import { outputTypeField } from './queries/output-field'
53
- import { type ResolvedDBField, resolveRelationships } from './resolve-relationships'
54
- import { areArraysEqual } from './utils'
55
-
56
- export type InitialisedAction = {
57
- actionKey: string
58
-
59
- access: ResolvedActionAccessControl
60
- resolve: BaseActions<BaseListTypeInfo>[keyof BaseActions<BaseListTypeInfo>]['resolve']
61
- graphql: {
62
- arguments: { name: string; type: string; source: { itemField: string } | null }[]
63
- names: {
64
- one: string
65
- many: string
66
- args: string
67
- }
68
- types: {
69
- arguments: Record<string, GArg<GInputType>>
70
- args: GInputObjectType<Record<string, GArg<GInputType>>>
71
- }
72
- }
73
- otel: string
74
- ui: {
75
- label: ActionMeta['label']
76
- icon: ActionMeta['icon']
77
- messages: ActionMeta['messages']
78
- itemView: Omit<ActionMeta['itemView'], 'actionMode'> & {
79
- actionMode: MaybeItemActionFunctionWithFilter<
80
- 'enabled' | 'disabled' | 'hidden',
81
- 'disabled' | 'hidden',
82
- BaseListTypeInfo
83
- >
84
- }
85
- listView: {
86
- actionMode: MaybeSessionFunctionWithFilter<
87
- 'enabled' | 'disabled' | 'hidden',
88
- 'disabled' | 'hidden',
89
- BaseListTypeInfo
90
- >
91
- }
92
- argSources: Record<string, { itemField: string }>
93
- }
94
- }
95
-
96
- function printGraphQLType(type: GraphQLType): string {
97
- if (type instanceof GraphQLNonNull) return `${printGraphQLType(type.ofType)}!`
98
- if (type instanceof GraphQLList) return `[${printGraphQLType(type.ofType)}]`
99
- return type.name
100
- }
101
-
102
- function getArgSources(
103
- action: NonNullable<NixxieConfig['lists'][string]['actions']>[string]
104
- ): Record<string, { itemField: string }> {
105
- return Object.fromEntries(
106
- Object.entries(action.args ?? {}).flatMap(([arg, value]) => {
107
- const field = value.ui?.source?.itemField
108
- if (!field) return []
109
- return [[arg, { itemField: field }]]
110
- })
111
- )
112
- }
113
-
114
- export type InitialisedField = {
115
- fieldKey: string
116
-
117
- access: ResolvedFieldAccessControl
118
- dbField: ResolvedDBField
119
- hooks: ResolvedFieldHooks<BaseListTypeInfo, BaseFieldTypeInfo>
120
- graphql: {
121
- isEnabled: {
122
- read: boolean
123
- create: boolean
124
- update: boolean
125
- filter: MaybeFieldFunction<BaseListTypeInfo>
126
- orderBy: MaybeFieldFunction<BaseListTypeInfo>
127
- }
128
- isNonNull: {
129
- read: boolean
130
- create: boolean
131
- update: boolean
132
- }
133
- cacheHint: CacheHint | undefined
134
- }
135
- ui: {
136
- label: string
137
- description: string
138
- views: string | null
139
- createView: {
140
- fieldMode: MaybeSessionFunctionWithFilter<'edit' | 'hidden', 'hidden', BaseListTypeInfo>
141
- isRequired: MaybeBooleanSessionFunctionWithFilter<BaseListTypeInfo>
142
- }
143
- itemView: {
144
- fieldMode: MaybeItemFieldFunctionWithFilter<
145
- 'read' | 'edit' | 'hidden',
146
- 'read' | 'hidden',
147
- BaseListTypeInfo,
148
- BaseFieldTypeInfo
149
- >
150
- fieldPosition: MaybeItemFieldFunction<'form' | 'sidebar', BaseListTypeInfo, BaseFieldTypeInfo>
151
- isRequired: MaybeBooleanItemFunctionWithFilter<BaseListTypeInfo, BaseFieldTypeInfo>
152
- }
153
- listView: {
154
- fieldMode: MaybeSessionFunction<'read' | 'hidden', BaseListTypeInfo>
155
- }
156
- }
157
- } & Pick<
158
- NextFieldType,
159
- | 'input'
160
- | 'output'
161
- | 'getAdminMeta'
162
- | 'views'
163
- | '__ksTelemetryFieldTypeName'
164
- | 'extraOutputFields'
165
- | 'unreferencedConcreteInterfaceImplementations'
166
- >
167
-
168
- export type InitialisedList = {
169
- listKey: string
170
-
171
- access: ResolvedListAccessControl
172
-
173
- fields: Record<string, InitialisedField>
174
- actions: InitialisedAction[]
175
- groups: GroupInfo<BaseListTypeInfo>[]
176
-
177
- hooks: ResolvedListHooks<BaseListTypeInfo>
178
-
179
- /** This will include the opposites to one-sided relationships */
180
- resolvedDbFields: Record<string, ResolvedDBField>
181
- lists: Record<string, InitialisedList>
182
-
183
- graphql: {
184
- types: GraphQLTypesForList
185
- names: GraphQLNames
186
- isEnabled: {
187
- type: boolean
188
- query: boolean
189
- create: boolean
190
- update: boolean
191
- delete: boolean
192
- filter: MaybeFieldFunction<BaseListTypeInfo>
193
- orderBy: MaybeFieldFunction<BaseListTypeInfo>
194
- }
195
- }
196
-
197
- prisma: {
198
- types: GraphQLNames // TODO: not completely appropriate, but what is used for now
199
- listKey: string
200
- mapping: string | undefined
201
- extendPrismaSchema: ((schema: string) => string) | undefined
202
- }
203
-
204
- ui: {
205
- labels: { label: string; singular: string; plural: string; path: string }
206
- labelField: string
207
- searchFields: Set<string>
208
- searchableFields: Map<string, 'default' | 'insensitive' | null>
209
- triviallySearchableFields: Set<string>
210
- }
211
-
212
- isSingleton: boolean
213
- cacheHint: ((args: CacheHintArgs<BaseListTypeInfo>) => CacheHint) | undefined
214
- }
215
-
216
- function throwIfNotAFilter(x: unknown, listKey: string, fieldKey: string) {
217
- if (['boolean', 'undefined', 'function'].includes(typeof x)) return
218
- throw new Error(
219
- `Configuration option '${listKey}.${fieldKey}' must be either a boolean value or a function. Received '${x}'.`
220
- )
221
- }
222
-
223
- type ListConfigType = NixxieConfig['lists'][string]
224
- type FieldConfigType = ReturnType<FieldTypeFunc<any>>
225
- type PartiallyInitialisedList1 = { graphql: { isEnabled: InitialisedList['graphql']['isEnabled'] } }
226
- type PartiallyInitialisedList2 = Omit<InitialisedList, 'lists' | 'resolvedDbFields'>
227
-
228
- // TODO: move to defaultLists?
229
- function getIsEnabled(listKey: string, listConfig: ListConfigType) {
230
- const omit = listConfig.graphql.omit ?? false
231
- const { defaultIsFilterable, defaultIsOrderable } = listConfig
232
-
233
- throwIfNotAFilter(defaultIsFilterable, listKey, 'defaultIsFilterable')
234
- throwIfNotAFilter(defaultIsOrderable, listKey, 'defaultIsOrderable')
235
-
236
- if (typeof omit === 'boolean') {
237
- const notOmit = !omit
238
- return {
239
- type: notOmit,
240
- query: notOmit,
241
- create: notOmit,
242
- update: notOmit,
243
- delete: notOmit,
244
- filter: notOmit ? defaultIsFilterable : false,
245
- orderBy: notOmit ? defaultIsOrderable : false,
246
- }
247
- }
248
-
249
- return {
250
- type: true,
251
- query: !omit.query,
252
- create: !omit.create,
253
- update: !omit.update,
254
- delete: !omit.delete,
255
- filter: defaultIsFilterable,
256
- orderBy: defaultIsOrderable,
257
- }
258
- }
259
-
260
- function getIsEnabledField(
261
- f: FieldConfigType,
262
- listKey: string,
263
- list: PartiallyInitialisedList1,
264
- lists: Record<string, PartiallyInitialisedList1>
265
- ) {
266
- const omit = f.graphql?.omit ?? false
267
- const {
268
- isFilterable = list.graphql.isEnabled.filter,
269
- isOrderable = list.graphql.isEnabled.orderBy,
270
- } = f
271
-
272
- // TODO: check types in initConfig
273
- throwIfNotAFilter(isFilterable, listKey, 'isFilterable')
274
- throwIfNotAFilter(isOrderable, listKey, 'isOrderable')
275
-
276
- if (f.dbField.kind === 'relation') {
277
- if (!lists[f.dbField.list].graphql.isEnabled.type) {
278
- return {
279
- type: false,
280
- read: false,
281
- create: false,
282
- update: false,
283
- filter: false,
284
- orderBy: false,
285
- }
286
- }
287
- }
288
-
289
- if (typeof omit === 'boolean') {
290
- const notOmit = !omit
291
- return {
292
- type: notOmit,
293
- read: notOmit,
294
- create: notOmit,
295
- update: notOmit,
296
- filter: notOmit ? isFilterable : false,
297
- orderBy: notOmit ? isOrderable : false,
298
- }
299
- }
300
-
301
- return {
302
- type: true,
303
- read: !omit.read,
304
- create: !omit.create,
305
- update: !omit.update,
306
- filter: !omit.read ? isFilterable : false, // prevent filtering if read is false
307
- orderBy: !omit.read ? isOrderable : false, // prevent ordering if read is false
308
- }
309
- }
310
-
311
- function defaultListHooksResolveInput({ resolvedData }: { resolvedData: any }) {
312
- return resolvedData
313
- }
314
-
315
- function parseListHooks(hooks: ListHooks<BaseListTypeInfo>): ResolvedListHooks<BaseListTypeInfo> {
316
- return {
317
- resolveInput: {
318
- create:
319
- typeof hooks.resolveInput === 'function'
320
- ? hooks.resolveInput
321
- : (hooks.resolveInput?.create ?? defaultListHooksResolveInput),
322
- update:
323
- typeof hooks.resolveInput === 'function'
324
- ? hooks.resolveInput
325
- : (hooks.resolveInput?.update ?? defaultListHooksResolveInput),
326
- },
327
- validate: expandVoidHooks(hooks.validate),
328
- beforeOperation: expandVoidHooks(hooks.beforeOperation),
329
- afterOperation: expandVoidHooks(hooks.afterOperation),
330
- }
331
- }
332
-
333
- function defaultFieldHooksResolveInput({
334
- resolvedData,
335
- fieldKey,
336
- }: {
337
- resolvedData: any
338
- fieldKey: string
339
- }) {
340
- return resolvedData[fieldKey]
341
- }
342
-
343
- function parseFieldHooks(
344
- hooks: FieldHooks<BaseListTypeInfo, BaseFieldTypeInfo>
345
- ): ResolvedFieldHooks<BaseListTypeInfo, BaseFieldTypeInfo> {
346
- return {
347
- resolveInput: {
348
- create:
349
- typeof hooks.resolveInput === 'function'
350
- ? hooks.resolveInput
351
- : (hooks.resolveInput?.create ?? defaultFieldHooksResolveInput),
352
- update:
353
- typeof hooks.resolveInput === 'function'
354
- ? hooks.resolveInput
355
- : (hooks.resolveInput?.update ?? defaultFieldHooksResolveInput),
356
- },
357
- validate: expandVoidHooks(hooks.validate),
358
- beforeOperation: expandVoidHooks(hooks.beforeOperation),
359
- afterOperation: expandVoidHooks(hooks.afterOperation),
360
- }
361
- }
362
-
363
- function getListsWithInitialisedFields(
364
- config: NixxieConfig,
365
- listsRef: Record<string, InitialisedList>
366
- ) {
367
- const {
368
- lists: listsConfig,
369
- db: { provider },
370
- } = config
371
- const intermediateLists = Object.fromEntries(
372
- Object.values(config.lists).map(listConfig => [
373
- listConfig.listKey,
374
- {
375
- graphql: {
376
- isEnabled: getIsEnabled(listConfig.listKey, listConfig),
377
- },
378
- },
379
- ])
380
- )
381
-
382
- const listGraphqlTypes: Record<string, ListGraphQLTypes<BaseListTypeInfo>> = {}
383
-
384
- for (const listConfig of Object.values(listsConfig)) {
385
- const { listKey } = listConfig
386
- const {
387
- graphql: { names },
388
- } = __getNames(listKey, listConfig)
389
-
390
- const output = g.object<BaseItem>()({
391
- name: names.outputTypeName,
392
- fields: () => {
393
- const { fields } = listsRef[listKey]
394
- return {
395
- ...Object.fromEntries(
396
- Object.entries(fields).flatMap(([fieldPath, field]) => {
397
- if (
398
- !field.output ||
399
- !field.graphql.isEnabled.read ||
400
- (field.dbField.kind === 'relation' &&
401
- !intermediateLists[field.dbField.list].graphql.isEnabled.query)
402
- ) {
403
- return []
404
- }
405
-
406
- const outputFieldRoot = graphqlForOutputField(field)
407
- return [
408
- [fieldPath, outputFieldRoot] as const,
409
- ...Object.entries(field.extraOutputFields || {}),
410
- ].map(([outputTypeFieldName, outputField]) => {
411
- return [
412
- outputTypeFieldName,
413
- outputTypeField(
414
- outputField,
415
- field.dbField,
416
- field.graphql?.cacheHint,
417
- field.access.read,
418
- listKey,
419
- fieldPath,
420
- listsRef
421
- ),
422
- ]
423
- })
424
- })
425
- ),
426
- }
427
- },
428
- })
429
-
430
- const uniqueWhere = g.inputObject({
431
- name: names.whereUniqueInputName,
432
- fields: () => {
433
- const { fields } = listsRef[listKey]
434
- return {
435
- ...Object.fromEntries(
436
- Object.entries(fields).flatMap(([key, field]) => {
437
- if (
438
- !field.input?.uniqueWhere?.arg ||
439
- !field.graphql.isEnabled.read ||
440
- !field.graphql.isEnabled.filter
441
- ) {
442
- return []
443
- }
444
-
445
- // only 1-to-1 relationships can have a uniqueWhere filter on relations
446
- if (field.dbField.kind === 'relation') {
447
- const remoteField =
448
- listsRef[field.dbField.list].resolvedDbFields[field.dbField.field]
449
- if (
450
- field.dbField.mode === 'one' &&
451
- remoteField.kind === 'relation' &&
452
- remoteField.mode === 'one'
453
- ) {
454
- return [[key, field.input.uniqueWhere.arg]] as const
455
- }
456
- return []
457
- }
458
-
459
- return [[key, field.input.uniqueWhere.arg]] as const
460
- })
461
- ),
462
- // this is exactly what the id field will add
463
- // but this does it more explicitly so that typescript understands
464
- id: g.arg({ type: g.ID }),
465
- }
466
- },
467
- })
468
-
469
- const where: GraphQLTypesForList['where'] = g.inputObject({
470
- name: names.whereInputName,
471
- fields: () => {
472
- const { fields } = listsRef[listKey]
473
- return Object.assign(
474
- {
475
- AND: g.arg({ type: g.list(g.nonNull(where)) }),
476
- OR: g.arg({ type: g.list(g.nonNull(where)) }),
477
- NOT: g.arg({ type: g.list(g.nonNull(where)) }),
478
- },
479
- ...Object.entries(fields).map(
480
- ([fieldKey, field]) =>
481
- field.input?.where?.arg &&
482
- field.graphql.isEnabled.read &&
483
- field.graphql.isEnabled.filter && { [fieldKey]: field.input?.where?.arg }
484
- )
485
- )
486
- },
487
- })
488
-
489
- const create = g.inputObject({
490
- name: names.createInputName,
491
- fields: () => {
492
- const { fields } = listsRef[listKey]
493
- const ret: Record<keyof typeof fields, GArg<GInputType>> = {}
494
-
495
- for (const key in fields) {
496
- const arg = graphqlArgForInputField(fields[key], 'create', listsRef)
497
- if (!arg) continue
498
- ret[key] = arg
499
- }
500
-
501
- return ret
502
- },
503
- })
504
-
505
- const update = g.inputObject({
506
- name: names.updateInputName,
507
- fields: () => {
508
- const { fields } = listsRef[listKey]
509
- const ret: Record<keyof typeof fields, GArg<GInputType>> = {}
510
-
511
- for (const key in fields) {
512
- const arg = graphqlArgForInputField(fields[key], 'update', listsRef)
513
- if (!arg) continue
514
- ret[key] = arg
515
- }
516
-
517
- return ret
518
- },
519
- })
520
-
521
- const orderBy = g.inputObject({
522
- name: names.listOrderName,
523
- fields: () => {
524
- const { fields } = listsRef[listKey]
525
- return Object.fromEntries(
526
- Object.entries(fields).flatMap(([key, field]) => {
527
- if (
528
- !field.input?.orderBy?.arg ||
529
- !field.graphql.isEnabled.read ||
530
- !field.graphql.isEnabled.orderBy
531
- ) {
532
- return []
533
- }
534
- return [[key, field.input.orderBy.arg]] as const
535
- })
536
- )
537
- },
538
- })
539
-
540
- let take: any = g.arg({ type: g.Int })
541
- if (listConfig.graphql?.maxTake !== undefined) {
542
- take = g.arg({
543
- type: g.nonNull(g.Int),
544
- // WARNING: used by queries/resolvers.ts to enforce the limit
545
- defaultValue: listConfig.graphql.maxTake,
546
- })
547
- }
548
-
549
- const findManyArgs: FindManyArgs = {
550
- where: g.arg({
551
- type: g.nonNull(where),
552
- defaultValue: listConfig.isSingleton ? { id: { equals: '1' } } : {},
553
- }),
554
- orderBy: g.arg({
555
- type: g.nonNull(g.list(g.nonNull(orderBy))),
556
- defaultValue: [],
557
- }),
558
- take,
559
- skip: g.arg({
560
- type: g.nonNull(g.Int),
561
- defaultValue: 0,
562
- }),
563
- cursor: g.arg({ type: uniqueWhere }),
564
- }
565
-
566
- const relateToOneForCreate = g.inputObject({
567
- name: names.relateToOneForCreateInputName,
568
- fields: () => {
569
- const listRef = listsRef[listKey]
570
- return {
571
- ...(listRef.graphql.isEnabled.create && {
572
- create: g.arg({ type: listRef.graphql.types.create }),
573
- }),
574
- connect: g.arg({ type: listRef.graphql.types.uniqueWhere }),
575
- }
576
- },
577
- })
578
-
579
- const relateToOneForUpdate = g.inputObject({
580
- name: names.relateToOneForUpdateInputName,
581
- fields: () => {
582
- const listRef = listsRef[listKey]
583
- return {
584
- ...(listRef.graphql.isEnabled.create && {
585
- create: g.arg({ type: listRef.graphql.types.create }),
586
- }),
587
- connect: g.arg({ type: listRef.graphql.types.uniqueWhere }),
588
- disconnect: g.arg({ type: g.Boolean }),
589
- }
590
- },
591
- })
592
-
593
- const relateToManyForCreate = g.inputObject({
594
- name: names.relateToManyForCreateInputName,
595
- fields: () => {
596
- const listRef = listsRef[listKey]
597
- return {
598
- ...(listRef.graphql.isEnabled.create && {
599
- create: g.arg({
600
- type: g.list(g.nonNull(listRef.graphql.types.create)),
601
- }),
602
- }),
603
- connect: g.arg({
604
- type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)),
605
- }),
606
- set: g.arg({
607
- type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)),
608
- }),
609
- }
610
- },
611
- })
612
-
613
- const relateToManyForUpdate = g.inputObject({
614
- name: names.relateToManyForUpdateInputName,
615
- fields: () => {
616
- const listRef = listsRef[listKey]
617
- return {
618
- // WARNING: the order of these fields reflects the order of mutations
619
- disconnect: g.arg({
620
- type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)),
621
- }),
622
- set: g.arg({
623
- type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)),
624
- }),
625
- ...(listRef.graphql.isEnabled.create && {
626
- create: g.arg({ type: g.list(g.nonNull(listRef.graphql.types.create)) }),
627
- }),
628
- connect: g.arg({ type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)) }),
629
- }
630
- },
631
- })
632
-
633
- listGraphqlTypes[listKey] = {
634
- types: {
635
- output,
636
- uniqueWhere,
637
- where,
638
- create,
639
- orderBy,
640
- update,
641
- findManyArgs,
642
- relateTo: {
643
- one: {
644
- create: relateToOneForCreate,
645
- update: relateToOneForUpdate,
646
- },
647
- many: {
648
- where: g.inputObject({
649
- name: `${listKey}ManyRelationFilter`,
650
- fields: {
651
- every: g.arg({ type: where }),
652
- some: g.arg({ type: where }),
653
- none: g.arg({ type: where }),
654
- },
655
- }),
656
- create: relateToManyForCreate,
657
- update: relateToManyForUpdate,
658
- },
659
- },
660
- },
661
- }
662
- }
663
-
664
- const result: Record<string, PartiallyInitialisedList2> = {}
665
-
666
- for (const listConfig of Object.values(listsConfig)) {
667
- const { listKey } = listConfig
668
- const intermediateList = intermediateLists[listKey]
669
- const resultFields: Record<string, InitialisedField> = {}
670
- const groups: GroupInfo<BaseListTypeInfo>[] = []
671
- const fieldKeys = Object.keys(listConfig.fields)
672
-
673
- const fieldKeysToGroup: Record<string, GroupInfo<BaseListTypeInfo>> = {}
674
- for (const [idx, [fieldKey, fieldFunc]] of Object.entries(listConfig.fields).entries()) {
675
- if (fieldKey.startsWith('__group')) {
676
- const group__ = fieldFunc as any
677
- if (
678
- typeof group__ === 'object' &&
679
- group__ !== null &&
680
- typeof group__.label === 'string' &&
681
- (group__.description === null || typeof group__.description === 'string') &&
682
- Array.isArray(group__.fields) &&
683
- areArraysEqual(group__.fields, fieldKeys.slice(idx + 1, idx + 1 + group__.fields.length))
684
- ) {
685
- groups.push(group__)
686
- for (const field of group__.fields) {
687
- fieldKeysToGroup[field] = group__
688
- }
689
- continue
690
- }
691
- throw new Error(`unexpected value for a group at ${listKey}.${fieldKey}`)
692
- }
693
- if (typeof fieldFunc !== 'function') {
694
- throw new Error(`The field at ${listKey}.${fieldKey} does not provide a function`)
695
- }
696
-
697
- const group = fieldKeysToGroup[fieldKey]
698
- const f = fieldFunc({
699
- provider,
700
- lists: listGraphqlTypes,
701
- listKey,
702
- fieldKey,
703
- })
704
-
705
- const isEnabledField = getIsEnabledField(f, listKey, intermediateList, intermediateLists)
706
- resultFields[fieldKey] = {
707
- fieldKey,
708
-
709
- dbField: f.dbField as ResolvedDBField,
710
- access: parseFieldAccessControl(f.access),
711
- hooks: parseFieldHooks(f.hooks ?? {}),
712
- graphql: {
713
- cacheHint: f.graphql?.cacheHint,
714
- isEnabled: isEnabledField,
715
- isNonNull: {
716
- read:
717
- typeof f.graphql?.isNonNull === 'boolean'
718
- ? f.graphql.isNonNull
719
- : (f.graphql?.isNonNull?.read ?? false),
720
- create:
721
- typeof f.graphql?.isNonNull === 'boolean'
722
- ? f.graphql.isNonNull
723
- : (f.graphql?.isNonNull?.create ?? false),
724
- update:
725
- typeof f.graphql?.isNonNull === 'boolean'
726
- ? f.graphql.isNonNull
727
- : (f.graphql?.isNonNull?.update ?? false),
728
- },
729
- },
730
- ui: {
731
- label: f.ui?.label || humanize(fieldKey),
732
- description: f.ui?.description ?? '',
733
- views: f.ui?.views ?? null,
734
- createView: {
735
- isRequired: f.ui?.createView?.isRequired ?? false,
736
- fieldMode: isEnabledField.create
737
- ? (f.ui?.createView?.fieldMode ??
738
- group?.ui?.createView?.defaultFieldMode ??
739
- listConfig.ui.createView?.defaultFieldMode ??
740
- 'edit')
741
- : 'hidden',
742
- },
743
-
744
- itemView: {
745
- isRequired: f.ui?.itemView?.isRequired ?? false,
746
- fieldPosition: f.ui?.itemView?.fieldPosition ?? 'form',
747
- fieldMode: isEnabledField.update
748
- ? (f.ui?.itemView?.fieldMode ??
749
- group?.ui?.itemView?.defaultFieldMode ??
750
- listConfig.ui.itemView?.defaultFieldMode ??
751
- 'edit')
752
- : isEnabledField.read
753
- ? (f.ui?.itemView?.fieldMode ??
754
- group?.ui?.itemView?.defaultFieldMode ??
755
- listConfig.ui.itemView?.defaultFieldMode ??
756
- 'read')
757
- : 'hidden',
758
- },
759
-
760
- listView: {
761
- fieldMode: isEnabledField.read
762
- ? (f.ui?.listView?.fieldMode ??
763
- group?.ui?.listView?.defaultFieldMode ??
764
- listConfig.ui.listView?.defaultFieldMode ??
765
- 'read')
766
- : 'hidden',
767
- },
768
- },
769
-
770
- // copy
771
- __ksTelemetryFieldTypeName: f.__ksTelemetryFieldTypeName,
772
- extraOutputFields: f.extraOutputFields,
773
- getAdminMeta: f.getAdminMeta,
774
- input: { ...f.input },
775
- output: { ...f.output },
776
- unreferencedConcreteInterfaceImplementations:
777
- f.unreferencedConcreteInterfaceImplementations,
778
- views: f.views,
779
- }
780
- }
781
-
782
- // default labelField to `name`, `label`, or `title`; fallback to `id`
783
- const labelField =
784
- listConfig.ui.labelField ??
785
- (listConfig.fields.label
786
- ? 'label'
787
- : listConfig.fields.name
788
- ? 'name'
789
- : listConfig.fields.title
790
- ? 'title'
791
- : 'id')
792
-
793
- const searchFields = new Set(listConfig.ui.searchFields ?? [])
794
- if (searchFields.has('id')) {
795
- throw new Error(`${listKey}.ui.searchFields cannot include 'id'`)
796
- }
797
-
798
- const names = __getNames(listKey, listConfig)
799
- result[listKey] = {
800
- access: parseListAccessControl(listConfig.access),
801
-
802
- fields: resultFields,
803
- groups,
804
- actions: [],
805
-
806
- graphql: {
807
- types: {
808
- ...listGraphqlTypes[listKey].types,
809
- },
810
- names: {
811
- ...names.graphql.names,
812
- },
813
- ...intermediateList.graphql,
814
- },
815
-
816
- prisma: {
817
- types: {
818
- ...names.graphql.names,
819
- },
820
- listKey: listKey[0].toLowerCase() + listKey.slice(1),
821
- mapping: listConfig.db.map,
822
- extendPrismaSchema: listConfig.db.extendPrismaSchema,
823
- },
824
-
825
- ui: {
826
- labels: names.ui.labels,
827
- labelField,
828
- searchFields,
829
- searchableFields: new Map<string, 'default' | 'insensitive' | null>(),
830
- triviallySearchableFields: new Set<string>(),
831
- },
832
- hooks: parseListHooks(listConfig.hooks ?? {}),
833
- listKey,
834
- cacheHint: (() => {
835
- const cacheHint = listConfig.graphql.cacheHint
836
- if (typeof cacheHint === 'function') return cacheHint
837
- if (cacheHint !== undefined) return () => cacheHint
838
- return undefined
839
- })(),
840
-
841
- isSingleton: listConfig.isSingleton ?? false,
842
- }
843
- }
844
-
845
- return result
846
- }
847
-
848
- function introspectGraphQLTypes(lists: Record<string, InitialisedList>) {
849
- const namesOfRelationInputs = new Set<string>()
850
- for (const list of Object.values(lists)) {
851
- const { types } = list.graphql
852
- namesOfRelationInputs.add(types.where.name)
853
- namesOfRelationInputs.add(types.relateTo.many.where.name)
854
- }
855
- for (const list of Object.values(lists)) {
856
- const {
857
- listKey,
858
- ui: { searchFields, searchableFields, triviallySearchableFields },
859
- } = list
860
-
861
- if (searchFields.has('id')) {
862
- throw new Error(
863
- `The ui.searchFields option on the ${listKey} list includes 'id'. Lists can always be searched by an item's id so it must not be specified as a search field`
864
- )
865
- }
866
-
867
- const whereInputFields = list.graphql.types.where.getFields()
868
- for (const [fieldKey, field] of Object.entries(list.fields)) {
869
- const filterType = whereInputFields[fieldKey]?.type
870
- const fieldFilterFields = isInputObjectType(filterType) ? filterType.getFields() : undefined
871
- const filterTypeName = isInputObjectType(filterType) ? filterType.name : undefined
872
- if (fieldFilterFields?.contains?.type === GraphQLString) {
873
- searchableFields.set(
874
- fieldKey,
875
- fieldFilterFields?.mode?.type === QueryMode ? 'insensitive' : 'default'
876
- )
877
- triviallySearchableFields.add(fieldKey)
878
- } else if (
879
- field.dbField.kind === 'relation' &&
880
- filterTypeName !== undefined &&
881
- namesOfRelationInputs.has(filterTypeName)
882
- ) {
883
- searchableFields.set(fieldKey, 'default')
884
- }
885
- }
886
-
887
- if (searchFields.size === 0) {
888
- if (searchableFields.has(list.ui.labelField)) {
889
- searchFields.add(list.ui.labelField)
890
- }
891
- }
892
- }
893
- }
894
-
895
- function stripDefaultValue(thing: GArg<GInputType, boolean>) {
896
- return g.arg({
897
- ...thing,
898
- defaultValue: undefined,
899
- })
900
- }
901
-
902
- function graphqlArgForInputField(
903
- field: InitialisedField,
904
- operation: 'create' | 'update',
905
- listsRef: Record<string, InitialisedList>
906
- ) {
907
- const input = field.input?.[operation]
908
- if (!input?.arg || !field.graphql.isEnabled[operation]) return
909
- if (field.dbField.kind === 'relation') {
910
- if (!listsRef[field.dbField.list].graphql.isEnabled.type) return
911
- }
912
- if (!field.graphql.isNonNull[operation]) return stripDefaultValue(input.arg)
913
- if (input.arg.type instanceof GNonNull) return input.arg
914
-
915
- return g.arg({
916
- ...input.arg,
917
- type: g.nonNull(input.arg.type),
918
- })
919
- }
920
-
921
- function graphqlForOutputField(field: InitialisedField) {
922
- const output = field.output
923
- if (!output || !field.graphql.isEnabled.read) return output
924
- if (!field.graphql.isNonNull.read) return output
925
- if (output.type instanceof GNonNull) return output
926
-
927
- return g.field({
928
- ...(output as any),
929
- type: g.nonNull(output.type),
930
- })
931
- }
932
-
933
- function getInitialisedActionGraphql(
934
- list: Pick<InitialisedList, 'graphql'>,
935
- listGraphqlNames: { singular: string; plural: string },
936
- actionKey: string,
937
- action: NonNullable<NixxieConfig['lists'][string]['actions']>[string]
938
- ): InitialisedAction['graphql'] {
939
- const graphqlNames = {
940
- one: action.graphql?.singular ?? `${actionKey}${listGraphqlNames.singular}`,
941
- many: action.graphql?.plural ?? `${actionKey}${listGraphqlNames.plural}`,
942
- }
943
- const argsName = `${graphqlNames.one[0].toUpperCase()}${graphqlNames.one.slice(1)}Args`
944
- const argumentFields = Object.fromEntries(
945
- Object.entries(action.args ?? {}).map(([arg, value]) => [arg, value.graphql])
946
- )
947
-
948
- return {
949
- arguments: Object.entries(argumentFields).map(([name, arg]) => ({
950
- name,
951
- type: printGraphQLType(arg.type as unknown as GraphQLType),
952
- source: getArgSources(action)[name] ?? null,
953
- })),
954
- names: {
955
- ...graphqlNames,
956
- args: argsName,
957
- },
958
- types: {
959
- arguments: argumentFields,
960
- args: g.inputObject({
961
- name: argsName,
962
- isOneOf: false,
963
- fields: {
964
- where: g.arg({
965
- type: g.nonNull(list.graphql.types.uniqueWhere),
966
- }),
967
- ...argumentFields,
968
- },
969
- }),
970
- },
971
- }
972
- }
973
-
974
- export function initialiseLists(config: NixxieConfig): Record<string, InitialisedList> {
975
- const listsRef: Record<string, InitialisedList> = {}
976
- let intermediateLists
977
-
978
- // step 1
979
- intermediateLists = getListsWithInitialisedFields(config, listsRef)
980
-
981
- // step 2
982
- const resolvedDBFieldsForLists = resolveRelationships(intermediateLists)
983
- intermediateLists = Object.fromEntries(
984
- Object.values(intermediateLists).map(list => [
985
- list.listKey,
986
- {
987
- ...list,
988
- resolvedDbFields: resolvedDBFieldsForLists[list.listKey],
989
- },
990
- ])
991
- )
992
-
993
- // step 3
994
- intermediateLists = Object.fromEntries(
995
- Object.values(intermediateLists).map(list => {
996
- const fields: Record<string, InitialisedField> = {}
997
-
998
- for (const [fieldKey, field] of Object.entries(list.fields)) {
999
- fields[fieldKey] = {
1000
- ...field,
1001
- dbField: list.resolvedDbFields[fieldKey],
1002
- }
1003
- }
1004
-
1005
- return [list.listKey, { ...list, fields }]
1006
- })
1007
- )
1008
-
1009
- // fixup the GraphQL refs
1010
- for (const list of Object.values(intermediateLists)) {
1011
- listsRef[list.listKey] = {
1012
- ...list,
1013
- lists: listsRef,
1014
- }
1015
- }
1016
-
1017
- for (const list of Object.values(listsRef)) {
1018
- const listConfig = config.lists[list.listKey]
1019
- let hasAnEnabledCreateField = false
1020
- let hasAnEnabledUpdateField = false
1021
-
1022
- for (const field of Object.values(list.fields)) {
1023
- if (field.input?.create?.arg && field.graphql.isEnabled.create) {
1024
- hasAnEnabledCreateField = true
1025
- }
1026
- if (field.input?.update && field.graphql.isEnabled.update) {
1027
- hasAnEnabledUpdateField = true
1028
- }
1029
- }
1030
-
1031
- if (!hasAnEnabledCreateField) {
1032
- list.graphql.types.create = g.Empty
1033
- list.graphql.names.createInputName = 'Empty'
1034
- }
1035
-
1036
- if (!hasAnEnabledUpdateField) {
1037
- list.graphql.types.update = g.Empty
1038
- list.graphql.names.updateInputName = 'Empty'
1039
- }
1040
-
1041
- list.actions = Object.entries(listConfig.actions ?? {}).map(
1042
- ([actionKey, action]): InitialisedAction => {
1043
- const { label } = action.ui
1044
- const graphql = getInitialisedActionGraphql(
1045
- list,
1046
- __getNames(list.listKey, listConfig).graphql,
1047
- actionKey,
1048
- action
1049
- )
1050
-
1051
- return {
1052
- ...action,
1053
- actionKey,
1054
- graphql,
1055
- otel: humanize(graphql.names.one, true).toLowerCase(),
1056
- ui: {
1057
- label,
1058
- icon: action.ui.icon ?? null,
1059
- messages: {
1060
- promptTitle: `{Label} {singular}`,
1061
- promptTitleMany: `{Label} {count} {singular|plural}`,
1062
- prompt: `Are you sure you want to {label} {singular} "{itemLabel}"?`,
1063
- promptMany: `Are you sure you want to {label} {count} {singular|plural}?`,
1064
- promptConfirmLabel: `Yes, {label} this {singular}`,
1065
- promptConfirmLabelMany: `Yes, {label} {count} {singular|plural}`,
1066
- fail: `Could not {label} {singular}`,
1067
- failMany: `Could not {label} {countFail} {singular|plural}`,
1068
- success: `Completed {label} action for {singular}`,
1069
- successMany: `Completed {label} action for {countSuccess} {singular|plural}`,
1070
- ...action.ui.messages,
1071
- },
1072
- itemView: {
1073
- actionMode: action.ui.itemView?.actionMode ?? 'enabled',
1074
- navigation: action.ui.itemView?.navigation ?? 'follow',
1075
- hidePrompt: action.ui.itemView?.hidePrompt ?? false,
1076
- hideToast: action.ui.itemView?.hideToast ?? false,
1077
- },
1078
- listView: {
1079
- actionMode: action.ui.listView?.actionMode ?? 'enabled',
1080
- },
1081
- argSources: getArgSources(action),
1082
- },
1083
- }
1084
- }
1085
- )
1086
- }
1087
-
1088
- // error checking
1089
- for (const list of Object.values(listsRef)) {
1090
- assertFieldsValid(list)
1091
- }
1092
-
1093
- // do some introspection
1094
- introspectGraphQLTypes(listsRef)
1095
-
1096
- return listsRef
1097
- }
1
+ import type { CacheHint } from '@apollo/cache-control-types'
2
+ import { GInputObjectType, type GArg, type GInputType } from '@graphql-ts/schema'
3
+ import { GNonNull } from '@graphql-ts/schema'
4
+ import {
5
+ GraphQLList,
6
+ GraphQLNonNull,
7
+ GraphQLString,
8
+ isInputObjectType,
9
+ type GraphQLType,
10
+ } from 'graphql'
11
+
12
+ import { g } from '../..'
13
+ import { expandVoidHooks } from '../../fields/resolve-hooks'
14
+ import { humanize } from '../../lib/utils'
15
+ import type { GroupInfo } from '../../schema'
16
+ import type {
17
+ ActionMeta,
18
+ BaseFieldTypeInfo,
19
+ BaseItem,
20
+ BaseListTypeInfo,
21
+ CacheHintArgs,
22
+ FieldTypeFunc,
23
+ FindManyArgs,
24
+ GraphQLTypesForList,
25
+ NixxieConfig,
26
+ ListGraphQLTypes,
27
+ ListHooks,
28
+ MaybeFieldFunction,
29
+ NextFieldType,
30
+ } from '../../types'
31
+ import { QueryMode } from '../../types'
32
+ import type { FieldHooks, ResolvedFieldHooks, ResolvedListHooks } from '../../types/config/hooks'
33
+ import type {
34
+ BaseActions,
35
+ MaybeBooleanItemFunctionWithFilter,
36
+ MaybeBooleanSessionFunctionWithFilter,
37
+ MaybeItemActionFunctionWithFilter,
38
+ MaybeItemFieldFunction,
39
+ MaybeItemFieldFunctionWithFilter,
40
+ MaybeSessionFunction,
41
+ MaybeSessionFunctionWithFilter,
42
+ } from '../../types/config/lists'
43
+ import { type GraphQLNames, __getNames } from '../../types/utils'
44
+ import {
45
+ type ResolvedActionAccessControl,
46
+ type ResolvedFieldAccessControl,
47
+ type ResolvedListAccessControl,
48
+ parseFieldAccessControl,
49
+ parseListAccessControl,
50
+ } from './access-control'
51
+ import { assertFieldsValid } from './field-assertions'
52
+ import { outputTypeField } from './queries/output-field'
53
+ import { type ResolvedDBField, resolveRelationships } from './resolve-relationships'
54
+ import { areArraysEqual } from './utils'
55
+
56
+ export type InitialisedAction = {
57
+ actionKey: string
58
+
59
+ access: ResolvedActionAccessControl
60
+ resolve: BaseActions<BaseListTypeInfo>[keyof BaseActions<BaseListTypeInfo>]['resolve']
61
+ graphql: {
62
+ arguments: { name: string; type: string; source: { itemField: string } | null }[]
63
+ names: {
64
+ one: string
65
+ many: string
66
+ args: string
67
+ }
68
+ types: {
69
+ arguments: Record<string, GArg<GInputType>>
70
+ args: GInputObjectType<Record<string, GArg<GInputType>>>
71
+ }
72
+ }
73
+ otel: string
74
+ ui: {
75
+ label: ActionMeta['label']
76
+ icon: ActionMeta['icon']
77
+ messages: ActionMeta['messages']
78
+ itemView: Omit<ActionMeta['itemView'], 'actionMode'> & {
79
+ actionMode: MaybeItemActionFunctionWithFilter<
80
+ 'enabled' | 'disabled' | 'hidden',
81
+ 'disabled' | 'hidden',
82
+ BaseListTypeInfo
83
+ >
84
+ }
85
+ listView: {
86
+ actionMode: MaybeSessionFunctionWithFilter<
87
+ 'enabled' | 'disabled' | 'hidden',
88
+ 'disabled' | 'hidden',
89
+ BaseListTypeInfo
90
+ >
91
+ }
92
+ argSources: Record<string, { itemField: string }>
93
+ }
94
+ }
95
+
96
+ function printGraphQLType(type: GraphQLType): string {
97
+ if (type instanceof GraphQLNonNull) return `${printGraphQLType(type.ofType)}!`
98
+ if (type instanceof GraphQLList) return `[${printGraphQLType(type.ofType)}]`
99
+ return type.name
100
+ }
101
+
102
+ function getArgSources(
103
+ action: NonNullable<NixxieConfig['lists'][string]['actions']>[string]
104
+ ): Record<string, { itemField: string }> {
105
+ return Object.fromEntries(
106
+ Object.entries(action.args ?? {}).flatMap(([arg, value]) => {
107
+ const field = value.ui?.source?.itemField
108
+ if (!field) return []
109
+ return [[arg, { itemField: field }]]
110
+ })
111
+ )
112
+ }
113
+
114
+ export type InitialisedField = {
115
+ fieldKey: string
116
+
117
+ access: ResolvedFieldAccessControl
118
+ dbField: ResolvedDBField
119
+ hooks: ResolvedFieldHooks<BaseListTypeInfo, BaseFieldTypeInfo>
120
+ graphql: {
121
+ isEnabled: {
122
+ read: boolean
123
+ create: boolean
124
+ update: boolean
125
+ filter: MaybeFieldFunction<BaseListTypeInfo>
126
+ orderBy: MaybeFieldFunction<BaseListTypeInfo>
127
+ }
128
+ isNonNull: {
129
+ read: boolean
130
+ create: boolean
131
+ update: boolean
132
+ }
133
+ cacheHint: CacheHint | undefined
134
+ }
135
+ ui: {
136
+ label: string
137
+ description: string
138
+ views: string | null
139
+ createView: {
140
+ fieldMode: MaybeSessionFunctionWithFilter<'edit' | 'hidden', 'hidden', BaseListTypeInfo>
141
+ isRequired: MaybeBooleanSessionFunctionWithFilter<BaseListTypeInfo>
142
+ }
143
+ itemView: {
144
+ fieldMode: MaybeItemFieldFunctionWithFilter<
145
+ 'read' | 'edit' | 'hidden',
146
+ 'read' | 'hidden',
147
+ BaseListTypeInfo,
148
+ BaseFieldTypeInfo
149
+ >
150
+ fieldPosition: MaybeItemFieldFunction<'form' | 'sidebar', BaseListTypeInfo, BaseFieldTypeInfo>
151
+ isRequired: MaybeBooleanItemFunctionWithFilter<BaseListTypeInfo, BaseFieldTypeInfo>
152
+ }
153
+ listView: {
154
+ fieldMode: MaybeSessionFunction<'read' | 'hidden', BaseListTypeInfo>
155
+ }
156
+ }
157
+ } & Pick<
158
+ NextFieldType,
159
+ | 'input'
160
+ | 'output'
161
+ | 'getAdminMeta'
162
+ | 'views'
163
+ | '__nxTelemetryFieldTypeName'
164
+ | 'extraOutputFields'
165
+ | 'unreferencedConcreteInterfaceImplementations'
166
+ >
167
+
168
+ export type InitialisedList = {
169
+ listKey: string
170
+
171
+ access: ResolvedListAccessControl
172
+
173
+ fields: Record<string, InitialisedField>
174
+ actions: InitialisedAction[]
175
+ groups: GroupInfo<BaseListTypeInfo>[]
176
+
177
+ hooks: ResolvedListHooks<BaseListTypeInfo>
178
+
179
+ /** This will include the opposites to one-sided relationships */
180
+ resolvedDbFields: Record<string, ResolvedDBField>
181
+ lists: Record<string, InitialisedList>
182
+
183
+ graphql: {
184
+ types: GraphQLTypesForList
185
+ names: GraphQLNames
186
+ isEnabled: {
187
+ type: boolean
188
+ query: boolean
189
+ create: boolean
190
+ update: boolean
191
+ delete: boolean
192
+ filter: MaybeFieldFunction<BaseListTypeInfo>
193
+ orderBy: MaybeFieldFunction<BaseListTypeInfo>
194
+ }
195
+ }
196
+
197
+ prisma: {
198
+ types: GraphQLNames // TODO: not completely appropriate, but what is used for now
199
+ listKey: string
200
+ mapping: string | undefined
201
+ extendPrismaSchema: ((schema: string) => string) | undefined
202
+ }
203
+
204
+ ui: {
205
+ labels: { label: string; singular: string; plural: string; path: string }
206
+ labelField: string
207
+ searchFields: Set<string>
208
+ searchableFields: Map<string, 'default' | 'insensitive' | null>
209
+ triviallySearchableFields: Set<string>
210
+ }
211
+
212
+ isSingleton: boolean
213
+ cacheHint: ((args: CacheHintArgs<BaseListTypeInfo>) => CacheHint) | undefined
214
+ }
215
+
216
+ function throwIfNotAFilter(x: unknown, listKey: string, fieldKey: string) {
217
+ if (['boolean', 'undefined', 'function'].includes(typeof x)) return
218
+ throw new Error(
219
+ `Configuration option '${listKey}.${fieldKey}' must be either a boolean value or a function. Received '${x}'.`
220
+ )
221
+ }
222
+
223
+ type ListConfigType = NixxieConfig['lists'][string]
224
+ type FieldConfigType = ReturnType<FieldTypeFunc<any>>
225
+ type PartiallyInitialisedList1 = { graphql: { isEnabled: InitialisedList['graphql']['isEnabled'] } }
226
+ type PartiallyInitialisedList2 = Omit<InitialisedList, 'lists' | 'resolvedDbFields'>
227
+
228
+ // TODO: move to defaultLists?
229
+ function getIsEnabled(listKey: string, listConfig: ListConfigType) {
230
+ const omit = listConfig.graphql.omit ?? false
231
+ const { defaultIsFilterable, defaultIsOrderable } = listConfig
232
+
233
+ throwIfNotAFilter(defaultIsFilterable, listKey, 'defaultIsFilterable')
234
+ throwIfNotAFilter(defaultIsOrderable, listKey, 'defaultIsOrderable')
235
+
236
+ if (typeof omit === 'boolean') {
237
+ const notOmit = !omit
238
+ return {
239
+ type: notOmit,
240
+ query: notOmit,
241
+ create: notOmit,
242
+ update: notOmit,
243
+ delete: notOmit,
244
+ filter: notOmit ? defaultIsFilterable : false,
245
+ orderBy: notOmit ? defaultIsOrderable : false,
246
+ }
247
+ }
248
+
249
+ return {
250
+ type: true,
251
+ query: !omit.query,
252
+ create: !omit.create,
253
+ update: !omit.update,
254
+ delete: !omit.delete,
255
+ filter: defaultIsFilterable,
256
+ orderBy: defaultIsOrderable,
257
+ }
258
+ }
259
+
260
+ function getIsEnabledField(
261
+ f: FieldConfigType,
262
+ listKey: string,
263
+ list: PartiallyInitialisedList1,
264
+ lists: Record<string, PartiallyInitialisedList1>
265
+ ) {
266
+ const omit = f.graphql?.omit ?? false
267
+ const {
268
+ isFilterable = list.graphql.isEnabled.filter,
269
+ isOrderable = list.graphql.isEnabled.orderBy,
270
+ } = f
271
+
272
+ // TODO: check types in initConfig
273
+ throwIfNotAFilter(isFilterable, listKey, 'isFilterable')
274
+ throwIfNotAFilter(isOrderable, listKey, 'isOrderable')
275
+
276
+ if (f.dbField.kind === 'relation') {
277
+ if (!lists[f.dbField.list].graphql.isEnabled.type) {
278
+ return {
279
+ type: false,
280
+ read: false,
281
+ create: false,
282
+ update: false,
283
+ filter: false,
284
+ orderBy: false,
285
+ }
286
+ }
287
+ }
288
+
289
+ if (typeof omit === 'boolean') {
290
+ const notOmit = !omit
291
+ return {
292
+ type: notOmit,
293
+ read: notOmit,
294
+ create: notOmit,
295
+ update: notOmit,
296
+ filter: notOmit ? isFilterable : false,
297
+ orderBy: notOmit ? isOrderable : false,
298
+ }
299
+ }
300
+
301
+ return {
302
+ type: true,
303
+ read: !omit.read,
304
+ create: !omit.create,
305
+ update: !omit.update,
306
+ filter: !omit.read ? isFilterable : false, // prevent filtering if read is false
307
+ orderBy: !omit.read ? isOrderable : false, // prevent ordering if read is false
308
+ }
309
+ }
310
+
311
+ function defaultListHooksResolveInput({ resolvedData }: { resolvedData: any }) {
312
+ return resolvedData
313
+ }
314
+
315
+ function parseListHooks(hooks: ListHooks<BaseListTypeInfo>): ResolvedListHooks<BaseListTypeInfo> {
316
+ return {
317
+ resolveInput: {
318
+ create:
319
+ typeof hooks.resolveInput === 'function'
320
+ ? hooks.resolveInput
321
+ : (hooks.resolveInput?.create ?? defaultListHooksResolveInput),
322
+ update:
323
+ typeof hooks.resolveInput === 'function'
324
+ ? hooks.resolveInput
325
+ : (hooks.resolveInput?.update ?? defaultListHooksResolveInput),
326
+ },
327
+ validate: expandVoidHooks(hooks.validate),
328
+ beforeOperation: expandVoidHooks(hooks.beforeOperation),
329
+ afterOperation: expandVoidHooks(hooks.afterOperation),
330
+ }
331
+ }
332
+
333
+ function defaultFieldHooksResolveInput({
334
+ resolvedData,
335
+ fieldKey,
336
+ }: {
337
+ resolvedData: any
338
+ fieldKey: string
339
+ }) {
340
+ return resolvedData[fieldKey]
341
+ }
342
+
343
+ function parseFieldHooks(
344
+ hooks: FieldHooks<BaseListTypeInfo, BaseFieldTypeInfo>
345
+ ): ResolvedFieldHooks<BaseListTypeInfo, BaseFieldTypeInfo> {
346
+ return {
347
+ resolveInput: {
348
+ create:
349
+ typeof hooks.resolveInput === 'function'
350
+ ? hooks.resolveInput
351
+ : (hooks.resolveInput?.create ?? defaultFieldHooksResolveInput),
352
+ update:
353
+ typeof hooks.resolveInput === 'function'
354
+ ? hooks.resolveInput
355
+ : (hooks.resolveInput?.update ?? defaultFieldHooksResolveInput),
356
+ },
357
+ validate: expandVoidHooks(hooks.validate),
358
+ beforeOperation: expandVoidHooks(hooks.beforeOperation),
359
+ afterOperation: expandVoidHooks(hooks.afterOperation),
360
+ }
361
+ }
362
+
363
+ function getListsWithInitialisedFields(
364
+ config: NixxieConfig,
365
+ listsRef: Record<string, InitialisedList>
366
+ ) {
367
+ const {
368
+ lists: listsConfig,
369
+ db: { provider },
370
+ } = config
371
+ const intermediateLists = Object.fromEntries(
372
+ Object.values(config.lists).map(listConfig => [
373
+ listConfig.listKey,
374
+ {
375
+ graphql: {
376
+ isEnabled: getIsEnabled(listConfig.listKey, listConfig),
377
+ },
378
+ },
379
+ ])
380
+ )
381
+
382
+ const listGraphqlTypes: Record<string, ListGraphQLTypes<BaseListTypeInfo>> = {}
383
+
384
+ for (const listConfig of Object.values(listsConfig)) {
385
+ const { listKey } = listConfig
386
+ const {
387
+ graphql: { names },
388
+ } = __getNames(listKey, listConfig)
389
+
390
+ const output = g.object<BaseItem>()({
391
+ name: names.outputTypeName,
392
+ fields: () => {
393
+ const { fields } = listsRef[listKey]
394
+ return {
395
+ ...Object.fromEntries(
396
+ Object.entries(fields).flatMap(([fieldPath, field]) => {
397
+ if (
398
+ !field.output ||
399
+ !field.graphql.isEnabled.read ||
400
+ (field.dbField.kind === 'relation' &&
401
+ !intermediateLists[field.dbField.list].graphql.isEnabled.query)
402
+ ) {
403
+ return []
404
+ }
405
+
406
+ const outputFieldRoot = graphqlForOutputField(field)
407
+ return [
408
+ [fieldPath, outputFieldRoot] as const,
409
+ ...Object.entries(field.extraOutputFields || {}),
410
+ ].map(([outputTypeFieldName, outputField]) => {
411
+ return [
412
+ outputTypeFieldName,
413
+ outputTypeField(
414
+ outputField,
415
+ field.dbField,
416
+ field.graphql?.cacheHint,
417
+ field.access.read,
418
+ listKey,
419
+ fieldPath,
420
+ listsRef
421
+ ),
422
+ ]
423
+ })
424
+ })
425
+ ),
426
+ }
427
+ },
428
+ })
429
+
430
+ const uniqueWhere = g.inputObject({
431
+ name: names.whereUniqueInputName,
432
+ fields: () => {
433
+ const { fields } = listsRef[listKey]
434
+ return {
435
+ ...Object.fromEntries(
436
+ Object.entries(fields).flatMap(([key, field]) => {
437
+ if (
438
+ !field.input?.uniqueWhere?.arg ||
439
+ !field.graphql.isEnabled.read ||
440
+ !field.graphql.isEnabled.filter
441
+ ) {
442
+ return []
443
+ }
444
+
445
+ // only 1-to-1 relationships can have a uniqueWhere filter on relations
446
+ if (field.dbField.kind === 'relation') {
447
+ const remoteField =
448
+ listsRef[field.dbField.list].resolvedDbFields[field.dbField.field]
449
+ if (
450
+ field.dbField.mode === 'one' &&
451
+ remoteField.kind === 'relation' &&
452
+ remoteField.mode === 'one'
453
+ ) {
454
+ return [[key, field.input.uniqueWhere.arg]] as const
455
+ }
456
+ return []
457
+ }
458
+
459
+ return [[key, field.input.uniqueWhere.arg]] as const
460
+ })
461
+ ),
462
+ // this is exactly what the id field will add
463
+ // but this does it more explicitly so that typescript understands
464
+ id: g.arg({ type: g.ID }),
465
+ }
466
+ },
467
+ })
468
+
469
+ const where: GraphQLTypesForList['where'] = g.inputObject({
470
+ name: names.whereInputName,
471
+ fields: () => {
472
+ const { fields } = listsRef[listKey]
473
+ return Object.assign(
474
+ {
475
+ AND: g.arg({ type: g.list(g.nonNull(where)) }),
476
+ OR: g.arg({ type: g.list(g.nonNull(where)) }),
477
+ NOT: g.arg({ type: g.list(g.nonNull(where)) }),
478
+ },
479
+ ...Object.entries(fields).map(
480
+ ([fieldKey, field]) =>
481
+ field.input?.where?.arg &&
482
+ field.graphql.isEnabled.read &&
483
+ field.graphql.isEnabled.filter && { [fieldKey]: field.input?.where?.arg }
484
+ )
485
+ )
486
+ },
487
+ })
488
+
489
+ const create = g.inputObject({
490
+ name: names.createInputName,
491
+ fields: () => {
492
+ const { fields } = listsRef[listKey]
493
+ const ret: Record<keyof typeof fields, GArg<GInputType>> = {}
494
+
495
+ for (const key in fields) {
496
+ const arg = graphqlArgForInputField(fields[key], 'create', listsRef)
497
+ if (!arg) continue
498
+ ret[key] = arg
499
+ }
500
+
501
+ return ret
502
+ },
503
+ })
504
+
505
+ const update = g.inputObject({
506
+ name: names.updateInputName,
507
+ fields: () => {
508
+ const { fields } = listsRef[listKey]
509
+ const ret: Record<keyof typeof fields, GArg<GInputType>> = {}
510
+
511
+ for (const key in fields) {
512
+ const arg = graphqlArgForInputField(fields[key], 'update', listsRef)
513
+ if (!arg) continue
514
+ ret[key] = arg
515
+ }
516
+
517
+ return ret
518
+ },
519
+ })
520
+
521
+ const orderBy = g.inputObject({
522
+ name: names.listOrderName,
523
+ fields: () => {
524
+ const { fields } = listsRef[listKey]
525
+ return Object.fromEntries(
526
+ Object.entries(fields).flatMap(([key, field]) => {
527
+ if (
528
+ !field.input?.orderBy?.arg ||
529
+ !field.graphql.isEnabled.read ||
530
+ !field.graphql.isEnabled.orderBy
531
+ ) {
532
+ return []
533
+ }
534
+ return [[key, field.input.orderBy.arg]] as const
535
+ })
536
+ )
537
+ },
538
+ })
539
+
540
+ let take: any = g.arg({ type: g.Int })
541
+ if (listConfig.graphql?.maxTake !== undefined) {
542
+ take = g.arg({
543
+ type: g.nonNull(g.Int),
544
+ // WARNING: used by queries/resolvers.ts to enforce the limit
545
+ defaultValue: listConfig.graphql.maxTake,
546
+ })
547
+ }
548
+
549
+ const findManyArgs: FindManyArgs = {
550
+ where: g.arg({
551
+ type: g.nonNull(where),
552
+ defaultValue: listConfig.isSingleton ? { id: { equals: '1' } } : {},
553
+ }),
554
+ orderBy: g.arg({
555
+ type: g.nonNull(g.list(g.nonNull(orderBy))),
556
+ defaultValue: [],
557
+ }),
558
+ take,
559
+ skip: g.arg({
560
+ type: g.nonNull(g.Int),
561
+ defaultValue: 0,
562
+ }),
563
+ cursor: g.arg({ type: uniqueWhere }),
564
+ }
565
+
566
+ const relateToOneForCreate = g.inputObject({
567
+ name: names.relateToOneForCreateInputName,
568
+ fields: () => {
569
+ const listRef = listsRef[listKey]
570
+ return {
571
+ ...(listRef.graphql.isEnabled.create && {
572
+ create: g.arg({ type: listRef.graphql.types.create }),
573
+ }),
574
+ connect: g.arg({ type: listRef.graphql.types.uniqueWhere }),
575
+ }
576
+ },
577
+ })
578
+
579
+ const relateToOneForUpdate = g.inputObject({
580
+ name: names.relateToOneForUpdateInputName,
581
+ fields: () => {
582
+ const listRef = listsRef[listKey]
583
+ return {
584
+ ...(listRef.graphql.isEnabled.create && {
585
+ create: g.arg({ type: listRef.graphql.types.create }),
586
+ }),
587
+ connect: g.arg({ type: listRef.graphql.types.uniqueWhere }),
588
+ disconnect: g.arg({ type: g.Boolean }),
589
+ }
590
+ },
591
+ })
592
+
593
+ const relateToManyForCreate = g.inputObject({
594
+ name: names.relateToManyForCreateInputName,
595
+ fields: () => {
596
+ const listRef = listsRef[listKey]
597
+ return {
598
+ ...(listRef.graphql.isEnabled.create && {
599
+ create: g.arg({
600
+ type: g.list(g.nonNull(listRef.graphql.types.create)),
601
+ }),
602
+ }),
603
+ connect: g.arg({
604
+ type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)),
605
+ }),
606
+ set: g.arg({
607
+ type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)),
608
+ }),
609
+ }
610
+ },
611
+ })
612
+
613
+ const relateToManyForUpdate = g.inputObject({
614
+ name: names.relateToManyForUpdateInputName,
615
+ fields: () => {
616
+ const listRef = listsRef[listKey]
617
+ return {
618
+ // WARNING: the order of these fields reflects the order of mutations
619
+ disconnect: g.arg({
620
+ type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)),
621
+ }),
622
+ set: g.arg({
623
+ type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)),
624
+ }),
625
+ ...(listRef.graphql.isEnabled.create && {
626
+ create: g.arg({ type: g.list(g.nonNull(listRef.graphql.types.create)) }),
627
+ }),
628
+ connect: g.arg({ type: g.list(g.nonNull(listRef.graphql.types.uniqueWhere)) }),
629
+ }
630
+ },
631
+ })
632
+
633
+ listGraphqlTypes[listKey] = {
634
+ types: {
635
+ output,
636
+ uniqueWhere,
637
+ where,
638
+ create,
639
+ orderBy,
640
+ update,
641
+ findManyArgs,
642
+ relateTo: {
643
+ one: {
644
+ create: relateToOneForCreate,
645
+ update: relateToOneForUpdate,
646
+ },
647
+ many: {
648
+ where: g.inputObject({
649
+ name: `${listKey}ManyRelationFilter`,
650
+ fields: {
651
+ every: g.arg({ type: where }),
652
+ some: g.arg({ type: where }),
653
+ none: g.arg({ type: where }),
654
+ },
655
+ }),
656
+ create: relateToManyForCreate,
657
+ update: relateToManyForUpdate,
658
+ },
659
+ },
660
+ },
661
+ }
662
+ }
663
+
664
+ const result: Record<string, PartiallyInitialisedList2> = {}
665
+
666
+ for (const listConfig of Object.values(listsConfig)) {
667
+ const { listKey } = listConfig
668
+ const intermediateList = intermediateLists[listKey]
669
+ const resultFields: Record<string, InitialisedField> = {}
670
+ const groups: GroupInfo<BaseListTypeInfo>[] = []
671
+ const fieldKeys = Object.keys(listConfig.fields)
672
+
673
+ const fieldKeysToGroup: Record<string, GroupInfo<BaseListTypeInfo>> = {}
674
+ for (const [idx, [fieldKey, fieldFunc]] of Object.entries(listConfig.fields).entries()) {
675
+ if (fieldKey.startsWith('__group')) {
676
+ const group__ = fieldFunc as any
677
+ if (
678
+ typeof group__ === 'object' &&
679
+ group__ !== null &&
680
+ typeof group__.label === 'string' &&
681
+ (group__.description === null || typeof group__.description === 'string') &&
682
+ Array.isArray(group__.fields) &&
683
+ areArraysEqual(group__.fields, fieldKeys.slice(idx + 1, idx + 1 + group__.fields.length))
684
+ ) {
685
+ groups.push(group__)
686
+ for (const field of group__.fields) {
687
+ fieldKeysToGroup[field] = group__
688
+ }
689
+ continue
690
+ }
691
+ throw new Error(`unexpected value for a group at ${listKey}.${fieldKey}`)
692
+ }
693
+ if (typeof fieldFunc !== 'function') {
694
+ throw new Error(`The field at ${listKey}.${fieldKey} does not provide a function`)
695
+ }
696
+
697
+ const group = fieldKeysToGroup[fieldKey]
698
+ const f = fieldFunc({
699
+ provider,
700
+ lists: listGraphqlTypes,
701
+ listKey,
702
+ fieldKey,
703
+ })
704
+
705
+ const isEnabledField = getIsEnabledField(f, listKey, intermediateList, intermediateLists)
706
+ resultFields[fieldKey] = {
707
+ fieldKey,
708
+
709
+ dbField: f.dbField as ResolvedDBField,
710
+ access: parseFieldAccessControl(f.access),
711
+ hooks: parseFieldHooks(f.hooks ?? {}),
712
+ graphql: {
713
+ cacheHint: f.graphql?.cacheHint,
714
+ isEnabled: isEnabledField,
715
+ isNonNull: {
716
+ read:
717
+ typeof f.graphql?.isNonNull === 'boolean'
718
+ ? f.graphql.isNonNull
719
+ : (f.graphql?.isNonNull?.read ?? false),
720
+ create:
721
+ typeof f.graphql?.isNonNull === 'boolean'
722
+ ? f.graphql.isNonNull
723
+ : (f.graphql?.isNonNull?.create ?? false),
724
+ update:
725
+ typeof f.graphql?.isNonNull === 'boolean'
726
+ ? f.graphql.isNonNull
727
+ : (f.graphql?.isNonNull?.update ?? false),
728
+ },
729
+ },
730
+ ui: {
731
+ label: f.ui?.label || humanize(fieldKey),
732
+ description: f.ui?.description ?? '',
733
+ views: f.ui?.views ?? null,
734
+ createView: {
735
+ isRequired: f.ui?.createView?.isRequired ?? false,
736
+ fieldMode: isEnabledField.create
737
+ ? (f.ui?.createView?.fieldMode ??
738
+ group?.ui?.createView?.defaultFieldMode ??
739
+ listConfig.ui.createView?.defaultFieldMode ??
740
+ 'edit')
741
+ : 'hidden',
742
+ },
743
+
744
+ itemView: {
745
+ isRequired: f.ui?.itemView?.isRequired ?? false,
746
+ fieldPosition: f.ui?.itemView?.fieldPosition ?? 'form',
747
+ fieldMode: isEnabledField.update
748
+ ? (f.ui?.itemView?.fieldMode ??
749
+ group?.ui?.itemView?.defaultFieldMode ??
750
+ listConfig.ui.itemView?.defaultFieldMode ??
751
+ 'edit')
752
+ : isEnabledField.read
753
+ ? (f.ui?.itemView?.fieldMode ??
754
+ group?.ui?.itemView?.defaultFieldMode ??
755
+ listConfig.ui.itemView?.defaultFieldMode ??
756
+ 'read')
757
+ : 'hidden',
758
+ },
759
+
760
+ listView: {
761
+ fieldMode: isEnabledField.read
762
+ ? (f.ui?.listView?.fieldMode ??
763
+ group?.ui?.listView?.defaultFieldMode ??
764
+ listConfig.ui.listView?.defaultFieldMode ??
765
+ 'read')
766
+ : 'hidden',
767
+ },
768
+ },
769
+
770
+ // copy
771
+ __nxTelemetryFieldTypeName: f.__nxTelemetryFieldTypeName,
772
+ extraOutputFields: f.extraOutputFields,
773
+ getAdminMeta: f.getAdminMeta,
774
+ input: { ...f.input },
775
+ output: { ...f.output },
776
+ unreferencedConcreteInterfaceImplementations:
777
+ f.unreferencedConcreteInterfaceImplementations,
778
+ views: f.views,
779
+ }
780
+ }
781
+
782
+ // default labelField to `name`, `label`, or `title`; fallback to `id`
783
+ const labelField =
784
+ listConfig.ui.labelField ??
785
+ (listConfig.fields.label
786
+ ? 'label'
787
+ : listConfig.fields.name
788
+ ? 'name'
789
+ : listConfig.fields.title
790
+ ? 'title'
791
+ : 'id')
792
+
793
+ const searchFields = new Set(listConfig.ui.searchFields ?? [])
794
+ if (searchFields.has('id')) {
795
+ throw new Error(`${listKey}.ui.searchFields cannot include 'id'`)
796
+ }
797
+
798
+ const names = __getNames(listKey, listConfig)
799
+ result[listKey] = {
800
+ access: parseListAccessControl(listConfig.access),
801
+
802
+ fields: resultFields,
803
+ groups,
804
+ actions: [],
805
+
806
+ graphql: {
807
+ types: {
808
+ ...listGraphqlTypes[listKey].types,
809
+ },
810
+ names: {
811
+ ...names.graphql.names,
812
+ },
813
+ ...intermediateList.graphql,
814
+ },
815
+
816
+ prisma: {
817
+ types: {
818
+ ...names.graphql.names,
819
+ },
820
+ listKey: listKey[0].toLowerCase() + listKey.slice(1),
821
+ mapping: listConfig.db.map,
822
+ extendPrismaSchema: listConfig.db.extendPrismaSchema,
823
+ },
824
+
825
+ ui: {
826
+ labels: names.ui.labels,
827
+ labelField,
828
+ searchFields,
829
+ searchableFields: new Map<string, 'default' | 'insensitive' | null>(),
830
+ triviallySearchableFields: new Set<string>(),
831
+ },
832
+ hooks: parseListHooks(listConfig.hooks ?? {}),
833
+ listKey,
834
+ cacheHint: (() => {
835
+ const cacheHint = listConfig.graphql.cacheHint
836
+ if (typeof cacheHint === 'function') return cacheHint
837
+ if (cacheHint !== undefined) return () => cacheHint
838
+ return undefined
839
+ })(),
840
+
841
+ isSingleton: listConfig.isSingleton ?? false,
842
+ }
843
+ }
844
+
845
+ return result
846
+ }
847
+
848
+ function introspectGraphQLTypes(lists: Record<string, InitialisedList>) {
849
+ const namesOfRelationInputs = new Set<string>()
850
+ for (const list of Object.values(lists)) {
851
+ const { types } = list.graphql
852
+ namesOfRelationInputs.add(types.where.name)
853
+ namesOfRelationInputs.add(types.relateTo.many.where.name)
854
+ }
855
+ for (const list of Object.values(lists)) {
856
+ const {
857
+ listKey,
858
+ ui: { searchFields, searchableFields, triviallySearchableFields },
859
+ } = list
860
+
861
+ if (searchFields.has('id')) {
862
+ throw new Error(
863
+ `The ui.searchFields option on the ${listKey} list includes 'id'. Lists can always be searched by an item's id so it must not be specified as a search field`
864
+ )
865
+ }
866
+
867
+ const whereInputFields = list.graphql.types.where.getFields()
868
+ for (const [fieldKey, field] of Object.entries(list.fields)) {
869
+ const filterType = whereInputFields[fieldKey]?.type
870
+ const fieldFilterFields = isInputObjectType(filterType) ? filterType.getFields() : undefined
871
+ const filterTypeName = isInputObjectType(filterType) ? filterType.name : undefined
872
+ if (fieldFilterFields?.contains?.type === GraphQLString) {
873
+ searchableFields.set(
874
+ fieldKey,
875
+ fieldFilterFields?.mode?.type === QueryMode ? 'insensitive' : 'default'
876
+ )
877
+ triviallySearchableFields.add(fieldKey)
878
+ } else if (
879
+ field.dbField.kind === 'relation' &&
880
+ filterTypeName !== undefined &&
881
+ namesOfRelationInputs.has(filterTypeName)
882
+ ) {
883
+ searchableFields.set(fieldKey, 'default')
884
+ }
885
+ }
886
+
887
+ if (searchFields.size === 0) {
888
+ if (searchableFields.has(list.ui.labelField)) {
889
+ searchFields.add(list.ui.labelField)
890
+ }
891
+ }
892
+ }
893
+ }
894
+
895
+ function stripDefaultValue(thing: GArg<GInputType, boolean>) {
896
+ return g.arg({
897
+ ...thing,
898
+ defaultValue: undefined,
899
+ })
900
+ }
901
+
902
+ function graphqlArgForInputField(
903
+ field: InitialisedField,
904
+ operation: 'create' | 'update',
905
+ listsRef: Record<string, InitialisedList>
906
+ ) {
907
+ const input = field.input?.[operation]
908
+ if (!input?.arg || !field.graphql.isEnabled[operation]) return
909
+ if (field.dbField.kind === 'relation') {
910
+ if (!listsRef[field.dbField.list].graphql.isEnabled.type) return
911
+ }
912
+ if (!field.graphql.isNonNull[operation]) return stripDefaultValue(input.arg)
913
+ if (input.arg.type instanceof GNonNull) return input.arg
914
+
915
+ return g.arg({
916
+ ...input.arg,
917
+ type: g.nonNull(input.arg.type),
918
+ })
919
+ }
920
+
921
+ function graphqlForOutputField(field: InitialisedField) {
922
+ const output = field.output
923
+ if (!output || !field.graphql.isEnabled.read) return output
924
+ if (!field.graphql.isNonNull.read) return output
925
+ if (output.type instanceof GNonNull) return output
926
+
927
+ return g.field({
928
+ ...(output as any),
929
+ type: g.nonNull(output.type),
930
+ })
931
+ }
932
+
933
+ function getInitialisedActionGraphql(
934
+ list: Pick<InitialisedList, 'graphql'>,
935
+ listGraphqlNames: { singular: string; plural: string },
936
+ actionKey: string,
937
+ action: NonNullable<NixxieConfig['lists'][string]['actions']>[string]
938
+ ): InitialisedAction['graphql'] {
939
+ const graphqlNames = {
940
+ one: action.graphql?.singular ?? `${actionKey}${listGraphqlNames.singular}`,
941
+ many: action.graphql?.plural ?? `${actionKey}${listGraphqlNames.plural}`,
942
+ }
943
+ const argsName = `${graphqlNames.one[0].toUpperCase()}${graphqlNames.one.slice(1)}Args`
944
+ const argumentFields = Object.fromEntries(
945
+ Object.entries(action.args ?? {}).map(([arg, value]) => [arg, value.graphql])
946
+ )
947
+
948
+ return {
949
+ arguments: Object.entries(argumentFields).map(([name, arg]) => ({
950
+ name,
951
+ type: printGraphQLType(arg.type as unknown as GraphQLType),
952
+ source: getArgSources(action)[name] ?? null,
953
+ })),
954
+ names: {
955
+ ...graphqlNames,
956
+ args: argsName,
957
+ },
958
+ types: {
959
+ arguments: argumentFields,
960
+ args: g.inputObject({
961
+ name: argsName,
962
+ isOneOf: false,
963
+ fields: {
964
+ where: g.arg({
965
+ type: g.nonNull(list.graphql.types.uniqueWhere),
966
+ }),
967
+ ...argumentFields,
968
+ },
969
+ }),
970
+ },
971
+ }
972
+ }
973
+
974
+ export function initialiseLists(config: NixxieConfig): Record<string, InitialisedList> {
975
+ const listsRef: Record<string, InitialisedList> = {}
976
+ let intermediateLists
977
+
978
+ // step 1
979
+ intermediateLists = getListsWithInitialisedFields(config, listsRef)
980
+
981
+ // step 2
982
+ const resolvedDBFieldsForLists = resolveRelationships(intermediateLists)
983
+ intermediateLists = Object.fromEntries(
984
+ Object.values(intermediateLists).map(list => [
985
+ list.listKey,
986
+ {
987
+ ...list,
988
+ resolvedDbFields: resolvedDBFieldsForLists[list.listKey],
989
+ },
990
+ ])
991
+ )
992
+
993
+ // step 3
994
+ intermediateLists = Object.fromEntries(
995
+ Object.values(intermediateLists).map(list => {
996
+ const fields: Record<string, InitialisedField> = {}
997
+
998
+ for (const [fieldKey, field] of Object.entries(list.fields)) {
999
+ fields[fieldKey] = {
1000
+ ...field,
1001
+ dbField: list.resolvedDbFields[fieldKey],
1002
+ }
1003
+ }
1004
+
1005
+ return [list.listKey, { ...list, fields }]
1006
+ })
1007
+ )
1008
+
1009
+ // fixup the GraphQL refs
1010
+ for (const list of Object.values(intermediateLists)) {
1011
+ listsRef[list.listKey] = {
1012
+ ...list,
1013
+ lists: listsRef,
1014
+ }
1015
+ }
1016
+
1017
+ for (const list of Object.values(listsRef)) {
1018
+ const listConfig = config.lists[list.listKey]
1019
+ let hasAnEnabledCreateField = false
1020
+ let hasAnEnabledUpdateField = false
1021
+
1022
+ for (const field of Object.values(list.fields)) {
1023
+ if (field.input?.create?.arg && field.graphql.isEnabled.create) {
1024
+ hasAnEnabledCreateField = true
1025
+ }
1026
+ if (field.input?.update && field.graphql.isEnabled.update) {
1027
+ hasAnEnabledUpdateField = true
1028
+ }
1029
+ }
1030
+
1031
+ if (!hasAnEnabledCreateField) {
1032
+ list.graphql.types.create = g.Empty
1033
+ list.graphql.names.createInputName = 'Empty'
1034
+ }
1035
+
1036
+ if (!hasAnEnabledUpdateField) {
1037
+ list.graphql.types.update = g.Empty
1038
+ list.graphql.names.updateInputName = 'Empty'
1039
+ }
1040
+
1041
+ list.actions = Object.entries(listConfig.actions ?? {}).map(
1042
+ ([actionKey, action]): InitialisedAction => {
1043
+ const { label } = action.ui
1044
+ const graphql = getInitialisedActionGraphql(
1045
+ list,
1046
+ __getNames(list.listKey, listConfig).graphql,
1047
+ actionKey,
1048
+ action
1049
+ )
1050
+
1051
+ return {
1052
+ ...action,
1053
+ actionKey,
1054
+ graphql,
1055
+ otel: humanize(graphql.names.one, true).toLowerCase(),
1056
+ ui: {
1057
+ label,
1058
+ icon: action.ui.icon ?? null,
1059
+ messages: {
1060
+ promptTitle: `{Label} {singular}`,
1061
+ promptTitleMany: `{Label} {count} {singular|plural}`,
1062
+ prompt: `Are you sure you want to {label} {singular} "{itemLabel}"?`,
1063
+ promptMany: `Are you sure you want to {label} {count} {singular|plural}?`,
1064
+ promptConfirmLabel: `Yes, {label} this {singular}`,
1065
+ promptConfirmLabelMany: `Yes, {label} {count} {singular|plural}`,
1066
+ fail: `Could not {label} {singular}`,
1067
+ failMany: `Could not {label} {countFail} {singular|plural}`,
1068
+ success: `Completed {label} action for {singular}`,
1069
+ successMany: `Completed {label} action for {countSuccess} {singular|plural}`,
1070
+ ...action.ui.messages,
1071
+ },
1072
+ itemView: {
1073
+ actionMode: action.ui.itemView?.actionMode ?? 'enabled',
1074
+ navigation: action.ui.itemView?.navigation ?? 'follow',
1075
+ hidePrompt: action.ui.itemView?.hidePrompt ?? false,
1076
+ hideToast: action.ui.itemView?.hideToast ?? false,
1077
+ },
1078
+ listView: {
1079
+ actionMode: action.ui.listView?.actionMode ?? 'enabled',
1080
+ },
1081
+ argSources: getArgSources(action),
1082
+ },
1083
+ }
1084
+ }
1085
+ )
1086
+ }
1087
+
1088
+ // error checking
1089
+ for (const list of Object.values(listsRef)) {
1090
+ assertFieldsValid(list)
1091
+ }
1092
+
1093
+ // do some introspection
1094
+ introspectGraphQLTypes(listsRef)
1095
+
1096
+ return listsRef
1097
+ }