@object-ui/core 17.5.0 → 17.7.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.
- package/CHANGELOG.md +7404 -0
- package/README.md +127 -26
- package/dist/actions/ActionEngine.d.ts +5 -3
- package/dist/actions/ActionEngine.js +14 -10
- package/dist/actions/ActionRunner.d.ts +363 -30
- package/dist/actions/ActionRunner.js +544 -58
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +75 -13
- package/dist/actions/actionKeys.js +131 -16
- package/dist/actions/actionResponse.js +3 -3
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -0
- package/dist/actions/recordIdParam.d.ts +77 -0
- package/dist/actions/recordIdParam.js +37 -0
- package/dist/actions/serverActionHandler.d.ts +1 -1
- package/dist/actions/serverActionHandler.js +1 -1
- package/dist/adapters/ValueDataSource.d.ts +6 -1
- package/dist/adapters/ValueDataSource.js +1090 -98
- package/dist/adapters/batchTransaction.d.ts +1 -1
- package/dist/adapters/batchTransaction.js +1 -1
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +1 -1
- package/dist/data-scope/ViewDataProvider.js +21 -1
- package/dist/data-scope/element-data-source.d.ts +261 -7
- package/dist/data-scope/element-data-source.js +342 -52
- package/dist/data-scope/index.d.ts +9 -4
- package/dist/data-scope/index.js +9 -4
- package/dist/evaluator/ExpressionEvaluator.d.ts +128 -1
- package/dist/evaluator/ExpressionEvaluator.js +226 -13
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/SafeExpressionParser.js +0 -4
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +118 -17
- package/dist/evaluator/fieldRules.d.ts +107 -4
- package/dist/evaluator/fieldRules.js +231 -35
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -0
- package/dist/evaluator/listConditional.d.ts +125 -9
- package/dist/evaluator/listConditional.js +167 -26
- package/dist/evaluator/optionRules.d.ts +126 -0
- package/dist/evaluator/optionRules.js +141 -4
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +56 -0
- package/dist/evaluator/rowPredicateCanon.js +158 -0
- package/dist/evaluator/subjectPermissions.d.ts +76 -0
- package/dist/evaluator/subjectPermissions.js +44 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +97 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +397 -48
- package/dist/registry/Registry.js +263 -11
- package/dist/registry/WidgetRegistry.js +7 -3
- package/dist/registry/html-tier-intrinsics.d.ts +90 -0
- package/dist/registry/html-tier-intrinsics.js +110 -0
- package/dist/registry/public-blocks.d.ts +8 -0
- package/dist/registry/public-blocks.js +57 -0
- package/dist/theme/ThemeEngine.js +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/types/index.d.ts +92 -13
- package/dist/utils/chart-category-key.d.ts +82 -0
- package/dist/utils/chart-category-key.js +84 -0
- package/dist/utils/chart-measure-key.d.ts +64 -0
- package/dist/utils/chart-measure-key.js +65 -0
- package/dist/utils/chart-presentation.d.ts +188 -0
- package/dist/utils/chart-presentation.js +345 -0
- package/dist/utils/chart-series.d.ts +271 -31
- package/dist/utils/chart-series.js +478 -55
- package/dist/utils/column-identity.d.ts +43 -0
- package/dist/utils/column-identity.js +47 -1
- package/dist/utils/column-sortability.d.ts +157 -0
- package/dist/utils/column-sortability.js +217 -0
- package/dist/utils/dashboard-filters.d.ts +38 -6
- package/dist/utils/dashboard-filters.js +107 -61
- package/dist/utils/dataset-format.d.ts +211 -30
- package/dist/utils/dataset-format.js +407 -52
- package/dist/utils/date-display.d.ts +354 -0
- package/dist/utils/date-display.js +463 -0
- package/dist/utils/date-macros.js +7 -0
- package/dist/utils/debug.js +7 -0
- package/dist/utils/drill-down.d.ts +78 -0
- package/dist/utils/drill-down.js +93 -0
- package/dist/utils/emptiness.d.ts +96 -0
- package/dist/utils/emptiness.js +101 -0
- package/dist/utils/expand-fields.d.ts +171 -11
- package/dist/utils/expand-fields.js +171 -11
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +152 -4
- package/dist/utils/filter-converter.js +1092 -22
- package/dist/utils/filter-tokens.d.ts +49 -5
- package/dist/utils/filter-tokens.js +105 -34
- package/dist/utils/freeze-schema.d.ts +38 -1
- package/dist/utils/freeze-schema.js +10 -0
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/humanize-label.d.ts +60 -0
- package/dist/utils/humanize-label.js +62 -0
- package/dist/utils/managedBy.d.ts +30 -5
- package/dist/utils/managedBy.js +5 -5
- package/dist/utils/native-date-value.d.ts +48 -0
- package/dist/utils/native-date-value.js +167 -0
- package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
- package/dist/utils/non-grid-row-ceiling.js +141 -0
- package/dist/utils/normalize-list-view.d.ts +131 -1
- package/dist/utils/normalize-list-view.js +248 -6
- package/dist/utils/number-display.d.ts +70 -17
- package/dist/utils/number-display.js +34 -12
- package/dist/utils/object-aggregate-query.d.ts +113 -0
- package/dist/utils/object-aggregate-query.js +50 -0
- package/dist/utils/parent-scope.d.ts +123 -0
- package/dist/utils/parent-scope.js +140 -0
- package/dist/utils/predicate-fields.d.ts +62 -4
- package/dist/utils/predicate-fields.js +169 -4
- package/dist/utils/record-color.d.ts +75 -0
- package/dist/utils/record-color.js +65 -0
- package/dist/utils/record-source.d.ts +298 -0
- package/dist/utils/record-source.js +331 -0
- package/dist/utils/record-title.d.ts +148 -8
- package/dist/utils/record-title.js +220 -26
- package/dist/utils/reference-keys.d.ts +21 -22
- package/dist/utils/reference-keys.js +532 -29
- package/dist/utils/retired-field-types.d.ts +90 -0
- package/dist/utils/retired-field-types.js +124 -0
- package/dist/utils/sort-query.d.ts +128 -26
- package/dist/utils/sort-query.js +240 -13
- package/dist/utils/text-comparand.d.ts +92 -0
- package/dist/utils/text-comparand.js +99 -0
- package/dist/utils/unmaterialized-fields.d.ts +52 -0
- package/dist/utils/unmaterialized-fields.js +57 -0
- package/dist/utils/without-denied-fields.d.ts +49 -0
- package/dist/utils/without-denied-fields.js +52 -0
- package/dist/validation/index.d.ts +9 -4
- package/dist/validation/index.js +9 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +305 -82
- package/dist/validation/server-owned-value.d.ts +62 -0
- package/dist/validation/server-owned-value.js +99 -0
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -5
- package/package.json +8 -9
- package/dist/__benchmarks__/core.bench.d.ts +0 -8
- package/dist/__benchmarks__/core.bench.js +0 -53
- package/dist/data-scope/DataScopeManager.d.ts +0 -127
- package/dist/data-scope/DataScopeManager.js +0 -229
- package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
- package/dist/protocols/ResponsiveProtocol.js +0 -158
- package/dist/validation/validation-engine.d.ts +0 -88
- package/dist/validation/validation-engine.js +0 -434
|
@@ -8,21 +8,181 @@
|
|
|
8
8
|
import { columnIdentity } from './column-identity.js';
|
|
9
9
|
/**
|
|
10
10
|
* Relational ("reference-bearing") field types whose stored value is a foreign
|
|
11
|
-
* key into another object — and which therefore benefit from `$expand
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
11
|
+
* key into another object — and which therefore benefit from `$expand`, so a
|
|
12
|
+
* response carries the related record instead of only its foreign key.
|
|
13
|
+
*
|
|
14
|
+
* `user` is a lookup specialised to `sys_user`: it carries the same `reference`
|
|
15
|
+
* + id storage and the server resolves it through the same expand path as
|
|
16
|
+
* `lookup` / `master_detail`, so a `user` column that is NOT requested for
|
|
17
|
+
* expansion comes back as a raw user id (objectui#2032). What a CELL then
|
|
18
|
+
* prints for that id is deliberately not stated here — see the next section.
|
|
19
|
+
*
|
|
20
|
+
* ## No rendering claim is made here, on purpose — objectui#8693
|
|
21
|
+
*
|
|
22
|
+
* Both paragraphs above used to make one: the `user` sentence ended "…and
|
|
23
|
+
* renders as '—'", and the opening one called the unexpanded cell "a bare id
|
|
24
|
+
* placeholder ('—')". Measured on `da5e4f69e` by resolving each family through
|
|
25
|
+
* `getCellRenderer` and rendering three unexpanded primitives:
|
|
26
|
+
*
|
|
27
|
+
* | unexpanded value | `lookup` / `master_detail` / `tree` | `user` |
|
|
28
|
+
* |----------------------|-------------------------------------|--------------------|
|
|
29
|
+
* | `'u_1'` | `u_1`, plain truncated span | `u_1`, same span |
|
|
30
|
+
* | `'01HQZX9K2M4N6P8R'` | muted `—` | the raw ULID |
|
|
31
|
+
* | `'Ada Lovelace'` | `Ada Lovelace`, plain span | the raw string |
|
|
32
|
+
*
|
|
33
|
+
* Wrong twice over. `user` never drew the em-dash at all — `UserCellRenderer`
|
|
34
|
+
* has no resolver, no `useLookupName`, no `isLikelyOpaqueId`. And the three
|
|
35
|
+
* families that do draw it draw it ONLY for values `isLikelyOpaqueId` accepts;
|
|
36
|
+
* that qualifier is the entire content of the behaviour and the sentence never
|
|
37
|
+
* carried it, so a reader was told a human-readable id would degrade visibly
|
|
38
|
+
* when it printed as if it were a name — the defect objectui#8434 was for.
|
|
39
|
+
*
|
|
40
|
+
* It is not replaced by a corrected sentence, because a corrected sentence
|
|
41
|
+
* could not be kept true HERE. `@object-ui/core` does not depend on
|
|
42
|
+
* `@object-ui/fields` in `dependencies`, `peerDependencies` or
|
|
43
|
+
* `devDependencies` — the arrow runs the other way — so no test that can reach
|
|
44
|
+
* this file can render the cell it would describe, and nothing in this repo
|
|
45
|
+
* compares a docblock to rendered output. A replacement would be checked by
|
|
46
|
+
* whatever checked the last one: nobody. Its shelf life was measured too —
|
|
47
|
+
* while objectui#8693 was open, PR #8698 (objectui#8434) sat in the merge
|
|
48
|
+
* queue rewriting the `user` column of that very table: on its head
|
|
49
|
+
* `3692f7218` all three primitives render an unresolved-reference affordance
|
|
50
|
+
* that KEEPS the raw value. A corrected sentence would have been stale before
|
|
51
|
+
* it landed. The other three families are byte-identical on both trees.
|
|
52
|
+
*
|
|
53
|
+
* The storage claim is the half this set actually depends on, and that half IS
|
|
54
|
+
* checkable here (`__tests__/expand-fields.test.ts`, plus the identity pins
|
|
55
|
+
* below). How an unexpanded reference prints belongs next to the renderers in
|
|
56
|
+
* `@object-ui/fields`, where a test can fail.
|
|
22
57
|
*
|
|
23
58
|
* Note on `tree`: a self-referencing hierarchy field is a reference too, so it
|
|
24
59
|
* belongs in this set; whether the backend materialises the expanded object
|
|
25
60
|
* for it is a server concern — requesting it is harmless and forward-compatible.
|
|
61
|
+
*
|
|
62
|
+
* ## One family, many consumers — and NO reliable count of them
|
|
63
|
+
*
|
|
64
|
+
* This section used to open "One family, four consumers" and enumerate four.
|
|
65
|
+
* That count was hand-kept, and hand-kept counts of this table are exactly what
|
|
66
|
+
* keeps going wrong: it was already stale by two when objectui#5692 measured it
|
|
67
|
+
* (`paramToField` in `app-shell` had joined with objectui#5312, and
|
|
68
|
+
* `ListView`'s relational-sort rule reads the set directly), and objectui#5312's
|
|
69
|
+
* claim to have converted "the LAST private copy" was false by two MORE — see
|
|
70
|
+
* the falsification note at the end. Read the list below as the lineage of the
|
|
71
|
+
* conversions, not as a census; the mechanical fact is the identity pins.
|
|
72
|
+
*
|
|
73
|
+
* Re-measured for objectui#8693 on `da5e4f69e`: stale again, by FOUR. The
|
|
74
|
+
* commit that corrected the "fourth and last" claim (objectui#5874 / #6064)
|
|
75
|
+
* converged four more private copies of its own and never added them here —
|
|
76
|
+
* the last four bullets below. Re-derive before trusting it; the recipe is at
|
|
77
|
+
* the end of the list.
|
|
78
|
+
*
|
|
79
|
+
* This is the reference-bearing FAMILY, not the `$expand` builder's private
|
|
80
|
+
* list, and these concerns read it under several different words:
|
|
81
|
+
*
|
|
82
|
+
* - `$expand` construction — `buildExpandFields` below ("expandable");
|
|
83
|
+
* - predicate-record projection — `predicate-record.ts` ("relational");
|
|
84
|
+
* - the object form's data-source wiring — `needsDataSourceWiring` in
|
|
85
|
+
* `packages/components/src/renderers/form/form.tsx`, which decides which
|
|
86
|
+
* registered widget gets `dataSource` / `dependentValues` /
|
|
87
|
+
* `dependsOnLabels` threaded to it;
|
|
88
|
+
* - the grid's bulk-action dialog — `widgetNeedsDataSource` in
|
|
89
|
+
* `packages/plugin-grid/src/components/bulkParamToField.ts`, which decides
|
|
90
|
+
* which param widget is handed the grid's `DataSource` and which param field
|
|
91
|
+
* shape carries `reference` / `displayField`;
|
|
92
|
+
* - the action-param dialog — `paramToField` in
|
|
93
|
+
* `packages/app-shell/src/utils/paramToField.ts`, which decides which param
|
|
94
|
+
* carries a reference target (objectui#5312);
|
|
95
|
+
* - the list view's relational-sort rule — `ListView.tsx`, which will not offer
|
|
96
|
+
* a server-side sort on a field whose stored value is a foreign key;
|
|
97
|
+
* - the dashboard table's `$expand` whitelist — `computeLookupExpand` in
|
|
98
|
+
* `packages/plugin-dashboard/src/ObjectDataTable.tsx` (objectui#5692);
|
|
99
|
+
* - the dashboard's relation/link test — `isLookupType` in
|
|
100
|
+
* `packages/plugin-dashboard/src/recordFields.tsx` (objectui#5692);
|
|
101
|
+
* - the action-param RESOLVER (one step before `paramToField`) —
|
|
102
|
+
* `packages/app-shell/src/utils/resolveActionParams.ts` (objectui#5874);
|
|
103
|
+
* - the detail header's relation test — `HeaderHighlight.tsx` in
|
|
104
|
+
* `packages/plugin-detail` (objectui#5874);
|
|
105
|
+
* - the detail drawer's relation test — `RecordDetailDrawer.tsx` in
|
|
106
|
+
* `packages/plugin-detail`, reading through `isExpandableFieldType`
|
|
107
|
+
* (objectui#5874);
|
|
108
|
+
* - the kanban board's `$expand` — `ObjectKanban.tsx` in
|
|
109
|
+
* `packages/plugin-kanban`, reading through `buildExpandFields`
|
|
110
|
+
* (objectui#5874).
|
|
111
|
+
*
|
|
112
|
+
* ⚠️ Re-derive rather than trust. One command, from the repo root, over the
|
|
113
|
+
* whole tree with NO pathspec. (Narrowing it to `packages/` + a `*` segment +
|
|
114
|
+
* `/src` matches ZERO files, and would drop `apps/` besides. The two-character
|
|
115
|
+
* sequence is spelled out in words here because writing it literally would end
|
|
116
|
+
* this comment.)
|
|
117
|
+
*
|
|
118
|
+
* git grep -lE 'EXPANDABLE_FIELD_TYPES|isExpandableFieldType|buildExpandFields'
|
|
119
|
+
*
|
|
120
|
+
* 96 files on `da5e4f69e`. Drop tests, changesets and changelogs and the
|
|
121
|
+
* production readers fall into three radii, only the first of which the list
|
|
122
|
+
* above is about: this file plus 8 more naming the set directly, plus 4 more
|
|
123
|
+
* calling `isExpandableFieldType`, plus 13 more calling `buildExpandFields`.
|
|
124
|
+
* The sweep was controlled by planting a reader under `apps/console/src/` and
|
|
125
|
+
* confirming the same command found it — `apps/` holds no reader today, and a
|
|
126
|
+
* zero-hit region is otherwise indistinguishable from a blind spot.
|
|
127
|
+
*
|
|
128
|
+
* The third one used to be a second hand-maintained copy, and this comment used
|
|
129
|
+
* to claim the set "mirrors the form layer's `DATA_SOURCE_FIELD_TYPES`
|
|
130
|
+
* (`lookup` / `master_detail` / `tree`)". That was true for 15 days. The form's
|
|
131
|
+
* copy then gained `capability-multiselect` (objectui#2403) and, the same day,
|
|
132
|
+
* `object-ref` / `filter-condition` / `recipient-picker` (objectui#2421), after
|
|
133
|
+
* which the two sets were not in a subset relation in EITHER direction — `user`
|
|
134
|
+
* only here, three picker names only there — and no gate could say so
|
|
135
|
+
* (objectui#4790). The form now DERIVES from this set instead of restating it:
|
|
136
|
+
*
|
|
137
|
+
* form's data-source rule == EXPANDABLE_FIELD_TYPES
|
|
138
|
+
* + three widget-hint-only picker names
|
|
139
|
+
*
|
|
140
|
+
* The extension is one-directional and cannot fold back into this set:
|
|
141
|
+
* `object-ref` / `filter-condition` / `recipient-picker` are widget HINTS, never
|
|
142
|
+
* declarable field types (`widgetHintOnly: true` in
|
|
143
|
+
* `app-shell/src/utils/paramValueShape.ts`), so no object schema can produce a
|
|
144
|
+
* field whose `type` is one of them.
|
|
145
|
+
*
|
|
146
|
+
* The fourth was a hand-maintained copy too, one member set removed from BOTH
|
|
147
|
+
* of the above (`lookup` / `master_detail` / `user` — no `tree`, no pickers, and
|
|
148
|
+
* for a while a fifth spelling, `owner`, that objectui#4814 retired). It now
|
|
149
|
+
* derives from this set with NO extension, because the bulk dialog's params
|
|
150
|
+
* cannot produce the three widget-hint names (objectui#4815):
|
|
151
|
+
*
|
|
152
|
+
* bulk dialog's data-source rule == EXPANDABLE_FIELD_TYPES
|
|
153
|
+
*
|
|
154
|
+
* Neither consumer copies the set — both call `.has()` on THIS object, and both
|
|
155
|
+
* carry an identity pin (a spy on this `has`) so a member-identical private copy
|
|
156
|
+
* fails rather than quietly re-forking the table.
|
|
157
|
+
*
|
|
158
|
+
* ## The "LAST private copy" claim was false — objectui#5692
|
|
159
|
+
*
|
|
160
|
+
* objectui#5312 converted `paramToField` (`app-shell`) and recorded it as the
|
|
161
|
+
* fourth and LAST private copy of this rule. `packages/plugin-dashboard` held
|
|
162
|
+
* two more the whole time — `LOOKUP_TYPES` in `recordFields.tsx` and an inline
|
|
163
|
+
* disjunction inside `computeLookupExpand` in `ObjectDataTable.tsx` — which
|
|
164
|
+
* predate that sweep and were outside its file surface, so nothing contradicted
|
|
165
|
+
* the claim. Both now derive from this set with NO extension and carry the same
|
|
166
|
+
* identity pin.
|
|
167
|
+
*
|
|
168
|
+
* Those two were NOT member-identical to this set, in either direction: they
|
|
169
|
+
* lacked `tree` and carried a fifth spelling, `reference`. Converging them was
|
|
170
|
+
* therefore a behaviour change in two directions, and the direction that could
|
|
171
|
+
* have widened THIS set was settled by measurement rather than by preference:
|
|
172
|
+
* `reference` is absent from `@objectstack/spec`'s closed `FieldType`
|
|
173
|
+
* vocabulary and is refused by `FieldSchema.safeParse` — measured with `lookup`
|
|
174
|
+
* / `master_detail` / `user` / `tree` as live controls and the retired `owner`
|
|
175
|
+
* plus a nonsense spelling as dead ones — so no spec-compliant object schema can
|
|
176
|
+
* declare a field whose stored type is `reference`, and the dashboard's copies
|
|
177
|
+
* were carrying a dead spelling, not a member this set was missing. This set is
|
|
178
|
+
* unchanged by objectui#5692; the two dashboard faces simply stopped answering
|
|
179
|
+
* for a type no producer can emit.
|
|
180
|
+
*
|
|
181
|
+
* Stated so it reads as a decision rather than a surprise: **adding a member
|
|
182
|
+
* here also grants that type the form's data-source wiring.** That is the
|
|
183
|
+
* intended coupling — a type whose stored value is a foreign key needs a
|
|
184
|
+
* `DataSource` to resolve that key on the read path (expand) and on the edit
|
|
185
|
+
* path (the picker) alike.
|
|
26
186
|
*/
|
|
27
187
|
export const EXPANDABLE_FIELD_TYPES = new Set([
|
|
28
188
|
'lookup',
|
|
@@ -6,11 +6,71 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* Extract
|
|
10
|
-
* Supports: raw array, { records: [] }, { data: [] }, { value: [] }.
|
|
9
|
+
* Extract the rows array out of a `DataSource.find()` answer.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
|
10
|
-
* Supports: raw array, { records: [] }, { data: [] }, { value: [] }.
|
|
9
|
+
* Extract the rows array out of a `DataSource.find()` answer.
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
|
|
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
|
-
* @
|
|
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
|
*
|
|
@@ -108,9 +208,57 @@ export declare function convertFiltersToAST(filter: Record<string, any>): Filter
|
|
|
108
208
|
* untouched.
|
|
109
209
|
*
|
|
110
210
|
* Returns `undefined` for an absent or empty source, so callers can skip
|
|
111
|
-
* `$filter` rather than sending an empty array
|
|
211
|
+
* `$filter` rather than sending an empty array — and, since objectui#8770, for
|
|
212
|
+
* an object source that is nothing but TRUE-identity combinators
|
|
213
|
+
* (`{ $and: [] }`), and since objectui#9020 for one whose every key is skipped
|
|
214
|
+
* for a null/undefined value (`{ a: null }`). Both constrain nothing and so
|
|
215
|
+
* contribute nothing to the `and` {@link mergeFilterNodes} builds. Those answers
|
|
216
|
+
* are inherited from {@link convertFiltersToAST}, not decided a second time
|
|
217
|
+
* here — including the fact that they are two separate decisions there.
|
|
112
218
|
*/
|
|
113
219
|
export declare function toFilterNode(source: unknown): FilterNode | Record<string, any> | undefined;
|
|
220
|
+
/**
|
|
221
|
+
* {@link toFilterNode}'s answer for a caller that has no `catch` above it.
|
|
222
|
+
*
|
|
223
|
+
* ## Why this exists (objectui#9050, ruling C′ step 2)
|
|
224
|
+
*
|
|
225
|
+
* `toFilterNode` is documented as "the last stop before the wire", and the two
|
|
226
|
+
* callers that reach the wire — `plugin-list`'s `buildEffectiveFilter` and
|
|
227
|
+
* `plugin-view`'s `ObjectView` — do have a `catch`: they call it inside a load
|
|
228
|
+
* `try`, `classifyLoadError` reads this file's `INVALID_FILTER` / 400 envelope,
|
|
229
|
+
* and the author sees "this filter is malformed". Three OTHER callers reach it
|
|
230
|
+
* from a RENDER-time `useMemo` (`plugin-detail`'s `RelatedList`,
|
|
231
|
+
* `plugin-form`'s `LineItemsPanel`, `plugin-grid`'s `ObjectGrid`), where a
|
|
232
|
+
* throw is a render error and there is no `classifyLoadError` in the path.
|
|
233
|
+
* A fourth is an ADAPTER rather than a renderer: `ValueDataSource.find` lowers
|
|
234
|
+
* an array `$filter` through this before matching it in memory
|
|
235
|
+
* (objectui#10767), and a `find` that never threw on a bad filter must not
|
|
236
|
+
* start to — it re-seats `refusal` as its own excluded-and-logged refusal.
|
|
237
|
+
*
|
|
238
|
+
* ## Why the return type is a UNION and not `node | undefined`
|
|
239
|
+
*
|
|
240
|
+
* Because `undefined` is the refused answer. A `try`/`catch` that swallows the
|
|
241
|
+
* refusal and hands back `undefined` means NO FILTER — every row — which is a
|
|
242
|
+
* silently unconstrained query on a view whose whole purpose can be to hide
|
|
243
|
+
* rows, and is the failure objectui#9001 closed. So the refusal is not
|
|
244
|
+
* representable as a node: `ok: false` carries the error and has no `node` to
|
|
245
|
+
* read at all, and TypeScript makes a caller narrow before it can build a
|
|
246
|
+
* query. That is the whole difference between this and a bare `catch`.
|
|
247
|
+
*
|
|
248
|
+
* ⚠️ Only a {@link FilterOperatorError} is caught. Anything else is a defect in
|
|
249
|
+
* this file rather than a statement about the author's filter, and is rethrown
|
|
250
|
+
* so it keeps reaching whatever boundary would have seen it — a render-time
|
|
251
|
+
* `TypeError` swallowed into "your filter is malformed" would send the author
|
|
252
|
+
* to edit a filter that is fine.
|
|
253
|
+
*/
|
|
254
|
+
export type FilterNodeResult = {
|
|
255
|
+
ok: true;
|
|
256
|
+
node: FilterNode | Record<string, any> | undefined;
|
|
257
|
+
} | {
|
|
258
|
+
ok: false;
|
|
259
|
+
refusal: FilterOperatorError;
|
|
260
|
+
};
|
|
261
|
+
export declare function toFilterNodeSafely(source: unknown): FilterNodeResult;
|
|
114
262
|
/**
|
|
115
263
|
* Combine filter sources under a single `and`, each as its OWN child.
|
|
116
264
|
*
|