@nixxie-cms/core 1.0.3 → 2.0.0

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 (203) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/CHANGES-1.1.md +134 -0
  3. package/context/dist/nixxie-cms-core-context.cjs.js +4 -3
  4. package/context/dist/nixxie-cms-core-context.esm.js +3 -2
  5. package/dist/declarations/src/access.d.ts +2 -2
  6. package/dist/declarations/src/access.d.ts.map +1 -1
  7. package/dist/declarations/src/admin-ui/components/Navigation.d.ts +2 -2
  8. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  9. package/dist/declarations/src/admin-ui/context.d.ts +6 -6
  10. package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
  11. package/dist/declarations/src/admin-ui/utils/Fields.d.ts +3 -3
  12. package/dist/declarations/src/admin-ui/utils/Fields.d.ts.map +1 -1
  13. package/dist/declarations/src/admin-ui/utils/filters.d.ts +5 -5
  14. package/dist/declarations/src/admin-ui/utils/filters.d.ts.map +1 -1
  15. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts +3 -3
  16. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
  17. package/dist/declarations/src/admin-ui/utils/utils.d.ts +2 -2
  18. package/dist/declarations/src/admin-ui/utils/utils.d.ts.map +1 -1
  19. package/dist/declarations/src/context.d.ts +1 -1
  20. package/dist/declarations/src/context.d.ts.map +1 -1
  21. package/dist/declarations/src/fields/types/bigInt/index.d.ts +3 -3
  22. package/dist/declarations/src/fields/types/bigInt/index.d.ts.map +1 -1
  23. package/dist/declarations/src/fields/types/bytes/index.d.ts +3 -3
  24. package/dist/declarations/src/fields/types/bytes/index.d.ts.map +1 -1
  25. package/dist/declarations/src/fields/types/calendarDay/index.d.ts +3 -3
  26. package/dist/declarations/src/fields/types/calendarDay/index.d.ts.map +1 -1
  27. package/dist/declarations/src/fields/types/checkbox/index.d.ts +3 -3
  28. package/dist/declarations/src/fields/types/checkbox/index.d.ts.map +1 -1
  29. package/dist/declarations/src/fields/types/decimal/index.d.ts +3 -3
  30. package/dist/declarations/src/fields/types/decimal/index.d.ts.map +1 -1
  31. package/dist/declarations/src/fields/types/file/index.d.ts +4 -4
  32. package/dist/declarations/src/fields/types/file/index.d.ts.map +1 -1
  33. package/dist/declarations/src/fields/types/float/index.d.ts +3 -3
  34. package/dist/declarations/src/fields/types/float/index.d.ts.map +1 -1
  35. package/dist/declarations/src/fields/types/image/index.d.ts +4 -4
  36. package/dist/declarations/src/fields/types/image/index.d.ts.map +1 -1
  37. package/dist/declarations/src/fields/types/integer/index.d.ts +3 -3
  38. package/dist/declarations/src/fields/types/integer/index.d.ts.map +1 -1
  39. package/dist/declarations/src/fields/types/json/index.d.ts +3 -3
  40. package/dist/declarations/src/fields/types/json/index.d.ts.map +1 -1
  41. package/dist/declarations/src/fields/types/multiselect/index.d.ts +3 -3
  42. package/dist/declarations/src/fields/types/multiselect/index.d.ts.map +1 -1
  43. package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
  44. package/dist/declarations/src/fields/types/password/index.d.ts +3 -3
  45. package/dist/declarations/src/fields/types/password/index.d.ts.map +1 -1
  46. package/dist/declarations/src/fields/types/relationship/index.d.ts +8 -8
  47. package/dist/declarations/src/fields/types/relationship/index.d.ts.map +1 -1
  48. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts +3 -3
  49. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts.map +1 -1
  50. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts +3 -3
  51. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts.map +1 -1
  52. package/dist/declarations/src/fields/types/relationship/views/index.d.ts +3 -3
  53. package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
  54. package/dist/declarations/src/fields/types/relationship/views/types.d.ts +3 -3
  55. package/dist/declarations/src/fields/types/relationship/views/types.d.ts.map +1 -1
  56. package/dist/declarations/src/fields/types/select/index.d.ts +3 -3
  57. package/dist/declarations/src/fields/types/select/index.d.ts.map +1 -1
  58. package/dist/declarations/src/fields/types/text/index.d.ts +3 -3
  59. package/dist/declarations/src/fields/types/text/index.d.ts.map +1 -1
  60. package/dist/declarations/src/fields/types/timestamp/index.d.ts +3 -3
  61. package/dist/declarations/src/fields/types/timestamp/index.d.ts.map +1 -1
  62. package/dist/declarations/src/fields/types/virtual/index.d.ts +7 -7
  63. package/dist/declarations/src/fields/types/virtual/index.d.ts.map +1 -1
  64. package/dist/declarations/src/helpers.d.ts +249 -13
  65. package/dist/declarations/src/helpers.d.ts.map +1 -1
  66. package/dist/declarations/src/index.d.ts +9 -4
  67. package/dist/declarations/src/index.d.ts.map +1 -1
  68. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -1
  69. package/dist/declarations/src/lib/admin-meta.d.ts +11 -11
  70. package/dist/declarations/src/lib/admin-meta.d.ts.map +1 -1
  71. package/dist/declarations/src/lib/core/access-control.d.ts +18 -18
  72. package/dist/declarations/src/lib/core/access-control.d.ts.map +1 -1
  73. package/dist/declarations/src/lib/core/cascade.d.ts +47 -0
  74. package/dist/declarations/src/lib/core/cascade.d.ts.map +1 -0
  75. package/dist/declarations/src/lib/core/initialise-lists.d.ts +27 -24
  76. package/dist/declarations/src/lib/core/initialise-lists.d.ts.map +1 -1
  77. package/dist/declarations/src/lib/env.d.ts +9 -0
  78. package/dist/declarations/src/lib/env.d.ts.map +1 -0
  79. package/dist/declarations/src/lib/system.d.ts +1 -1
  80. package/dist/declarations/src/lib/system.d.ts.map +1 -1
  81. package/dist/declarations/src/list-features.d.ts +162 -0
  82. package/dist/declarations/src/list-features.d.ts.map +1 -0
  83. package/dist/declarations/src/schema.d.ts +24 -23
  84. package/dist/declarations/src/schema.d.ts.map +1 -1
  85. package/dist/declarations/src/session.d.ts +75 -0
  86. package/dist/declarations/src/session.d.ts.map +1 -1
  87. package/dist/declarations/src/types/admin-meta.d.ts +11 -11
  88. package/dist/declarations/src/types/admin-meta.d.ts.map +1 -1
  89. package/dist/declarations/src/types/config/access-control.d.ts +42 -42
  90. package/dist/declarations/src/types/config/access-control.d.ts.map +1 -1
  91. package/dist/declarations/src/types/config/fields.d.ts +19 -19
  92. package/dist/declarations/src/types/config/fields.d.ts.map +1 -1
  93. package/dist/declarations/src/types/config/hooks.d.ts +131 -131
  94. package/dist/declarations/src/types/config/hooks.d.ts.map +1 -1
  95. package/dist/declarations/src/types/config/index.d.ts +190 -8
  96. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  97. package/dist/declarations/src/types/config/lists.d.ts +146 -108
  98. package/dist/declarations/src/types/config/lists.d.ts.map +1 -1
  99. package/dist/declarations/src/types/context.d.ts +507 -47
  100. package/dist/declarations/src/types/context.d.ts.map +1 -1
  101. package/dist/declarations/src/types/next-fields.d.ts +28 -28
  102. package/dist/declarations/src/types/next-fields.d.ts.map +1 -1
  103. package/dist/declarations/src/types/type-info.d.ts +3 -3
  104. package/dist/declarations/src/types/type-info.d.ts.map +1 -1
  105. package/dist/{express-455ae20c.cjs.js → express-84d534c2.cjs.js} +6 -6
  106. package/dist/{express-7559ca2d.esm.js → express-d0a4ce99.esm.js} +6 -6
  107. package/dist/{index-15c8f81e.esm.js → index-5d8b0b4e.esm.js} +363 -183
  108. package/dist/index-6055753b.cjs.js +393 -0
  109. package/dist/{index-42045902.cjs.js → index-ac29f382.cjs.js} +363 -185
  110. package/dist/index-f1703b7b.esm.js +386 -0
  111. package/dist/nixxie-cms-core.cjs.js +1388 -30
  112. package/dist/nixxie-cms-core.esm.js +1362 -24
  113. package/dist/{non-null-graphql-add6bb3d.cjs.js → non-null-graphql-4a44c122.cjs.js} +1 -1
  114. package/dist/{non-null-graphql-a84ed64d.esm.js → non-null-graphql-8c5feaae.esm.js} +1 -1
  115. package/dist/{resolve-hooks-165a9ce2.cjs.js → resolve-hooks-10a5f84c.cjs.js} +240 -6
  116. package/dist/{resolve-hooks-6813a045.esm.js → resolve-hooks-9e676794.esm.js} +238 -7
  117. package/dist/{system-a321642d.cjs.js → system-6b37a5f8.cjs.js} +33 -7
  118. package/dist/{system-03e49e4f.esm.js → system-e591d821.esm.js} +33 -7
  119. package/fields/dist/nixxie-cms-core-fields.cjs.js +29 -576
  120. package/fields/dist/nixxie-cms-core-fields.esm.js +18 -565
  121. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +4 -2
  122. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +4 -2
  123. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +1 -6
  124. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +1 -6
  125. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +4 -2
  126. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +4 -2
  127. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +4 -3
  128. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +4 -3
  129. package/package.json +4 -4
  130. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +4 -3
  131. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +4 -3
  132. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +4 -3
  133. package/scripts/dist/nixxie-cms-core-scripts.esm.js +4 -3
  134. package/session/dist/nixxie-cms-core-session.cjs.js +286 -0
  135. package/session/dist/nixxie-cms-core-session.esm.js +279 -1
  136. package/src/access.ts +25 -25
  137. package/src/admin-ui/admin-meta-graphql.ts +5 -5
  138. package/src/admin-ui/components/CreateButtonLink.tsx +46 -46
  139. package/src/admin-ui/components/Navigation.tsx +3 -3
  140. package/src/admin-ui/context.tsx +6 -6
  141. package/src/admin-ui/utils/Fields.tsx +241 -241
  142. package/src/admin-ui/utils/actionData.ts +36 -36
  143. package/src/admin-ui/utils/filters.ts +148 -148
  144. package/src/admin-ui/utils/useCreateItem.ts +171 -171
  145. package/src/admin-ui/utils/utils.tsx +127 -127
  146. package/src/context.ts +1 -1
  147. package/src/fields/non-null-graphql.ts +115 -115
  148. package/src/fields/types/bigInt/index.ts +6 -6
  149. package/src/fields/types/bytes/index.ts +6 -6
  150. package/src/fields/types/calendarDay/index.ts +18 -19
  151. package/src/fields/types/checkbox/index.ts +6 -6
  152. package/src/fields/types/decimal/index.ts +6 -6
  153. package/src/fields/types/file/index.ts +8 -8
  154. package/src/fields/types/float/index.ts +6 -6
  155. package/src/fields/types/image/index.ts +8 -8
  156. package/src/fields/types/integer/index.ts +6 -6
  157. package/src/fields/types/json/index.ts +5 -5
  158. package/src/fields/types/multiselect/index.ts +7 -7
  159. package/src/fields/types/multiselect/views/index.tsx +149 -151
  160. package/src/fields/types/password/index.ts +6 -6
  161. package/src/fields/types/relationship/index.ts +13 -13
  162. package/src/fields/types/relationship/views/ComboboxMany.tsx +110 -110
  163. package/src/fields/types/relationship/views/ComboboxSingle.tsx +115 -115
  164. package/src/fields/types/relationship/views/ContextualActions.tsx +139 -139
  165. package/src/fields/types/relationship/views/index.tsx +492 -492
  166. package/src/fields/types/relationship/views/types.ts +46 -46
  167. package/src/fields/types/relationship/views/useApolloQuery.ts +185 -185
  168. package/src/fields/types/relationship/views/useFilter.tsx +109 -109
  169. package/src/fields/types/select/index.ts +6 -6
  170. package/src/fields/types/text/index.ts +6 -6
  171. package/src/fields/types/timestamp/index.ts +23 -21
  172. package/src/fields/types/virtual/index.ts +11 -11
  173. package/src/helpers.ts +773 -42
  174. package/src/index.ts +66 -24
  175. package/src/internal-unstable/admin-ui/pages/ItemPage/common.tsx +4 -4
  176. package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +5 -5
  177. package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +8 -8
  178. package/src/lib/admin-meta.ts +369 -369
  179. package/src/lib/context/createContext.ts +6 -0
  180. package/src/lib/core/access-control.ts +434 -434
  181. package/src/lib/core/cascade.ts +236 -0
  182. package/src/lib/core/initialise-lists.ts +49 -33
  183. package/src/lib/core/mutations/index.ts +7 -0
  184. package/src/lib/core/mutations/nested-mutation-many-input-resolvers.ts +145 -145
  185. package/src/lib/core/mutations/nested-mutation-one-input-resolvers.ts +71 -71
  186. package/src/lib/core/queries/output-field.ts +178 -178
  187. package/src/lib/env.ts +50 -0
  188. package/src/lib/id-field.ts +2 -2
  189. package/src/lib/system.ts +221 -207
  190. package/src/lib/typescript-schema-printer.ts +227 -227
  191. package/src/list-features.ts +476 -0
  192. package/src/schema.ts +92 -22
  193. package/src/session.ts +225 -0
  194. package/src/types/admin-meta.ts +218 -218
  195. package/src/types/config/access-control.ts +186 -186
  196. package/src/types/config/fields.ts +96 -96
  197. package/src/types/config/hooks.ts +529 -529
  198. package/src/types/config/index.ts +206 -7
  199. package/src/types/config/lists.ts +606 -565
  200. package/src/types/context.ts +592 -55
  201. package/src/types/next-fields.ts +31 -31
  202. package/src/types/type-info.ts +38 -38
  203. package/src/types/type-tests.ts +21 -21
@@ -1,565 +1,606 @@
1
- import type { CacheHint } from '@apollo/cache-control-types'
2
- import type { allIcons as KeystarIcons } from '@keystar/ui/icon/all'
3
- import type { GArg, InferValueFromArgs } from '@graphql-ts/schema'
4
-
5
- import type { NixxieContext } from '../context'
6
- import type { BaseListTypeInfo } from '../type-info'
7
- import type { MaybePromise } from '../utils'
8
- import type { ListAccessControl, ActionAccessControlFunction } from './access-control'
9
- import type { BaseFields, BaseFieldTypeInfo } from './fields'
10
- import type { ListHooks } from './hooks'
11
-
12
- export type ActionArgumentsConfig = Record<string, GArg<any, any>>
13
-
14
- export type ActionArgsConfig<ListTypeInfo extends BaseListTypeInfo> = Record<
15
- string,
16
- {
17
- graphql: GArg<any, any>
18
- ui?: {
19
- source?: {
20
- itemField: ListTypeInfo['fields']
21
- }
22
- }
23
- }
24
- >
25
-
26
- type ActionArgs<Args extends ActionArgsConfig<any> | undefined> =
27
- Args extends ActionArgsConfig<any>
28
- ? InferValueFromArgs<{ [K in keyof Args]: Args[K]['graphql'] }>
29
- : Record<string, never>
30
-
31
- export type DeclaredAction<ListTypeInfo extends BaseListTypeInfo> = {
32
- ___defineActionsWithActionFunction: true
33
- } & Action<ListTypeInfo, ActionArgsConfig<ListTypeInfo> | undefined>
34
-
35
- export type Action<
36
- ListTypeInfo extends BaseListTypeInfo,
37
- Args extends ActionArgsConfig<ListTypeInfo> | undefined,
38
- > = {
39
- /**
40
- * Controls who or what can use this action
41
- * @see https://www.nixxieinternational.com/guides/auth-and-access-control
42
- */
43
- access: ActionAccessControlFunction<ListTypeInfo>
44
- resolve: (
45
- args: {
46
- listKey: ListTypeInfo['key']
47
- actionKey: ListTypeInfo['actions']
48
- where: ListTypeInfo['inputs']['uniqueWhere']
49
- args: ActionArgs<Args>
50
- },
51
- context: NixxieContext<ListTypeInfo['all']>
52
- ) => MaybePromise<ListTypeInfo['item'] | null>
53
- graphql?: {
54
- /**
55
- * The name of the singular mutation in the GraphQL schema
56
- * @default {action}{list.graphql.singular}
57
- */
58
- singular?: string
59
- /**
60
- * The name of the plural mutation in the GraphQL schema
61
- * @default {action}{list.graphql.plural}
62
- */
63
- plural?: string
64
- /**
65
- * The description added to the GraphQL schema
66
- * @default empty
67
- */
68
- description?: string
69
- }
70
- /**
71
- * Defines what arguments the action should accept as input.
72
- * @experimental This feature is not yet stable.
73
- */
74
- args?: Args
75
- ui: {
76
- /**
77
- * The label used to identify the action in the Admin UI.
78
- */
79
- label: string
80
-
81
- /**
82
- * The icon name used to represent the action in the Admin UI.
83
- * @default null
84
- */
85
- icon?: keyof typeof KeystarIcons | null
86
-
87
- /**
88
- * The style used for the success message in the Admin UI.
89
- * @default 'neutral'
90
- */
91
- // TODO: ? maybe ?
92
- // result?: 'positive' | 'info' | 'neutral' | 'critical'
93
-
94
- /**
95
- * The locale messages used in the Admin UI, with support for the following template tags:
96
- * - {label}: The action label in lowercase
97
- * - {Label}: The action label as-is
98
- * - {singular}: Uses {list}.ui.singular
99
- * - {plural}: Uses {list}.ui.plural
100
- * - {singular|plural}: Uses {list}.ui.singular if {count} is 1, otherwise uses {list}.ui.plural
101
- * - {itemLabel}: The label of the item being acted upon
102
- * - {count}: The number of items being acted upon
103
- * - {countSuccess}: The number of items successfully acted upon
104
- * - {countFail}: The number of items that failed to be acted upon
105
- */
106
- messages?: {
107
- promptTitle?: string
108
- promptTitleMany?: string
109
- prompt?: string
110
- promptMany?: string
111
- promptConfirmLabel?: string
112
- promptConfirmLabelMany?: string
113
- fail?: string
114
- failMany?: string
115
- success?: string
116
- successMany?: string
117
- }
118
-
119
- itemView?: {
120
- /**
121
- * Controls the action mode for this action in the item view in the Admin UI.
122
- * @default 'enabled'
123
- */
124
- actionMode?: MaybeItemActionFunctionWithFilter<
125
- 'enabled' | 'disabled' | 'hidden',
126
- 'disabled' | 'hidden',
127
- ListTypeInfo
128
- >
129
- /**
130
- * Controls the navigation behaviour after the action completes in the item view in the Admin UI.
131
- * @default 'follow'
132
- */
133
- navigation?: 'follow' | 'refetch' | 'return'
134
-
135
- /**
136
- * Controls whether to show a prompt prior to the action in the item view in the Admin UI.
137
- * @default false
138
- */
139
- hidePrompt?: boolean
140
-
141
- /**
142
- * Controls whether to show a toast notification after completing the action in the item view in the Admin UI.
143
- * @default false
144
- */
145
- hideToast?: boolean
146
- }
147
- listView?: {
148
- /**
149
- * Controls the action mode for this action in the list view in the Admin UI.
150
- * @default 'enabled'
151
- */
152
- actionMode?: MaybeSessionFunctionWithFilter<
153
- 'enabled' | 'disabled' | 'hidden',
154
- 'disabled' | 'hidden',
155
- ListTypeInfo
156
- >
157
- }
158
- }
159
- }
160
-
161
- export type BaseActions<ListTypeInfo extends BaseListTypeInfo> = {
162
- /**
163
- * The key of the action, by default this is used to prefix the mutation in the GraphQL schema
164
- */
165
- [action: string]: DeclaredAction<ListTypeInfo>
166
- }
167
-
168
- export type ListConfig<ListTypeInfo extends BaseListTypeInfo> = {
169
- /**
170
- * Controls what data users of the Admin UI and GraphQL can access and change
171
- * @see https://nixxieinternational.com/guides/auth-and-access-control
172
- */
173
- access: ListAccessControl<ListTypeInfo>
174
-
175
- fields: BaseFields<ListTypeInfo>
176
- actions?: BaseActions<ListTypeInfo>
177
-
178
- /** Options for how this list should show in the Admin UI */
179
- ui?: ListAdminUIConfig<ListTypeInfo>
180
-
181
- /**
182
- * Hooks to modify the behaviour of GraphQL operations at certain points
183
- * @see https://nixxieinternational.com/guides/hooks
184
- */
185
- hooks?: ListHooks<ListTypeInfo>
186
-
187
- graphql?: ListGraphQLConfig<ListTypeInfo>
188
-
189
- db?: ListDBConfig
190
-
191
- /**
192
- * Controls if this list is a singleton
193
- * @see https://nixxieinternational.com/docs/config/lists#is-singleton
194
- */
195
- isSingleton?: boolean
196
-
197
- /**
198
- * The default value to use for graphql.isEnabled.filter on all fields for this list
199
- */
200
- defaultIsFilterable?: MaybeFieldFunction<ListTypeInfo>
201
-
202
- /**
203
- * The default value to use for graphql.isEnabled.orderBy on all fields for this list
204
- */
205
- defaultIsOrderable?: MaybeFieldFunction<ListTypeInfo>
206
- }
207
-
208
- export type ListSortDescriptor<Fields extends string> = {
209
- field: 'id' | Fields
210
- direction: 'ASC' | 'DESC'
211
- }
212
-
213
- export type ListAdminUIConfig<ListTypeInfo extends BaseListTypeInfo> = {
214
- /**
215
- * The label used to identify the list in navigation and etc.
216
- * @default listKey.replace(/([a-z])([A-Z])/g, '$1 $2').split(/\s|_|\-/).filter(i => i).map(upcase).join(' ');
217
- */
218
- label?: string
219
-
220
- /**
221
- * The singular form of the list key.
222
- *
223
- * It is used in sentences like `Are you sure you want to delete these {plural}?`
224
- * @default pluralize.singular(label)
225
- */
226
- singular?: string
227
-
228
- /**
229
- * The plural form of the list key.
230
- *
231
- * It is used in sentences like `Are you sure you want to delete this {singular}?`.
232
- * @default pluralize.plural(label)
233
- */
234
- plural?: string
235
-
236
- /**
237
- * The path segment to identify the list in URLs.
238
- *
239
- * It must match the pattern `/^[a-z-_][a-z0-9-_]*$/`.
240
- * @default label.split(' ').join('-').toLowerCase()
241
- */
242
- path?: string
243
-
244
- /**
245
- * The field to use as a label in the Admin UI. If you want to base the label off more than a single field, use a virtual field and reference that field here.
246
- * @default 'label', if it exists, falling back to 'name', then 'title', and finally 'id', which is guaranteed to exist.
247
- */
248
- labelField?: 'id' | Exclude<ListTypeInfo['fields'], number>
249
-
250
- /**
251
- * The fields used by the Admin UI when searching this list.
252
- * It is always possible to search by id and `id` should not be specified in this option.
253
- * @default The `labelField` if it has a string `contains` filter, otherwise none.
254
- */
255
- searchFields?: ListTypeInfo['fields'][]
256
-
257
- /**
258
- * Hides this list from the Admin UI navigation, it only hides the list, you can still navigate directly.
259
- * @default false
260
- */
261
- hideNavigation?: MaybeSessionFunction<boolean, ListTypeInfo>
262
- /**
263
- * Hides the create button in the Admin UI.
264
- * Note that this does **not** disable creating items through the GraphQL API, it only hides the button to create an item for this list in the Admin UI.
265
- * @default false
266
- */
267
- hideCreate?: MaybeSessionFunction<boolean, ListTypeInfo>
268
- /**
269
- * Hides the delete button in the Admin UI.
270
- * Note that this does **not** disable deleting items through the GraphQL API, it only hides the button to delete an item for this list in the Admin UI.
271
- * @default false
272
- */
273
- hideDelete?: MaybeSessionFunction<boolean, ListTypeInfo>
274
- /**
275
- * Configuration specific to the create view in the Admin UI
276
- */
277
- createView?: {
278
- /**
279
- * The default field mode for fields on the create view for this list.
280
- * Specific field modes on a per-field basis via a field's config.
281
- * @default 'edit'
282
- */
283
- defaultFieldMode?: MaybeSessionFunctionWithFilter<'edit' | 'hidden', 'hidden', ListTypeInfo>
284
- }
285
-
286
- /**
287
- * Configuration specific to the item view in the Admin UI
288
- */
289
- itemView?: {
290
- /**
291
- * The default field mode for fields on the item view for this list.
292
- * This controls what people can do for fields
293
- * Specific field modes on a per-field basis via a field's config.
294
- * @default 'edit'
295
- */
296
- defaultFieldMode?: MaybeItemFunctionWithFilter<
297
- 'edit' | 'read' | 'hidden',
298
- 'read' | 'hidden',
299
- ListTypeInfo
300
- >
301
- }
302
-
303
- /**
304
- * Configuration specific to the list view in the Admin UI
305
- */
306
- listView?: {
307
- /**
308
- * The default field mode for fields on the list view for this list.
309
- * Specific field modes on a per-field basis via a field's config.
310
- * @default 'read'
311
- */
312
- defaultFieldMode?: MaybeSessionFunction<'read' | 'hidden', ListTypeInfo>
313
- /**
314
- * The columns(which refer to fields) that should be shown to users of the Admin UI.
315
- * Users of the Admin UI can select different columns to show in the UI.
316
- * @default the first three fields in the list
317
- */
318
- initialColumns?: readonly ('id' | ListTypeInfo['fields'])[]
319
- initialFilter?: MaybeSessionFunction<
320
- Omit<ListTypeInfo['inputs']['where'], 'AND' | 'OR' | 'NOT'>,
321
- ListTypeInfo
322
- >
323
- initialSort?: ListSortDescriptor<ListTypeInfo['fields']>
324
-
325
- // TODO: rename to initialItemsPerPage or initialPageSize?
326
- pageSize?: number // default number of items to display per page on the list screen
327
- }
328
- }
329
-
330
- export type MaybeFieldFunction<ListTypeInfo extends BaseListTypeInfo> =
331
- | boolean
332
- | ((args: {
333
- context: NixxieContext<ListTypeInfo['all']>
334
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
335
- listKey: ListTypeInfo['key']
336
- fieldKey: ListTypeInfo['fields']
337
- }) => MaybePromise<boolean>)
338
-
339
- export type MaybeSessionFunction<T, ListTypeInfo extends BaseListTypeInfo> =
340
- | T
341
- | ((args: {
342
- context: NixxieContext<ListTypeInfo['all']>
343
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
344
- }) => MaybePromise<T>)
345
-
346
- type ConditionalFilterFieldValue<Value> = {
347
- equals?: Value
348
- in?: Value[]
349
- not?: {
350
- equals?: Value
351
- in?: Value[]
352
- }
353
- }
354
-
355
- // this conditional type is to avoid inconvenient type errors
356
- // [] is just a placeholder for something that is never a valid BaseListTypeInfo so
357
- // ListTypeInfo must be `any`
358
- type ConditionalFilterObject<ListTypeInfo extends BaseListTypeInfo> = ListTypeInfo extends []
359
- ? any
360
- : {
361
- AND?: ReadonlyArray<ConditionalFilterObject<ListTypeInfo>>
362
- OR?: ReadonlyArray<ConditionalFilterObject<ListTypeInfo>>
363
- NOT?: ConditionalFilterObject<ListTypeInfo>
364
- } & {
365
- [K in keyof ListTypeInfo['inputs']['update']]?: ConditionalFilterFieldValue<
366
- ListTypeInfo['inputs']['update'][K]
367
- >
368
- }
369
-
370
- export type ConditionalFilterCase<ListTypeInfo extends BaseListTypeInfo> =
371
- | boolean
372
- | ConditionalFilterObject<ListTypeInfo>
373
-
374
- export type ConditionalFilter<
375
- StaticState extends string,
376
- Filterable extends string,
377
- ListTypeInfo extends BaseListTypeInfo,
378
- > = StaticState | { [_ in Filterable]?: ConditionalFilterCase<ListTypeInfo> }
379
-
380
- export type MaybeSessionFunctionWithFilter<
381
- StaticState extends string,
382
- Filterable extends string,
383
- ListTypeInfo extends BaseListTypeInfo,
384
- > =
385
- | ConditionalFilter<StaticState, Filterable, ListTypeInfo>
386
- | ((args: {
387
- context: NixxieContext<ListTypeInfo['all']>
388
- session?: NixxieContext<ListTypeInfo['all']>['session']
389
- }) => MaybePromise<ConditionalFilter<StaticState, Filterable, ListTypeInfo>>)
390
-
391
- export type MaybeBooleanSessionFunctionWithFilter<ListTypeInfo extends BaseListTypeInfo> =
392
- | ConditionalFilterCase<ListTypeInfo>
393
- | ((args: {
394
- context: NixxieContext<ListTypeInfo['all']>
395
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
396
- }) => MaybePromise<ConditionalFilterCase<ListTypeInfo>>)
397
-
398
- export type MaybeBooleanItemFunctionWithFilter<
399
- ListTypeInfo extends BaseListTypeInfo,
400
- FieldTypeInfo extends BaseFieldTypeInfo,
401
- > =
402
- | ConditionalFilterCase<ListTypeInfo>
403
- | ((args: {
404
- context: NixxieContext<ListTypeInfo['all']>
405
- listKey: ListTypeInfo['key']
406
- fieldKey: ListTypeInfo['fields']
407
- item: ListTypeInfo['item'] | null
408
- itemField: FieldTypeInfo['item'] | null
409
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
410
- }) => MaybePromise<ConditionalFilterCase<ListTypeInfo>>)
411
-
412
- export type MaybeItemFieldFunctionWithFilter<
413
- StaticState extends string,
414
- Filterable extends string,
415
- ListTypeInfo extends BaseListTypeInfo,
416
- FieldTypeInfo extends BaseFieldTypeInfo,
417
- > =
418
- | ConditionalFilter<StaticState, Filterable, ListTypeInfo>
419
- | ((args: {
420
- context: NixxieContext<ListTypeInfo['all']>
421
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
422
- listKey: ListTypeInfo['key']
423
- fieldKey: ListTypeInfo['fields']
424
- item: ListTypeInfo['item'] | null
425
- itemField: FieldTypeInfo['item'] | null
426
- }) => MaybePromise<ConditionalFilter<StaticState, Filterable, ListTypeInfo>>)
427
-
428
- export type MaybeItemFieldFunction<
429
- T,
430
- ListTypeInfo extends BaseListTypeInfo,
431
- FieldTypeInfo extends BaseFieldTypeInfo,
432
- > =
433
- | T
434
- | ((args: {
435
- context: NixxieContext<ListTypeInfo['all']>
436
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
437
- listKey: ListTypeInfo['key']
438
- fieldKey: ListTypeInfo['fields']
439
- item: ListTypeInfo['item'] | null
440
- itemField: FieldTypeInfo['item'] | null
441
- }) => MaybePromise<T>)
442
-
443
- export type MaybeItemFunctionWithFilter<
444
- StaticState extends string,
445
- Filterable extends string,
446
- ListTypeInfo extends BaseListTypeInfo,
447
- > =
448
- | ConditionalFilter<StaticState, Filterable, ListTypeInfo>
449
- | ((args: {
450
- context: NixxieContext<ListTypeInfo['all']>
451
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
452
- listKey: ListTypeInfo['key']
453
- item: ListTypeInfo['item'] | null
454
- }) => MaybePromise<ConditionalFilter<StaticState, Filterable, ListTypeInfo>>)
455
-
456
- export type MaybeItemActionFunction<
457
- T extends string,
458
- ListTypeInfo extends BaseListTypeInfo,
459
- > = (args: {
460
- context: NixxieContext<ListTypeInfo['all']>
461
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
462
- listKey: ListTypeInfo['key']
463
- actionKey: ListTypeInfo['actions']
464
- item: ListTypeInfo['item'] | null
465
- }) => MaybePromise<T>
466
-
467
- export type MaybeItemActionFunctionWithFilter<
468
- StaticState extends string,
469
- Filterable extends string,
470
- ListTypeInfo extends BaseListTypeInfo,
471
- > =
472
- | ConditionalFilter<StaticState, Filterable, ListTypeInfo>
473
- | ((args: {
474
- context: NixxieContext<ListTypeInfo['all']>
475
- session?: ListTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
476
- listKey: ListTypeInfo['key']
477
- actionKey: ListTypeInfo['actions']
478
- item: ListTypeInfo['item'] | null
479
- }) => MaybePromise<ConditionalFilter<StaticState, Filterable, ListTypeInfo>>)
480
-
481
- export type ListGraphQLConfig<ListTypeInfo extends BaseListTypeInfo> = {
482
- /**
483
- * The description added to the GraphQL schema
484
- * @default empty
485
- */
486
- description?: string
487
- /**
488
- * The singular form of the list key to use in the generated GraphQL schema.
489
- */
490
- singular?: string
491
- /**
492
- * The plural form of the list key to use in the generated GraphQL schema.
493
- */
494
- plural?: string
495
- /**
496
- * The maximum value for the take parameter when querying this list
497
- */
498
- maxTake?: number
499
- cacheHint?: ((args: CacheHintArgs<ListTypeInfo>) => CacheHint) | CacheHint
500
- // Setting any of these values will remove the corresponding operations from the GraphQL schema.
501
- // Queries:
502
- // 'query': Does item()/items() exist?
503
- // Mutations:
504
- // 'create': Does createItem/createItems exist? Does `create` exist on the RelationshipInput types?
505
- // 'update': Does updateItem/updateItems exist?
506
- // 'delete': Does deleteItem/deleteItems exist?
507
- // If `true`, then everything will be omitted, including the output type. This makes it a DB only list,
508
- // including from the point of view of relationships to this list.
509
- //
510
- // Default: undefined
511
- omit?:
512
- | boolean
513
- | {
514
- query?: boolean
515
- create?: boolean
516
- update?: boolean
517
- delete?: boolean
518
- }
519
- }
520
-
521
- export type CacheHintArgs<ListTypeInfo extends BaseListTypeInfo> =
522
- | {
523
- results: ListTypeInfo['item'][]
524
- operationName?: string
525
- meta: false
526
- }
527
- | {
528
- results: number
529
- operationName?: string
530
- meta: true
531
- }
532
-
533
- // TODO: duplicate, merge with next-fields?
534
- export type IdFieldConfig =
535
- | {
536
- kind: 'random'
537
- type?: 'String'
538
- bytes?: number
539
- encoding?: 'hex' | 'base64url'
540
- }
541
- | { kind: 'string' | 'ulid'; type?: 'String' }
542
- | { kind: 'cuid'; version?: 1 | 2; type?: 'String' }
543
- | { kind: 'uuid'; version?: 4 | 7; type?: 'String' }
544
- | { kind: 'nanoid'; length?: number; type?: 'String' }
545
- | { kind: 'autoincrement'; type?: 'Int' | 'BigInt' }
546
- | { kind: 'number'; type: 'Int' | 'BigInt' }
547
-
548
- export type ListDBConfig = {
549
- /**
550
- * The kind of id to use.
551
- * @default { kind: "cuid" }
552
- */
553
- idField?: IdFieldConfig
554
- /**
555
- * Specifies an alternative name name for the table to use, if you don't want
556
- * the default (derived from the list key)
557
- */
558
- map?: string
559
- /**
560
- * Customise the Prisma Schema for this list. This function is passed the
561
- * Prisma Model for this list and should return a string containing the valid
562
- * Prisma Model definition.
563
- */
564
- extendPrismaSchema?: (schema: string) => string
565
- }
1
+ import type { CacheHint } from '@apollo/cache-control-types'
2
+ import type { allIcons as KeystarIcons } from '@keystar/ui/icon/all'
3
+ import type { GArg, InferValueFromArgs } from '@graphql-ts/schema'
4
+
5
+ import type { NixxieContext } from '../context'
6
+ import type { BaseCollectionTypeInfo } from '../type-info'
7
+ import type { MaybePromise } from '../utils'
8
+ import type { CollectionAccessControl, ActionAccessControlFunction } from './access-control'
9
+ import type { BaseFields, BaseFieldTypeInfo } from './fields'
10
+ import type { CollectionHooks } from './hooks'
11
+
12
+ export type ActionArgumentsConfig = Record<string, GArg<any, any>>
13
+
14
+ export type ActionArgsConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = Record<
15
+ string,
16
+ {
17
+ graphql: GArg<any, any>
18
+ ui?: {
19
+ source?: {
20
+ itemField: CollectionTypeInfo['fields']
21
+ }
22
+ }
23
+ }
24
+ >
25
+
26
+ type ActionArgs<Args extends ActionArgsConfig<any> | undefined> =
27
+ Args extends ActionArgsConfig<any>
28
+ ? InferValueFromArgs<{ [K in keyof Args]: Args[K]['graphql'] }>
29
+ : Record<string, never>
30
+
31
+ export type DeclaredAction<CollectionTypeInfo extends BaseCollectionTypeInfo> = {
32
+ ___defineActionsWithActionFunction: true
33
+ } & Action<CollectionTypeInfo, ActionArgsConfig<CollectionTypeInfo> | undefined>
34
+
35
+ export type Action<
36
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
37
+ Args extends ActionArgsConfig<CollectionTypeInfo> | undefined,
38
+ > = {
39
+ /**
40
+ * Controls who or what can use this action
41
+ * @see https://www.nixxieinternational.com/guides/auth-and-access-control
42
+ */
43
+ access: ActionAccessControlFunction<CollectionTypeInfo>
44
+ resolve: (
45
+ args: {
46
+ listKey: CollectionTypeInfo['key']
47
+ actionKey: CollectionTypeInfo['actions']
48
+ where: CollectionTypeInfo['inputs']['uniqueWhere']
49
+ args: ActionArgs<Args>
50
+ },
51
+ context: NixxieContext<CollectionTypeInfo['all']>
52
+ ) => MaybePromise<CollectionTypeInfo['item'] | null>
53
+ graphql?: {
54
+ /**
55
+ * The name of the singular mutation in the GraphQL schema
56
+ * @default {action}{list.graphql.singular}
57
+ */
58
+ singular?: string
59
+ /**
60
+ * The name of the plural mutation in the GraphQL schema
61
+ * @default {action}{list.graphql.plural}
62
+ */
63
+ plural?: string
64
+ /**
65
+ * The description added to the GraphQL schema
66
+ * @default empty
67
+ */
68
+ description?: string
69
+ }
70
+ /**
71
+ * Defines what arguments the action should accept as input.
72
+ * @experimental This feature is not yet stable.
73
+ */
74
+ args?: Args
75
+ ui: {
76
+ /**
77
+ * The label used to identify the action in the Admin UI.
78
+ */
79
+ label: string
80
+
81
+ /**
82
+ * The icon name used to represent the action in the Admin UI.
83
+ * @default null
84
+ */
85
+ icon?: keyof typeof KeystarIcons | null
86
+
87
+ /**
88
+ * The style used for the success message in the Admin UI.
89
+ * @default 'neutral'
90
+ */
91
+ // TODO: ? maybe ?
92
+ // result?: 'positive' | 'info' | 'neutral' | 'critical'
93
+
94
+ /**
95
+ * The locale messages used in the Admin UI, with support for the following template tags:
96
+ * - {label}: The action label in lowercase
97
+ * - {Label}: The action label as-is
98
+ * - {singular}: Uses {list}.ui.singular
99
+ * - {plural}: Uses {list}.ui.plural
100
+ * - {singular|plural}: Uses {list}.ui.singular if {count} is 1, otherwise uses {list}.ui.plural
101
+ * - {itemLabel}: The label of the item being acted upon
102
+ * - {count}: The number of items being acted upon
103
+ * - {countSuccess}: The number of items successfully acted upon
104
+ * - {countFail}: The number of items that failed to be acted upon
105
+ */
106
+ messages?: {
107
+ promptTitle?: string
108
+ promptTitleMany?: string
109
+ prompt?: string
110
+ promptMany?: string
111
+ promptConfirmLabel?: string
112
+ promptConfirmLabelMany?: string
113
+ fail?: string
114
+ failMany?: string
115
+ success?: string
116
+ successMany?: string
117
+ }
118
+
119
+ itemView?: {
120
+ /**
121
+ * Controls the action mode for this action in the item view in the Admin UI.
122
+ * @default 'enabled'
123
+ */
124
+ actionMode?: MaybeItemActionFunctionWithFilter<
125
+ 'enabled' | 'disabled' | 'hidden',
126
+ 'disabled' | 'hidden',
127
+ CollectionTypeInfo
128
+ >
129
+ /**
130
+ * Controls the navigation behaviour after the action completes in the item view in the Admin UI.
131
+ * @default 'follow'
132
+ */
133
+ navigation?: 'follow' | 'refetch' | 'return'
134
+
135
+ /**
136
+ * Controls whether to show a prompt prior to the action in the item view in the Admin UI.
137
+ * @default false
138
+ */
139
+ hidePrompt?: boolean
140
+
141
+ /**
142
+ * Controls whether to show a toast notification after completing the action in the item view in the Admin UI.
143
+ * @default false
144
+ */
145
+ hideToast?: boolean
146
+ }
147
+ listView?: {
148
+ /**
149
+ * Controls the action mode for this action in the list view in the Admin UI.
150
+ * @default 'enabled'
151
+ */
152
+ actionMode?: MaybeSessionFunctionWithFilter<
153
+ 'enabled' | 'disabled' | 'hidden',
154
+ 'disabled' | 'hidden',
155
+ CollectionTypeInfo
156
+ >
157
+ }
158
+ }
159
+ }
160
+
161
+ export type BaseActions<CollectionTypeInfo extends BaseCollectionTypeInfo> = {
162
+ /**
163
+ * The key of the action, by default this is used to prefix the mutation in the GraphQL schema
164
+ */
165
+ [action: string]: DeclaredAction<CollectionTypeInfo>
166
+ }
167
+
168
+ /** What to do with related records when an item in this collection is deleted. */
169
+ export type CascadeAction = 'delete' | 'setNull' | 'restrict' | 'softDelete'
170
+
171
+ /**
172
+ * A referential-integrity rule enacted when an item of this collection is deleted.
173
+ * Rules run inside the delete pipeline through the normal mutation APIs, so the target
174
+ * collections' own hooks, events and cascade rules all fire (cascades recurse, with
175
+ * cycle protection).
176
+ *
177
+ * @example
178
+ * cascade: [
179
+ * { collection: 'Comment', field: 'post', action: 'delete' },
180
+ * { collection: 'Draft', field: 'original', action: 'setNull' },
181
+ * { collection: 'Order', field: 'product', action: 'restrict' },
182
+ * ]
183
+ */
184
+ export type CascadeRule = {
185
+ /** Target collection holding the related records. */
186
+ collection: string
187
+ /** Relationship field on the target collection that points at this collection. */
188
+ field: string
189
+ /**
190
+ * - `'delete'` — delete the related records (recursing into their own cascade rules)
191
+ * - `'setNull'` — disconnect the relationship, keeping the records
192
+ * - `'restrict'` refuse the delete while related records exist
193
+ * - `'softDelete'` — stamp the records' `softDeleteField` instead of deleting
194
+ * @default 'delete'
195
+ */
196
+ action?: CascadeAction
197
+ /** Set when `field` on the target collection is a many-relationship. @default false */
198
+ many?: boolean
199
+ /** Timestamp field stamped by `'softDelete'`. @default 'deletedAt' */
200
+ softDeleteField?: string
201
+ }
202
+
203
+ export type CollectionConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = {
204
+ /**
205
+ * Controls what data users of the Admin UI and GraphQL can access and change
206
+ * @see https://nixxieinternational.com/guides/auth-and-access-control
207
+ */
208
+ access: CollectionAccessControl<CollectionTypeInfo>
209
+
210
+ /**
211
+ * Referential-integrity rules enacted when items of this collection are deleted.
212
+ * Use `previewDelete()` to compute what a delete would affect before running it.
213
+ */
214
+ cascade?: CascadeRule[]
215
+
216
+ fields: BaseFields<CollectionTypeInfo>
217
+ actions?: BaseActions<CollectionTypeInfo>
218
+
219
+ /** Options for how this list should show in the Admin UI */
220
+ ui?: CollectionAdminUIConfig<CollectionTypeInfo>
221
+
222
+ /**
223
+ * Hooks to modify the behaviour of GraphQL operations at certain points
224
+ * @see https://nixxieinternational.com/guides/hooks
225
+ */
226
+ hooks?: CollectionHooks<CollectionTypeInfo>
227
+
228
+ graphql?: CollectionGraphQLConfig<CollectionTypeInfo>
229
+
230
+ db?: CollectionDBConfig
231
+
232
+ /**
233
+ * Controls if this list is a singleton
234
+ * @see https://nixxieinternational.com/docs/config/lists#is-singleton
235
+ */
236
+ isSingleton?: boolean
237
+
238
+ /**
239
+ * The default value to use for graphql.isEnabled.filter on all fields for this list
240
+ */
241
+ defaultIsFilterable?: MaybeFieldFunction<CollectionTypeInfo>
242
+
243
+ /**
244
+ * The default value to use for graphql.isEnabled.orderBy on all fields for this list
245
+ */
246
+ defaultIsOrderable?: MaybeFieldFunction<CollectionTypeInfo>
247
+ }
248
+
249
+ export type CollectionSortDescriptor<Fields extends string> = {
250
+ field: 'id' | Fields
251
+ direction: 'ASC' | 'DESC'
252
+ }
253
+
254
+ export type CollectionAdminUIConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = {
255
+ /**
256
+ * The label used to identify the list in navigation and etc.
257
+ * @default listKey.replace(/([a-z])([A-Z])/g, '$1 $2').split(/\s|_|\-/).filter(i => i).map(upcase).join(' ');
258
+ */
259
+ label?: string
260
+
261
+ /**
262
+ * The singular form of the list key.
263
+ *
264
+ * It is used in sentences like `Are you sure you want to delete these {plural}?`
265
+ * @default pluralize.singular(label)
266
+ */
267
+ singular?: string
268
+
269
+ /**
270
+ * The plural form of the list key.
271
+ *
272
+ * It is used in sentences like `Are you sure you want to delete this {singular}?`.
273
+ * @default pluralize.plural(label)
274
+ */
275
+ plural?: string
276
+
277
+ /**
278
+ * The path segment to identify the list in URLs.
279
+ *
280
+ * It must match the pattern `/^[a-z-_][a-z0-9-_]*$/`.
281
+ * @default label.split(' ').join('-').toLowerCase()
282
+ */
283
+ path?: string
284
+
285
+ /**
286
+ * The field to use as a label in the Admin UI. If you want to base the label off more than a single field, use a virtual field and reference that field here.
287
+ * @default 'label', if it exists, falling back to 'name', then 'title', and finally 'id', which is guaranteed to exist.
288
+ */
289
+ labelField?: 'id' | Exclude<CollectionTypeInfo['fields'], number>
290
+
291
+ /**
292
+ * The fields used by the Admin UI when searching this list.
293
+ * It is always possible to search by id and `id` should not be specified in this option.
294
+ * @default The `labelField` if it has a string `contains` filter, otherwise none.
295
+ */
296
+ searchFields?: CollectionTypeInfo['fields'][]
297
+
298
+ /**
299
+ * Hides this list from the Admin UI navigation, it only hides the list, you can still navigate directly.
300
+ * @default false
301
+ */
302
+ hideNavigation?: MaybeSessionFunction<boolean, CollectionTypeInfo>
303
+ /**
304
+ * Hides the create button in the Admin UI.
305
+ * Note that this does **not** disable creating items through the GraphQL API, it only hides the button to create an item for this list in the Admin UI.
306
+ * @default false
307
+ */
308
+ hideCreate?: MaybeSessionFunction<boolean, CollectionTypeInfo>
309
+ /**
310
+ * Hides the delete button in the Admin UI.
311
+ * Note that this does **not** disable deleting items through the GraphQL API, it only hides the button to delete an item for this list in the Admin UI.
312
+ * @default false
313
+ */
314
+ hideDelete?: MaybeSessionFunction<boolean, CollectionTypeInfo>
315
+ /**
316
+ * Configuration specific to the create view in the Admin UI
317
+ */
318
+ createView?: {
319
+ /**
320
+ * The default field mode for fields on the create view for this list.
321
+ * Specific field modes on a per-field basis via a field's config.
322
+ * @default 'edit'
323
+ */
324
+ defaultFieldMode?: MaybeSessionFunctionWithFilter<'edit' | 'hidden', 'hidden', CollectionTypeInfo>
325
+ }
326
+
327
+ /**
328
+ * Configuration specific to the item view in the Admin UI
329
+ */
330
+ itemView?: {
331
+ /**
332
+ * The default field mode for fields on the item view for this list.
333
+ * This controls what people can do for fields
334
+ * Specific field modes on a per-field basis via a field's config.
335
+ * @default 'edit'
336
+ */
337
+ defaultFieldMode?: MaybeItemFunctionWithFilter<
338
+ 'edit' | 'read' | 'hidden',
339
+ 'read' | 'hidden',
340
+ CollectionTypeInfo
341
+ >
342
+ }
343
+
344
+ /**
345
+ * Configuration specific to the list view in the Admin UI
346
+ */
347
+ listView?: {
348
+ /**
349
+ * The default field mode for fields on the list view for this list.
350
+ * Specific field modes on a per-field basis via a field's config.
351
+ * @default 'read'
352
+ */
353
+ defaultFieldMode?: MaybeSessionFunction<'read' | 'hidden', CollectionTypeInfo>
354
+ /**
355
+ * The columns(which refer to fields) that should be shown to users of the Admin UI.
356
+ * Users of the Admin UI can select different columns to show in the UI.
357
+ * @default the first three fields in the list
358
+ */
359
+ initialColumns?: readonly ('id' | CollectionTypeInfo['fields'])[]
360
+ initialFilter?: MaybeSessionFunction<
361
+ Omit<CollectionTypeInfo['inputs']['where'], 'AND' | 'OR' | 'NOT'>,
362
+ CollectionTypeInfo
363
+ >
364
+ initialSort?: CollectionSortDescriptor<CollectionTypeInfo['fields']>
365
+
366
+ // TODO: rename to initialItemsPerPage or initialPageSize?
367
+ pageSize?: number // default number of items to display per page on the list screen
368
+ }
369
+ }
370
+
371
+ export type MaybeFieldFunction<CollectionTypeInfo extends BaseCollectionTypeInfo> =
372
+ | boolean
373
+ | ((args: {
374
+ context: NixxieContext<CollectionTypeInfo['all']>
375
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
376
+ listKey: CollectionTypeInfo['key']
377
+ fieldKey: CollectionTypeInfo['fields']
378
+ }) => MaybePromise<boolean>)
379
+
380
+ export type MaybeSessionFunction<T, CollectionTypeInfo extends BaseCollectionTypeInfo> =
381
+ | T
382
+ | ((args: {
383
+ context: NixxieContext<CollectionTypeInfo['all']>
384
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
385
+ }) => MaybePromise<T>)
386
+
387
+ type ConditionalFilterFieldValue<Value> = {
388
+ equals?: Value
389
+ in?: Value[]
390
+ not?: {
391
+ equals?: Value
392
+ in?: Value[]
393
+ }
394
+ }
395
+
396
+ // this conditional type is to avoid inconvenient type errors
397
+ // [] is just a placeholder for something that is never a valid BaseCollectionTypeInfo so
398
+ // CollectionTypeInfo must be `any`
399
+ type ConditionalFilterObject<CollectionTypeInfo extends BaseCollectionTypeInfo> = CollectionTypeInfo extends []
400
+ ? any
401
+ : {
402
+ AND?: ReadonlyArray<ConditionalFilterObject<CollectionTypeInfo>>
403
+ OR?: ReadonlyArray<ConditionalFilterObject<CollectionTypeInfo>>
404
+ NOT?: ConditionalFilterObject<CollectionTypeInfo>
405
+ } & {
406
+ [K in keyof CollectionTypeInfo['inputs']['update']]?: ConditionalFilterFieldValue<
407
+ CollectionTypeInfo['inputs']['update'][K]
408
+ >
409
+ }
410
+
411
+ export type ConditionalFilterCase<CollectionTypeInfo extends BaseCollectionTypeInfo> =
412
+ | boolean
413
+ | ConditionalFilterObject<CollectionTypeInfo>
414
+
415
+ export type ConditionalFilter<
416
+ StaticState extends string,
417
+ Filterable extends string,
418
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
419
+ > = StaticState | { [_ in Filterable]?: ConditionalFilterCase<CollectionTypeInfo> }
420
+
421
+ export type MaybeSessionFunctionWithFilter<
422
+ StaticState extends string,
423
+ Filterable extends string,
424
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
425
+ > =
426
+ | ConditionalFilter<StaticState, Filterable, CollectionTypeInfo>
427
+ | ((args: {
428
+ context: NixxieContext<CollectionTypeInfo['all']>
429
+ session?: NixxieContext<CollectionTypeInfo['all']>['session']
430
+ }) => MaybePromise<ConditionalFilter<StaticState, Filterable, CollectionTypeInfo>>)
431
+
432
+ export type MaybeBooleanSessionFunctionWithFilter<CollectionTypeInfo extends BaseCollectionTypeInfo> =
433
+ | ConditionalFilterCase<CollectionTypeInfo>
434
+ | ((args: {
435
+ context: NixxieContext<CollectionTypeInfo['all']>
436
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
437
+ }) => MaybePromise<ConditionalFilterCase<CollectionTypeInfo>>)
438
+
439
+ export type MaybeBooleanItemFunctionWithFilter<
440
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
441
+ FieldTypeInfo extends BaseFieldTypeInfo,
442
+ > =
443
+ | ConditionalFilterCase<CollectionTypeInfo>
444
+ | ((args: {
445
+ context: NixxieContext<CollectionTypeInfo['all']>
446
+ listKey: CollectionTypeInfo['key']
447
+ fieldKey: CollectionTypeInfo['fields']
448
+ item: CollectionTypeInfo['item'] | null
449
+ itemField: FieldTypeInfo['item'] | null
450
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
451
+ }) => MaybePromise<ConditionalFilterCase<CollectionTypeInfo>>)
452
+
453
+ export type MaybeItemFieldFunctionWithFilter<
454
+ StaticState extends string,
455
+ Filterable extends string,
456
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
457
+ FieldTypeInfo extends BaseFieldTypeInfo,
458
+ > =
459
+ | ConditionalFilter<StaticState, Filterable, CollectionTypeInfo>
460
+ | ((args: {
461
+ context: NixxieContext<CollectionTypeInfo['all']>
462
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
463
+ listKey: CollectionTypeInfo['key']
464
+ fieldKey: CollectionTypeInfo['fields']
465
+ item: CollectionTypeInfo['item'] | null
466
+ itemField: FieldTypeInfo['item'] | null
467
+ }) => MaybePromise<ConditionalFilter<StaticState, Filterable, CollectionTypeInfo>>)
468
+
469
+ export type MaybeItemFieldFunction<
470
+ T,
471
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
472
+ FieldTypeInfo extends BaseFieldTypeInfo,
473
+ > =
474
+ | T
475
+ | ((args: {
476
+ context: NixxieContext<CollectionTypeInfo['all']>
477
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
478
+ listKey: CollectionTypeInfo['key']
479
+ fieldKey: CollectionTypeInfo['fields']
480
+ item: CollectionTypeInfo['item'] | null
481
+ itemField: FieldTypeInfo['item'] | null
482
+ }) => MaybePromise<T>)
483
+
484
+ export type MaybeItemFunctionWithFilter<
485
+ StaticState extends string,
486
+ Filterable extends string,
487
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
488
+ > =
489
+ | ConditionalFilter<StaticState, Filterable, CollectionTypeInfo>
490
+ | ((args: {
491
+ context: NixxieContext<CollectionTypeInfo['all']>
492
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
493
+ listKey: CollectionTypeInfo['key']
494
+ item: CollectionTypeInfo['item'] | null
495
+ }) => MaybePromise<ConditionalFilter<StaticState, Filterable, CollectionTypeInfo>>)
496
+
497
+ export type MaybeItemActionFunction<
498
+ T extends string,
499
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
500
+ > = (args: {
501
+ context: NixxieContext<CollectionTypeInfo['all']>
502
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
503
+ listKey: CollectionTypeInfo['key']
504
+ actionKey: CollectionTypeInfo['actions']
505
+ item: CollectionTypeInfo['item'] | null
506
+ }) => MaybePromise<T>
507
+
508
+ export type MaybeItemActionFunctionWithFilter<
509
+ StaticState extends string,
510
+ Filterable extends string,
511
+ CollectionTypeInfo extends BaseCollectionTypeInfo,
512
+ > =
513
+ | ConditionalFilter<StaticState, Filterable, CollectionTypeInfo>
514
+ | ((args: {
515
+ context: NixxieContext<CollectionTypeInfo['all']>
516
+ session?: CollectionTypeInfo['all']['session'] // TODO: use context.session, remove in breaking change
517
+ listKey: CollectionTypeInfo['key']
518
+ actionKey: CollectionTypeInfo['actions']
519
+ item: CollectionTypeInfo['item'] | null
520
+ }) => MaybePromise<ConditionalFilter<StaticState, Filterable, CollectionTypeInfo>>)
521
+
522
+ export type CollectionGraphQLConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = {
523
+ /**
524
+ * The description added to the GraphQL schema
525
+ * @default empty
526
+ */
527
+ description?: string
528
+ /**
529
+ * The singular form of the list key to use in the generated GraphQL schema.
530
+ */
531
+ singular?: string
532
+ /**
533
+ * The plural form of the list key to use in the generated GraphQL schema.
534
+ */
535
+ plural?: string
536
+ /**
537
+ * The maximum value for the take parameter when querying this list
538
+ */
539
+ maxTake?: number
540
+ cacheHint?: ((args: CacheHintArgs<CollectionTypeInfo>) => CacheHint) | CacheHint
541
+ // Setting any of these values will remove the corresponding operations from the GraphQL schema.
542
+ // Queries:
543
+ // 'query': Does item()/items() exist?
544
+ // Mutations:
545
+ // 'create': Does createItem/createItems exist? Does `create` exist on the RelationshipInput types?
546
+ // 'update': Does updateItem/updateItems exist?
547
+ // 'delete': Does deleteItem/deleteItems exist?
548
+ // If `true`, then everything will be omitted, including the output type. This makes it a DB only list,
549
+ // including from the point of view of relationships to this list.
550
+ //
551
+ // Default: undefined
552
+ omit?:
553
+ | boolean
554
+ | {
555
+ query?: boolean
556
+ create?: boolean
557
+ update?: boolean
558
+ delete?: boolean
559
+ }
560
+ }
561
+
562
+ export type CacheHintArgs<CollectionTypeInfo extends BaseCollectionTypeInfo> =
563
+ | {
564
+ results: CollectionTypeInfo['item'][]
565
+ operationName?: string
566
+ meta: false
567
+ }
568
+ | {
569
+ results: number
570
+ operationName?: string
571
+ meta: true
572
+ }
573
+
574
+ // TODO: duplicate, merge with next-fields?
575
+ export type IdFieldConfig =
576
+ | {
577
+ kind: 'random'
578
+ type?: 'String'
579
+ bytes?: number
580
+ encoding?: 'hex' | 'base64url'
581
+ }
582
+ | { kind: 'string' | 'ulid'; type?: 'String' }
583
+ | { kind: 'cuid'; version?: 1 | 2; type?: 'String' }
584
+ | { kind: 'uuid'; version?: 4 | 7; type?: 'String' }
585
+ | { kind: 'nanoid'; length?: number; type?: 'String' }
586
+ | { kind: 'autoincrement'; type?: 'Int' | 'BigInt' }
587
+ | { kind: 'number'; type: 'Int' | 'BigInt' }
588
+
589
+ export type CollectionDBConfig = {
590
+ /**
591
+ * The kind of id to use.
592
+ * @default { kind: "cuid" }
593
+ */
594
+ idField?: IdFieldConfig
595
+ /**
596
+ * Specifies an alternative name name for the table to use, if you don't want
597
+ * the default (derived from the list key)
598
+ */
599
+ map?: string
600
+ /**
601
+ * Customise the Prisma Schema for this list. This function is passed the
602
+ * Prisma Model for this list and should return a string containing the valid
603
+ * Prisma Model definition.
604
+ */
605
+ extendPrismaSchema?: (schema: string) => string
606
+ }