@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,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 => {