@nixxie-cms/core 1.0.3 → 1.1.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 (202) hide show
  1. package/CHANGES-1.1.md +134 -0
  2. package/context/dist/nixxie-cms-core-context.cjs.js +4 -3
  3. package/context/dist/nixxie-cms-core-context.esm.js +3 -2
  4. package/dist/declarations/src/access.d.ts +2 -2
  5. package/dist/declarations/src/access.d.ts.map +1 -1
  6. package/dist/declarations/src/admin-ui/components/Navigation.d.ts +2 -2
  7. package/dist/declarations/src/admin-ui/components/Navigation.d.ts.map +1 -1
  8. package/dist/declarations/src/admin-ui/context.d.ts +6 -6
  9. package/dist/declarations/src/admin-ui/context.d.ts.map +1 -1
  10. package/dist/declarations/src/admin-ui/utils/Fields.d.ts +3 -3
  11. package/dist/declarations/src/admin-ui/utils/Fields.d.ts.map +1 -1
  12. package/dist/declarations/src/admin-ui/utils/filters.d.ts +5 -5
  13. package/dist/declarations/src/admin-ui/utils/filters.d.ts.map +1 -1
  14. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts +3 -3
  15. package/dist/declarations/src/admin-ui/utils/useCreateItem.d.ts.map +1 -1
  16. package/dist/declarations/src/admin-ui/utils/utils.d.ts +2 -2
  17. package/dist/declarations/src/admin-ui/utils/utils.d.ts.map +1 -1
  18. package/dist/declarations/src/context.d.ts +1 -1
  19. package/dist/declarations/src/context.d.ts.map +1 -1
  20. package/dist/declarations/src/fields/types/bigInt/index.d.ts +3 -3
  21. package/dist/declarations/src/fields/types/bigInt/index.d.ts.map +1 -1
  22. package/dist/declarations/src/fields/types/bytes/index.d.ts +3 -3
  23. package/dist/declarations/src/fields/types/bytes/index.d.ts.map +1 -1
  24. package/dist/declarations/src/fields/types/calendarDay/index.d.ts +3 -3
  25. package/dist/declarations/src/fields/types/calendarDay/index.d.ts.map +1 -1
  26. package/dist/declarations/src/fields/types/checkbox/index.d.ts +3 -3
  27. package/dist/declarations/src/fields/types/checkbox/index.d.ts.map +1 -1
  28. package/dist/declarations/src/fields/types/decimal/index.d.ts +3 -3
  29. package/dist/declarations/src/fields/types/decimal/index.d.ts.map +1 -1
  30. package/dist/declarations/src/fields/types/file/index.d.ts +4 -4
  31. package/dist/declarations/src/fields/types/file/index.d.ts.map +1 -1
  32. package/dist/declarations/src/fields/types/float/index.d.ts +3 -3
  33. package/dist/declarations/src/fields/types/float/index.d.ts.map +1 -1
  34. package/dist/declarations/src/fields/types/image/index.d.ts +4 -4
  35. package/dist/declarations/src/fields/types/image/index.d.ts.map +1 -1
  36. package/dist/declarations/src/fields/types/integer/index.d.ts +3 -3
  37. package/dist/declarations/src/fields/types/integer/index.d.ts.map +1 -1
  38. package/dist/declarations/src/fields/types/json/index.d.ts +3 -3
  39. package/dist/declarations/src/fields/types/json/index.d.ts.map +1 -1
  40. package/dist/declarations/src/fields/types/multiselect/index.d.ts +3 -3
  41. package/dist/declarations/src/fields/types/multiselect/index.d.ts.map +1 -1
  42. package/dist/declarations/src/fields/types/multiselect/views/index.d.ts.map +1 -1
  43. package/dist/declarations/src/fields/types/password/index.d.ts +3 -3
  44. package/dist/declarations/src/fields/types/password/index.d.ts.map +1 -1
  45. package/dist/declarations/src/fields/types/relationship/index.d.ts +8 -8
  46. package/dist/declarations/src/fields/types/relationship/index.d.ts.map +1 -1
  47. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts +3 -3
  48. package/dist/declarations/src/fields/types/relationship/views/ComboboxMany.d.ts.map +1 -1
  49. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts +3 -3
  50. package/dist/declarations/src/fields/types/relationship/views/ComboboxSingle.d.ts.map +1 -1
  51. package/dist/declarations/src/fields/types/relationship/views/index.d.ts +3 -3
  52. package/dist/declarations/src/fields/types/relationship/views/index.d.ts.map +1 -1
  53. package/dist/declarations/src/fields/types/relationship/views/types.d.ts +3 -3
  54. package/dist/declarations/src/fields/types/relationship/views/types.d.ts.map +1 -1
  55. package/dist/declarations/src/fields/types/select/index.d.ts +3 -3
  56. package/dist/declarations/src/fields/types/select/index.d.ts.map +1 -1
  57. package/dist/declarations/src/fields/types/text/index.d.ts +3 -3
  58. package/dist/declarations/src/fields/types/text/index.d.ts.map +1 -1
  59. package/dist/declarations/src/fields/types/timestamp/index.d.ts +3 -3
  60. package/dist/declarations/src/fields/types/timestamp/index.d.ts.map +1 -1
  61. package/dist/declarations/src/fields/types/virtual/index.d.ts +7 -7
  62. package/dist/declarations/src/fields/types/virtual/index.d.ts.map +1 -1
  63. package/dist/declarations/src/helpers.d.ts +249 -13
  64. package/dist/declarations/src/helpers.d.ts.map +1 -1
  65. package/dist/declarations/src/index.d.ts +9 -4
  66. package/dist/declarations/src/index.d.ts.map +1 -1
  67. package/dist/declarations/src/internal-unstable/admin-ui/pages/ListPage/index.d.ts.map +1 -1
  68. package/dist/declarations/src/lib/admin-meta.d.ts +11 -11
  69. package/dist/declarations/src/lib/admin-meta.d.ts.map +1 -1
  70. package/dist/declarations/src/lib/core/access-control.d.ts +18 -18
  71. package/dist/declarations/src/lib/core/access-control.d.ts.map +1 -1
  72. package/dist/declarations/src/lib/core/cascade.d.ts +47 -0
  73. package/dist/declarations/src/lib/core/cascade.d.ts.map +1 -0
  74. package/dist/declarations/src/lib/core/initialise-lists.d.ts +27 -24
  75. package/dist/declarations/src/lib/core/initialise-lists.d.ts.map +1 -1
  76. package/dist/declarations/src/lib/env.d.ts +9 -0
  77. package/dist/declarations/src/lib/env.d.ts.map +1 -0
  78. package/dist/declarations/src/lib/system.d.ts +1 -1
  79. package/dist/declarations/src/lib/system.d.ts.map +1 -1
  80. package/dist/declarations/src/list-features.d.ts +162 -0
  81. package/dist/declarations/src/list-features.d.ts.map +1 -0
  82. package/dist/declarations/src/schema.d.ts +24 -23
  83. package/dist/declarations/src/schema.d.ts.map +1 -1
  84. package/dist/declarations/src/session.d.ts +75 -0
  85. package/dist/declarations/src/session.d.ts.map +1 -1
  86. package/dist/declarations/src/types/admin-meta.d.ts +11 -11
  87. package/dist/declarations/src/types/admin-meta.d.ts.map +1 -1
  88. package/dist/declarations/src/types/config/access-control.d.ts +42 -42
  89. package/dist/declarations/src/types/config/access-control.d.ts.map +1 -1
  90. package/dist/declarations/src/types/config/fields.d.ts +19 -19
  91. package/dist/declarations/src/types/config/fields.d.ts.map +1 -1
  92. package/dist/declarations/src/types/config/hooks.d.ts +131 -131
  93. package/dist/declarations/src/types/config/hooks.d.ts.map +1 -1
  94. package/dist/declarations/src/types/config/index.d.ts +171 -8
  95. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  96. package/dist/declarations/src/types/config/lists.d.ts +146 -108
  97. package/dist/declarations/src/types/config/lists.d.ts.map +1 -1
  98. package/dist/declarations/src/types/context.d.ts +349 -47
  99. package/dist/declarations/src/types/context.d.ts.map +1 -1
  100. package/dist/declarations/src/types/next-fields.d.ts +28 -28
  101. package/dist/declarations/src/types/next-fields.d.ts.map +1 -1
  102. package/dist/declarations/src/types/type-info.d.ts +3 -3
  103. package/dist/declarations/src/types/type-info.d.ts.map +1 -1
  104. package/dist/{express-7559ca2d.esm.js → express-0abbce07.esm.js} +6 -6
  105. package/dist/{express-455ae20c.cjs.js → express-7ca6f76a.cjs.js} +6 -6
  106. package/dist/{index-15c8f81e.esm.js → index-5d8b0b4e.esm.js} +363 -183
  107. package/dist/index-6055753b.cjs.js +393 -0
  108. package/dist/{index-42045902.cjs.js → index-ac29f382.cjs.js} +363 -185
  109. package/dist/index-f1703b7b.esm.js +386 -0
  110. package/dist/nixxie-cms-core.cjs.js +1387 -30
  111. package/dist/nixxie-cms-core.esm.js +1361 -24
  112. package/dist/{non-null-graphql-add6bb3d.cjs.js → non-null-graphql-4a44c122.cjs.js} +1 -1
  113. package/dist/{non-null-graphql-a84ed64d.esm.js → non-null-graphql-8c5feaae.esm.js} +1 -1
  114. package/dist/{resolve-hooks-165a9ce2.cjs.js → resolve-hooks-10a5f84c.cjs.js} +240 -6
  115. package/dist/{resolve-hooks-6813a045.esm.js → resolve-hooks-9e676794.esm.js} +238 -7
  116. package/dist/{system-03e49e4f.esm.js → system-4d2a2648.esm.js} +32 -7
  117. package/dist/{system-a321642d.cjs.js → system-69e1a285.cjs.js} +32 -7
  118. package/fields/dist/nixxie-cms-core-fields.cjs.js +29 -576
  119. package/fields/dist/nixxie-cms-core-fields.esm.js +18 -565
  120. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.cjs.js +4 -2
  121. package/fields/types/bytes/dist/nixxie-cms-core-fields-types-bytes.esm.js +4 -2
  122. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.cjs.js +1 -6
  123. package/fields/types/multiselect/views/dist/nixxie-cms-core-fields-types-multiselect-views.esm.js +1 -6
  124. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.cjs.js +4 -2
  125. package/fields/types/password/dist/nixxie-cms-core-fields-types-password.esm.js +4 -2
  126. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +4 -3
  127. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +4 -3
  128. package/package.json +4 -4
  129. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +4 -3
  130. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +4 -3
  131. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +4 -3
  132. package/scripts/dist/nixxie-cms-core-scripts.esm.js +4 -3
  133. package/session/dist/nixxie-cms-core-session.cjs.js +286 -0
  134. package/session/dist/nixxie-cms-core-session.esm.js +279 -1
  135. package/src/access.ts +25 -25
  136. package/src/admin-ui/admin-meta-graphql.ts +5 -5
  137. package/src/admin-ui/components/CreateButtonLink.tsx +46 -46
  138. package/src/admin-ui/components/Navigation.tsx +3 -3
  139. package/src/admin-ui/context.tsx +6 -6
  140. package/src/admin-ui/utils/Fields.tsx +241 -241
  141. package/src/admin-ui/utils/actionData.ts +36 -36
  142. package/src/admin-ui/utils/filters.ts +148 -148
  143. package/src/admin-ui/utils/useCreateItem.ts +171 -171
  144. package/src/admin-ui/utils/utils.tsx +127 -127
  145. package/src/context.ts +1 -1
  146. package/src/fields/non-null-graphql.ts +115 -115
  147. package/src/fields/types/bigInt/index.ts +6 -6
  148. package/src/fields/types/bytes/index.ts +6 -6
  149. package/src/fields/types/calendarDay/index.ts +18 -19
  150. package/src/fields/types/checkbox/index.ts +6 -6
  151. package/src/fields/types/decimal/index.ts +6 -6
  152. package/src/fields/types/file/index.ts +8 -8
  153. package/src/fields/types/float/index.ts +6 -6
  154. package/src/fields/types/image/index.ts +8 -8
  155. package/src/fields/types/integer/index.ts +6 -6
  156. package/src/fields/types/json/index.ts +5 -5
  157. package/src/fields/types/multiselect/index.ts +7 -7
  158. package/src/fields/types/multiselect/views/index.tsx +149 -151
  159. package/src/fields/types/password/index.ts +6 -6
  160. package/src/fields/types/relationship/index.ts +13 -13
  161. package/src/fields/types/relationship/views/ComboboxMany.tsx +110 -110
  162. package/src/fields/types/relationship/views/ComboboxSingle.tsx +115 -115
  163. package/src/fields/types/relationship/views/ContextualActions.tsx +139 -139
  164. package/src/fields/types/relationship/views/index.tsx +492 -492
  165. package/src/fields/types/relationship/views/types.ts +46 -46
  166. package/src/fields/types/relationship/views/useApolloQuery.ts +185 -185
  167. package/src/fields/types/relationship/views/useFilter.tsx +109 -109
  168. package/src/fields/types/select/index.ts +6 -6
  169. package/src/fields/types/text/index.ts +6 -6
  170. package/src/fields/types/timestamp/index.ts +23 -21
  171. package/src/fields/types/virtual/index.ts +11 -11
  172. package/src/helpers.ts +773 -42
  173. package/src/index.ts +66 -24
  174. package/src/internal-unstable/admin-ui/pages/ItemPage/common.tsx +4 -4
  175. package/src/internal-unstable/admin-ui/pages/ItemPage/index.tsx +5 -5
  176. package/src/internal-unstable/admin-ui/pages/ListPage/index.tsx +8 -8
  177. package/src/lib/admin-meta.ts +369 -369
  178. package/src/lib/context/createContext.ts +5 -0
  179. package/src/lib/core/access-control.ts +434 -434
  180. package/src/lib/core/cascade.ts +236 -0
  181. package/src/lib/core/initialise-lists.ts +49 -33
  182. package/src/lib/core/mutations/index.ts +7 -0
  183. package/src/lib/core/mutations/nested-mutation-many-input-resolvers.ts +145 -145
  184. package/src/lib/core/mutations/nested-mutation-one-input-resolvers.ts +71 -71
  185. package/src/lib/core/queries/output-field.ts +178 -178
  186. package/src/lib/env.ts +50 -0
  187. package/src/lib/id-field.ts +2 -2
  188. package/src/lib/system.ts +221 -207
  189. package/src/lib/typescript-schema-printer.ts +227 -227
  190. package/src/list-features.ts +476 -0
  191. package/src/schema.ts +91 -22
  192. package/src/session.ts +225 -0
  193. package/src/types/admin-meta.ts +218 -218
  194. package/src/types/config/access-control.ts +186 -186
  195. package/src/types/config/fields.ts +96 -96
  196. package/src/types/config/hooks.ts +529 -529
  197. package/src/types/config/index.ts +185 -7
  198. package/src/types/config/lists.ts +606 -565
  199. package/src/types/context.ts +426 -55
  200. package/src/types/next-fields.ts +31 -31
  201. package/src/types/type-info.ts +38 -38
  202. package/src/types/type-tests.ts +21 -21
@@ -1,178 +1,178 @@
1
- import { type CacheHint, maybeCacheControlFromInfo } from '@apollo/cache-control-types'
2
- import DataLoader from 'dataloader'
3
- import type { GraphQLResolveInfo } from 'graphql'
4
-
5
- import { g } from '../../..'
6
- import type {
7
- BaseItem,
8
- BaseListTypeInfo,
9
- FieldAccessControlFunction,
10
- FieldReadItemAccessArgs,
11
- FindManyArgsValue,
12
- NixxieContext,
13
- NextFieldType,
14
- } from '../../../types'
15
- import { getAccessFilters, getOperationAccess, getOperationFieldAccess } from '../access-control'
16
- import type { InitialisedList } from '../initialise-lists'
17
- import type { ResolvedDBField, ResolvedRelationDBField } from '../resolve-relationships'
18
- import { type IdType, getDBFieldKeyForFieldOnMultiField, weakMemoize } from '../utils'
19
- import * as queries from './resolvers'
20
- import { accessControlledFilter } from './resolvers'
21
-
22
- function getRelationVal(
23
- dbField: ResolvedRelationDBField,
24
- id: IdType,
25
- foreignList: InitialisedList,
26
- context: NixxieContext,
27
- info: GraphQLResolveInfo,
28
- fk: IdType | null | undefined
29
- ) {
30
- const oppositeDbField = foreignList.resolvedDbFields[dbField.field]
31
- if (oppositeDbField.kind !== 'relation') throw new Error('failed assert')
32
-
33
- if (dbField.mode === 'many') {
34
- const relationFilter = {
35
- [dbField.field]: oppositeDbField.mode === 'many' ? { some: { id } } : { id },
36
- }
37
- return {
38
- findMany: async (args: FindManyArgsValue) =>
39
- queries.findMany(args, foreignList, context, info, relationFilter),
40
- count: async ({ where }: { where: Record<string, unknown> }) =>
41
- queries.count({ where }, foreignList, context, info, relationFilter),
42
- }
43
- } else {
44
- return async () => {
45
- if (fk === null) {
46
- // If the foreign key is explicitly null, there's no need to anything else,
47
- // since we know the related item doesn't exist.
48
- return null
49
- }
50
- // for one-to-many relationships, the one side always owns the foreign key
51
- // so that means we have the id for the related item and we're fetching it by _its_ id.
52
- // for the a one-to-one relationship though, the id might be on the related item
53
- // so we need to fetch the related item by the id of the current item on the foreign key field
54
- const currentItemOwnsForeignKey = fk !== undefined
55
- return fetchRelatedItem(context)(foreignList)(
56
- currentItemOwnsForeignKey ? 'id' : `${dbField.field}Id`
57
- )(currentItemOwnsForeignKey ? fk : id)
58
- }
59
- }
60
- }
61
-
62
- function memoize<Arg, Return>(cb: (arg: Arg) => Return) {
63
- const cache = new Map<Arg, Return>()
64
- return (arg: Arg) => {
65
- if (!cache.has(arg)) {
66
- const result = cb(arg)
67
- cache.set(arg, result)
68
- }
69
- return cache.get(arg)!
70
- }
71
- }
72
-
73
- const fetchRelatedItem = weakMemoize((context: NixxieContext) =>
74
- weakMemoize((foreignList: InitialisedList) =>
75
- memoize((idFieldKey: string) => {
76
- const relatedItemLoader = new DataLoader(
77
- (keys: readonly IdType[]) => fetchRelatedItems(context, foreignList, idFieldKey, keys),
78
- { cache: false }
79
- )
80
- return (id: IdType) => relatedItemLoader.load(id)
81
- })
82
- )
83
- )
84
-
85
- async function fetchRelatedItems(
86
- context: NixxieContext,
87
- foreignList: InitialisedList,
88
- idFieldKey: string,
89
- toFetch: readonly IdType[]
90
- ) {
91
- const operationAccess = await getOperationAccess(foreignList, context, 'query')
92
- if (!operationAccess) {
93
- return toFetch.map(() => undefined)
94
- }
95
-
96
- const accessFilters = await getAccessFilters(foreignList, context, 'query')
97
- if (accessFilters === false) {
98
- return toFetch.map(() => undefined)
99
- }
100
-
101
- const toFetchUnique = Array.from(new Set(toFetch))
102
- const resolvedWhere = await accessControlledFilter(
103
- foreignList,
104
- context,
105
- { [idFieldKey]: { in: toFetchUnique } },
106
- accessFilters
107
- )
108
-
109
- const results = await context.prisma[foreignList.listKey].findMany({ where: resolvedWhere })
110
- const resultsById = new Map(results.map((x: any) => [x[idFieldKey], x]))
111
- return toFetch.map(id => resultsById.get(id))
112
- }
113
-
114
- function getValueForDBField(
115
- item: BaseItem,
116
- dbField: ResolvedDBField,
117
- id: IdType,
118
- fieldPath: string,
119
- context: NixxieContext,
120
- lists: Record<string, InitialisedList>,
121
- info: GraphQLResolveInfo
122
- ) {
123
- if (dbField.kind === 'multi') {
124
- return Object.fromEntries(
125
- Object.keys(dbField.fields).map(innerDBFieldKey => {
126
- const keyOnDbValue = getDBFieldKeyForFieldOnMultiField(fieldPath, innerDBFieldKey)
127
- return [innerDBFieldKey, item[keyOnDbValue] as any]
128
- })
129
- )
130
- }
131
- if (dbField.kind === 'relation') {
132
- // If we're holding a foreign key value, let's take advantage of that.
133
- let fk: IdType | undefined
134
- if (dbField.mode === 'one' && dbField.foreignIdField.kind !== 'none') {
135
- fk = item[`${fieldPath}Id`] as IdType
136
- }
137
- return getRelationVal(dbField, id, lists[dbField.list], context, info, fk)
138
- } else {
139
- return item[fieldPath] as any
140
- }
141
- }
142
-
143
- export function outputTypeField(
144
- output: NextFieldType['output'],
145
- dbField: ResolvedDBField,
146
- cacheHint: CacheHint | undefined,
147
- access: FieldAccessControlFunction<FieldReadItemAccessArgs<BaseListTypeInfo>>,
148
- listKey: string,
149
- fieldKey: string,
150
- lists: Record<string, InitialisedList>
151
- ) {
152
- const list = lists[listKey]
153
-
154
- return g.field({
155
- type: output.type,
156
- deprecationReason: output.deprecationReason,
157
- description: output.description,
158
- args: output.args,
159
- extensions: output.extensions,
160
- async resolve(item: BaseItem, args, context, info) {
161
- const id = item.id as IdType
162
- const fieldAccess = await getOperationFieldAccess(item, list, fieldKey, context, 'read')
163
- if (!fieldAccess) return null
164
-
165
- // only static cache hints are supported at the field level until a use-case makes it clear what parameters a dynamic hint would take
166
- if (cacheHint && info) {
167
- maybeCacheControlFromInfo(info)?.setCacheHint(cacheHint)
168
- }
169
-
170
- const value = getValueForDBField(item, dbField, id, fieldKey, context, lists, info)
171
- if (output.resolve) {
172
- return output.resolve({ value, item: item }, args, context, info)
173
- } else {
174
- return value
175
- }
176
- },
177
- })
178
- }
1
+ import { type CacheHint, maybeCacheControlFromInfo } from '@apollo/cache-control-types'
2
+ import DataLoader from 'dataloader'
3
+ import type { GraphQLResolveInfo } from 'graphql'
4
+
5
+ import { g } from '../../..'
6
+ import type {
7
+ BaseItem,
8
+ BaseCollectionTypeInfo,
9
+ FieldAccessControlFunction,
10
+ FieldReadItemAccessArgs,
11
+ FindManyArgsValue,
12
+ NixxieContext,
13
+ NextFieldType,
14
+ } from '../../../types'
15
+ import { getAccessFilters, getOperationAccess, getOperationFieldAccess } from '../access-control'
16
+ import type { InitialisedList } from '../initialise-lists'
17
+ import type { ResolvedDBField, ResolvedRelationDBField } from '../resolve-relationships'
18
+ import { type IdType, getDBFieldKeyForFieldOnMultiField, weakMemoize } from '../utils'
19
+ import * as queries from './resolvers'
20
+ import { accessControlledFilter } from './resolvers'
21
+
22
+ function getRelationVal(
23
+ dbField: ResolvedRelationDBField,
24
+ id: IdType,
25
+ foreignList: InitialisedList,
26
+ context: NixxieContext,
27
+ info: GraphQLResolveInfo,
28
+ fk: IdType | null | undefined
29
+ ) {
30
+ const oppositeDbField = foreignList.resolvedDbFields[dbField.field]
31
+ if (oppositeDbField.kind !== 'relation') throw new Error('failed assert')
32
+
33
+ if (dbField.mode === 'many') {
34
+ const relationFilter = {
35
+ [dbField.field]: oppositeDbField.mode === 'many' ? { some: { id } } : { id },
36
+ }
37
+ return {
38
+ findMany: async (args: FindManyArgsValue) =>
39
+ queries.findMany(args, foreignList, context, info, relationFilter),
40
+ count: async ({ where }: { where: Record<string, unknown> }) =>
41
+ queries.count({ where }, foreignList, context, info, relationFilter),
42
+ }
43
+ } else {
44
+ return async () => {
45
+ if (fk === null) {
46
+ // If the foreign key is explicitly null, there's no need to anything else,
47
+ // since we know the related item doesn't exist.
48
+ return null
49
+ }
50
+ // for one-to-many relationships, the one side always owns the foreign key
51
+ // so that means we have the id for the related item and we're fetching it by _its_ id.
52
+ // for the a one-to-one relationship though, the id might be on the related item
53
+ // so we need to fetch the related item by the id of the current item on the foreign key field
54
+ const currentItemOwnsForeignKey = fk !== undefined
55
+ return fetchRelatedItem(context)(foreignList)(
56
+ currentItemOwnsForeignKey ? 'id' : `${dbField.field}Id`
57
+ )(currentItemOwnsForeignKey ? fk : id)
58
+ }
59
+ }
60
+ }
61
+
62
+ function memoize<Arg, Return>(cb: (arg: Arg) => Return) {
63
+ const cache = new Map<Arg, Return>()
64
+ return (arg: Arg) => {
65
+ if (!cache.has(arg)) {
66
+ const result = cb(arg)
67
+ cache.set(arg, result)
68
+ }
69
+ return cache.get(arg)!
70
+ }
71
+ }
72
+
73
+ const fetchRelatedItem = weakMemoize((context: NixxieContext) =>
74
+ weakMemoize((foreignList: InitialisedList) =>
75
+ memoize((idFieldKey: string) => {
76
+ const relatedItemLoader = new DataLoader(
77
+ (keys: readonly IdType[]) => fetchRelatedItems(context, foreignList, idFieldKey, keys),
78
+ { cache: false }
79
+ )
80
+ return (id: IdType) => relatedItemLoader.load(id)
81
+ })
82
+ )
83
+ )
84
+
85
+ async function fetchRelatedItems(
86
+ context: NixxieContext,
87
+ foreignList: InitialisedList,
88
+ idFieldKey: string,
89
+ toFetch: readonly IdType[]
90
+ ) {
91
+ const operationAccess = await getOperationAccess(foreignList, context, 'query')
92
+ if (!operationAccess) {
93
+ return toFetch.map(() => undefined)
94
+ }
95
+
96
+ const accessFilters = await getAccessFilters(foreignList, context, 'query')
97
+ if (accessFilters === false) {
98
+ return toFetch.map(() => undefined)
99
+ }
100
+
101
+ const toFetchUnique = Array.from(new Set(toFetch))
102
+ const resolvedWhere = await accessControlledFilter(
103
+ foreignList,
104
+ context,
105
+ { [idFieldKey]: { in: toFetchUnique } },
106
+ accessFilters
107
+ )
108
+
109
+ const results = await context.prisma[foreignList.listKey].findMany({ where: resolvedWhere })
110
+ const resultsById = new Map(results.map((x: any) => [x[idFieldKey], x]))
111
+ return toFetch.map(id => resultsById.get(id))
112
+ }
113
+
114
+ function getValueForDBField(
115
+ item: BaseItem,
116
+ dbField: ResolvedDBField,
117
+ id: IdType,
118
+ fieldPath: string,
119
+ context: NixxieContext,
120
+ lists: Record<string, InitialisedList>,
121
+ info: GraphQLResolveInfo
122
+ ) {
123
+ if (dbField.kind === 'multi') {
124
+ return Object.fromEntries(
125
+ Object.keys(dbField.fields).map(innerDBFieldKey => {
126
+ const keyOnDbValue = getDBFieldKeyForFieldOnMultiField(fieldPath, innerDBFieldKey)
127
+ return [innerDBFieldKey, item[keyOnDbValue] as any]
128
+ })
129
+ )
130
+ }
131
+ if (dbField.kind === 'relation') {
132
+ // If we're holding a foreign key value, let's take advantage of that.
133
+ let fk: IdType | undefined
134
+ if (dbField.mode === 'one' && dbField.foreignIdField.kind !== 'none') {
135
+ fk = item[`${fieldPath}Id`] as IdType
136
+ }
137
+ return getRelationVal(dbField, id, lists[dbField.list], context, info, fk)
138
+ } else {
139
+ return item[fieldPath] as any
140
+ }
141
+ }
142
+
143
+ export function outputTypeField(
144
+ output: NextFieldType['output'],
145
+ dbField: ResolvedDBField,
146
+ cacheHint: CacheHint | undefined,
147
+ access: FieldAccessControlFunction<FieldReadItemAccessArgs<BaseCollectionTypeInfo>>,
148
+ listKey: string,
149
+ fieldKey: string,
150
+ lists: Record<string, InitialisedList>
151
+ ) {
152
+ const list = lists[listKey]
153
+
154
+ return g.field({
155
+ type: output.type,
156
+ deprecationReason: output.deprecationReason,
157
+ description: output.description,
158
+ args: output.args,
159
+ extensions: output.extensions,
160
+ async resolve(item: BaseItem, args, context, info) {
161
+ const id = item.id as IdType
162
+ const fieldAccess = await getOperationFieldAccess(item, list, fieldKey, context, 'read')
163
+ if (!fieldAccess) return null
164
+
165
+ // only static cache hints are supported at the field level until a use-case makes it clear what parameters a dynamic hint would take
166
+ if (cacheHint && info) {
167
+ maybeCacheControlFromInfo(info)?.setCacheHint(cacheHint)
168
+ }
169
+
170
+ const value = getValueForDBField(item, dbField, id, fieldKey, context, lists, info)
171
+ if (output.resolve) {
172
+ return output.resolve({ value, item: item }, args, context, info)
173
+ } else {
174
+ return value
175
+ }
176
+ },
177
+ })
178
+ }
package/src/lib/env.ts ADDED
@@ -0,0 +1,50 @@
1
+ import type { NixxieEnvSpec, NixxieEnvVarSpec } from '../types'
2
+
3
+ function normalise(spec: NixxieEnvSpec): Record<string, NixxieEnvVarSpec> {
4
+ if (Array.isArray(spec)) {
5
+ return Object.fromEntries((spec as readonly string[]).map(name => [name, {}]))
6
+ }
7
+ return spec as Record<string, NixxieEnvVarSpec>
8
+ }
9
+
10
+ /**
11
+ * Validate `env` (default `process.env`) against a spec. Applies `default`s to the env
12
+ * object for unset variables, then throws one aggregated error listing every missing or
13
+ * malformed variable. Called automatically when the config declares `env`, and exported
14
+ * for standalone use.
15
+ */
16
+ export function validateEnv(
17
+ spec: NixxieEnvSpec,
18
+ env: Record<string, string | undefined> = process.env
19
+ ): void {
20
+ const problems: string[] = []
21
+
22
+ for (const [name, requirement] of Object.entries(normalise(spec))) {
23
+ let value = env[name]
24
+
25
+ if ((value === undefined || value === '') && requirement.default !== undefined) {
26
+ env[name] = requirement.default
27
+ value = requirement.default
28
+ }
29
+
30
+ const describe = requirement.description ? ` — ${requirement.description}` : ''
31
+
32
+ if (value === undefined || value === '') {
33
+ if (requirement.required !== false) {
34
+ problems.push(` • ${name} is not set${describe}`)
35
+ }
36
+ continue
37
+ }
38
+
39
+ if (requirement.pattern && !requirement.pattern.test(value)) {
40
+ problems.push(` • ${name} does not match ${requirement.pattern}${describe}`)
41
+ }
42
+ }
43
+
44
+ if (problems.length) {
45
+ throw new Error(
46
+ `Environment validation failed (${problems.length} problem${problems.length === 1 ? '' : 's'}):\n` +
47
+ problems.join('\n')
48
+ )
49
+ }
50
+ }
@@ -1,7 +1,7 @@
1
1
  import type { GArg, GInputObjectType, InferValueFromArg } from '../graphql-ts'
2
2
  import type { ScalarDBField } from '../types'
3
3
  import {
4
- type BaseListTypeInfo,
4
+ type BaseCollectionTypeInfo,
5
5
  type DatabaseProvider,
6
6
  type FieldTypeFunc,
7
7
  type IdFieldConfig,
@@ -140,7 +140,7 @@ function unpack(
140
140
  throw new Error(`Unknown id type ${kind}`)
141
141
  }
142
142
 
143
- export function idFieldType(config: IdFieldConfig): FieldTypeFunc<BaseListTypeInfo> {
143
+ export function idFieldType(config: IdFieldConfig): FieldTypeFunc<BaseCollectionTypeInfo> {
144
144
  const kind = config.kind
145
145
  const dbFieldOptions = unpack(config)
146
146
  const parseTypeFn = {