@object-ui/core 17.6.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 +6610 -0
- package/README.md +101 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +288 -27
- package/dist/actions/ActionRunner.js +443 -63
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +57 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -0
- 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 +86 -0
- package/dist/evaluator/ExpressionEvaluator.js +184 -12
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +117 -16
- package/dist/evaluator/fieldRules.d.ts +88 -4
- package/dist/evaluator/fieldRules.js +203 -31
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/evaluator/listConditional.d.ts +103 -34
- package/dist/evaluator/listConditional.js +127 -50
- package/dist/evaluator/optionRules.d.ts +77 -6
- package/dist/evaluator/optionRules.js +87 -10
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
- package/dist/evaluator/rowPredicateCanon.js +65 -115
- 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 +14 -0
- package/dist/index.js +80 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +358 -31
- 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/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 +95 -112
- package/dist/utils/chart-presentation.js +169 -104
- package/dist/utils/chart-series.d.ts +131 -5
- package/dist/utils/chart-series.js +98 -16
- 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 +389 -46
- 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/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 +76 -6
- package/dist/utils/expand-fields.js +76 -6
- 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 -33
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/managedBy.d.ts +3 -3
- 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 +121 -1
- package/dist/utils/normalize-list-view.js +186 -6
- package/dist/utils/number-display.d.ts +63 -14
- 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 +45 -12
- package/dist/utils/predicate-fields.js +137 -12
- 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/sort-query.d.ts +115 -15
- 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/without-denied-fields.d.ts +49 -0
- package/dist/utils/without-denied-fields.js +52 -0
- package/dist/validation/index.d.ts +8 -4
- package/dist/validation/index.js +8 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +200 -82
- package/dist/validation/validators/object-validation-engine.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -3
- package/package.json +8 -9
- 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
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import { toPredicateInput } from './predicateInput.js';
|
|
9
|
+
// A cycle with `fieldRules.ts`, which imports `isBlankPredicateText` from here.
|
|
10
|
+
// Benign by construction: each side reads the other only inside a function
|
|
11
|
+
// body, never while its module is being evaluated.
|
|
12
|
+
import { evalFieldPredicate } from './fieldRules.js';
|
|
9
13
|
/**
|
|
10
14
|
* Is the predicate TEXT blank — in EITHER spelling? (objectui#3960)
|
|
11
15
|
*
|
|
@@ -18,13 +22,22 @@ import { toPredicateInput } from './predicateInput.js';
|
|
|
18
22
|
* `ActionRunner` refused to execute, for a predicate that says nothing).
|
|
19
23
|
*
|
|
20
24
|
* This is not a fourth dialect of "empty": it is the SAME rule core's evaluation
|
|
21
|
-
* entries already apply on the value side — `evaluateCondition`
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
* entries already apply on the value side — `evaluateCondition` on both of its
|
|
26
|
+
* routes (which ask THIS function: the CEL route since objectui#8069, the
|
|
27
|
+
* legacy path since objectui#11262), `evalRowPredicate` (`listConditional.ts`,
|
|
28
|
+
* whose blank reaches `evalFieldPredicate`, which asks it too) — brought to the
|
|
29
|
+
* one place that answers "is there a condition at all?", so the two halves
|
|
30
|
+
* cannot disagree about the same blank.
|
|
31
|
+
*
|
|
32
|
+
* Exported since objectui#8069 for its second consumer, `evalFieldPredicate`
|
|
33
|
+
* (`evaluator/fieldRules.ts`), which is the entry this docblock's list did NOT
|
|
34
|
+
* name because it did not apply the rule: it trimmed the STRING spelling only,
|
|
35
|
+
* so `{ dialect: 'cel', source: ' ' }` went to the engine and came back as a
|
|
36
|
+
* parse fault while `' '` returned the caller's fallback in silence. Reusing
|
|
37
|
+
* this definition rather than writing a fourth `trim()` is the whole point of
|
|
38
|
+
* the paragraph above.
|
|
26
39
|
*/
|
|
27
|
-
function isBlankPredicateText(value) {
|
|
40
|
+
export function isBlankPredicateText(value) {
|
|
28
41
|
if (typeof value === 'string')
|
|
29
42
|
return value.trim() === '';
|
|
30
43
|
if (value !== null && typeof value === 'object') {
|
|
@@ -67,18 +80,55 @@ function isBlankPredicateText(value) {
|
|
|
67
80
|
* Neither can be read off the normalizer's answer, so both are named here —
|
|
68
81
|
* see {@link isBlankPredicateText} for why this is the layer that says it.
|
|
69
82
|
* Core's other predicate entries already treat both as blank:
|
|
70
|
-
* `evaluateCondition` (`
|
|
71
|
-
*
|
|
72
|
-
* (`evaluator/listConditional.ts`,
|
|
83
|
+
* `evaluateCondition` (`isBlankPredicateText`, then `true` — diagnosed on
|
|
84
|
+
* the CEL route since objectui#8069 and on the legacy path since
|
|
85
|
+
* objectui#11262) and `evalRowPredicate` (`evaluator/listConditional.ts`,
|
|
86
|
+
* the caller's fallback, through `evalFieldPredicate`'s `[blank]` report
|
|
87
|
+
* since objectui#11262).
|
|
73
88
|
* - `{ dialect, source: '' }` — the empty ENVELOPE. This is not an exotic
|
|
74
89
|
* spelling: `@objectstack/spec`'s `ExpressionInputSchema` normalizes every
|
|
75
90
|
* authored predicate into an envelope, so "author left the predicate empty"
|
|
76
91
|
* compiles to exactly this, which makes it the likeliest empty shape in real
|
|
77
92
|
* metadata (objectui#3850).
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
93
|
+
*
|
|
94
|
+
* The three BLANK shapes above (`''`, whitespace-only text, and an envelope
|
|
95
|
+
* whose `source` is blank) are still folded to "not declared" — that is the
|
|
96
|
+
* verdict — but no longer in silence: each is reported once (objectui#8069,
|
|
97
|
+
* ADR-0137 D4), see {@link reportBlankGate}. `null` / `undefined` (no key) is
|
|
98
|
+
* not blank TEXT and stays silent: nothing was declared there to report.
|
|
99
|
+
*
|
|
100
|
+
* ## What is DECLARED although nothing in it can be evaluated (objectui#11358)
|
|
101
|
+
*
|
|
102
|
+
* A value that is present but is not a predicate the client can evaluate — an
|
|
103
|
+
* envelope with no string `source` (the `ast`-only `{ dialect: 'cel', ast }` an
|
|
104
|
+
* unmirrored authoring door admits), a number (`0`), an object with no `source`
|
|
105
|
+
* (`{}`), an array — IS a declared gate. Triage's ruling on objectui#11358:
|
|
106
|
+
* "declared and faulting", "reported once per predicate", and the action
|
|
107
|
+
* "hides". It used to be the fifth "nothing to evaluate" shape here, answered
|
|
108
|
+
* "not declared" in silence, so a bulk action gated that way ran over every
|
|
109
|
+
* selected record and the toolbars and row menu showed it to everyone.
|
|
110
|
+
*
|
|
111
|
+
* This function's CODE did not change for it: the normalizer did.
|
|
112
|
+
* {@link toPredicateInput} no longer folds such a value into `undefined`; it
|
|
113
|
+
* returns a `cel` envelope with no `source`, so `toPredicateInput(x) !==
|
|
114
|
+
* undefined` — the line below — answers "declared", and the derivation this
|
|
115
|
+
* definition rests on still holds with no second list. The state is told apart
|
|
116
|
+
* in one place, `isUnevaluablePredicate` (`./unevaluablePredicate.ts`).
|
|
117
|
+
*
|
|
118
|
+
* What a reader then DOES with it is the evaluation step's, not this
|
|
119
|
+
* function's: `ExpressionEvaluator.evaluateCondition` and `evalFieldPredicate`
|
|
120
|
+
* treat it as a FAULT, so each reader answers with the fail direction its key
|
|
121
|
+
* already has for a predicate that cannot be evaluated, and reports it there,
|
|
122
|
+
* once — through `evalFieldPredicate`'s report with the `[unevaluable]` reason,
|
|
123
|
+
* or through the caller's own fault report. That is why this function reports
|
|
124
|
+
* nothing for it: a declared gate always goes on to an evaluation, and two
|
|
125
|
+
* reports for one fault is the noise objectstack-ai/objectstack#5149 removed.
|
|
126
|
+
* On every action `visible` leg that direction is CLOSED (objectui#11212), on
|
|
127
|
+
* every `disabled` leg too (objectui#11242), and the hide-polarity `hidden` /
|
|
128
|
+
* `hiddenOn` legs of `SchemaRenderer` answer "hidden". The keys whose fault
|
|
129
|
+
* direction is OPEN keep it — a `condition` still lets the action run, a
|
|
130
|
+
* legacy `enabled` leaves the control enabled, an action param's `visible`
|
|
131
|
+
* still shows the param — now diagnosed rather than silent.
|
|
82
132
|
*
|
|
83
133
|
* A declared-and-`false` gate is DECLARED (`toPredicateInput` returns the
|
|
84
134
|
* boolean unchanged): `disabled: false` / `visible: false` are verdicts, and
|
|
@@ -142,11 +192,62 @@ function isBlankPredicateText(value) {
|
|
|
142
192
|
* an empty predicate made the node VANISH (objectui#3955), and
|
|
143
193
|
* `ActionEngine.getActionsForLocation`'s `visible` filter, which folded some
|
|
144
194
|
* empty spellings by hand and coerced the rest with `Boolean(raw)`, so
|
|
145
|
-
* `visible: 0` hid an action the renderers showed (objectui#3957).
|
|
146
|
-
*
|
|
195
|
+
* `visible: 0` hid an action the renderers showed (objectui#3957).
|
|
196
|
+
*
|
|
197
|
+
* The last one joined with objectui#11322: plugin-grid's `hasVisibilityGate`,
|
|
198
|
+
* on the grid's selection bar, asks it through the renderer-side name
|
|
199
|
+
* `hasDeclaredVisibilityGate`. It decides whether a bulk button whose `visible`
|
|
200
|
+
* admits no record is hidden, and `partitionBulkRows` asks it before handing a
|
|
201
|
+
* bulk def's `visible` to `partitionRowsByPredicate`. It had kept the old
|
|
202
|
+
* `!= null && !== ''` scope after every other member of the family moved here,
|
|
203
|
+
* so a whitespace-only `visible` counted as declared on that bar, failed closed
|
|
204
|
+
* for every record, and hid an action the row menu and toolbars showed.
|
|
205
|
+
*
|
|
206
|
+
* Nothing in the repo now asks "is a gate declared?" anywhere but here for
|
|
207
|
+
* the keys this definition serves. What stays outside is a different family,
|
|
208
|
+
* not a twin: the field-rule `*When` keys (ADR-0137) decide it by rules of
|
|
209
|
+
* their own. Examples are the built-in Edit / Delete row item's
|
|
210
|
+
* `visibleWhen != null` (`isBuiltinRowActionVisible`), and the opening test of
|
|
211
|
+
* the shared partitioner `partitionRowsByPredicate` (`null` / `undefined` /
|
|
212
|
+
* `''`), which is the answer its built-in Delete `visibleWhen` callers get. The
|
|
213
|
+
* action family asks here before it calls that partitioner.
|
|
214
|
+
* ⚠️ Nothing re-derives this paragraph: it was read off a source search when
|
|
215
|
+
* objectui#11322 landed, and no test or gate repeats that search.
|
|
147
216
|
*/
|
|
148
217
|
export function hasDeclaredPredicate(value) {
|
|
149
|
-
if (isBlankPredicateText(value))
|
|
218
|
+
if (isBlankPredicateText(value)) {
|
|
219
|
+
reportBlankGate(value);
|
|
150
220
|
return false;
|
|
221
|
+
}
|
|
151
222
|
return toPredicateInput(value) !== undefined;
|
|
152
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Say out loud that a DECLARED gate was blank — the one side effect
|
|
226
|
+
* {@link hasDeclaredPredicate} has (objectui#8069).
|
|
227
|
+
*
|
|
228
|
+
* The fold above is objectui#3850 / #3960's ruled verdict and it stands: a
|
|
229
|
+
* blank gate is "no gate". What ADR-0137 D4 removed is the SILENCE of it — "a
|
|
230
|
+
* gate predicate that is blank or faulting is diagnosed, never a silent
|
|
231
|
+
* `true`" — and this fold is where a blank `visible` / `hidden` / `enabled` /
|
|
232
|
+
* `disabled` / `condition` stopped before it could reach any evaluator that
|
|
233
|
+
* might have said so.
|
|
234
|
+
*
|
|
235
|
+
* Reported through `evalFieldPredicate`'s own `[blank]` report — the channel
|
|
236
|
+
* every other predicate fault already uses, with its once-per-(text, locator)
|
|
237
|
+
* dedupe — and not through a warner of its own. It makes no engine call: a
|
|
238
|
+
* blank predicate never reaches the engine. The `false` it is handed is this
|
|
239
|
+
* function's answer ("not declared"); nothing reads it back.
|
|
240
|
+
*
|
|
241
|
+
* ⚠️ The locator is FIXED, because this question is key-neutral and receives
|
|
242
|
+
* nothing that names the node. So the dedupe is per blank SPELLING across the
|
|
243
|
+
* app: `''`, `' '` and a blank envelope each warn once, and a second node
|
|
244
|
+
* with the same blank spelling does not warn again. That is weaker than the
|
|
245
|
+
* field-rule path, which joins the field's locator to a blank's key, and it is
|
|
246
|
+
* the price of diagnosing at the one definition rather than at each of its
|
|
247
|
+
* callers.
|
|
248
|
+
*/
|
|
249
|
+
function reportBlankGate(value) {
|
|
250
|
+
evalFieldPredicate(value, {}, false, undefined, undefined, {
|
|
251
|
+
context: 'a declared gate, read as no gate',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
@@ -44,12 +44,54 @@ export interface FieldPredicateDiagnostic {
|
|
|
44
44
|
/**
|
|
45
45
|
* Evaluate a field-rule CEL predicate against a record.
|
|
46
46
|
*
|
|
47
|
+
* ## `fallback` is a per-CALL-SITE policy, not a default this helper owns
|
|
48
|
+
*
|
|
49
|
+
* The parameter is required and carries no default value, so every call site
|
|
50
|
+
* states a direction of its own — and the directions in this repo do not
|
|
51
|
+
* agree. Five distinct fault policies share this one helper today, which is
|
|
52
|
+
* readable nowhere but here (objectui#8069):
|
|
53
|
+
*
|
|
54
|
+
* 1. **Fixed permissive, paired with an equal "no rule" literal.**
|
|
55
|
+
* {@link resolveFieldRuleState} (`true` / `false` / `false`) and
|
|
56
|
+
* `resolveVisibleOptions` (`evaluator/optionRules.ts`, `true`). A fault
|
|
57
|
+
* produces the permissive verdict, indistinguishable from the verdict for
|
|
58
|
+
* "the author wrote no rule" — see the `…_FAULTED` / `…_ABSENT` note below.
|
|
59
|
+
* 2. **Fixed permissive, unguarded — ONE literal answers absent *and*
|
|
60
|
+
* faulted.** Every visibility call site outside core: the form renderer
|
|
61
|
+
* (`components/renderers/form/form.tsx`), console's `FormPage`, app-shell's
|
|
62
|
+
* `ScreenView`, `plugin-form`'s `WizardForm`. All of them pass `true`, and
|
|
63
|
+
* none has a separate absent branch at all.
|
|
64
|
+
* 3. **Caller-parameterised.** `evalRowPredicate`'s single-eval fast route
|
|
65
|
+
* (`evaluator/listConditional.ts`) forwards `opts.fallback`, so the
|
|
66
|
+
* direction is the mounting surface's to state.
|
|
67
|
+
* 4. **Divergence probe → fault FLAG.** `evalCel`
|
|
68
|
+
* (`evaluator/listConditional.ts`) calls this helper once per direction with
|
|
69
|
+
* `warn: false` + `onFault`: a verdict that tracks the fallback in BOTH runs
|
|
70
|
+
* is a fault, and the caller emits its own labelled warning and returns its
|
|
71
|
+
* own fallback.
|
|
72
|
+
* 5. **Divergence probe → THROW.** `ExpressionEvaluator.evaluateCelCondition`
|
|
73
|
+
* under `throwOnError` runs the same two-call trick and converts the
|
|
74
|
+
* disagreement into `throw new Error('CEL predicate failed to evaluate: …')`.
|
|
75
|
+
*
|
|
76
|
+
* ⚠️ The last two policies exist **because** `fallback` is free to specify: they
|
|
77
|
+
* detect a fault by disagreeing with themselves. Fixing a direction *inside*
|
|
78
|
+
* this helper would remove the mechanism they are built on, which is why
|
|
79
|
+
* ADR-0137 D5 keeps `fallback` free and declares the direction at the
|
|
80
|
+
* field-rule and gate layer instead.
|
|
81
|
+
*
|
|
47
82
|
* @param pred The `visibleWhen` / `readonlyWhen` / `requiredWhen` predicate.
|
|
48
83
|
* @param record The live form values (overlays prior persisted record).
|
|
49
|
-
* @param fallback Value to return when the predicate is
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
84
|
+
* @param fallback Value to return when the predicate is ABSENT (`null` /
|
|
85
|
+
* `undefined`), and — separately — the value returned when a
|
|
86
|
+
* present predicate cannot be evaluated. ⚠️ Those are two
|
|
87
|
+
* questions this one parameter answers with one value; a
|
|
88
|
+
* caller that wants different answers must branch before the
|
|
89
|
+
* call, as {@link resolveFieldRuleState} does. The historic
|
|
90
|
+
* advice is to pick the *safe* default (`false` for
|
|
91
|
+
* readonly/required — don't lock/block on error; `true` for
|
|
92
|
+
* visibility — don't hide on error). ADR-0137 kept those
|
|
93
|
+
* directions for RENDER (D3) and put the answer to a broken
|
|
94
|
+
* field rule at SUBMIT (D2) — see {@link FieldRuleFaults}.
|
|
53
95
|
* @param previous The prior persisted record, if any (for `previous.*` refs).
|
|
54
96
|
* @param scope Extra top-level scope variables bound alongside `record` —
|
|
55
97
|
* e.g. `{ parent }` so an inline line-item cell can reference
|
|
@@ -61,6 +103,45 @@ export interface FieldPredicateDiagnostic {
|
|
|
61
103
|
* is unaffected either way.
|
|
62
104
|
*/
|
|
63
105
|
export declare function evalFieldPredicate(pred: FieldRulePredicate | undefined | null, record: Record<string, unknown>, fallback: boolean, previous?: Record<string, unknown>, scope?: Record<string, unknown>, diagnostic?: FieldPredicateDiagnostic): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Which of {@link resolveFieldRuleState}'s rules could NOT be evaluated, and
|
|
108
|
+
* why — keyed by rule, valued by the reason the one-time warning prints after
|
|
109
|
+
* `Reason:` (`"[kind] message"`). A key is present only for a rule that ran and
|
|
110
|
+
* faulted (objectui#8069).
|
|
111
|
+
*
|
|
112
|
+
* This is the report a SUBMIT path reads — ADR-0137 D2, "at submit time, a
|
|
113
|
+
* field-rule predicate that cannot be evaluated refuses the write and names the
|
|
114
|
+
* field and the rule" — so that the refusal is decided by the very evaluation
|
|
115
|
+
* that drew the verdict, never by a second one with its own record assembly.
|
|
116
|
+
* Which rules a submit path refuses on is that path's ruling, not this
|
|
117
|
+
* function's: under objectui#8069's ruling (Q1 = B, one judge per rule) the
|
|
118
|
+
* client refuses `visibleWhen` only, because no server evaluates it, and leaves
|
|
119
|
+
* `readonlyWhen` / `requiredWhen` to the server's own D2 refusal. The other two
|
|
120
|
+
* keys are reported all the same, so the report does not encode that ruling.
|
|
121
|
+
*
|
|
122
|
+
* A BLANK predicate IS in it, as the `[blank]` reason. ADR-0137 makes a
|
|
123
|
+
* declared-but-blank field rule a third state, not a spelling of "no rule":
|
|
124
|
+
* "A blank predicate takes this path too, wherever one is already stored — D1
|
|
125
|
+
* keeps new ones from being authored, and D2 is what a stored one meets" (D2),
|
|
126
|
+
* and its Alternatives refuse "blank means no rule" for this triad by name. So
|
|
127
|
+
* a stored blank `visibleWhen` is refused at submit like any other fault. The
|
|
128
|
+
* trap that would otherwise make — an authorable value that no submit can ever
|
|
129
|
+
* pass — is closed at AUTHORING: the triad's form wire refuses a blank at parse
|
|
130
|
+
* (`FieldRulePredicateWireSchema` in `@object-ui/types`' `form.zod.ts`, D1).
|
|
131
|
+
* The render verdict of a blank rule is unchanged (D3: the permissive one).
|
|
132
|
+
* A blank GATE is a different thing: `hasDeclaredPredicate` still folds it to
|
|
133
|
+
* "no gate" and only diagnoses it (D4) — it never reaches this function.
|
|
134
|
+
*
|
|
135
|
+
* Deliberately NOT in it: a rule the verdict did not need. `readonlyWhen` under
|
|
136
|
+
* a static `readonly: true`, and `requiredWhen` under `serverOwnedValue` or a
|
|
137
|
+
* static `required: true`, are short-circuited and never run, so they cannot
|
|
138
|
+
* fault.
|
|
139
|
+
*/
|
|
140
|
+
export interface FieldRuleFaults {
|
|
141
|
+
visibleWhen?: string;
|
|
142
|
+
readonlyWhen?: string;
|
|
143
|
+
requiredWhen?: string;
|
|
144
|
+
}
|
|
64
145
|
/**
|
|
65
146
|
* Resolve the effective `{ visible, readonly, required }` state for a field
|
|
66
147
|
* given its conditional rules and the live record. Each `*When` rule, when
|
|
@@ -90,6 +171,8 @@ export declare function evalFieldPredicate(pred: FieldRulePredicate | undefined
|
|
|
90
171
|
* `"field 'amount'"`. The warning then reads
|
|
91
172
|
* `visibleWhen of field 'amount' …`; without it, the rule
|
|
92
173
|
* kind alone is reported.
|
|
174
|
+
* @returns The three verdicts, plus `faults` — see {@link FieldRuleFaults} for
|
|
175
|
+
* what a submit path reads from it and what it leaves out.
|
|
93
176
|
*/
|
|
94
177
|
export declare function resolveFieldRuleState(rules: {
|
|
95
178
|
visibleWhen?: FieldRulePredicate;
|
|
@@ -103,4 +186,5 @@ export declare function resolveFieldRuleState(rules: {
|
|
|
103
186
|
visible: boolean;
|
|
104
187
|
readonly: boolean;
|
|
105
188
|
required: boolean;
|
|
189
|
+
faults: FieldRuleFaults;
|
|
106
190
|
};
|
|
@@ -61,8 +61,22 @@
|
|
|
61
61
|
* logs `treating the field as LOCKED` instead). The *default* stays
|
|
62
62
|
* fail-open on purpose — flipping it is a shipped-behavior change tracked
|
|
63
63
|
* separately in objectstack#5149 (appeal 1, undecided).
|
|
64
|
+
*
|
|
65
|
+
* A predicate the author DECLARED and left BLANK is loud too, since
|
|
66
|
+
* objectui#8069 — and it did not used to be. It is a third state, not a
|
|
67
|
+
* spelling of either neighbour: the key is present (so it is not "no rule")
|
|
68
|
+
* and nothing evaluates (so no engine fault is raised), and it used to return
|
|
69
|
+
* the caller's permissive fallback before any warning could fire. `''` and
|
|
70
|
+
* `' '` are authorable — `ExpressionWireSchema` is a bare `z.string()`, and
|
|
71
|
+
* `resolveFieldRuleState`'s own guard is `!= null` — so the one state an author
|
|
72
|
+
* reaches by *starting* a rule and not finishing it was the one state that said
|
|
73
|
+
* nothing at all. Both spellings now report `[blank]` through the same single
|
|
74
|
+
* site as every other fault; the VERDICT is unchanged for every input.
|
|
64
75
|
*/
|
|
65
76
|
import { ExpressionEngine } from '@objectstack/formula';
|
|
77
|
+
import { subjectPermissionsOf } from './subjectPermissions.js';
|
|
78
|
+
import { isBlankPredicateText } from './declaredPredicate.js';
|
|
79
|
+
import { isUnevaluablePredicate, UNEVALUABLE_PREDICATE_REASON } from './unevaluablePredicate.js';
|
|
66
80
|
/** Normalize a predicate into the `Expression` shape the engine expects. */
|
|
67
81
|
function toExpression(pred) {
|
|
68
82
|
if (typeof pred === 'string')
|
|
@@ -70,15 +84,35 @@ function toExpression(pred) {
|
|
|
70
84
|
return { dialect: (pred.dialect ?? 'cel'), source: pred.source };
|
|
71
85
|
}
|
|
72
86
|
const warnedPredicates = new Set();
|
|
87
|
+
/**
|
|
88
|
+
* The `reason` reported for a predicate the author DECLARED and left blank.
|
|
89
|
+
*
|
|
90
|
+
* Tagged like an engine reason (`[parse]`, `[type]`, `[throw]`) because it
|
|
91
|
+
* travels the same two channels — the built-in warning and the `onFault`
|
|
92
|
+
* passback — and a caller that routes on the tag must be able to tell this
|
|
93
|
+
* apart from a predicate that faulted with text in it: the fix for a blank one
|
|
94
|
+
* is to finish it or delete the key, never to debug its syntax.
|
|
95
|
+
*/
|
|
96
|
+
const BLANK_PREDICATE_REASON = '[blank] the predicate is declared but empty — nothing to evaluate';
|
|
73
97
|
/**
|
|
74
98
|
* One-time warning for a predicate that could not be evaluated. Deduped per
|
|
75
99
|
* predicate TEXT (dialect + source): a broken predicate is re-evaluated on
|
|
76
100
|
* every render/keystroke, and the point is one loud line, not a scrolling
|
|
77
101
|
* wall. The dedupe key is JSON-encoded — never a control-character separator
|
|
78
102
|
* (objectstack#5450 made a sibling of this file binary to grep that way).
|
|
103
|
+
*
|
|
104
|
+
* ⚠️ One class joins the LOCATOR to that key: a BLANK predicate. The text is
|
|
105
|
+
* what identifies the authoring site for every other fault — it carries the
|
|
106
|
+
* typo — but every blank predicate in an app shares the key `""`, so text
|
|
107
|
+
* alone would let the first blank rule silence every other author's. Non-blank
|
|
108
|
+
* keys are unchanged (objectui#8069).
|
|
79
109
|
*/
|
|
80
110
|
function warnPredicateFailure(expr, fallback, reason, context) {
|
|
81
|
-
|
|
111
|
+
// `Expression['source']` is optional in the spec; an absent source is blank
|
|
112
|
+
// by the same rule as a whitespace-only one, and lands in the same key shape.
|
|
113
|
+
const key = expr.source?.trim()
|
|
114
|
+
? JSON.stringify([expr.dialect, expr.source])
|
|
115
|
+
: JSON.stringify([expr.dialect, expr.source, context ?? '']);
|
|
82
116
|
if (warnedPredicates.has(key))
|
|
83
117
|
return;
|
|
84
118
|
warnedPredicates.add(key);
|
|
@@ -92,12 +126,54 @@ function warnPredicateFailure(expr, fallback, reason, context) {
|
|
|
92
126
|
/**
|
|
93
127
|
* Evaluate a field-rule CEL predicate against a record.
|
|
94
128
|
*
|
|
129
|
+
* ## `fallback` is a per-CALL-SITE policy, not a default this helper owns
|
|
130
|
+
*
|
|
131
|
+
* The parameter is required and carries no default value, so every call site
|
|
132
|
+
* states a direction of its own — and the directions in this repo do not
|
|
133
|
+
* agree. Five distinct fault policies share this one helper today, which is
|
|
134
|
+
* readable nowhere but here (objectui#8069):
|
|
135
|
+
*
|
|
136
|
+
* 1. **Fixed permissive, paired with an equal "no rule" literal.**
|
|
137
|
+
* {@link resolveFieldRuleState} (`true` / `false` / `false`) and
|
|
138
|
+
* `resolveVisibleOptions` (`evaluator/optionRules.ts`, `true`). A fault
|
|
139
|
+
* produces the permissive verdict, indistinguishable from the verdict for
|
|
140
|
+
* "the author wrote no rule" — see the `…_FAULTED` / `…_ABSENT` note below.
|
|
141
|
+
* 2. **Fixed permissive, unguarded — ONE literal answers absent *and*
|
|
142
|
+
* faulted.** Every visibility call site outside core: the form renderer
|
|
143
|
+
* (`components/renderers/form/form.tsx`), console's `FormPage`, app-shell's
|
|
144
|
+
* `ScreenView`, `plugin-form`'s `WizardForm`. All of them pass `true`, and
|
|
145
|
+
* none has a separate absent branch at all.
|
|
146
|
+
* 3. **Caller-parameterised.** `evalRowPredicate`'s single-eval fast route
|
|
147
|
+
* (`evaluator/listConditional.ts`) forwards `opts.fallback`, so the
|
|
148
|
+
* direction is the mounting surface's to state.
|
|
149
|
+
* 4. **Divergence probe → fault FLAG.** `evalCel`
|
|
150
|
+
* (`evaluator/listConditional.ts`) calls this helper once per direction with
|
|
151
|
+
* `warn: false` + `onFault`: a verdict that tracks the fallback in BOTH runs
|
|
152
|
+
* is a fault, and the caller emits its own labelled warning and returns its
|
|
153
|
+
* own fallback.
|
|
154
|
+
* 5. **Divergence probe → THROW.** `ExpressionEvaluator.evaluateCelCondition`
|
|
155
|
+
* under `throwOnError` runs the same two-call trick and converts the
|
|
156
|
+
* disagreement into `throw new Error('CEL predicate failed to evaluate: …')`.
|
|
157
|
+
*
|
|
158
|
+
* ⚠️ The last two policies exist **because** `fallback` is free to specify: they
|
|
159
|
+
* detect a fault by disagreeing with themselves. Fixing a direction *inside*
|
|
160
|
+
* this helper would remove the mechanism they are built on, which is why
|
|
161
|
+
* ADR-0137 D5 keeps `fallback` free and declares the direction at the
|
|
162
|
+
* field-rule and gate layer instead.
|
|
163
|
+
*
|
|
95
164
|
* @param pred The `visibleWhen` / `readonlyWhen` / `requiredWhen` predicate.
|
|
96
165
|
* @param record The live form values (overlays prior persisted record).
|
|
97
|
-
* @param fallback Value to return when the predicate is
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
166
|
+
* @param fallback Value to return when the predicate is ABSENT (`null` /
|
|
167
|
+
* `undefined`), and — separately — the value returned when a
|
|
168
|
+
* present predicate cannot be evaluated. ⚠️ Those are two
|
|
169
|
+
* questions this one parameter answers with one value; a
|
|
170
|
+
* caller that wants different answers must branch before the
|
|
171
|
+
* call, as {@link resolveFieldRuleState} does. The historic
|
|
172
|
+
* advice is to pick the *safe* default (`false` for
|
|
173
|
+
* readonly/required — don't lock/block on error; `true` for
|
|
174
|
+
* visibility — don't hide on error). ADR-0137 kept those
|
|
175
|
+
* directions for RENDER (D3) and put the answer to a broken
|
|
176
|
+
* field rule at SUBMIT (D2) — see {@link FieldRuleFaults}.
|
|
101
177
|
* @param previous The prior persisted record, if any (for `previous.*` refs).
|
|
102
178
|
* @param scope Extra top-level scope variables bound alongside `record` —
|
|
103
179
|
* e.g. `{ parent }` so an inline line-item cell can reference
|
|
@@ -109,30 +185,63 @@ function warnPredicateFailure(expr, fallback, reason, context) {
|
|
|
109
185
|
* is unaffected either way.
|
|
110
186
|
*/
|
|
111
187
|
export function evalFieldPredicate(pred, record, fallback, previous, scope, diagnostic) {
|
|
112
|
-
if (pred == null
|
|
188
|
+
if (pred == null)
|
|
113
189
|
return fallback;
|
|
114
190
|
const expr = toExpression(pred);
|
|
115
|
-
// The
|
|
116
|
-
// engine's "never throws" contract — converge on one reason
|
|
117
|
-
// reporting site below, so the built-in warning and the
|
|
118
|
-
// can never describe the failure differently.
|
|
191
|
+
// The fault sources — a blank predicate, a not-ok verdict, and a throw that
|
|
192
|
+
// slipped past the engine's "never throws" contract — converge on one reason
|
|
193
|
+
// string and one reporting site below, so the built-in warning and the
|
|
194
|
+
// `onFault` passback can never describe the failure differently.
|
|
119
195
|
let reason;
|
|
120
196
|
let value = fallback;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
value = res.value === true;
|
|
197
|
+
if (isBlankPredicateText(pred)) {
|
|
198
|
+
// Declared-and-blank, in EITHER spelling. Answered here rather than by the
|
|
199
|
+
// engine for two reasons: the bare-string spelling never reached the engine
|
|
200
|
+
// at all (this line used to `return fallback` before any warning could
|
|
201
|
+
// fire — objectui#8069), and the envelope spelling reached it and came back
|
|
202
|
+
// with `AST-only evaluation not yet supported; persist \`source\`` for
|
|
203
|
+
// `{ source: '' }`, a reason that sends the author looking for a
|
|
204
|
+
// serialization bug. `isBlankPredicateText` is the repo's one definition of
|
|
205
|
+
// this question (objectui#3960) — a fourth local `trim()` here is exactly
|
|
206
|
+
// the drift it was consolidated to stop.
|
|
207
|
+
reason = BLANK_PREDICATE_REASON;
|
|
133
208
|
}
|
|
134
|
-
|
|
135
|
-
|
|
209
|
+
else if (isUnevaluablePredicate(pred)) {
|
|
210
|
+
// Declared, and nothing here can be evaluated: an envelope with no string
|
|
211
|
+
// `source` (an `ast`-only one), a number, `{}`, an array (objectui#11358).
|
|
212
|
+
// Answered here, beside the blank, for the blank's reasons: there is no
|
|
213
|
+
// `source` to hand the engine, and the engine's answer for one —
|
|
214
|
+
// `AST-only evaluation not yet supported; persist \`source\`` — was what
|
|
215
|
+
// a `0` reported too, which sends its author looking for a serializer.
|
|
216
|
+
// One reason, one tag, the same two channels.
|
|
217
|
+
reason = UNEVALUABLE_PREDICATE_REASON;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
try {
|
|
221
|
+
// objectui#4421 — the acting subject's effective object permissions, the
|
|
222
|
+
// one input `current_user.can(object, verb)` is answered from. They ride
|
|
223
|
+
// on the subject the scope already carries (`subjectPermissions.ts`),
|
|
224
|
+
// never under a scope key, because every key of `scope` is a CEL root
|
|
225
|
+
// and the engine keeps this map out of the variable namespace. A subject
|
|
226
|
+
// with none passes none, and `can` then refuses loudly: that fault goes
|
|
227
|
+
// through the caller's `fallback` like any other.
|
|
228
|
+
const permissions = subjectPermissionsOf(scope?.current_user);
|
|
229
|
+
const res = ExpressionEngine.evaluate(expr, {
|
|
230
|
+
record,
|
|
231
|
+
previous,
|
|
232
|
+
...(scope ? { extra: scope } : {}),
|
|
233
|
+
...(permissions !== undefined ? { permissions } : {}),
|
|
234
|
+
});
|
|
235
|
+
// Parse error, type error, unbound identifier, engine fault … — every
|
|
236
|
+
// not-ok verdict resolves to the fallback, but never silently (objectstack-ai/objectstack#5149).
|
|
237
|
+
if (!res.ok)
|
|
238
|
+
reason = `[${res.error.kind}] ${res.error.message}`;
|
|
239
|
+
else
|
|
240
|
+
value = res.value === true;
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
reason = `[throw] ${err instanceof Error ? err.message : String(err)}`;
|
|
244
|
+
}
|
|
136
245
|
}
|
|
137
246
|
if (reason !== undefined) {
|
|
138
247
|
if (diagnostic?.warn !== false) {
|
|
@@ -145,6 +254,60 @@ export function evalFieldPredicate(pred, record, fallback, previous, scope, diag
|
|
|
145
254
|
}
|
|
146
255
|
return value;
|
|
147
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* The verdict {@link resolveFieldRuleState} applies when a rule's predicate
|
|
259
|
+
* CANNOT BE EVALUATED — parse error, unbound identifier, engine fault, or a
|
|
260
|
+
* predicate the author left blank.
|
|
261
|
+
*
|
|
262
|
+
* ⚠️ These answer **"what should the form do when this rule is BROKEN?"** —
|
|
263
|
+
* *not* "what should the form do when the author declared NO rule?" That
|
|
264
|
+
* second question is answered separately, by the `…_ABSENT` set below. The two
|
|
265
|
+
* sets hold pairwise EQUAL values today, and that equality is a copy, not an
|
|
266
|
+
* implication: each `rules.<key> != null` ternary answers the absent case with
|
|
267
|
+
* its own literal and hands `evalFieldPredicate` the same literal as the fault
|
|
268
|
+
* fallback beside it, so every permissive value is written twice and the "it
|
|
269
|
+
* broke" answer was chosen by aligning it with the "it is absent" answer next
|
|
270
|
+
* to it. Nothing was *inherited* from a shared default — `evalFieldPredicate`'s
|
|
271
|
+
* `fallback` parameter is required and has none (objectui#8069 measured both
|
|
272
|
+
* halves).
|
|
273
|
+
*
|
|
274
|
+
* All three directions are the PERMISSIVE one, so a single mistyped column in
|
|
275
|
+
* one authored predicate yields a form that shows more, locks less and demands
|
|
276
|
+
* less — three faults from one typo, composing rather than cancelling.
|
|
277
|
+
* ADR-0137 answered that without moving any of the three: they stay the RENDER
|
|
278
|
+
* direction (D3 — a faulting `visibleWhen` shows the field), and the composite
|
|
279
|
+
* is closed at SUBMIT instead (D2), from the `faults` report
|
|
280
|
+
* {@link resolveFieldRuleState} returns beside the verdicts.
|
|
281
|
+
*
|
|
282
|
+
* **What the history records** (read on full, unshallowed history — a shallow
|
|
283
|
+
* clone answers this with one commit and no warning): the direction *was*
|
|
284
|
+
* reasoned about, once, when the helper landed (objectui#1578). This module's
|
|
285
|
+
* head and ADR-0036 both record the same rationale — the fallbacks are "chosen
|
|
286
|
+
* so a fault is *safe*: `true` for visibility (don't hide content on error),
|
|
287
|
+
* `false` for required/readonly (don't block submit or lock a field on
|
|
288
|
+
* error)". What no commit records is the COMPOSITION: every recorded argument
|
|
289
|
+
* is per-key, and the case where all three faults arrive from one typo was
|
|
290
|
+
* never put. objectstack#5149 then removed the SILENCE and left the direction
|
|
291
|
+
* explicitly undecided ("appeal 1").
|
|
292
|
+
*
|
|
293
|
+
* ⛔ These values are shipped behaviour, declared by ADR-0137 D3. Changing one
|
|
294
|
+
* is not a refactor — and `6a449fc49` leans on the `visibleWhen` half staying
|
|
295
|
+
* fail-open (a broken predicate must never silently null a stored column).
|
|
296
|
+
*/
|
|
297
|
+
const VISIBLE_WHEN_FAULTED = true;
|
|
298
|
+
const READONLY_WHEN_FAULTED = false;
|
|
299
|
+
const REQUIRED_WHEN_FAULTED = false;
|
|
300
|
+
/**
|
|
301
|
+
* The verdict {@link resolveFieldRuleState} applies when the author declared NO
|
|
302
|
+
* rule for that key at all — the `: <literal>` arm of each `!= null` ternary.
|
|
303
|
+
*
|
|
304
|
+
* Spelled apart from the `…_FAULTED` set above because they answer a different
|
|
305
|
+
* question, not because they differ: changing one of THESE changes what an
|
|
306
|
+
* unconditional field does, which is a louder and quite separate decision.
|
|
307
|
+
*/
|
|
308
|
+
const VISIBLE_WHEN_ABSENT = true;
|
|
309
|
+
const READONLY_WHEN_ABSENT = false;
|
|
310
|
+
const REQUIRED_WHEN_ABSENT = false;
|
|
148
311
|
/**
|
|
149
312
|
* Resolve the effective `{ visible, readonly, required }` state for a field
|
|
150
313
|
* given its conditional rules and the live record. Each `*When` rule, when
|
|
@@ -174,18 +337,27 @@ export function evalFieldPredicate(pred, record, fallback, previous, scope, diag
|
|
|
174
337
|
* `"field 'amount'"`. The warning then reads
|
|
175
338
|
* `visibleWhen of field 'amount' …`; without it, the rule
|
|
176
339
|
* kind alone is reported.
|
|
340
|
+
* @returns The three verdicts, plus `faults` — see {@link FieldRuleFaults} for
|
|
341
|
+
* what a submit path reads from it and what it leaves out.
|
|
177
342
|
*/
|
|
178
343
|
export function resolveFieldRuleState(rules, record, statics, previous, scope, fieldContext) {
|
|
344
|
+
const faults = {};
|
|
345
|
+
// `onFault` fires beside the built-in warning, not instead of it (`warn`
|
|
346
|
+
// stays on), so recording the report costs no diagnostic and no second
|
|
347
|
+
// engine call.
|
|
179
348
|
const diag = (rule) => ({
|
|
180
349
|
context: fieldContext ? `${rule} of ${fieldContext}` : rule,
|
|
350
|
+
onFault: (reason) => {
|
|
351
|
+
faults[rule] = reason;
|
|
352
|
+
},
|
|
181
353
|
});
|
|
182
354
|
const visible = rules.visibleWhen != null
|
|
183
|
-
? evalFieldPredicate(rules.visibleWhen, record,
|
|
184
|
-
:
|
|
355
|
+
? evalFieldPredicate(rules.visibleWhen, record, VISIBLE_WHEN_FAULTED, previous, scope, diag('visibleWhen'))
|
|
356
|
+
: VISIBLE_WHEN_ABSENT;
|
|
185
357
|
const readonly = statics.readonly === true ||
|
|
186
358
|
(rules.readonlyWhen != null
|
|
187
|
-
? evalFieldPredicate(rules.readonlyWhen, record,
|
|
188
|
-
:
|
|
359
|
+
? evalFieldPredicate(rules.readonlyWhen, record, READONLY_WHEN_FAULTED, previous, scope, diag('readonlyWhen'))
|
|
360
|
+
: READONLY_WHEN_ABSENT);
|
|
189
361
|
// Short-circuited, not evaluated-and-discarded: the verdict cannot depend on
|
|
190
362
|
// the predicate, so running it would only spend an engine call per field per
|
|
191
363
|
// render. A broken `requiredWhen` still warns wherever it decides something
|
|
@@ -195,7 +367,7 @@ export function resolveFieldRuleState(rules, record, statics, previous, scope, f
|
|
|
195
367
|
? false
|
|
196
368
|
: statics.required === true ||
|
|
197
369
|
(rules.requiredWhen != null
|
|
198
|
-
? evalFieldPredicate(rules.requiredWhen, record,
|
|
199
|
-
:
|
|
200
|
-
return { visible, readonly, required };
|
|
370
|
+
? evalFieldPredicate(rules.requiredWhen, record, REQUIRED_WHEN_FAULTED, previous, scope, diag('requiredWhen'))
|
|
371
|
+
: REQUIRED_WHEN_ABSENT);
|
|
372
|
+
return { visible, readonly, required, faults };
|
|
201
373
|
}
|
|
@@ -10,6 +10,7 @@ export * from './ExpressionEvaluator.js';
|
|
|
10
10
|
export * from './predicateInput.js';
|
|
11
11
|
export * from './declaredPredicate.js';
|
|
12
12
|
export * from './fieldRules.js';
|
|
13
|
+
export * from './subjectPermissions.js';
|
|
13
14
|
export * from './rowPredicateCanon.js';
|
|
14
15
|
export * from './listConditional.js';
|
|
15
16
|
export * from './optionRules.js';
|
package/dist/evaluator/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export * from './ExpressionEvaluator.js';
|
|
|
10
10
|
export * from './predicateInput.js';
|
|
11
11
|
export * from './declaredPredicate.js';
|
|
12
12
|
export * from './fieldRules.js';
|
|
13
|
+
export * from './subjectPermissions.js';
|
|
13
14
|
export * from './rowPredicateCanon.js';
|
|
14
15
|
export * from './listConditional.js';
|
|
15
16
|
export * from './optionRules.js';
|