@jskit-ai/kernel 0.1.55 → 0.1.57

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 (57) hide show
  1. package/package.json +3 -2
  2. package/server/actions/ActionRuntimeServiceProvider.test.js +23 -15
  3. package/server/http/lib/kernel.test.js +447 -0
  4. package/server/http/lib/routeRegistration.js +236 -15
  5. package/server/http/lib/routeTransport.js +126 -0
  6. package/server/http/lib/routeValidator.js +133 -198
  7. package/server/http/lib/routeValidator.test.js +385 -278
  8. package/server/http/lib/router.js +17 -2
  9. package/server/platform/providerRuntime.test.js +7 -7
  10. package/server/runtime/bootBootstrapRoutes.js +2 -18
  11. package/server/runtime/bootBootstrapRoutes.test.js +5 -14
  12. package/server/runtime/fastifyBootstrap.js +119 -0
  13. package/server/runtime/fastifyBootstrap.test.js +119 -1
  14. package/server/runtime/moduleConfig.js +32 -62
  15. package/server/runtime/moduleConfig.test.js +48 -24
  16. package/server/support/pageTargets.js +15 -9
  17. package/server/support/pageTargets.test.js +1 -1
  18. package/shared/actions/actionContributorHelpers.js +5 -11
  19. package/shared/actions/actionDefinitions.js +37 -150
  20. package/shared/actions/actionDefinitions.test.js +117 -136
  21. package/shared/actions/policies.js +25 -169
  22. package/shared/actions/policies.test.js +76 -87
  23. package/shared/actions/registry.test.js +24 -50
  24. package/shared/support/crudFieldContract.js +322 -0
  25. package/shared/support/crudFieldContract.test.js +67 -0
  26. package/shared/support/crudListFilters.js +582 -38
  27. package/shared/support/crudListFilters.test.js +178 -8
  28. package/shared/support/crudLookup.js +14 -7
  29. package/shared/support/crudLookup.test.js +91 -66
  30. package/shared/support/normalize.js +7 -0
  31. package/shared/support/normalize.test.js +4 -2
  32. package/shared/support/shellLayoutTargets.test.js +1 -1
  33. package/shared/validators/composeSchemaDefinitions.js +53 -0
  34. package/shared/validators/composeSchemaDefinitions.test.js +156 -0
  35. package/shared/validators/createCursorListValidator.js +22 -35
  36. package/shared/validators/createCursorListValidator.test.js +22 -23
  37. package/shared/validators/cursorPaginationQueryValidator.js +14 -24
  38. package/shared/validators/cursorPaginationQueryValidator.test.js +18 -8
  39. package/shared/validators/htmlTimeSchemas.js +6 -4
  40. package/shared/validators/index.js +15 -7
  41. package/shared/validators/jsonRestSchemaSupport.js +139 -0
  42. package/shared/validators/mergeObjectSchemas.js +44 -6
  43. package/shared/validators/mergeObjectSchemas.test.js +60 -35
  44. package/shared/validators/recordIdParamsValidator.js +19 -52
  45. package/shared/validators/recordIdParamsValidator.test.js +13 -8
  46. package/shared/validators/resourceRequiredMetadata.js +3 -3
  47. package/shared/validators/resourceRequiredMetadata.test.js +29 -16
  48. package/shared/validators/schemaDefinitions.js +126 -0
  49. package/shared/validators/schemaDefinitions.test.js +51 -0
  50. package/shared/validators/schemaPayloadValidation.js +65 -0
  51. package/test/barrelExposure.test.js +30 -0
  52. package/test/routeInputContractGuard.test.js +10 -6
  53. package/shared/validators/mergeValidators.js +0 -89
  54. package/shared/validators/mergeValidators.test.js +0 -116
  55. package/shared/validators/nestValidator.js +0 -53
  56. package/shared/validators/nestValidator.test.js +0 -60
  57. package/shared/validators/settingsFieldNormalization.js +0 -40
@@ -1,5 +1,8 @@
1
1
  import { deepFreeze } from "./deepFreeze.js";
2
2
  import {
3
+ normalizeBoolean,
4
+ normalizeCanonicalRecordIdText,
5
+ normalizeUniqueTextList,
3
6
  normalizeText,
4
7
  normalizeObject
5
8
  } from "./normalize.js";
@@ -43,6 +46,563 @@ const CRUD_LIST_FILTER_PRESENCE_OPTIONS = Object.freeze([
43
46
  label: "Missing"
44
47
  })
45
48
  ]);
49
+ const CRUD_LIST_FILTER_INVALID_VALUES_REJECT = "reject";
50
+ const CRUD_LIST_FILTER_INVALID_VALUES_DISCARD = "discard";
51
+ const CRUD_LIST_FILTER_INVALID_VALUES_MODES = Object.freeze([
52
+ CRUD_LIST_FILTER_INVALID_VALUES_REJECT,
53
+ CRUD_LIST_FILTER_INVALID_VALUES_DISCARD
54
+ ]);
55
+ const INVALID_CRUD_LIST_FILTER_QUERY_VALUE = Symbol("invalidCrudListFilterQueryValue");
56
+ const DATE_FILTER_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
57
+
58
+ function parseCrudListRangeQueryExpression(value = null) {
59
+ const sourceValue = Array.isArray(value) ? value[0] : value;
60
+ if (sourceValue == null) {
61
+ return null;
62
+ }
63
+
64
+ const normalized = typeof sourceValue === "number"
65
+ ? String(sourceValue)
66
+ : normalizeText(sourceValue);
67
+ if (!normalized) {
68
+ return null;
69
+ }
70
+
71
+ const separatorIndex = normalized.indexOf("..");
72
+ if (separatorIndex < 0) {
73
+ return deepFreeze({
74
+ exact: true,
75
+ start: normalized,
76
+ end: normalized
77
+ });
78
+ }
79
+
80
+ const start = normalizeText(normalized.slice(0, separatorIndex));
81
+ const end = normalizeText(normalized.slice(separatorIndex + 2));
82
+ if (!start && !end) {
83
+ return null;
84
+ }
85
+
86
+ return deepFreeze({
87
+ exact: false,
88
+ start,
89
+ end
90
+ });
91
+ }
92
+
93
+ function formatCrudListRangeQueryExpression(startValue = "", endValue = "", { collapseExact = false } = {}) {
94
+ const start = normalizeText(startValue);
95
+ const end = normalizeText(endValue);
96
+ if (!start && !end) {
97
+ return "";
98
+ }
99
+
100
+ if (collapseExact === true && start && end && start === end) {
101
+ return start;
102
+ }
103
+
104
+ return `${start}..${end}`;
105
+ }
106
+
107
+ function normalizeCrudListFilterInvalidValues(value = "") {
108
+ const normalized = normalizeText(value).toLowerCase();
109
+ if (CRUD_LIST_FILTER_INVALID_VALUES_MODES.includes(normalized)) {
110
+ return normalized;
111
+ }
112
+
113
+ throw new TypeError(
114
+ `Unsupported CRUD list filter invalidValues mode "${value}". Expected one of: ${CRUD_LIST_FILTER_INVALID_VALUES_MODES.join(", ")}.`
115
+ );
116
+ }
117
+
118
+ function firstCrudListFilterValue(value) {
119
+ return Array.isArray(value) ? value[0] : value;
120
+ }
121
+
122
+ function isPrimitiveCrudListFilterInput(value) {
123
+ const valueType = typeof value;
124
+ return valueType === "string" || valueType === "number" || valueType === "boolean";
125
+ }
126
+
127
+ function isPrimitiveOrPrimitiveArrayCrudListFilterInput(value) {
128
+ if (Array.isArray(value)) {
129
+ return value.every((entry) => isPrimitiveCrudListFilterInput(entry));
130
+ }
131
+
132
+ return isPrimitiveCrudListFilterInput(value);
133
+ }
134
+
135
+ function normalizeDateFilterText(value) {
136
+ const normalized = normalizeText(firstCrudListFilterValue(value));
137
+ if (!normalized || !DATE_FILTER_PATTERN.test(normalized)) {
138
+ return "";
139
+ }
140
+
141
+ return normalized;
142
+ }
143
+
144
+ function normalizeCanonicalRecordIdList(value) {
145
+ return normalizeUniqueTextList(value, {
146
+ acceptSingle: true
147
+ })
148
+ .map((entry) => normalizeCanonicalRecordIdText(entry, { fallback: "" }))
149
+ .filter(Boolean);
150
+ }
151
+
152
+ function normalizeFiniteFilterNumber(value) {
153
+ if (value == null || value === "") {
154
+ return null;
155
+ }
156
+
157
+ const normalized = typeof value === "number"
158
+ ? value
159
+ : Number(normalizeText(firstCrudListFilterValue(value)));
160
+ return Number.isFinite(normalized) ? normalized : null;
161
+ }
162
+
163
+ function normalizeAllowedFilterTextValue(value, allowedValues = new Set()) {
164
+ const normalized = normalizeText(firstCrudListFilterValue(value));
165
+ if (!normalized || !allowedValues.has(normalized)) {
166
+ return "";
167
+ }
168
+
169
+ return normalized;
170
+ }
171
+
172
+ function normalizeAllowedFilterTextValues(value, allowedValues = new Set()) {
173
+ return normalizeUniqueTextList(value, {
174
+ acceptSingle: true
175
+ }).filter((entry) => allowedValues.has(entry));
176
+ }
177
+
178
+ function resolveCrudListFilterAllowedValues(filter = {}) {
179
+ return new Set(
180
+ (Array.isArray(filter.options) ? filter.options : [])
181
+ .map((entry) => normalizeText(entry?.value))
182
+ .filter(Boolean)
183
+ );
184
+ }
185
+
186
+ function normalizeCrudListDateRangeUiValue(rawValue) {
187
+ const source = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue)
188
+ ? rawValue
189
+ : null;
190
+ if (source) {
191
+ return {
192
+ from: normalizeDateFilterText(source.from),
193
+ to: normalizeDateFilterText(source.to)
194
+ };
195
+ }
196
+
197
+ const parsed = parseCrudListRangeQueryExpression(rawValue);
198
+ return {
199
+ from: normalizeDateFilterText(parsed?.start),
200
+ to: normalizeDateFilterText(parsed?.end)
201
+ };
202
+ }
203
+
204
+ function normalizeCrudListNumberRangeUiValue(rawValue) {
205
+ const source = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue)
206
+ ? rawValue
207
+ : null;
208
+ if (source) {
209
+ return {
210
+ min: normalizeText(source.min),
211
+ max: normalizeText(source.max)
212
+ };
213
+ }
214
+
215
+ const parsed = parseCrudListRangeQueryExpression(rawValue);
216
+ return {
217
+ min: normalizeText(parsed?.start),
218
+ max: normalizeText(parsed?.end)
219
+ };
220
+ }
221
+
222
+ function createCrudListFilterInitialValue(filter = {}) {
223
+ if (normalizeCrudListFilterType(filter.type) === CRUD_LIST_FILTER_TYPE_FLAG) {
224
+ return false;
225
+ }
226
+ if (isCrudListFilterMultiValue(filter)) {
227
+ return [];
228
+ }
229
+ if (normalizeCrudListFilterType(filter.type) === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
230
+ return {
231
+ from: "",
232
+ to: ""
233
+ };
234
+ }
235
+ if (normalizeCrudListFilterType(filter.type) === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
236
+ return {
237
+ min: "",
238
+ max: ""
239
+ };
240
+ }
241
+
242
+ return "";
243
+ }
244
+
245
+ function isCrudListFilterMultiValue(filter = {}) {
246
+ const type = normalizeCrudListFilterType(filter.type);
247
+ return type === CRUD_LIST_FILTER_TYPE_ENUM_MANY || type === CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY;
248
+ }
249
+
250
+ function isCrudListFilterStructuredValue(filter = {}) {
251
+ const type = normalizeCrudListFilterType(filter.type);
252
+ return type === CRUD_LIST_FILTER_TYPE_DATE_RANGE || type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE;
253
+ }
254
+
255
+ function normalizeCrudListFilterUiValue(filter = {}, rawValue) {
256
+ const type = normalizeCrudListFilterType(filter.type);
257
+
258
+ if (type === CRUD_LIST_FILTER_TYPE_FLAG) {
259
+ return rawValue === true;
260
+ }
261
+
262
+ if (type === CRUD_LIST_FILTER_TYPE_ENUM) {
263
+ return normalizeAllowedFilterTextValue(rawValue, resolveCrudListFilterAllowedValues(filter));
264
+ }
265
+
266
+ if (type === CRUD_LIST_FILTER_TYPE_PRESENCE) {
267
+ return normalizeAllowedFilterTextValue(rawValue, new Set([
268
+ CRUD_LIST_FILTER_PRESENCE_PRESENT,
269
+ CRUD_LIST_FILTER_PRESENCE_MISSING
270
+ ]));
271
+ }
272
+
273
+ if (type === CRUD_LIST_FILTER_TYPE_ENUM_MANY) {
274
+ return normalizeAllowedFilterTextValues(rawValue, resolveCrudListFilterAllowedValues(filter));
275
+ }
276
+
277
+ if (type === CRUD_LIST_FILTER_TYPE_RECORD_ID) {
278
+ return normalizeCanonicalRecordIdText(rawValue, { fallback: "" }) || "";
279
+ }
280
+
281
+ if (type === CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY) {
282
+ return normalizeCanonicalRecordIdList(rawValue);
283
+ }
284
+
285
+ if (type === CRUD_LIST_FILTER_TYPE_DATE) {
286
+ return normalizeDateFilterText(rawValue);
287
+ }
288
+
289
+ if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
290
+ return normalizeCrudListDateRangeUiValue(rawValue);
291
+ }
292
+
293
+ if (type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
294
+ return normalizeCrudListNumberRangeUiValue(rawValue);
295
+ }
296
+
297
+ return normalizeText(rawValue);
298
+ }
299
+
300
+ function matchCrudListFilterValues(currentValue, expectedValue) {
301
+ const currentList = Array.isArray(currentValue) ? [...currentValue].sort() : [];
302
+ const expectedList = Array.isArray(expectedValue) ? [...expectedValue].sort() : [];
303
+ if (currentList.length !== expectedList.length) {
304
+ return false;
305
+ }
306
+
307
+ return currentList.every((entry, index) => entry === expectedList[index]);
308
+ }
309
+
310
+ function areCrudListFilterUiValuesEqual(filter = {}, currentValue, expectedValue) {
311
+ const normalizedCurrentValue = normalizeCrudListFilterUiValue(filter, currentValue);
312
+ const normalizedExpectedValue = normalizeCrudListFilterUiValue(filter, expectedValue);
313
+
314
+ if (isCrudListFilterMultiValue(filter)) {
315
+ return matchCrudListFilterValues(normalizedCurrentValue, normalizedExpectedValue);
316
+ }
317
+
318
+ if (normalizeCrudListFilterType(filter.type) === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
319
+ return (
320
+ normalizeText(normalizedCurrentValue?.from) === normalizeText(normalizedExpectedValue?.from) &&
321
+ normalizeText(normalizedCurrentValue?.to) === normalizeText(normalizedExpectedValue?.to)
322
+ );
323
+ }
324
+
325
+ if (normalizeCrudListFilterType(filter.type) === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
326
+ return (
327
+ normalizeText(normalizedCurrentValue?.min) === normalizeText(normalizedExpectedValue?.min) &&
328
+ normalizeText(normalizedCurrentValue?.max) === normalizeText(normalizedExpectedValue?.max)
329
+ );
330
+ }
331
+
332
+ return normalizedCurrentValue === normalizedExpectedValue;
333
+ }
334
+
335
+ function formatCrudListFilterQueryValue(filter = {}, value) {
336
+ const type = normalizeCrudListFilterType(filter.type);
337
+
338
+ if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
339
+ return formatCrudListRangeQueryExpression(value?.from, value?.to, {
340
+ collapseExact: true
341
+ });
342
+ }
343
+
344
+ if (type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
345
+ return formatCrudListRangeQueryExpression(value?.min, value?.max, {
346
+ collapseExact: true
347
+ });
348
+ }
349
+
350
+ return value;
351
+ }
352
+
353
+ function rejectInvalidCrudListFilterValue({ invalidValues = CRUD_LIST_FILTER_INVALID_VALUES_REJECT } = {}) {
354
+ return invalidValues === CRUD_LIST_FILTER_INVALID_VALUES_DISCARD
355
+ ? null
356
+ : INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
357
+ }
358
+
359
+ function normalizeCrudListDateRangeQueryValue(value) {
360
+ const parsed = parseCrudListRangeQueryExpression(firstCrudListFilterValue(value));
361
+ if (!parsed) {
362
+ return undefined;
363
+ }
364
+
365
+ const from = normalizeDateFilterText(parsed.start);
366
+ const to = normalizeDateFilterText(parsed.end);
367
+
368
+ if (parsed.exact) {
369
+ if (!from) {
370
+ return undefined;
371
+ }
372
+
373
+ return {
374
+ from,
375
+ to: from
376
+ };
377
+ }
378
+
379
+ if (!from && !to) {
380
+ return undefined;
381
+ }
382
+
383
+ return {
384
+ ...(from ? { from } : {}),
385
+ ...(to ? { to } : {})
386
+ };
387
+ }
388
+
389
+ function normalizeCrudListNumberRangeQueryValue(value) {
390
+ const parsed = parseCrudListRangeQueryExpression(firstCrudListFilterValue(value));
391
+ if (!parsed) {
392
+ return undefined;
393
+ }
394
+
395
+ const min = normalizeFiniteFilterNumber(parsed.start);
396
+ const max = normalizeFiniteFilterNumber(parsed.end);
397
+
398
+ if (parsed.exact) {
399
+ if (min == null) {
400
+ return undefined;
401
+ }
402
+
403
+ return {
404
+ min,
405
+ max: min
406
+ };
407
+ }
408
+
409
+ if (min == null && max == null) {
410
+ return undefined;
411
+ }
412
+
413
+ return {
414
+ ...(min != null ? { min } : {}),
415
+ ...(max != null ? { max } : {})
416
+ };
417
+ }
418
+
419
+ function parseCrudListFilterQueryValue(
420
+ filter = {},
421
+ value,
422
+ { invalidValues = CRUD_LIST_FILTER_INVALID_VALUES_REJECT } = {}
423
+ ) {
424
+ const type = normalizeCrudListFilterType(filter.type);
425
+ const allowedValues = resolveCrudListFilterAllowedValues(filter);
426
+
427
+ if (type === CRUD_LIST_FILTER_TYPE_FLAG) {
428
+ if (value !== null && value !== "" && !isPrimitiveCrudListFilterInput(value)) {
429
+ return rejectInvalidCrudListFilterValue({ invalidValues });
430
+ }
431
+ } else if (isCrudListFilterMultiValue(filter)) {
432
+ if (!isPrimitiveOrPrimitiveArrayCrudListFilterInput(value)) {
433
+ return rejectInvalidCrudListFilterValue({ invalidValues });
434
+ }
435
+ } else if (!isPrimitiveCrudListFilterInput(value)) {
436
+ return rejectInvalidCrudListFilterValue({ invalidValues });
437
+ }
438
+
439
+ if (type === CRUD_LIST_FILTER_TYPE_FLAG) {
440
+ if (value === null || value === "") {
441
+ return true;
442
+ }
443
+
444
+ try {
445
+ return normalizeBoolean(firstCrudListFilterValue(value));
446
+ } catch {
447
+ return rejectInvalidCrudListFilterValue({ invalidValues });
448
+ }
449
+ }
450
+
451
+ if (type === CRUD_LIST_FILTER_TYPE_ENUM) {
452
+ return normalizeAllowedFilterTextValue(value, allowedValues) || rejectInvalidCrudListFilterValue({ invalidValues });
453
+ }
454
+
455
+ if (type === CRUD_LIST_FILTER_TYPE_PRESENCE) {
456
+ return normalizeAllowedFilterTextValue(value, new Set([
457
+ CRUD_LIST_FILTER_PRESENCE_PRESENT,
458
+ CRUD_LIST_FILTER_PRESENCE_MISSING
459
+ ])) || rejectInvalidCrudListFilterValue({ invalidValues });
460
+ }
461
+
462
+ if (type === CRUD_LIST_FILTER_TYPE_ENUM_MANY) {
463
+ const values = Array.isArray(value) ? value : [value];
464
+ const normalizedValues = normalizeAllowedFilterTextValues(value, allowedValues);
465
+
466
+ if (invalidValues === CRUD_LIST_FILTER_INVALID_VALUES_DISCARD) {
467
+ return normalizedValues.length > 0 ? normalizedValues : null;
468
+ }
469
+
470
+ return normalizedValues.length > 0 && normalizedValues.length === values.length
471
+ ? normalizedValues
472
+ : INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
473
+ }
474
+
475
+ if (type === CRUD_LIST_FILTER_TYPE_RECORD_ID) {
476
+ return normalizeCanonicalRecordIdText(firstCrudListFilterValue(value), {
477
+ fallback: ""
478
+ }) || rejectInvalidCrudListFilterValue({ invalidValues });
479
+ }
480
+
481
+ if (type === CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY) {
482
+ const values = Array.isArray(value) ? value : [value];
483
+ const normalizedValues = normalizeCanonicalRecordIdList(value);
484
+
485
+ if (invalidValues === CRUD_LIST_FILTER_INVALID_VALUES_DISCARD) {
486
+ return normalizedValues.length > 0 ? normalizedValues : null;
487
+ }
488
+
489
+ return normalizedValues.length > 0 && normalizedValues.length === values.length
490
+ ? normalizedValues
491
+ : INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
492
+ }
493
+
494
+ if (type === CRUD_LIST_FILTER_TYPE_DATE) {
495
+ return normalizeDateFilterText(value) || rejectInvalidCrudListFilterValue({ invalidValues });
496
+ }
497
+
498
+ if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
499
+ const normalized = normalizeCrudListDateRangeQueryValue(value);
500
+ if (invalidValues === CRUD_LIST_FILTER_INVALID_VALUES_DISCARD) {
501
+ return normalized || null;
502
+ }
503
+ return normalized || INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
504
+ }
505
+
506
+ if (type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
507
+ const normalized = normalizeCrudListNumberRangeQueryValue(value);
508
+ if (invalidValues === CRUD_LIST_FILTER_INVALID_VALUES_DISCARD) {
509
+ return normalized || null;
510
+ }
511
+ return normalized || INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
512
+ }
513
+
514
+ return INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
515
+ }
516
+
517
+ function hasCrudListFilterUiValue(filter = {}, rawValue) {
518
+ const normalizedValue = normalizeCrudListFilterUiValue(filter, rawValue);
519
+ const type = normalizeCrudListFilterType(filter.type);
520
+
521
+ if (type === CRUD_LIST_FILTER_TYPE_FLAG) {
522
+ return normalizedValue === true;
523
+ }
524
+
525
+ if (isCrudListFilterMultiValue(filter)) {
526
+ return Array.isArray(normalizedValue) && normalizedValue.length > 0;
527
+ }
528
+
529
+ if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
530
+ return Boolean(normalizeText(normalizedValue?.from) || normalizeText(normalizedValue?.to));
531
+ }
532
+
533
+ if (type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
534
+ return Boolean(normalizeText(normalizedValue?.min) || normalizeText(normalizedValue?.max));
535
+ }
536
+
537
+ return Boolean(normalizeText(normalizedValue));
538
+ }
539
+
540
+ function listCrudListFilterChipValues(filter = {}, rawValue) {
541
+ const normalizedValue = normalizeCrudListFilterUiValue(filter, rawValue);
542
+
543
+ if (!hasCrudListFilterUiValue(filter, normalizedValue)) {
544
+ return [];
545
+ }
546
+
547
+ if (isCrudListFilterMultiValue(filter)) {
548
+ return Array.isArray(normalizedValue) ? normalizedValue : [];
549
+ }
550
+
551
+ return [normalizedValue];
552
+ }
553
+
554
+ function formatCrudListFilterDefaultChipLabel(filter = {}, rawValue, { resolveAtomicValue = null } = {}) {
555
+ const normalizedValue = normalizeCrudListFilterUiValue(filter, rawValue);
556
+ const type = normalizeCrudListFilterType(filter.type);
557
+ const resolveAtomicLabel = typeof resolveAtomicValue === "function"
558
+ ? resolveAtomicValue
559
+ : (value) => String(value || "");
560
+
561
+ if (type === CRUD_LIST_FILTER_TYPE_FLAG) {
562
+ return filter.label;
563
+ }
564
+
565
+ if (
566
+ type === CRUD_LIST_FILTER_TYPE_ENUM ||
567
+ type === CRUD_LIST_FILTER_TYPE_PRESENCE ||
568
+ type === CRUD_LIST_FILTER_TYPE_RECORD_ID
569
+ ) {
570
+ return `${filter.label}: ${resolveAtomicLabel(normalizedValue, filter)}`;
571
+ }
572
+
573
+ if (type === CRUD_LIST_FILTER_TYPE_ENUM_MANY || type === CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY) {
574
+ const atomicValue = Array.isArray(normalizedValue)
575
+ ? normalizedValue[0] || ""
576
+ : normalizeText(normalizedValue);
577
+ return `${filter.label}: ${resolveAtomicLabel(atomicValue, filter)}`;
578
+ }
579
+
580
+ if (type === CRUD_LIST_FILTER_TYPE_DATE) {
581
+ return `${filter.label}: ${normalizedValue}`;
582
+ }
583
+
584
+ if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
585
+ if (normalizedValue?.from && normalizedValue?.to) {
586
+ return `${filter.label}: ${normalizedValue.from} to ${normalizedValue.to}`;
587
+ }
588
+ if (normalizedValue?.from) {
589
+ return `${filter.label}: from ${normalizedValue.from}`;
590
+ }
591
+ return `${filter.label}: to ${normalizedValue?.to || ""}`;
592
+ }
593
+
594
+ if (type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
595
+ if (normalizedValue?.min && normalizedValue?.max) {
596
+ return `${filter.label}: ${normalizedValue.min} to ${normalizedValue.max}`;
597
+ }
598
+ if (normalizedValue?.min) {
599
+ return `${filter.label}: min ${normalizedValue.min}`;
600
+ }
601
+ return `${filter.label}: max ${normalizedValue?.max || ""}`;
602
+ }
603
+
604
+ return filter.label;
605
+ }
46
606
 
47
607
  function normalizeCrudListFilterType(value = "") {
48
608
  const normalized = normalizeText(value);
@@ -149,20 +709,6 @@ function resolveCrudListFilterOptionSet(rawDefinition = {}, type = "") {
149
709
  }
150
710
 
151
711
  function resolveCrudListFilterQueryKeys(definition = {}) {
152
- const type = normalizeCrudListFilterType(definition.type);
153
- if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
154
- return Object.freeze([
155
- normalizeText(definition.fromKey),
156
- normalizeText(definition.toKey)
157
- ].filter(Boolean));
158
- }
159
- if (type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
160
- return Object.freeze([
161
- normalizeText(definition.minKey),
162
- normalizeText(definition.maxKey)
163
- ].filter(Boolean));
164
- }
165
-
166
712
  return Object.freeze([normalizeText(definition.queryKey)].filter(Boolean));
167
713
  }
168
714
 
@@ -201,33 +747,15 @@ function normalizeCrudListFilterDefinition(rawKey = "", rawDefinition = null) {
201
747
  : null;
202
748
 
203
749
  if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
204
- return Object.freeze({
205
- key,
206
- type,
207
- label,
208
- fromKey: normalizeText(source.fromKey) || `${key}From`,
209
- toKey: normalizeText(source.toKey) || `${key}To`,
210
- options,
211
- lookup,
212
- chipLabel,
213
- ui,
214
- meta
215
- });
750
+ if (normalizeText(source.fromKey) || normalizeText(source.toKey)) {
751
+ throw new TypeError(`CRUD list filter "${key}" uses unsupported split range keys. Use queryKey.`);
752
+ }
216
753
  }
217
754
 
218
755
  if (type === CRUD_LIST_FILTER_TYPE_NUMBER_RANGE) {
219
- return Object.freeze({
220
- key,
221
- type,
222
- label,
223
- minKey: normalizeText(source.minKey) || `${key}Min`,
224
- maxKey: normalizeText(source.maxKey) || `${key}Max`,
225
- options,
226
- lookup,
227
- chipLabel,
228
- ui,
229
- meta
230
- });
756
+ if (normalizeText(source.minKey) || normalizeText(source.maxKey)) {
757
+ throw new TypeError(`CRUD list filter "${key}" uses unsupported split range keys. Use queryKey.`);
758
+ }
231
759
  }
232
760
 
233
761
  return Object.freeze({
@@ -288,7 +816,23 @@ export {
288
816
  CRUD_LIST_FILTER_PRESENCE_PRESENT,
289
817
  CRUD_LIST_FILTER_PRESENCE_MISSING,
290
818
  CRUD_LIST_FILTER_PRESENCE_OPTIONS,
819
+ CRUD_LIST_FILTER_INVALID_VALUES_REJECT,
820
+ CRUD_LIST_FILTER_INVALID_VALUES_DISCARD,
821
+ INVALID_CRUD_LIST_FILTER_QUERY_VALUE,
822
+ normalizeCrudListFilterInvalidValues,
823
+ parseCrudListRangeQueryExpression,
824
+ formatCrudListRangeQueryExpression,
291
825
  defineCrudListFilters,
826
+ createCrudListFilterInitialValue,
827
+ isCrudListFilterMultiValue,
828
+ isCrudListFilterStructuredValue,
829
+ normalizeCrudListFilterUiValue,
830
+ areCrudListFilterUiValuesEqual,
831
+ hasCrudListFilterUiValue,
832
+ listCrudListFilterChipValues,
833
+ formatCrudListFilterDefaultChipLabel,
834
+ formatCrudListFilterQueryValue,
835
+ parseCrudListFilterQueryValue,
292
836
  resolveCrudListFilterQueryKeys,
293
837
  resolveCrudListFilterOptionLabel
294
838
  };