@nixxie-cms/core 1.1.0 → 2.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 (27) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/context/dist/nixxie-cms-core-context.cjs.js +1 -1
  3. package/context/dist/nixxie-cms-core-context.esm.js +1 -1
  4. package/dist/declarations/src/schema.d.ts.map +1 -1
  5. package/dist/declarations/src/types/config/index.d.ts +20 -1
  6. package/dist/declarations/src/types/config/index.d.ts.map +1 -1
  7. package/dist/declarations/src/types/context.d.ts +158 -0
  8. package/dist/declarations/src/types/context.d.ts.map +1 -1
  9. package/dist/{express-7ca6f76a.cjs.js → express-84d534c2.cjs.js} +1 -1
  10. package/dist/{express-0abbce07.esm.js → express-d0a4ce99.esm.js} +1 -1
  11. package/dist/nixxie-cms-core.cjs.js +1 -0
  12. package/dist/nixxie-cms-core.esm.js +1 -0
  13. package/dist/{system-69e1a285.cjs.js → system-6b37a5f8.cjs.js} +2 -1
  14. package/dist/{system-4d2a2648.esm.js → system-e591d821.esm.js} +2 -1
  15. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.cjs.js +2 -2
  16. package/internal-unstable/artifacts/dist/nixxie-cms-core-internal-unstable-artifacts.esm.js +2 -2
  17. package/package.json +2 -2
  18. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.cjs.js +2 -2
  19. package/scripts/cli/dist/nixxie-cms-core-scripts-cli.esm.js +2 -2
  20. package/scripts/dist/nixxie-cms-core-scripts.cjs.js +2 -2
  21. package/scripts/dist/nixxie-cms-core-scripts.esm.js +2 -2
  22. package/src/fields/types/timestamp/views/__tests__/index.tsx +68 -68
  23. package/src/lib/context/createContext.ts +1 -0
  24. package/src/schema.ts +1 -0
  25. package/src/types/config/index.ts +21 -0
  26. package/src/types/context.ts +166 -0
  27. package/tests/conditional-filters.test.ts +333 -326
@@ -1,326 +1,333 @@
1
- import { getConditionalFilterFieldKeys, testFilter } from '../src/admin-ui/utils/filters'
2
- import type { ActionMeta, ConditionalFilterCase } from '../src/types'
3
-
4
- describe('conditional filters', () => {
5
- test('flat field predicates still use implicit AND and field-level not', () => {
6
- expect(
7
- testFilter(
8
- {
9
- status: { equals: 'published' },
10
- priority: { in: ['high', 'urgent'], not: { equals: 'urgent' } },
11
- },
12
- {
13
- status: 'published',
14
- priority: 'high',
15
- }
16
- )
17
- ).toBe(true)
18
-
19
- expect(
20
- testFilter(
21
- {
22
- status: { equals: 'published' },
23
- priority: { in: ['high', 'urgent'], not: { equals: 'urgent' } },
24
- },
25
- {
26
- status: 'published',
27
- priority: 'urgent',
28
- }
29
- )
30
- ).toBe(false)
31
- })
32
-
33
- test('AND requires every nested clause to pass', () => {
34
- expect(
35
- testFilter(
36
- {
37
- AND: [{ status: { equals: 'published' } }, { featured: { equals: true } }],
38
- },
39
- {
40
- status: 'published',
41
- featured: true,
42
- }
43
- )
44
- ).toBe(true)
45
-
46
- expect(
47
- testFilter(
48
- {
49
- AND: [{ status: { equals: 'published' } }, { featured: { equals: true } }],
50
- },
51
- {
52
- status: 'published',
53
- featured: false,
54
- }
55
- )
56
- ).toBe(false)
57
- })
58
-
59
- test('OR requires at least one nested clause to pass', () => {
60
- expect(
61
- testFilter(
62
- {
63
- OR: [{ status: { equals: 'draft' } }, { featured: { equals: true } }],
64
- },
65
- {
66
- status: 'published',
67
- featured: true,
68
- }
69
- )
70
- ).toBe(true)
71
-
72
- expect(
73
- testFilter(
74
- {
75
- OR: [{ status: { equals: 'draft' } }, { featured: { equals: true } }],
76
- },
77
- {
78
- status: 'published',
79
- featured: false,
80
- }
81
- )
82
- ).toBe(false)
83
- })
84
-
85
- test('NOT negates a nested clause', () => {
86
- expect(
87
- testFilter(
88
- {
89
- NOT: { archived: { equals: true } },
90
- },
91
- {
92
- archived: false,
93
- }
94
- )
95
- ).toBe(true)
96
-
97
- expect(
98
- testFilter(
99
- {
100
- NOT: { archived: { equals: true } },
101
- },
102
- {
103
- archived: true,
104
- }
105
- )
106
- ).toBe(false)
107
- })
108
-
109
- test('field predicates and logical operators at the same level are implicitly ANDed', () => {
110
- expect(
111
- testFilter(
112
- {
113
- status: { equals: 'published' },
114
- OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
115
- },
116
- {
117
- status: 'published',
118
- featured: false,
119
- priority: 'high',
120
- }
121
- )
122
- ).toBe(true)
123
-
124
- expect(
125
- testFilter(
126
- {
127
- status: { equals: 'published' },
128
- OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
129
- },
130
- {
131
- status: 'draft',
132
- featured: true,
133
- priority: 'high',
134
- }
135
- )
136
- ).toBe(false)
137
- })
138
-
139
- test('AND, OR, NOT, and sibling field predicates are all implicitly ANDed together', () => {
140
- expect(
141
- testFilter(
142
- {
143
- status: { equals: 'published' },
144
- AND: [{ author: { equals: 'emma' } }],
145
- OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
146
- NOT: { archived: { equals: true } },
147
- },
148
- {
149
- status: 'published',
150
- author: 'emma',
151
- featured: false,
152
- priority: 'high',
153
- archived: false,
154
- }
155
- )
156
- ).toBe(true)
157
-
158
- expect(
159
- testFilter(
160
- {
161
- status: { equals: 'published' },
162
- AND: [{ author: { equals: 'emma' } }],
163
- OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
164
- NOT: { archived: { equals: true } },
165
- },
166
- {
167
- status: 'published',
168
- author: 'emma',
169
- featured: false,
170
- priority: 'low',
171
- archived: false,
172
- }
173
- )
174
- ).toBe(false)
175
-
176
- expect(
177
- testFilter(
178
- {
179
- status: { equals: 'published' },
180
- AND: [{ author: { equals: 'emma' } }],
181
- OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
182
- NOT: { archived: { equals: true } },
183
- },
184
- {
185
- status: 'published',
186
- author: 'emma',
187
- featured: true,
188
- priority: 'low',
189
- archived: true,
190
- }
191
- )
192
- ).toBe(false)
193
- })
194
-
195
- test('recursive combinations evaluate correctly', () => {
196
- const filter: ConditionalFilterCase<any> = {
197
- AND: [
198
- {
199
- OR: [{ priority: { equals: 'high' } }, { priority: { equals: 'urgent' } }],
200
- },
201
- {
202
- NOT: { isComplete: { equals: true } },
203
- },
204
- ],
205
- }
206
-
207
- expect(
208
- testFilter(filter, {
209
- priority: 'urgent',
210
- isComplete: false,
211
- })
212
- ).toBe(true)
213
-
214
- expect(
215
- testFilter(filter, {
216
- priority: 'low',
217
- isComplete: false,
218
- })
219
- ).toBe(false)
220
-
221
- expect(
222
- testFilter(filter, {
223
- priority: 'high',
224
- isComplete: true,
225
- })
226
- ).toBe(false)
227
- })
228
-
229
- test('collects field dependencies from nested action filters', () => {
230
- const actions = [
231
- {
232
- key: 'publish',
233
- graphql: { arguments: [], names: { one: 'publishPost', many: 'publishPosts' } },
234
- label: 'Publish',
235
- icon: null,
236
- messages: {
237
- promptTitle: '',
238
- promptTitleMany: '',
239
- prompt: '',
240
- promptMany: '',
241
- promptConfirmLabel: '',
242
- promptConfirmLabelMany: '',
243
- fail: '',
244
- failMany: '',
245
- success: '',
246
- successMany: '',
247
- },
248
- itemView: {
249
- actionMode: 'enabled',
250
- navigation: 'follow',
251
- hidePrompt: false,
252
- hideToast: false,
253
- },
254
- listView: {
255
- actionMode: {
256
- disabled: {
257
- AND: [
258
- { status: { equals: 'draft' } },
259
- {
260
- OR: [{ priority: { equals: 'high' } }, { NOT: { isComplete: { equals: true } } }],
261
- },
262
- ],
263
- },
264
- },
265
- },
266
- } as ActionMeta,
267
- ] satisfies ActionMeta[]
268
-
269
- const fieldKeys = new Set<string>(['title'])
270
- for (const action of actions) {
271
- for (const fieldKey of getConditionalFilterFieldKeys(action.listView.actionMode)) {
272
- fieldKeys.add(fieldKey)
273
- }
274
- }
275
-
276
- expect(fieldKeys).toEqual(['title', 'status', 'priority', 'isComplete'])
277
- })
278
-
279
- test('collects field dependencies from sibling AND, OR, NOT, and field predicates', () => {
280
- const actions = [
281
- {
282
- key: 'publish',
283
- graphql: { arguments: [], names: { one: 'publishPost', many: 'publishPosts' } },
284
- label: 'Publish',
285
- icon: null,
286
- messages: {
287
- promptTitle: '',
288
- promptTitleMany: '',
289
- prompt: '',
290
- promptMany: '',
291
- promptConfirmLabel: '',
292
- promptConfirmLabelMany: '',
293
- fail: '',
294
- failMany: '',
295
- success: '',
296
- successMany: '',
297
- },
298
- itemView: {
299
- actionMode: 'enabled',
300
- navigation: 'follow',
301
- hidePrompt: false,
302
- hideToast: false,
303
- },
304
- listView: {
305
- actionMode: {
306
- disabled: {
307
- status: { equals: 'draft' },
308
- AND: [{ author: { equals: 'emma' } }],
309
- OR: [{ priority: { equals: 'high' } }, { featured: { equals: true } }],
310
- NOT: { archived: { equals: true } },
311
- },
312
- },
313
- },
314
- } as ActionMeta,
315
- ] satisfies ActionMeta[]
316
-
317
- const fieldKeys = new Set<string>(['title'])
318
- for (const action of actions) {
319
- for (const fieldKey of getConditionalFilterFieldKeys(action.listView.actionMode)) {
320
- fieldKeys.add(fieldKey)
321
- }
322
- }
323
-
324
- expect(fieldKeys).toEqual(['title', 'author', 'priority', 'featured', 'archived', 'status'])
325
- })
326
- })
1
+ import { getConditionalFilterFieldKeys, testFilter } from '../src/admin-ui/utils/filters'
2
+ import type { ActionMeta, ConditionalFilterCase } from '../src/types'
3
+
4
+ describe('conditional filters', () => {
5
+ test('flat field predicates still use implicit AND and field-level not', () => {
6
+ expect(
7
+ testFilter(
8
+ {
9
+ status: { equals: 'published' },
10
+ priority: { in: ['high', 'urgent'], not: { equals: 'urgent' } },
11
+ },
12
+ {
13
+ status: 'published',
14
+ priority: 'high',
15
+ }
16
+ )
17
+ ).toBe(true)
18
+
19
+ expect(
20
+ testFilter(
21
+ {
22
+ status: { equals: 'published' },
23
+ priority: { in: ['high', 'urgent'], not: { equals: 'urgent' } },
24
+ },
25
+ {
26
+ status: 'published',
27
+ priority: 'urgent',
28
+ }
29
+ )
30
+ ).toBe(false)
31
+ })
32
+
33
+ test('AND requires every nested clause to pass', () => {
34
+ expect(
35
+ testFilter(
36
+ {
37
+ AND: [{ status: { equals: 'published' } }, { featured: { equals: true } }],
38
+ },
39
+ {
40
+ status: 'published',
41
+ featured: true,
42
+ }
43
+ )
44
+ ).toBe(true)
45
+
46
+ expect(
47
+ testFilter(
48
+ {
49
+ AND: [{ status: { equals: 'published' } }, { featured: { equals: true } }],
50
+ },
51
+ {
52
+ status: 'published',
53
+ featured: false,
54
+ }
55
+ )
56
+ ).toBe(false)
57
+ })
58
+
59
+ test('OR requires at least one nested clause to pass', () => {
60
+ expect(
61
+ testFilter(
62
+ {
63
+ OR: [{ status: { equals: 'draft' } }, { featured: { equals: true } }],
64
+ },
65
+ {
66
+ status: 'published',
67
+ featured: true,
68
+ }
69
+ )
70
+ ).toBe(true)
71
+
72
+ expect(
73
+ testFilter(
74
+ {
75
+ OR: [{ status: { equals: 'draft' } }, { featured: { equals: true } }],
76
+ },
77
+ {
78
+ status: 'published',
79
+ featured: false,
80
+ }
81
+ )
82
+ ).toBe(false)
83
+ })
84
+
85
+ test('NOT negates a nested clause', () => {
86
+ expect(
87
+ testFilter(
88
+ {
89
+ NOT: { archived: { equals: true } },
90
+ },
91
+ {
92
+ archived: false,
93
+ }
94
+ )
95
+ ).toBe(true)
96
+
97
+ expect(
98
+ testFilter(
99
+ {
100
+ NOT: { archived: { equals: true } },
101
+ },
102
+ {
103
+ archived: true,
104
+ }
105
+ )
106
+ ).toBe(false)
107
+ })
108
+
109
+ test('field predicates and logical operators at the same level are implicitly ANDed', () => {
110
+ expect(
111
+ testFilter(
112
+ {
113
+ status: { equals: 'published' },
114
+ OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
115
+ },
116
+ {
117
+ status: 'published',
118
+ featured: false,
119
+ priority: 'high',
120
+ }
121
+ )
122
+ ).toBe(true)
123
+
124
+ expect(
125
+ testFilter(
126
+ {
127
+ status: { equals: 'published' },
128
+ OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
129
+ },
130
+ {
131
+ status: 'draft',
132
+ featured: true,
133
+ priority: 'high',
134
+ }
135
+ )
136
+ ).toBe(false)
137
+ })
138
+
139
+ test('AND, OR, NOT, and sibling field predicates are all implicitly ANDed together', () => {
140
+ expect(
141
+ testFilter(
142
+ {
143
+ status: { equals: 'published' },
144
+ AND: [{ author: { equals: 'emma' } }],
145
+ OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
146
+ NOT: { archived: { equals: true } },
147
+ },
148
+ {
149
+ status: 'published',
150
+ author: 'emma',
151
+ featured: false,
152
+ priority: 'high',
153
+ archived: false,
154
+ }
155
+ )
156
+ ).toBe(true)
157
+
158
+ expect(
159
+ testFilter(
160
+ {
161
+ status: { equals: 'published' },
162
+ AND: [{ author: { equals: 'emma' } }],
163
+ OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
164
+ NOT: { archived: { equals: true } },
165
+ },
166
+ {
167
+ status: 'published',
168
+ author: 'emma',
169
+ featured: false,
170
+ priority: 'low',
171
+ archived: false,
172
+ }
173
+ )
174
+ ).toBe(false)
175
+
176
+ expect(
177
+ testFilter(
178
+ {
179
+ status: { equals: 'published' },
180
+ AND: [{ author: { equals: 'emma' } }],
181
+ OR: [{ featured: { equals: true } }, { priority: { equals: 'high' } }],
182
+ NOT: { archived: { equals: true } },
183
+ },
184
+ {
185
+ status: 'published',
186
+ author: 'emma',
187
+ featured: true,
188
+ priority: 'low',
189
+ archived: true,
190
+ }
191
+ )
192
+ ).toBe(false)
193
+ })
194
+
195
+ test('recursive combinations evaluate correctly', () => {
196
+ const filter: ConditionalFilterCase<any> = {
197
+ AND: [
198
+ {
199
+ OR: [{ priority: { equals: 'high' } }, { priority: { equals: 'urgent' } }],
200
+ },
201
+ {
202
+ NOT: { isComplete: { equals: true } },
203
+ },
204
+ ],
205
+ }
206
+
207
+ expect(
208
+ testFilter(filter, {
209
+ priority: 'urgent',
210
+ isComplete: false,
211
+ })
212
+ ).toBe(true)
213
+
214
+ expect(
215
+ testFilter(filter, {
216
+ priority: 'low',
217
+ isComplete: false,
218
+ })
219
+ ).toBe(false)
220
+
221
+ expect(
222
+ testFilter(filter, {
223
+ priority: 'high',
224
+ isComplete: true,
225
+ })
226
+ ).toBe(false)
227
+ })
228
+
229
+ test('collects field dependencies from nested action filters', () => {
230
+ const actions = [
231
+ {
232
+ key: 'publish',
233
+ graphql: { arguments: [], names: { one: 'publishPost', many: 'publishPosts' } },
234
+ label: 'Publish',
235
+ icon: null,
236
+ messages: {
237
+ promptTitle: '',
238
+ promptTitleMany: '',
239
+ prompt: '',
240
+ promptMany: '',
241
+ promptConfirmLabel: '',
242
+ promptConfirmLabelMany: '',
243
+ fail: '',
244
+ failMany: '',
245
+ success: '',
246
+ successMany: '',
247
+ },
248
+ itemView: {
249
+ actionMode: 'enabled',
250
+ navigation: 'follow',
251
+ hidePrompt: false,
252
+ hideToast: false,
253
+ },
254
+ listView: {
255
+ actionMode: {
256
+ disabled: {
257
+ AND: [
258
+ { status: { equals: 'draft' } },
259
+ {
260
+ OR: [{ priority: { equals: 'high' } }, { NOT: { isComplete: { equals: true } } }],
261
+ },
262
+ ],
263
+ },
264
+ },
265
+ },
266
+ } as ActionMeta,
267
+ ] satisfies ActionMeta[]
268
+
269
+ const fieldKeys = new Set<string>(['title'])
270
+ for (const action of actions) {
271
+ for (const fieldKey of getConditionalFilterFieldKeys(action.listView.actionMode)) {
272
+ fieldKeys.add(fieldKey)
273
+ }
274
+ }
275
+
276
+ expect([...fieldKeys]).toEqual(['title', 'status', 'priority', 'isComplete'])
277
+ })
278
+
279
+ test('collects field dependencies from sibling AND, OR, NOT, and field predicates', () => {
280
+ const actions = [
281
+ {
282
+ key: 'publish',
283
+ graphql: { arguments: [], names: { one: 'publishPost', many: 'publishPosts' } },
284
+ label: 'Publish',
285
+ icon: null,
286
+ messages: {
287
+ promptTitle: '',
288
+ promptTitleMany: '',
289
+ prompt: '',
290
+ promptMany: '',
291
+ promptConfirmLabel: '',
292
+ promptConfirmLabelMany: '',
293
+ fail: '',
294
+ failMany: '',
295
+ success: '',
296
+ successMany: '',
297
+ },
298
+ itemView: {
299
+ actionMode: 'enabled',
300
+ navigation: 'follow',
301
+ hidePrompt: false,
302
+ hideToast: false,
303
+ },
304
+ listView: {
305
+ actionMode: {
306
+ disabled: {
307
+ status: { equals: 'draft' },
308
+ AND: [{ author: { equals: 'emma' } }],
309
+ OR: [{ priority: { equals: 'high' } }, { featured: { equals: true } }],
310
+ NOT: { archived: { equals: true } },
311
+ },
312
+ },
313
+ },
314
+ } as ActionMeta,
315
+ ] satisfies ActionMeta[]
316
+
317
+ const fieldKeys = new Set<string>(['title'])
318
+ for (const action of actions) {
319
+ for (const fieldKey of getConditionalFilterFieldKeys(action.listView.actionMode)) {
320
+ fieldKeys.add(fieldKey)
321
+ }
322
+ }
323
+
324
+ expect([...fieldKeys]).toEqual([
325
+ 'title',
326
+ 'author',
327
+ 'priority',
328
+ 'featured',
329
+ 'archived',
330
+ 'status',
331
+ ])
332
+ })
333
+ })