@object-ui/core 17.6.0 → 18.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 (147) hide show
  1. package/CHANGELOG.md +7260 -0
  2. package/README.md +228 -14
  3. package/dist/actions/ActionEngine.d.ts +4 -2
  4. package/dist/actions/ActionEngine.js +13 -8
  5. package/dist/actions/ActionRunner.d.ts +473 -69
  6. package/dist/actions/ActionRunner.js +792 -125
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/UndoManager.d.ts +8 -13
  9. package/dist/actions/actionKeys.d.ts +9 -7
  10. package/dist/actions/actionKeys.js +67 -14
  11. package/dist/actions/actionResponse.js +2 -2
  12. package/dist/actions/index.d.ts +2 -0
  13. package/dist/actions/index.js +2 -0
  14. package/dist/actions/navigationTarget.d.ts +41 -0
  15. package/dist/actions/navigationTarget.js +20 -0
  16. package/dist/actions/recordDelete.d.ts +154 -0
  17. package/dist/actions/recordDelete.js +110 -0
  18. package/dist/adapters/ApiDataSource.d.ts +8 -4
  19. package/dist/adapters/ApiDataSource.js +19 -7
  20. package/dist/adapters/ValueDataSource.d.ts +6 -1
  21. package/dist/adapters/ValueDataSource.js +1090 -98
  22. package/dist/adapters/batchTransaction.d.ts +1 -1
  23. package/dist/adapters/batchTransaction.js +1 -1
  24. package/dist/adapters/resolveDataSource.d.ts +9 -6
  25. package/dist/adapters/resolveDataSource.js +9 -12
  26. package/dist/builder/schema-builder.d.ts +38 -81
  27. package/dist/builder/schema-builder.js +33 -152
  28. package/dist/data-scope/ViewDataProvider.d.ts +8 -25
  29. package/dist/data-scope/ViewDataProvider.js +27 -54
  30. package/dist/data-scope/element-data-source.d.ts +261 -7
  31. package/dist/data-scope/element-data-source.js +342 -52
  32. package/dist/data-scope/index.d.ts +9 -4
  33. package/dist/data-scope/index.js +9 -4
  34. package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
  35. package/dist/evaluator/ExpressionEvaluator.js +184 -12
  36. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  37. package/dist/evaluator/FormulaFunctions.js +166 -27
  38. package/dist/evaluator/deadlineSettled.d.ts +40 -0
  39. package/dist/evaluator/deadlineSettled.js +45 -0
  40. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  41. package/dist/evaluator/declaredPredicate.js +117 -16
  42. package/dist/evaluator/fieldRules.d.ts +88 -4
  43. package/dist/evaluator/fieldRules.js +208 -31
  44. package/dist/evaluator/index.d.ts +2 -0
  45. package/dist/evaluator/index.js +2 -0
  46. package/dist/evaluator/listConditional.d.ts +103 -34
  47. package/dist/evaluator/listConditional.js +127 -50
  48. package/dist/evaluator/optionRules.d.ts +77 -6
  49. package/dist/evaluator/optionRules.js +87 -10
  50. package/dist/evaluator/predicateInput.d.ts +15 -4
  51. package/dist/evaluator/predicateInput.js +35 -2
  52. package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
  53. package/dist/evaluator/rowPredicateCanon.js +65 -115
  54. package/dist/evaluator/subjectPermissions.d.ts +105 -0
  55. package/dist/evaluator/subjectPermissions.js +86 -0
  56. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  57. package/dist/evaluator/unevaluablePredicate.js +70 -0
  58. package/dist/index.d.ts +15 -0
  59. package/dist/index.js +83 -2
  60. package/dist/protocols/index.d.ts +0 -1
  61. package/dist/protocols/index.js +9 -1
  62. package/dist/registry/Registry.d.ts +382 -34
  63. package/dist/registry/Registry.js +247 -12
  64. package/dist/registry/WidgetRegistry.js +7 -3
  65. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  66. package/dist/registry/html-tier-intrinsics.js +110 -0
  67. package/dist/registry/public-blocks.d.ts +8 -0
  68. package/dist/registry/public-blocks.js +57 -0
  69. package/dist/theme/ThemeEngine.js +1 -1
  70. package/dist/utils/affordanceGrants.d.ts +371 -0
  71. package/dist/utils/affordanceGrants.js +252 -0
  72. package/dist/utils/chart-category-key.d.ts +82 -0
  73. package/dist/utils/chart-category-key.js +84 -0
  74. package/dist/utils/chart-measure-key.d.ts +64 -0
  75. package/dist/utils/chart-measure-key.js +65 -0
  76. package/dist/utils/chart-presentation.d.ts +95 -112
  77. package/dist/utils/chart-presentation.js +169 -104
  78. package/dist/utils/chart-series.d.ts +165 -5
  79. package/dist/utils/chart-series.js +123 -18
  80. package/dist/utils/column-sortability.d.ts +157 -0
  81. package/dist/utils/column-sortability.js +217 -0
  82. package/dist/utils/dashboard-filters.d.ts +38 -6
  83. package/dist/utils/dashboard-filters.js +107 -61
  84. package/dist/utils/dataset-format.d.ts +211 -30
  85. package/dist/utils/dataset-format.js +389 -46
  86. package/dist/utils/date-display.d.ts +346 -0
  87. package/dist/utils/date-display.js +644 -0
  88. package/dist/utils/date-macros.js +7 -0
  89. package/dist/utils/drill-down.d.ts +78 -0
  90. package/dist/utils/drill-down.js +93 -0
  91. package/dist/utils/emptiness.d.ts +96 -0
  92. package/dist/utils/emptiness.js +101 -0
  93. package/dist/utils/expand-fields.d.ts +81 -9
  94. package/dist/utils/expand-fields.js +81 -9
  95. package/dist/utils/extract-records.d.ts +65 -5
  96. package/dist/utils/extract-records.js +65 -8
  97. package/dist/utils/filter-converter.d.ts +158 -6
  98. package/dist/utils/filter-converter.js +1098 -24
  99. package/dist/utils/filter-tokens.d.ts +49 -5
  100. package/dist/utils/filter-tokens.js +105 -33
  101. package/dist/utils/grouping-fields.d.ts +61 -0
  102. package/dist/utils/grouping-fields.js +84 -0
  103. package/dist/utils/managedBy.d.ts +3 -3
  104. package/dist/utils/managedBy.js +5 -5
  105. package/dist/utils/native-date-value.d.ts +48 -0
  106. package/dist/utils/native-date-value.js +167 -0
  107. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  108. package/dist/utils/non-grid-row-ceiling.js +141 -0
  109. package/dist/utils/normalize-list-view.d.ts +135 -11
  110. package/dist/utils/normalize-list-view.js +243 -64
  111. package/dist/utils/number-display.d.ts +63 -14
  112. package/dist/utils/number-display.js +34 -12
  113. package/dist/utils/object-aggregate-query.d.ts +113 -0
  114. package/dist/utils/object-aggregate-query.js +50 -0
  115. package/dist/utils/parent-scope.d.ts +123 -0
  116. package/dist/utils/parent-scope.js +140 -0
  117. package/dist/utils/predicate-fields.d.ts +45 -12
  118. package/dist/utils/predicate-fields.js +141 -12
  119. package/dist/utils/record-color.d.ts +75 -0
  120. package/dist/utils/record-color.js +65 -0
  121. package/dist/utils/record-source.d.ts +298 -0
  122. package/dist/utils/record-source.js +331 -0
  123. package/dist/utils/record-title.d.ts +148 -8
  124. package/dist/utils/record-title.js +220 -26
  125. package/dist/utils/reference-keys.d.ts +21 -22
  126. package/dist/utils/reference-keys.js +532 -29
  127. package/dist/utils/sort-query.d.ts +115 -15
  128. package/dist/utils/sort-query.js +240 -13
  129. package/dist/utils/text-comparand.d.ts +92 -0
  130. package/dist/utils/text-comparand.js +99 -0
  131. package/dist/utils/without-denied-fields.d.ts +49 -0
  132. package/dist/utils/without-denied-fields.js +52 -0
  133. package/dist/validation/index.d.ts +8 -4
  134. package/dist/validation/index.js +8 -4
  135. package/dist/validation/schema-validator.d.ts +1 -1
  136. package/dist/validation/schema-validator.js +225 -83
  137. package/dist/validation/server-owned-value.d.ts +5 -1
  138. package/dist/validation/server-owned-value.js +5 -1
  139. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  140. package/dist/validation/validators/object-validation-engine.js +14 -3
  141. package/package.json +8 -9
  142. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  143. package/dist/data-scope/DataScopeManager.js +0 -229
  144. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  145. package/dist/protocols/ResponsiveProtocol.js +0 -158
  146. package/dist/validation/validation-engine.d.ts +0 -88
  147. package/dist/validation/validation-engine.js +0 -434
@@ -6,11 +6,71 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  /**
9
- * Extract an array of records from various API response formats.
10
- * Supports: raw array, { records: [] }, { data: [] }, { value: [] }.
9
+ * Extract the rows array out of a `DataSource.find()` answer.
11
10
  *
12
- * This utility normalises the different shapes returned by ObjectStack,
13
- * OData, and MSW mock endpoints so that every data-fetching component
14
- * can rely on a single extraction path.
11
+ * Accepted shapes, in the order they are tried:
12
+ *
13
+ * 1. a bare array — the live non-envelope shape fakes and `ValueDataSource`
14
+ * answer with;
15
+ * 2. `{ data: [] }` — the ONE rows member `QueryResult` (`@object-ui/types`)
16
+ * declares;
17
+ * 3. `{ value: [] }` — the OData spelling, still LIVE at this seam (see
18
+ * below).
19
+ *
20
+ * ## `records` is NOT read here (objectui#6839, following #5945 / #6726)
21
+ *
22
+ * A `records` arm used to sit AHEAD of `data` — the precedence inversion
23
+ * objectui#5945 was filed about and objectui#6726 repaired by hand in seven
24
+ * modules. `records` is the below-the-adapter spelling: both
25
+ * `ObjectStackAdapter.normalizeQueryResult` (`@object-ui/data-objectstack`)
26
+ * and `ApiDataSource.normalizeQueryResult` (its `['data','items','results',
27
+ * 'records','value']` envelope loop) CONSUME the server/SDK `records` envelope
28
+ * and return `data` before the answer ever reaches this helper. Every consumer
29
+ * below calls it strictly ABOVE that fold, so the arm was unreachable — and an
30
+ * unreachable tolerant arm is exactly where a non-conforming producer keeps
31
+ * working unrejected (AGENTS.md #0.1). Worse, being FIRST, it outranked the
32
+ * contract's own member: a producer emitting both would have had `data`
33
+ * ignored.
34
+ *
35
+ * Measured on this tree, per consumer rather than once for all of them, since
36
+ * this helper is reached from ten call sites in nine packages:
37
+ *
38
+ * packages/plugin-charts/src/ObjectChart.tsx (x2: rows, ref labels)
39
+ * packages/plugin-dashboard/src/ObjectDataTable.tsx
40
+ * packages/plugin-dashboard/src/ObjectPivotTable.tsx
41
+ * packages/plugin-gantt/src/ObjectGantt.tsx (ref option labels)
42
+ * packages/plugin-kanban/src/ObjectKanban.tsx
43
+ * packages/plugin-timeline/src/ObjectTimeline.tsx
44
+ * packages/core/src/utils/non-grid-row-ceiling.ts (applyNonGridRowCeiling,
45
+ * itself the seam for ObjectCalendar, ObjectGantt, ObjectMap, ObjectTree;
46
+ * measured when it lived in `@object-ui/react`, moved here unchanged by
47
+ * objectui#7508)
48
+ *
49
+ * ZERO of them can be reached by a `records` envelope: no `find()` in any of
50
+ * those packages, nor in the apps and examples that mount them, emits one.
51
+ * CONTROL, so the zero is a reading rather than a miss — the same sweep DOES
52
+ * find `records` envelopes elsewhere: `ViewDataProvider`'s own `ResolvedData`
53
+ * (a different contract, served by that module's own private reader), the raw
54
+ * Cloud HTTP payloads, the client-SDK doubles below `normalizeQueryResult`,
55
+ * the record-visibility batch route stubs, and one live `find()` double at
56
+ * `plugin-list`'s ObjectGallery — a consumer with its OWN unwrap ladder, which
57
+ * does not come through here.
58
+ *
59
+ * ⛔ Do not restore the arm, and ⛔ do not widen `QueryResult` to bless
60
+ * `records` instead — that is a published-type change and the maintainer's
61
+ * call, the same floor objectui#6726 and #6840 respected. A producer that
62
+ * really does speak `records` belongs behind an adapter that folds it into
63
+ * `data`, which is what both adapters above already do.
64
+ *
65
+ * ## `value` STAYS — it is live here, and that is seam-local
66
+ *
67
+ * objectui#6840 / PR #6916 deleted the `value` arm from `ObjectView`'s ladder
68
+ * on a measured zero at THAT seam, and said in as many words that its zero
69
+ * must not be carried here. It does not: five `find()` doubles emit
70
+ * `{ value: [...] }` into this helper today (three in `plugin-kanban`, two in
71
+ * `plugin-calendar`). Deleting it here would break them. Whether `value`
72
+ * should survive at this seam is its own card with its own measurement.
73
+ *
74
+ * Pinned per module by the `*.contractEnvelope-6839.*` suites.
15
75
  */
16
76
  export declare function extractRecords(results: unknown): any[];
@@ -6,21 +6,78 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  /**
9
- * Extract an array of records from various API response formats.
10
- * Supports: raw array, { records: [] }, { data: [] }, { value: [] }.
9
+ * Extract the rows array out of a `DataSource.find()` answer.
11
10
  *
12
- * This utility normalises the different shapes returned by ObjectStack,
13
- * OData, and MSW mock endpoints so that every data-fetching component
14
- * can rely on a single extraction path.
11
+ * Accepted shapes, in the order they are tried:
12
+ *
13
+ * 1. a bare array — the live non-envelope shape fakes and `ValueDataSource`
14
+ * answer with;
15
+ * 2. `{ data: [] }` — the ONE rows member `QueryResult` (`@object-ui/types`)
16
+ * declares;
17
+ * 3. `{ value: [] }` — the OData spelling, still LIVE at this seam (see
18
+ * below).
19
+ *
20
+ * ## `records` is NOT read here (objectui#6839, following #5945 / #6726)
21
+ *
22
+ * A `records` arm used to sit AHEAD of `data` — the precedence inversion
23
+ * objectui#5945 was filed about and objectui#6726 repaired by hand in seven
24
+ * modules. `records` is the below-the-adapter spelling: both
25
+ * `ObjectStackAdapter.normalizeQueryResult` (`@object-ui/data-objectstack`)
26
+ * and `ApiDataSource.normalizeQueryResult` (its `['data','items','results',
27
+ * 'records','value']` envelope loop) CONSUME the server/SDK `records` envelope
28
+ * and return `data` before the answer ever reaches this helper. Every consumer
29
+ * below calls it strictly ABOVE that fold, so the arm was unreachable — and an
30
+ * unreachable tolerant arm is exactly where a non-conforming producer keeps
31
+ * working unrejected (AGENTS.md #0.1). Worse, being FIRST, it outranked the
32
+ * contract's own member: a producer emitting both would have had `data`
33
+ * ignored.
34
+ *
35
+ * Measured on this tree, per consumer rather than once for all of them, since
36
+ * this helper is reached from ten call sites in nine packages:
37
+ *
38
+ * packages/plugin-charts/src/ObjectChart.tsx (x2: rows, ref labels)
39
+ * packages/plugin-dashboard/src/ObjectDataTable.tsx
40
+ * packages/plugin-dashboard/src/ObjectPivotTable.tsx
41
+ * packages/plugin-gantt/src/ObjectGantt.tsx (ref option labels)
42
+ * packages/plugin-kanban/src/ObjectKanban.tsx
43
+ * packages/plugin-timeline/src/ObjectTimeline.tsx
44
+ * packages/core/src/utils/non-grid-row-ceiling.ts (applyNonGridRowCeiling,
45
+ * itself the seam for ObjectCalendar, ObjectGantt, ObjectMap, ObjectTree;
46
+ * measured when it lived in `@object-ui/react`, moved here unchanged by
47
+ * objectui#7508)
48
+ *
49
+ * ZERO of them can be reached by a `records` envelope: no `find()` in any of
50
+ * those packages, nor in the apps and examples that mount them, emits one.
51
+ * CONTROL, so the zero is a reading rather than a miss — the same sweep DOES
52
+ * find `records` envelopes elsewhere: `ViewDataProvider`'s own `ResolvedData`
53
+ * (a different contract, served by that module's own private reader), the raw
54
+ * Cloud HTTP payloads, the client-SDK doubles below `normalizeQueryResult`,
55
+ * the record-visibility batch route stubs, and one live `find()` double at
56
+ * `plugin-list`'s ObjectGallery — a consumer with its OWN unwrap ladder, which
57
+ * does not come through here.
58
+ *
59
+ * ⛔ Do not restore the arm, and ⛔ do not widen `QueryResult` to bless
60
+ * `records` instead — that is a published-type change and the maintainer's
61
+ * call, the same floor objectui#6726 and #6840 respected. A producer that
62
+ * really does speak `records` belongs behind an adapter that folds it into
63
+ * `data`, which is what both adapters above already do.
64
+ *
65
+ * ## `value` STAYS — it is live here, and that is seam-local
66
+ *
67
+ * objectui#6840 / PR #6916 deleted the `value` arm from `ObjectView`'s ladder
68
+ * on a measured zero at THAT seam, and said in as many words that its zero
69
+ * must not be carried here. It does not: five `find()` doubles emit
70
+ * `{ value: [...] }` into this helper today (three in `plugin-kanban`, two in
71
+ * `plugin-calendar`). Deleting it here would break them. Whether `value`
72
+ * should survive at this seam is its own card with its own measurement.
73
+ *
74
+ * Pinned per module by the `*.contractEnvelope-6839.*` suites.
15
75
  */
16
76
  export function extractRecords(results) {
17
77
  if (Array.isArray(results)) {
18
78
  return results;
19
79
  }
20
80
  if (results && typeof results === 'object') {
21
- if (Array.isArray(results.records)) {
22
- return results.records;
23
- }
24
81
  if (Array.isArray(results.data)) {
25
82
  return results.data;
26
83
  }
@@ -23,6 +23,34 @@ export type FilterNode = [string, string, any] | [string, ...FilterNode[]];
23
23
  * @param operator - MongoDB-style operator (e.g., '$gte', '$in')
24
24
  * @returns ObjectStack operator or null if not recognized
25
25
  */
26
+ /**
27
+ * What the refusal is ABOUT, as structured data rather than as prose to scrape.
28
+ *
29
+ * `message` already names both, but a renderer that wants to say "the
30
+ * $icontains condition cannot be applied" must not recover the token with a
31
+ * regular expression over an English paragraph that every one of these
32
+ * refusals deliberately writes differently. objectui#9050's step 2 requires the
33
+ * render-time diagnostic to NAME the operator, so the name travels as a field.
34
+ *
35
+ * `operator` is the spelling the AUTHOR wrote — `$startswith` for a retired
36
+ * alias, `icontains` for a view rule — never a canonical form substituted for
37
+ * it, for the reason the view-rule arm's own tail already gives: prescribing a
38
+ * spelling the author's vocabulary does not have sends them looking for a key
39
+ * their metadata cannot contain.
40
+ *
41
+ * It is OPTIONAL because some refusals are not about an operator at all: the
42
+ * bare-array and exotic-comparand arms judge a COMPARAND written in the
43
+ * implicit-equality position, and the empty-operator-map arm judges a field
44
+ * that names NO operator (`{ a: {} }`, objectui#9164). In each, no operator was
45
+ * spelled and `field` is the only handle the author can act on. A renderer
46
+ * reads {@link filterRefusalSubject} rather than choosing between them itself.
47
+ */
48
+ export interface FilterRefusalSubject {
49
+ /** The operator spelling the author wrote, when the refusal is about one. */
50
+ operator?: string;
51
+ /** The field the refused condition is on, when the refusal is about one. */
52
+ field?: string;
53
+ }
26
54
  /**
27
55
  * A filter operator this layer will not translate.
28
56
  *
@@ -34,8 +62,22 @@ export type FilterNode = [string, string, any] | [string, ...FilterNode[]];
34
62
  export declare class FilterOperatorError extends Error {
35
63
  readonly code = "INVALID_FILTER";
36
64
  readonly httpStatus = 400;
37
- constructor(message: string);
65
+ /** @see FilterRefusalSubject */
66
+ readonly operator: string | undefined;
67
+ /** @see FilterRefusalSubject */
68
+ readonly field: string | undefined;
69
+ constructor(message: string, subject?: FilterRefusalSubject);
38
70
  }
71
+ /**
72
+ * The one token a "this view's filter is malformed" state names.
73
+ *
74
+ * Operator first, because that is what the ruling asks the state to name and
75
+ * what an author can act on — `$regex` is not supported, `$startswith` is
76
+ * retired. Field second, for the refusals that have no operator to name — the
77
+ * two comparand arms and the empty operator map. `undefined` for neither, which no arm produces today and which a
78
+ * renderer must still handle rather than print "undefined" at a user.
79
+ */
80
+ export declare function filterRefusalSubject(error: FilterOperatorError): string | undefined;
39
81
  export declare function convertOperatorToAST(operator: string): string | null;
40
82
  /**
41
83
  * Convert object-based filters to ObjectStack FilterNode AST format.
@@ -59,9 +101,67 @@ export declare function convertOperatorToAST(operator: string): string | null;
59
101
  * convertFiltersToAST({ age: { $gte: 18, $lte: 65 }, status: 'active' })
60
102
  * // => ['and', ['age', '>=', 18], ['age', '<=', 65], ['status', '=', 'active']]
61
103
  *
62
- * @throws {Error} If an unknown operator is encountered
104
+ * @example
105
+ * // Logical combinators (objectui#6948) — children lower recursively
106
+ * convertFiltersToAST({ $or: [{ status: 'open' }, { status: 'blocked' }] })
107
+ * // => ['or', ['status', '=', 'open'], ['status', '=', 'blocked']]
108
+ *
109
+ * @example
110
+ * // A Date is a comparand, not an operator map (objectui#8555). It lowers as
111
+ * // the Date INSTANCE — the spec accepts one, and the operator form already
112
+ * // emits one.
113
+ * convertFiltersToAST({ created: new Date('2026-01-01') })
114
+ * // => ['created', '=', Date(2026-01-01)]
115
+ *
116
+ * @throws {FilterOperatorError} If an unknown operator is encountered, if
117
+ * `$not` is used — see the `$not` arm for why the AST cannot carry it — if a
118
+ * field's value is a bare ARRAY (`{ tags: ['a', 'b'] }`) — see the array arm for
119
+ * why that is refused rather than read as `$in` — or if a field's value is an
120
+ * EXOTIC object the spec does not accept as a comparand (`{ name: /abc/ }`,
121
+ * a `Set`, a `Map`): see the exotic-comparand arm for why that is refused
122
+ * rather than dropped (objectui#8567), or if an `$icontains` comparand is not a
123
+ * NON-EMPTY STRING (`{ name: { $icontains: '' } }`, `{ name: { $icontains: 42 } }`)
124
+ * — two shapes `@objectstack/spec`'s `FILTER_TEXT_CASES` declares REFUSED and
125
+ * `ValueDataSource` has refused since objectui#8748; see
126
+ * {@link refuseTextComparand} (objectui#9001), or if a field is an EMPTY
127
+ * operator map (`{ a: {} }`) — alone (objectui#9164) or
128
+ * beside a key that lowers (objectui#10788): see {@link refuseEmptyOperatorMap},
129
+ * or if a `$empty` flag is not a boolean (`{ a: { $empty: 'yes' } }`,
130
+ * objectui#11094).
131
+ *
132
+ * @example
133
+ * // A field with NO operator (objectui#9164)
134
+ * convertFiltersToAST({ a: {} })
135
+ * // => throws FilterOperatorError (INVALID_FILTER / 400), field 'a'
136
+ *
137
+ * @example
138
+ * // … and beside a key that lowers, where it used to vanish and widen the
139
+ * // result to `['status', '=', 'a']` (objectui#10788)
140
+ * convertFiltersToAST({ status: 'a', created: {} })
141
+ * // => throws FilterOperatorError (INVALID_FILTER / 400), field 'created'
142
+ *
143
+ * @example
144
+ * // A filter that is NOTHING BUT combinators reducing to the TRUE identity
145
+ * // constrains nothing, and says so (objectui#8770). Callers skip the slot.
146
+ * convertFiltersToAST({ $and: [] })
147
+ * // => undefined
148
+ *
149
+ * @example
150
+ * // A filter whose EVERY key is skipped for a null/undefined value also
151
+ * // constrains nothing — the same answer this function already gives that key
152
+ * // when a sibling survives (objectui#9020). Callers skip the slot.
153
+ * convertFiltersToAST({ a: null })
154
+ * // => undefined
155
+ *
156
+ * @example
157
+ * // … and a filter that MIXES the two constrains nothing either, because the
158
+ * // identity fold counts only the keys the loop actually PROCESSED
159
+ * // (objectui#9030). A skipped key used to be counted against that fold, so a
160
+ * // filter whose every key folds alone did not fold together.
161
+ * convertFiltersToAST({ $and: [], b: undefined })
162
+ * // => undefined
63
163
  */
64
- export declare function convertFiltersToAST(filter: Record<string, any>): FilterNode | Record<string, any>;
164
+ export declare function convertFiltersToAST(filter: Record<string, any>): FilterNode | Record<string, any> | undefined;
65
165
  /**
66
166
  * Normalize ONE filter source into a single filter node.
67
167
  *
@@ -74,8 +174,12 @@ export declare function convertFiltersToAST(filter: Record<string, any>): Filter
74
174
  *
75
175
  * The third is the one that kept getting lost. Renderers tested `source.length
76
176
  * > 0` before using it, which is `undefined > 0` for an object — so a
77
- * `table.defaultFilters` (declared `Record<string, any>`) was DROPPED and the
78
- * view returned every record. Silently: no error, just a wider answer.
177
+ * `table.defaultFilters` (declared `Record<string, any>` until objectui#6152
178
+ * round 10; the protocol's `ViewFilterRule` array since, and the record no
179
+ * longer validates there) was DROPPED and the view returned every record.
180
+ * Silently: no error, just a wider answer. That narrowing moved what an author
181
+ * may write, not this sink: the object arm below still lowers a record whatever
182
+ * caller hands it one.
79
183
  *
80
184
  * The FIRST never worked at all (objectui#3431). The array branch returned
81
185
  * every array VERBATIM, so a saved view's `ViewFilterRule[]` travelled to
@@ -108,9 +212,57 @@ export declare function convertFiltersToAST(filter: Record<string, any>): Filter
108
212
  * untouched.
109
213
  *
110
214
  * Returns `undefined` for an absent or empty source, so callers can skip
111
- * `$filter` rather than sending an empty array.
215
+ * `$filter` rather than sending an empty array — and, since objectui#8770, for
216
+ * an object source that is nothing but TRUE-identity combinators
217
+ * (`{ $and: [] }`), and since objectui#9020 for one whose every key is skipped
218
+ * for a null/undefined value (`{ a: null }`). Both constrain nothing and so
219
+ * contribute nothing to the `and` {@link mergeFilterNodes} builds. Those answers
220
+ * are inherited from {@link convertFiltersToAST}, not decided a second time
221
+ * here — including the fact that they are two separate decisions there.
112
222
  */
113
223
  export declare function toFilterNode(source: unknown): FilterNode | Record<string, any> | undefined;
224
+ /**
225
+ * {@link toFilterNode}'s answer for a caller that has no `catch` above it.
226
+ *
227
+ * ## Why this exists (objectui#9050, ruling C′ step 2)
228
+ *
229
+ * `toFilterNode` is documented as "the last stop before the wire", and the two
230
+ * callers that reach the wire — `plugin-list`'s `buildEffectiveFilter` and
231
+ * `plugin-view`'s `ObjectView` — do have a `catch`: they call it inside a load
232
+ * `try`, `classifyLoadError` reads this file's `INVALID_FILTER` / 400 envelope,
233
+ * and the author sees "this filter is malformed". Three OTHER callers reach it
234
+ * from a RENDER-time `useMemo` (`plugin-detail`'s `RelatedList`,
235
+ * `plugin-form`'s `LineItemsPanel`, `plugin-grid`'s `ObjectGrid`), where a
236
+ * throw is a render error and there is no `classifyLoadError` in the path.
237
+ * A fourth is an ADAPTER rather than a renderer: `ValueDataSource.find` lowers
238
+ * an array `$filter` through this before matching it in memory
239
+ * (objectui#10767), and a `find` that never threw on a bad filter must not
240
+ * start to — it re-seats `refusal` as its own excluded-and-logged refusal.
241
+ *
242
+ * ## Why the return type is a UNION and not `node | undefined`
243
+ *
244
+ * Because `undefined` is the refused answer. A `try`/`catch` that swallows the
245
+ * refusal and hands back `undefined` means NO FILTER — every row — which is a
246
+ * silently unconstrained query on a view whose whole purpose can be to hide
247
+ * rows, and is the failure objectui#9001 closed. So the refusal is not
248
+ * representable as a node: `ok: false` carries the error and has no `node` to
249
+ * read at all, and TypeScript makes a caller narrow before it can build a
250
+ * query. That is the whole difference between this and a bare `catch`.
251
+ *
252
+ * ⚠️ Only a {@link FilterOperatorError} is caught. Anything else is a defect in
253
+ * this file rather than a statement about the author's filter, and is rethrown
254
+ * so it keeps reaching whatever boundary would have seen it — a render-time
255
+ * `TypeError` swallowed into "your filter is malformed" would send the author
256
+ * to edit a filter that is fine.
257
+ */
258
+ export type FilterNodeResult = {
259
+ ok: true;
260
+ node: FilterNode | Record<string, any> | undefined;
261
+ } | {
262
+ ok: false;
263
+ refusal: FilterOperatorError;
264
+ };
265
+ export declare function toFilterNodeSafely(source: unknown): FilterNodeResult;
114
266
  /**
115
267
  * Combine filter sources under a single `and`, each as its OWN child.
116
268
  *