@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,127 +1,127 @@
1
- import isDeepEqual from 'fast-deep-equal'
2
- import { type FragmentDefinitionNode, type SelectionSetNode, parse } from 'graphql'
3
- import { useMemo } from 'react'
4
-
5
- import type {
6
- BaseListTypeInfo,
7
- ConditionalFilterCase,
8
- FieldController,
9
- FieldMeta,
10
- } from '../../types'
11
- import { serializeItemForConditionalFilters, testFilter } from './filters'
12
-
13
- function extractRootFields(selectedFields: Set<string>, selectionSet: SelectionSetNode) {
14
- selectionSet.selections.forEach(selection => {
15
- if (selection.kind === 'Field') {
16
- selectedFields.add(selection.alias ? selection.alias.value : selection.name.value)
17
- }
18
- if (selection.kind === 'InlineFragment') {
19
- extractRootFields(selectedFields, selection.selectionSet)
20
- }
21
- // FragmentSpread will never happen for the use cases of getRootFieldsFromSelection
22
- })
23
- }
24
-
25
- export function getRootGraphQLFieldsFromFieldController(controller: FieldController<any, any>) {
26
- const ast = parse(`fragment X on Y { id, ${controller.graphqlSelection} }`)
27
- const selectedFields = new Set<string>()
28
- const fragmentNode = ast.definitions[0] as FragmentDefinitionNode
29
- extractRootFields(selectedFields, fragmentNode.selectionSet)
30
- return [...selectedFields]
31
- }
32
-
33
- export function useInvalidFields(
34
- fields: Record<string, FieldMeta>,
35
- item: Record<string, unknown>,
36
- isRequireds: Record<string, ConditionalFilterCase<BaseListTypeInfo>>
37
- ): ReadonlySet<string> {
38
- return useMemo(() => {
39
- const invalidFields = new Set<string>()
40
- const serialized = serializeItemForConditionalFilters(fields, item)
41
-
42
- for (const fieldKey in item) {
43
- const validateFn = fields[fieldKey]?.controller?.validate
44
- if (!validateFn) continue
45
- const isRequired = testFilter(isRequireds[fieldKey] ?? false, serialized)
46
- const fieldValue = item[fieldKey]
47
- const valid = validateFn(fieldValue, { isRequired })
48
- if (valid) continue
49
-
50
- invalidFields.add(fieldKey)
51
- }
52
- return invalidFields
53
- }, [fields, isRequireds, item])
54
- }
55
-
56
- export function makeDefaultValueState(fields: Record<string, FieldMeta>) {
57
- const result: Record<string, unknown> = {}
58
- for (const fieldKey in fields) {
59
- result[fieldKey] = fields[fieldKey].controller.defaultValue
60
- }
61
- return result
62
- }
63
-
64
- export function deserializeItemToValue(
65
- fields: Record<string, FieldMeta>,
66
- item: Record<string, unknown | null>
67
- ) {
68
- const result: Record<string, unknown | null> = {}
69
- for (const [fieldKey, field] of Object.entries(fields)) {
70
- const itemForField: Record<string, unknown> = {}
71
- for (const graphqlField of getRootGraphQLFieldsFromFieldController(field.controller)) {
72
- itemForField[graphqlField] = item?.[graphqlField] ?? null
73
- }
74
- result[fieldKey] = field.controller.deserialize(itemForField)
75
- }
76
- return result
77
- }
78
-
79
- export function serializeValueToOperationItem(
80
- operation: 'create' | 'update',
81
- fields: Record<string, FieldMeta>,
82
- value: Record<string, unknown>,
83
- valueReference?: Record<string, unknown>
84
- ) {
85
- const result: Record<string, unknown> = {}
86
- valueReference ??= makeDefaultValueState(fields)
87
-
88
- for (const fieldKey in fields) {
89
- const field = fields[fieldKey]
90
- const fieldValue = value[fieldKey]
91
- const fieldValueSerialized = field.controller.serialize(fieldValue)
92
- const fieldValueReference = valueReference[fieldKey]
93
-
94
- const isAlwaysRequired = field.isNonNull.includes(operation)
95
- if (!isAlwaysRequired) {
96
- if (isDeepEqual(fieldValueSerialized, field.controller.serialize(fieldValueReference)))
97
- continue
98
- }
99
-
100
- Object.assign(result, fieldValueSerialized)
101
- }
102
-
103
- return result
104
- }
105
-
106
- export function useHasChanges(
107
- operation: 'create' | 'update',
108
- fields: Record<string, FieldMeta>,
109
- value: Record<string, unknown>,
110
- valueReference?: Record<string, unknown>
111
- ) {
112
- return useMemo(() => {
113
- valueReference ??= makeDefaultValueState(fields)
114
-
115
- for (const fieldKey in fields) {
116
- const field = fields[fieldKey]
117
- const fieldValue = value[fieldKey]
118
- const fieldValueSerialized = field.controller.serialize(fieldValue)
119
- const fieldValueReference = valueReference[fieldKey]
120
-
121
- if (!isDeepEqual(fieldValueSerialized, field.controller.serialize(fieldValueReference)))
122
- return true
123
- }
124
-
125
- return false
126
- }, [fields, operation, value, valueReference])
127
- }
1
+ import isDeepEqual from 'fast-deep-equal'
2
+ import { type FragmentDefinitionNode, type SelectionSetNode, parse } from 'graphql'
3
+ import { useMemo } from 'react'
4
+
5
+ import type {
6
+ BaseCollectionTypeInfo,
7
+ ConditionalFilterCase,
8
+ FieldController,
9
+ FieldMeta,
10
+ } from '../../types'
11
+ import { serializeItemForConditionalFilters, testFilter } from './filters'
12
+
13
+ function extractRootFields(selectedFields: Set<string>, selectionSet: SelectionSetNode) {
14
+ selectionSet.selections.forEach(selection => {
15
+ if (selection.kind === 'Field') {
16
+ selectedFields.add(selection.alias ? selection.alias.value : selection.name.value)
17
+ }
18
+ if (selection.kind === 'InlineFragment') {
19
+ extractRootFields(selectedFields, selection.selectionSet)
20
+ }
21
+ // FragmentSpread will never happen for the use cases of getRootFieldsFromSelection
22
+ })
23
+ }
24
+
25
+ export function getRootGraphQLFieldsFromFieldController(controller: FieldController<any, any>) {
26
+ const ast = parse(`fragment X on Y { id, ${controller.graphqlSelection} }`)
27
+ const selectedFields = new Set<string>()
28
+ const fragmentNode = ast.definitions[0] as FragmentDefinitionNode
29
+ extractRootFields(selectedFields, fragmentNode.selectionSet)
30
+ return [...selectedFields]
31
+ }
32
+
33
+ export function useInvalidFields(
34
+ fields: Record<string, FieldMeta>,
35
+ item: Record<string, unknown>,
36
+ isRequireds: Record<string, ConditionalFilterCase<BaseCollectionTypeInfo>>
37
+ ): ReadonlySet<string> {
38
+ return useMemo(() => {
39
+ const invalidFields = new Set<string>()
40
+ const serialized = serializeItemForConditionalFilters(fields, item)
41
+
42
+ for (const fieldKey in item) {
43
+ const validateFn = fields[fieldKey]?.controller?.validate
44
+ if (!validateFn) continue
45
+ const isRequired = testFilter(isRequireds[fieldKey] ?? false, serialized)
46
+ const fieldValue = item[fieldKey]
47
+ const valid = validateFn(fieldValue, { isRequired })
48
+ if (valid) continue
49
+
50
+ invalidFields.add(fieldKey)
51
+ }
52
+ return invalidFields
53
+ }, [fields, isRequireds, item])
54
+ }
55
+
56
+ export function makeDefaultValueState(fields: Record<string, FieldMeta>) {
57
+ const result: Record<string, unknown> = {}
58
+ for (const fieldKey in fields) {
59
+ result[fieldKey] = fields[fieldKey].controller.defaultValue
60
+ }
61
+ return result
62
+ }
63
+
64
+ export function deserializeItemToValue(
65
+ fields: Record<string, FieldMeta>,
66
+ item: Record<string, unknown | null>
67
+ ) {
68
+ const result: Record<string, unknown | null> = {}
69
+ for (const [fieldKey, field] of Object.entries(fields)) {
70
+ const itemForField: Record<string, unknown> = {}
71
+ for (const graphqlField of getRootGraphQLFieldsFromFieldController(field.controller)) {
72
+ itemForField[graphqlField] = item?.[graphqlField] ?? null
73
+ }
74
+ result[fieldKey] = field.controller.deserialize(itemForField)
75
+ }
76
+ return result
77
+ }
78
+
79
+ export function serializeValueToOperationItem(
80
+ operation: 'create' | 'update',
81
+ fields: Record<string, FieldMeta>,
82
+ value: Record<string, unknown>,
83
+ valueReference?: Record<string, unknown>
84
+ ) {
85
+ const result: Record<string, unknown> = {}
86
+ valueReference ??= makeDefaultValueState(fields)
87
+
88
+ for (const fieldKey in fields) {
89
+ const field = fields[fieldKey]
90
+ const fieldValue = value[fieldKey]
91
+ const fieldValueSerialized = field.controller.serialize(fieldValue)
92
+ const fieldValueReference = valueReference[fieldKey]
93
+
94
+ const isAlwaysRequired = field.isNonNull.includes(operation)
95
+ if (!isAlwaysRequired) {
96
+ if (isDeepEqual(fieldValueSerialized, field.controller.serialize(fieldValueReference)))
97
+ continue
98
+ }
99
+
100
+ Object.assign(result, fieldValueSerialized)
101
+ }
102
+
103
+ return result
104
+ }
105
+
106
+ export function useHasChanges(
107
+ operation: 'create' | 'update',
108
+ fields: Record<string, FieldMeta>,
109
+ value: Record<string, unknown>,
110
+ valueReference?: Record<string, unknown>
111
+ ) {
112
+ return useMemo(() => {
113
+ valueReference ??= makeDefaultValueState(fields)
114
+
115
+ for (const fieldKey in fields) {
116
+ const field = fields[fieldKey]
117
+ const fieldValue = value[fieldKey]
118
+ const fieldValueSerialized = field.controller.serialize(fieldValue)
119
+ const fieldValueReference = valueReference[fieldKey]
120
+
121
+ if (!isDeepEqual(fieldValueSerialized, field.controller.serialize(fieldValueReference)))
122
+ return true
123
+ }
124
+
125
+ return false
126
+ }, [fields, operation, value, valueReference])
127
+ }
package/src/context.ts CHANGED
@@ -1 +1 @@
1
- export { getContext } from './lib/system'
1
+ export { createNixxieContext } from './lib/system'
@@ -1,115 +1,115 @@
1
- import type { BaseFieldTypeInfo, CommonFieldConfig } from '../types'
2
- import { type BaseListTypeInfo, type FieldData } from '../types'
3
- import type { FieldHooks } from '../types/config/hooks'
4
- import { type ValidateFieldHook } from '../types/config/hooks'
5
- import { merge } from './resolve-hooks'
6
-
7
- export function resolveDbNullable(
8
- validation: undefined | { isRequired?: boolean },
9
- db: undefined | { isNullable?: boolean }
10
- ): boolean {
11
- if (db?.isNullable === false) return false
12
- if (db?.isNullable === undefined && validation?.isRequired) {
13
- return false
14
- }
15
- return true
16
- }
17
-
18
- export function makeValidateHook<ListTypeInfo extends BaseListTypeInfo>(
19
- meta: FieldData<ListTypeInfo>,
20
- config: {
21
- label?: string
22
- db?: {
23
- isNullable?: boolean
24
- }
25
- graphql?: {
26
- isNonNull?:
27
- | boolean
28
- | {
29
- read?: boolean
30
- }
31
- }
32
- validation?: {
33
- isRequired?: boolean
34
- [key: string]: unknown
35
- }
36
- hooks?: {
37
- validate?: FieldHooks<ListTypeInfo, any>['validate']
38
- }
39
- },
40
- f?: ValidateFieldHook<ListTypeInfo, 'create' | 'update' | 'delete', BaseFieldTypeInfo>,
41
- hasPrismaDefaultValue: boolean = false
42
- ) {
43
- const dbNullable = resolveDbNullable(config.validation, config.db)
44
- const mode = dbNullable ? ('optional' as const) : ('required' as const)
45
- const valueRequired = (config.validation?.isRequired || !dbNullable) && !hasPrismaDefaultValue
46
-
47
- assertReadIsNonNullAllowed(meta, config, dbNullable)
48
- const addValidation = config.db?.isNullable === false || config.validation?.isRequired
49
- if (addValidation) {
50
- const validate = async function (args) {
51
- const { operation, addValidationError, resolvedData } = args
52
-
53
- if (valueRequired) {
54
- const value = resolvedData?.[meta.fieldKey]
55
- if (
56
- (operation === 'create' && value === undefined) ||
57
- ((operation === 'create' || operation === 'update') && value === null)
58
- ) {
59
- addValidationError(`missing value`)
60
- }
61
- }
62
-
63
- await f?.(args)
64
- } satisfies ValidateFieldHook<ListTypeInfo, 'create' | 'update' | 'delete', BaseFieldTypeInfo>
65
-
66
- return {
67
- mode,
68
- validate: merge(validate, config.hooks?.validate),
69
- }
70
- }
71
-
72
- return {
73
- mode,
74
- validate: merge(f, config.hooks?.validate),
75
- }
76
- }
77
-
78
- export function assertReadIsNonNullAllowed<ListTypeInfo extends BaseListTypeInfo>(
79
- meta: FieldData<ListTypeInfo>,
80
- config: {
81
- graphql?: {
82
- isNonNull?:
83
- | boolean
84
- | {
85
- read?: boolean
86
- }
87
- }
88
- },
89
- dbNullable: boolean
90
- ) {
91
- if (!dbNullable) return
92
- if (!config.graphql?.isNonNull) return
93
- if (typeof config.graphql?.isNonNull === 'object' && !config.graphql.isNonNull.read) return
94
-
95
- throw new Error(
96
- `${meta.listKey}.${meta.fieldKey} sets graphql.isNonNull.read: true, but not validation.isRequired: true (or db.isNullable: false)\n` +
97
- `Set validation.isRequired: true, or db.isNullable: false, or graphql.isNonNull.read: false`
98
- )
99
- }
100
-
101
- export function defaultIsRequired(config: CommonFieldConfig<any, any>, isRequired: boolean) {
102
- return {
103
- ui: {
104
- ...config.ui,
105
- createView: {
106
- ...config.ui?.createView,
107
- isRequired: config.ui?.createView?.isRequired ?? isRequired,
108
- },
109
- itemView: {
110
- ...config.ui?.itemView,
111
- isRequired: config.ui?.itemView?.isRequired ?? isRequired,
112
- },
113
- },
114
- }
115
- }
1
+ import type { BaseFieldTypeInfo, CommonFieldConfig } from '../types'
2
+ import { type BaseCollectionTypeInfo, type FieldData } from '../types'
3
+ import type { FieldHooks } from '../types/config/hooks'
4
+ import { type ValidateFieldHook } from '../types/config/hooks'
5
+ import { merge } from './resolve-hooks'
6
+
7
+ export function resolveDbNullable(
8
+ validation: undefined | { isRequired?: boolean },
9
+ db: undefined | { isNullable?: boolean }
10
+ ): boolean {
11
+ if (db?.isNullable === false) return false
12
+ if (db?.isNullable === undefined && validation?.isRequired) {
13
+ return false
14
+ }
15
+ return true
16
+ }
17
+
18
+ export function makeValidateHook<CollectionTypeInfo extends BaseCollectionTypeInfo>(
19
+ meta: FieldData<CollectionTypeInfo>,
20
+ config: {
21
+ label?: string
22
+ db?: {
23
+ isNullable?: boolean
24
+ }
25
+ graphql?: {
26
+ isNonNull?:
27
+ | boolean
28
+ | {
29
+ read?: boolean
30
+ }
31
+ }
32
+ validation?: {
33
+ isRequired?: boolean
34
+ [key: string]: unknown
35
+ }
36
+ hooks?: {
37
+ validate?: FieldHooks<CollectionTypeInfo, any>['validate']
38
+ }
39
+ },
40
+ f?: ValidateFieldHook<CollectionTypeInfo, 'create' | 'update' | 'delete', BaseFieldTypeInfo>,
41
+ hasPrismaDefaultValue: boolean = false
42
+ ) {
43
+ const dbNullable = resolveDbNullable(config.validation, config.db)
44
+ const mode = dbNullable ? ('optional' as const) : ('required' as const)
45
+ const valueRequired = (config.validation?.isRequired || !dbNullable) && !hasPrismaDefaultValue
46
+
47
+ assertReadIsNonNullAllowed(meta, config, dbNullable)
48
+ const addValidation = config.db?.isNullable === false || config.validation?.isRequired
49
+ if (addValidation) {
50
+ const validate = async function (args) {
51
+ const { operation, addValidationError, resolvedData } = args
52
+
53
+ if (valueRequired) {
54
+ const value = resolvedData?.[meta.fieldKey]
55
+ if (
56
+ (operation === 'create' && value === undefined) ||
57
+ ((operation === 'create' || operation === 'update') && value === null)
58
+ ) {
59
+ addValidationError(`missing value`)
60
+ }
61
+ }
62
+
63
+ await f?.(args)
64
+ } satisfies ValidateFieldHook<CollectionTypeInfo, 'create' | 'update' | 'delete', BaseFieldTypeInfo>
65
+
66
+ return {
67
+ mode,
68
+ validate: merge(validate, config.hooks?.validate),
69
+ }
70
+ }
71
+
72
+ return {
73
+ mode,
74
+ validate: merge(f, config.hooks?.validate),
75
+ }
76
+ }
77
+
78
+ export function assertReadIsNonNullAllowed<CollectionTypeInfo extends BaseCollectionTypeInfo>(
79
+ meta: FieldData<CollectionTypeInfo>,
80
+ config: {
81
+ graphql?: {
82
+ isNonNull?:
83
+ | boolean
84
+ | {
85
+ read?: boolean
86
+ }
87
+ }
88
+ },
89
+ dbNullable: boolean
90
+ ) {
91
+ if (!dbNullable) return
92
+ if (!config.graphql?.isNonNull) return
93
+ if (typeof config.graphql?.isNonNull === 'object' && !config.graphql.isNonNull.read) return
94
+
95
+ throw new Error(
96
+ `${meta.listKey}.${meta.fieldKey} sets graphql.isNonNull.read: true, but not validation.isRequired: true (or db.isNullable: false)\n` +
97
+ `Set validation.isRequired: true, or db.isNullable: false, or graphql.isNonNull.read: false`
98
+ )
99
+ }
100
+
101
+ export function defaultIsRequired(config: CommonFieldConfig<any, any>, isRequired: boolean) {
102
+ return {
103
+ ui: {
104
+ ...config.ui,
105
+ createView: {
106
+ ...config.ui?.createView,
107
+ isRequired: config.ui?.createView?.isRequired ?? isRequired,
108
+ },
109
+ itemView: {
110
+ ...config.ui?.itemView,
111
+ isRequired: config.ui?.itemView?.isRequired ?? isRequired,
112
+ },
113
+ },
114
+ }
115
+ }
@@ -1,6 +1,6 @@
1
1
  import type { SimpleFieldTypeInfo } from '../../../types'
2
2
  import {
3
- type BaseListTypeInfo,
3
+ type BaseCollectionTypeInfo,
4
4
  type CommonFieldConfig,
5
5
  type FieldTypeFunc,
6
6
  fieldType,
@@ -11,8 +11,8 @@ import { filters } from '../../filters'
11
11
  import { resolveDbNullable, makeValidateHook, defaultIsRequired } from '../../non-null-graphql'
12
12
  import type { controller } from './views'
13
13
 
14
- export type BigIntFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
15
- ListTypeInfo,
14
+ export type BigIntFieldConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = CommonFieldConfig<
15
+ CollectionTypeInfo,
16
16
  SimpleFieldTypeInfo<'BigInt'>
17
17
  > & {
18
18
  isIndexed?: boolean | 'unique'
@@ -34,9 +34,9 @@ const MAX_INT = 9223372036854775807n
34
34
  const MIN_INT = -9223372036854775808n
35
35
 
36
36
  // TODO: https://github.com/Nixxie/keystatic/blob/main/design-system/pkg/src/number-field/NumberField.tsx
37
- export function bigInt<ListTypeInfo extends BaseListTypeInfo>(
38
- config: BigIntFieldConfig<ListTypeInfo> = {}
39
- ): FieldTypeFunc<ListTypeInfo> {
37
+ export function bigInt<CollectionTypeInfo extends BaseCollectionTypeInfo>(
38
+ config: BigIntFieldConfig<CollectionTypeInfo> = {}
39
+ ): FieldTypeFunc<CollectionTypeInfo> {
40
40
  const { defaultValue: defaultValue_ = null, isIndexed, validation = {} } = config
41
41
 
42
42
  const { isRequired = false, min, max } = validation
@@ -1,5 +1,5 @@
1
1
  import {
2
- type BaseListTypeInfo,
2
+ type BaseCollectionTypeInfo,
3
3
  type CommonFieldConfig,
4
4
  type FieldTypeFunc,
5
5
  fieldType,
@@ -33,8 +33,8 @@ export type FieldTypeInfo = {
33
33
  }
34
34
  }
35
35
 
36
- export type BytesFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
37
- ListTypeInfo,
36
+ export type BytesFieldConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = CommonFieldConfig<
37
+ CollectionTypeInfo,
38
38
  FieldTypeInfo
39
39
  > & {
40
40
  isIndexed?: boolean | 'unique'
@@ -73,9 +73,9 @@ export type TextFieldMeta = {
73
73
  defaultValue: string | null
74
74
  }
75
75
 
76
- export function bytes<ListTypeInfo extends BaseListTypeInfo>(
77
- config: BytesFieldConfig<ListTypeInfo> = {}
78
- ): FieldTypeFunc<ListTypeInfo> {
76
+ export function bytes<CollectionTypeInfo extends BaseCollectionTypeInfo>(
77
+ config: BytesFieldConfig<CollectionTypeInfo> = {}
78
+ ): FieldTypeFunc<CollectionTypeInfo> {
79
79
  const { defaultValue = null, isIndexed, validation = {} } = config
80
80
 
81
81
  const scalar = config.graphql?.scalar ?? g.Hex
@@ -8,7 +8,7 @@ import type {
8
8
 
9
9
  import { g } from '../../..'
10
10
  import {
11
- type BaseListTypeInfo,
11
+ type BaseCollectionTypeInfo,
12
12
  type CommonFieldConfig,
13
13
  type FieldTypeFunc,
14
14
  type SimpleFieldTypeInfo,
@@ -19,25 +19,23 @@ import { filters } from '../../filters'
19
19
  import { defaultIsRequired, makeValidateHook } from '../../non-null-graphql'
20
20
  import type { CalendarDayFieldMeta } from './views'
21
21
 
22
- export type CalendarDayFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
23
- ListTypeInfo,
24
- SimpleFieldTypeInfo<'String' | 'DateTime'>
25
- > & {
26
- isIndexed?: boolean | 'unique'
27
- validation?: {
28
- isRequired?: boolean
29
- }
30
- defaultValue?: string
31
- db?: {
32
- isNullable?: boolean
33
- extendPrismaSchema?: (field: string) => string
34
- map?: string
22
+ export type CalendarDayFieldConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> =
23
+ CommonFieldConfig<CollectionTypeInfo, SimpleFieldTypeInfo<'String' | 'DateTime'>> & {
24
+ isIndexed?: boolean | 'unique'
25
+ validation?: {
26
+ isRequired?: boolean
27
+ }
28
+ defaultValue?: string
29
+ db?: {
30
+ isNullable?: boolean
31
+ extendPrismaSchema?: (field: string) => string
32
+ map?: string
33
+ }
35
34
  }
36
- }
37
35
 
38
- export function calendarDay<ListTypeInfo extends BaseListTypeInfo>(
39
- config: CalendarDayFieldConfig<ListTypeInfo> = {}
40
- ): FieldTypeFunc<ListTypeInfo> {
36
+ export function calendarDay<CollectionTypeInfo extends BaseCollectionTypeInfo>(
37
+ config: CalendarDayFieldConfig<CollectionTypeInfo> = {}
38
+ ): FieldTypeFunc<CollectionTypeInfo> {
41
39
  const { isIndexed, validation, defaultValue } = config
42
40
  return meta => {
43
41
  if (typeof defaultValue === 'string') {
@@ -45,7 +43,8 @@ export function calendarDay<ListTypeInfo extends BaseListTypeInfo>(
45
43
  g.CalendarDay.parseValue(defaultValue)
46
44
  } catch (err) {
47
45
  throw new Error(
48
- `The calendarDay field at ${meta.listKey}.${meta.fieldKey} specifies defaultValue: ${defaultValue} but values must be provided as a full-date ISO8601 string such as 1970-01-01`
46
+ `The calendarDay field at ${meta.listKey}.${meta.fieldKey} specifies defaultValue: ${defaultValue} but values must be provided as a full-date ISO8601 string such as 1970-01-01`,
47
+ { cause: err }
49
48
  )
50
49
  }
51
50
  }
@@ -1,7 +1,7 @@
1
1
  import { userInputError } from '../../../lib/core/graphql-errors'
2
2
  import type { SimpleFieldTypeInfo } from '../../../types'
3
3
  import {
4
- type BaseListTypeInfo,
4
+ type BaseCollectionTypeInfo,
5
5
  type CommonFieldConfig,
6
6
  type FieldTypeFunc,
7
7
  fieldType,
@@ -12,8 +12,8 @@ import { assertReadIsNonNullAllowed } from '../../non-null-graphql'
12
12
  import { filters } from '../../filters'
13
13
  import type { controller } from './views'
14
14
 
15
- export type CheckboxFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonFieldConfig<
16
- ListTypeInfo,
15
+ export type CheckboxFieldConfig<CollectionTypeInfo extends BaseCollectionTypeInfo> = CommonFieldConfig<
16
+ CollectionTypeInfo,
17
17
  SimpleFieldTypeInfo<'Boolean'>
18
18
  > & {
19
19
  defaultValue?: boolean
@@ -23,9 +23,9 @@ export type CheckboxFieldConfig<ListTypeInfo extends BaseListTypeInfo> = CommonF
23
23
  }
24
24
  }
25
25
 
26
- export function checkbox<ListTypeInfo extends BaseListTypeInfo>(
27
- config: CheckboxFieldConfig<ListTypeInfo> = {}
28
- ): FieldTypeFunc<ListTypeInfo> {
26
+ export function checkbox<CollectionTypeInfo extends BaseCollectionTypeInfo>(
27
+ config: CheckboxFieldConfig<CollectionTypeInfo> = {}
28
+ ): FieldTypeFunc<CollectionTypeInfo> {
29
29
  const { defaultValue = false } = config
30
30
 
31
31
  return meta => {