@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.
- package/CHANGELOG.md +7260 -0
- package/README.md +228 -14
- package/dist/actions/ActionEngine.d.ts +4 -2
- package/dist/actions/ActionEngine.js +13 -8
- package/dist/actions/ActionRunner.d.ts +473 -69
- package/dist/actions/ActionRunner.js +792 -125
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/UndoManager.d.ts +8 -13
- package/dist/actions/actionKeys.d.ts +9 -7
- package/dist/actions/actionKeys.js +67 -14
- package/dist/actions/actionResponse.js +2 -2
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/navigationTarget.d.ts +41 -0
- package/dist/actions/navigationTarget.js +20 -0
- package/dist/actions/recordDelete.d.ts +154 -0
- package/dist/actions/recordDelete.js +110 -0
- package/dist/adapters/ApiDataSource.d.ts +8 -4
- package/dist/adapters/ApiDataSource.js +19 -7
- 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/adapters/resolveDataSource.d.ts +9 -6
- package/dist/adapters/resolveDataSource.js +9 -12
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +8 -25
- package/dist/data-scope/ViewDataProvider.js +27 -54
- 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/deadlineSettled.d.ts +40 -0
- package/dist/evaluator/deadlineSettled.js +45 -0
- 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 +208 -31
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -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 +105 -0
- package/dist/evaluator/subjectPermissions.js +86 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +83 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +382 -34
- package/dist/registry/Registry.js +247 -12
- 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/affordanceGrants.d.ts +371 -0
- package/dist/utils/affordanceGrants.js +252 -0
- 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 +165 -5
- package/dist/utils/chart-series.js +123 -18
- 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 +346 -0
- package/dist/utils/date-display.js +644 -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 +81 -9
- package/dist/utils/expand-fields.js +81 -9
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +158 -6
- package/dist/utils/filter-converter.js +1098 -24
- 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 +135 -11
- package/dist/utils/normalize-list-view.js +243 -64
- 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 +141 -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 +225 -83
- package/dist/validation/server-owned-value.d.ts +5 -1
- package/dist/validation/server-owned-value.js +5 -1
- 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,70 +6,76 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* The row-predicate spelling CANON
|
|
10
|
-
*
|
|
9
|
+
* The row-predicate spelling CANON: on a runtime record surface the row is bound
|
|
10
|
+
* as `record.*` and nothing else (objectui#5330, maintainer ruling 2026-08-20,
|
|
11
|
+
* option B; Phase 2 executed by `83fe6e741`).
|
|
11
12
|
*
|
|
12
|
-
* ## The canon
|
|
13
|
+
* ## The canon
|
|
13
14
|
*
|
|
14
15
|
* A row predicate — `visible` / `disabled` / `enabled` on an action renderer, a
|
|
15
|
-
* row scope, a `record:alert
|
|
16
|
-
* `record.status`, bare shorthand `status`,
|
|
17
|
-
* (objectui#4075 / PR #4079 bound all four action
|
|
18
|
-
* restore consistency, deliberately without
|
|
19
|
-
* CONTRACT).
|
|
20
|
-
*
|
|
21
|
-
* > **The canon is `record.*`.**
|
|
22
|
-
* >
|
|
23
|
-
* >
|
|
24
|
-
* >
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* ## The canon states the SERVER's accept set, not this client's
|
|
35
|
-
*
|
|
36
|
-
* The ruling made that the dev's first measurement, because a canon that only
|
|
37
|
-
* describes the renderer would be the very thing it exists to end. Measured
|
|
38
|
-
* against `@objectstack/formula@17.1.0` — the engine the server evaluates with,
|
|
39
|
-
* and the one `fieldRules.ts` already delegates to:
|
|
16
|
+
* row scope, a `record:alert`, a conditional-formatting `condition` — used to
|
|
17
|
+
* bind the row THREE ways: canonical `record.status`, bare shorthand `status`,
|
|
18
|
+
* and legacy `data.status` (objectui#4075 / PR #4079 bound all four action
|
|
19
|
+
* renderers all three ways to restore consistency, deliberately without
|
|
20
|
+
* deciding which of them was CONTRACT). The ruling decided it, in two phases:
|
|
21
|
+
*
|
|
22
|
+
* > **The canon is `record.*`.** Phase 1 (`d1ab06f0f`) declared it and warned
|
|
23
|
+
* > once, in dev, on the two other spellings. Phase 2 (`83fe6e741`, ruled
|
|
24
|
+
* > 2026-09-02 and amended 2026-09-05) retired them: the bare shorthand and
|
|
25
|
+
* > `data.*` are no longer bound on runtime record surfaces, and the Phase-1
|
|
26
|
+
* > warning went with them. No stored-metadata survey was run (「不考虑存量」);
|
|
27
|
+
* > the Phase-1 warning period was the notice.
|
|
28
|
+
*
|
|
29
|
+
* ## What a retired spelling does now: it FAULTS, as it always did on the server
|
|
30
|
+
*
|
|
31
|
+
* The canon states the SERVER's accept set. Measured against
|
|
32
|
+
* `@objectstack/formula@17.1.0` — the engine the server evaluates with, and the
|
|
33
|
+
* one `fieldRules.ts` delegates to:
|
|
40
34
|
*
|
|
41
35
|
* | spelling | server runtime (`buildScope` + `celEngine`) | server authoring oracle |
|
|
42
36
|
* |---|---|---|
|
|
43
37
|
* | `record.status` | ✅ `{ ok: true, value: true }` | ✅ accepted |
|
|
44
38
|
* | bare `status` | ❌ `Unknown variable: status` | ❌ refused (`'status'`) |
|
|
45
|
-
* | `data.status` | ❌ `Unknown variable: data` | ⚠️
|
|
46
|
-
*
|
|
47
|
-
* `buildScope({ record })` mounts exactly `['record']
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
39
|
+
* | `data.status` | ❌ `Unknown variable: data` | ⚠️ silently accepted |
|
|
40
|
+
*
|
|
41
|
+
* `buildScope({ record })` mounts exactly `['record']`. Since Phase 2 the client
|
|
42
|
+
* binds the same set (`listConditional.ts`' scope bag and `@object-ui/react`'s
|
|
43
|
+
* `usePredicateRecordContext`), so both retired spellings fault on the client
|
|
44
|
+
* with the server's verdict, and each surface applies its EXISTING fault policy:
|
|
45
|
+
* fail-closed `visible` on the throwing `useCondition` legs, the caller's
|
|
46
|
+
* `fallback` on `evalRowPredicate` / `partitionRowsByPredicate`, fail-soft on
|
|
47
|
+
* the non-throwing `useCondition` legs. There is no runtime detector, no
|
|
48
|
+
* "treat as absent" special case and no uniform override — a retired spelling
|
|
49
|
+
* is not a recognised-and-rejected thing, it is an unknown variable like any
|
|
50
|
+
* other, and the existing fault warnings are what name it.
|
|
51
|
+
*
|
|
52
|
+
* One consequence, stated so it is not read back as a bug: a host scope may
|
|
53
|
+
* legitimately carry its OWN `data` (a rowless dialog's, or app-shell's ambient
|
|
54
|
+
* `data: {}`), and it is left standing. `data.*` on a record surface then reads
|
|
55
|
+
* the host's object rather than the row — which is exactly what "no longer bound
|
|
56
|
+
* to the row" means, and, against an ambient `data: {}`, the constant-false
|
|
57
|
+
* signature the Phase-1 warning text already described for the server.
|
|
58
|
+
*
|
|
59
|
+
* ## `data.*` is retired HERE, not everywhere — the canon is layer-scoped
|
|
64
60
|
*
|
|
65
61
|
* `data` is the CANONICAL root one layer over, in a metadata-editing form (the
|
|
66
62
|
* row under edit): objectstack's `CANONICAL_ROOT_BY_LAYER` reads
|
|
67
63
|
* `{ runtime: 'record', metadata: 'data' }` (ADR-0089 D3), and objectui's own
|
|
68
|
-
* `app-shell` metadata-admin `SchemaForm` binds `{ data: row }` on purpose
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
64
|
+
* `app-shell` metadata-admin `SchemaForm` binds `{ data: row }` on purpose,
|
|
65
|
+
* through its own evaluator (`views/metadata-admin/predicate.ts`) — never
|
|
66
|
+
* through `evalRowPredicate` or `usePredicateRecordContext`. So `data.*` is not
|
|
67
|
+
* retired platform-wide: it is a WRONG-LAYER spelling on a runtime record
|
|
68
|
+
* surface, and that is the only thing the detector below reports.
|
|
69
|
+
*
|
|
70
|
+
* ## What is left in this module, and why
|
|
71
|
+
*
|
|
72
|
+
* {@link detectNonCanonicalRowSpelling} is the OFFLINE instrument: it classifies
|
|
73
|
+
* an authored predicate's spelling against a row without evaluating it, so a
|
|
74
|
+
* sweep over stored or in-repo metadata (the `702c48a62` corpus sweep's
|
|
75
|
+
* recipe) can find the documents that still need rewriting. It is
|
|
76
|
+
* exported for that purpose and nothing on the hot path calls it — the runtime
|
|
77
|
+
* warning half Phase 1 built on it (`warnNonCanonicalRowSpelling`,
|
|
78
|
+
* `resetRowPredicateCanonWarnings`) was removed with the bindings.
|
|
73
79
|
*
|
|
74
80
|
* ## Why the detection reuses the server's oracle instead of a regex
|
|
75
81
|
*
|
|
@@ -94,8 +100,8 @@ const METADATA_LAYER_ROOT = 'data';
|
|
|
94
100
|
* predicate is already canonical (or is not this module's verdict to give).
|
|
95
101
|
*
|
|
96
102
|
* Deliberately conservative in both arms — every condition below can only
|
|
97
|
-
* REMOVE a finding, never invent one, because a false
|
|
98
|
-
*
|
|
103
|
+
* REMOVE a finding, never invent one, because a false finding sends an author
|
|
104
|
+
* to rewrite a predicate that was correct:
|
|
99
105
|
*
|
|
100
106
|
* - **Unparseable source** → `null`. Syntax is another gate's verdict.
|
|
101
107
|
* - **Bare shorthand** is reported only when the undeclared identifier is an own
|
|
@@ -113,10 +119,11 @@ const METADATA_LAYER_ROOT = 'data';
|
|
|
113
119
|
* predicate manages both it is the one the author must fix to have anything
|
|
114
120
|
* evaluate at all.
|
|
115
121
|
*
|
|
116
|
-
* @param source The predicate's CEL text.
|
|
117
|
-
*
|
|
118
|
-
* dialect
|
|
119
|
-
*
|
|
122
|
+
* @param source The predicate's CEL text. A legacy `${…}`-dialect string
|
|
123
|
+
* is not CEL and returns `null` here (unparseable); classify
|
|
124
|
+
* it by its own dialect's rules — on a runtime record surface
|
|
125
|
+
* it retired with the CEL spellings (`83fe6e741`), while on
|
|
126
|
+
* the schema/widget tier `data` is a different scope entirely.
|
|
120
127
|
* @param row The row the predicate is bound against.
|
|
121
128
|
* @param dataNamesRow Whether `data` is bound to that same row on this surface.
|
|
122
129
|
*/
|
|
@@ -149,60 +156,3 @@ export function detectNonCanonicalRowSpelling(source, row, dataNamesRow) {
|
|
|
149
156
|
}
|
|
150
157
|
return null;
|
|
151
158
|
}
|
|
152
|
-
/**
|
|
153
|
-
* Dev-mode gate, matching `utils/dashboard-filters.ts` and `actions/actionKeys.ts`
|
|
154
|
-
* — a deprecation warning that floods a production console is a warning that
|
|
155
|
-
* gets muted.
|
|
156
|
-
*/
|
|
157
|
-
const isDev = () => globalThis.process?.env
|
|
158
|
-
?.NODE_ENV !== 'production';
|
|
159
|
-
/**
|
|
160
|
-
* Warn-once memo, keyed by the `(label, predicate source)` pair — the same
|
|
161
|
-
* identity `warnEvalError` uses, and JSON-encoded for the same reason: the
|
|
162
|
-
* separator that boundary once used was a raw U+0000, which made the file
|
|
163
|
-
* carrying it binary to grep (objectstack#5450). Keying on the source alone
|
|
164
|
-
* would report the first surface carrying a shorthand `status` predicate and
|
|
165
|
-
* stay silent about every other one; the label is what sends the author to the
|
|
166
|
-
* right screen.
|
|
167
|
-
*
|
|
168
|
-
* Module scope, not per-call: these predicates are re-evaluated on every row of
|
|
169
|
-
* every render, so per-call state would warn once per frame — the flood the
|
|
170
|
-
* dedupe exists to prevent.
|
|
171
|
-
*/
|
|
172
|
-
const warnedSpellings = new Set();
|
|
173
|
-
/** Reset the row-predicate spelling warn-once memo. Exported for tests. */
|
|
174
|
-
export function resetRowPredicateCanonWarnings() {
|
|
175
|
-
warnedSpellings.clear();
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Report a non-canonical row-predicate spelling once, in dev.
|
|
179
|
-
*
|
|
180
|
-
* Phase 1 of the objectui#5330 window: the binding is UNCHANGED and every
|
|
181
|
-
* spelling still resolves — this only says so out loud, so the stored
|
|
182
|
-
* population stops growing and a later survey has something to count. It is a
|
|
183
|
-
* warning and deliberately not a refusal: turning it into one would move the
|
|
184
|
-
* accept/reject set, which this card is explicitly not entitled to do.
|
|
185
|
-
*/
|
|
186
|
-
export function warnNonCanonicalRowSpelling(source, row, dataNamesRow, label) {
|
|
187
|
-
if (!isDev())
|
|
188
|
-
return;
|
|
189
|
-
const finding = detectNonCanonicalRowSpelling(source, row, dataNamesRow);
|
|
190
|
-
if (!finding)
|
|
191
|
-
return;
|
|
192
|
-
const key = JSON.stringify([label ?? '', source, finding.kind]);
|
|
193
|
-
if (warnedSpellings.has(key))
|
|
194
|
-
return;
|
|
195
|
-
warnedSpellings.add(key);
|
|
196
|
-
const where = label ? ` (${label})` : '';
|
|
197
|
-
const detail = finding.kind === 'bare-shorthand'
|
|
198
|
-
? `it references the bare field ${JSON.stringify(finding.identifier)}; ` +
|
|
199
|
-
`the server refuses that spelling outright ("Unknown variable: ${finding.identifier}")`
|
|
200
|
-
: 'it is rooted at `data.`, which is the metadata-editing-form root — on a ' +
|
|
201
|
-
'record surface the server binds no `data` at all, so the predicate is a ' +
|
|
202
|
-
'constant false there rather than an error';
|
|
203
|
-
console.warn(`[object-ui] A row predicate${where} uses a DEPRECATED spelling: ` +
|
|
204
|
-
`${JSON.stringify(source)} — ${detail}. The canon is \`record.*\` ` +
|
|
205
|
-
`(objectui#5330, ruled 2026-08-20): write \`${finding.canonical}\`. ` +
|
|
206
|
-
'This still evaluates here for now; the tolerance retires after a ' +
|
|
207
|
-
'stored-metadata survey.');
|
|
208
|
-
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The acting subject's effective object permissions, carried to the one place
|
|
10
|
+
* this repo hands a predicate to the CEL engine (objectui#4421).
|
|
11
|
+
*
|
|
12
|
+
* ## What it is for
|
|
13
|
+
*
|
|
14
|
+
* `@objectstack/formula` registers `current_user.can(object, verb)` as a
|
|
15
|
+
* receiver method on the acting subject, and answers it from
|
|
16
|
+
* `EvalContext.permissions` — the verbatim `objects` map of the
|
|
17
|
+
* `/auth/me/permissions` response. That map is a separate field of the engine
|
|
18
|
+
* context, and the engine is explicit that it is ⛔ NOT a CEL variable: an
|
|
19
|
+
* authored predicate must not be able to read `permissions.crm_lead.allowEdit`
|
|
20
|
+
* and step around the closed verb table.
|
|
21
|
+
*
|
|
22
|
+
* This repo's evaluation seam (`evalFieldPredicate`) is not handed an engine
|
|
23
|
+
* context by its callers, though. Every action surface — the row menu, the
|
|
24
|
+
* record header, the `action:*` renderers, `DeclaredActionsBar` — hands it a
|
|
25
|
+
* variable BAG (the host's predicate scope, spread, merged, pushed through an
|
|
26
|
+
* `ExpressionContext`), and the bag's string keys all become CEL roots. So the
|
|
27
|
+
* permissions cannot ride on the bag under a name: any name would be a root.
|
|
28
|
+
*
|
|
29
|
+
* They ride on the SUBJECT instead, under a symbol:
|
|
30
|
+
*
|
|
31
|
+
* - The engine models the map as the subject's own answer sheet — it binds
|
|
32
|
+
* `permissions` only alongside a user, and `can` refuses any receiver that
|
|
33
|
+
* is not that user (`receiver !== subject`). The subject is the one object
|
|
34
|
+
* every bag already carries by reference under `current_user` (and the
|
|
35
|
+
* `user` / `ctx.user` / `os.user` aliases of it), through every spread and
|
|
36
|
+
* every `ExpressionContext` scope, so no surface needs its own copy of the
|
|
37
|
+
* hand-off.
|
|
38
|
+
* - A symbol is the one kind of key no predicate dialect can name: CEL
|
|
39
|
+
* identifiers and member names are strings, and so are the legacy
|
|
40
|
+
* evaluator's. `current_user.permissions` still faults as an unknown key.
|
|
41
|
+
*
|
|
42
|
+
* ## Absent is not empty
|
|
43
|
+
*
|
|
44
|
+
* A subject that carries NO map — the payload has not loaded, no permission
|
|
45
|
+
* provider is mounted, or the provider has no `/auth/me/permissions` answer at
|
|
46
|
+
* all — hands the engine no `permissions`, and `can` then refuses loudly
|
|
47
|
+
* (`ok: false`, `kind: 'runtime'`). That refusal is a FAULT, so each surface
|
|
48
|
+
* applies its own fault policy to it; it is never turned into a quiet `false`
|
|
49
|
+
* here, because the engine's contract is that an empty map means "holds
|
|
50
|
+
* nothing" and a caller that has no data must not claim it has that answer.
|
|
51
|
+
*/
|
|
52
|
+
import type { EvalPermissions, EvalSystemPermissions } from '@objectstack/formula';
|
|
53
|
+
/**
|
|
54
|
+
* The key the subject carries its effective object permissions under.
|
|
55
|
+
*
|
|
56
|
+
* `Symbol.for` rather than `Symbol()` so two copies of this module in one page
|
|
57
|
+
* (a host that bundles `@object-ui/core` twice) still agree on the key; the
|
|
58
|
+
* namespace keeps it from colliding with anyone else's registry entry.
|
|
59
|
+
*/
|
|
60
|
+
export declare const SUBJECT_PERMISSIONS: unique symbol;
|
|
61
|
+
/**
|
|
62
|
+
* The subject to publish as `current_user`, carrying `permissions` when there
|
|
63
|
+
* are any.
|
|
64
|
+
*
|
|
65
|
+
* Returns a shallow copy so the caller's own user object is never mutated; the
|
|
66
|
+
* copy is what the whole predicate scope must use for every alias of the
|
|
67
|
+
* subject, because the engine compares the `can` receiver by IDENTITY. With no
|
|
68
|
+
* `permissions` the result carries none — a subject built from one that did is
|
|
69
|
+
* stripped rather than left answering from a stale map.
|
|
70
|
+
*/
|
|
71
|
+
export declare function bindSubjectPermissions<T extends object>(subject: T, permissions: EvalPermissions | undefined): T;
|
|
72
|
+
/**
|
|
73
|
+
* The effective object permissions a subject carries, or `undefined` when it
|
|
74
|
+
* carries none (see "Absent is not empty" above).
|
|
75
|
+
*/
|
|
76
|
+
export declare function subjectPermissionsOf(subject: unknown): EvalPermissions | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* The key the subject carries its held system capabilities under — the
|
|
79
|
+
* `systemPermissions` list of the `/auth/me/permissions` response, the one
|
|
80
|
+
* input `current_user.hasCapability(name)` is answered from (objectui#13006).
|
|
81
|
+
*
|
|
82
|
+
* It rides beside {@link SUBJECT_PERMISSIONS} for the same reasons: the engine
|
|
83
|
+
* reads it from `EvalContext.systemPermissions`, ⛔ never from a CEL variable,
|
|
84
|
+
* so it can be neither a scope key (every scope key is a root) nor a string
|
|
85
|
+
* member of `current_user` (`current_user.systemPermissions` would be a
|
|
86
|
+
* readable member reaching around the function). A symbol is neither.
|
|
87
|
+
*
|
|
88
|
+
* Absent is not empty, exactly as for the permissions map: a subject carrying
|
|
89
|
+
* no list hands the engine none, and `hasCapability` refuses loudly; an empty
|
|
90
|
+
* list is a real answer ("holds no capability") and evaluates to `false`.
|
|
91
|
+
*/
|
|
92
|
+
export declare const SUBJECT_CAPABILITIES: unique symbol;
|
|
93
|
+
/**
|
|
94
|
+
* The subject to publish as `current_user`, carrying `systemPermissions` when
|
|
95
|
+
* there is a list — the sibling of {@link bindSubjectPermissions}, on the same
|
|
96
|
+
* terms: a shallow copy (the caller's object is never mutated, and every alias
|
|
97
|
+
* must be the returned object), and with no list a subject built from one that
|
|
98
|
+
* had a list is stripped rather than left answering from a stale one.
|
|
99
|
+
*/
|
|
100
|
+
export declare function bindSubjectCapabilities<T extends object>(subject: T, systemPermissions: EvalSystemPermissions | undefined): T;
|
|
101
|
+
/**
|
|
102
|
+
* The held system capabilities a subject carries, or `undefined` when it
|
|
103
|
+
* carries none (absent is not empty — see {@link SUBJECT_CAPABILITIES}).
|
|
104
|
+
*/
|
|
105
|
+
export declare function subjectCapabilitiesOf(subject: unknown): EvalSystemPermissions | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The key the subject carries its effective object permissions under.
|
|
10
|
+
*
|
|
11
|
+
* `Symbol.for` rather than `Symbol()` so two copies of this module in one page
|
|
12
|
+
* (a host that bundles `@object-ui/core` twice) still agree on the key; the
|
|
13
|
+
* namespace keeps it from colliding with anyone else's registry entry.
|
|
14
|
+
*/
|
|
15
|
+
export const SUBJECT_PERMISSIONS = Symbol.for('@object-ui/core:subject-permissions');
|
|
16
|
+
/**
|
|
17
|
+
* The subject to publish as `current_user`, carrying `permissions` when there
|
|
18
|
+
* are any.
|
|
19
|
+
*
|
|
20
|
+
* Returns a shallow copy so the caller's own user object is never mutated; the
|
|
21
|
+
* copy is what the whole predicate scope must use for every alias of the
|
|
22
|
+
* subject, because the engine compares the `can` receiver by IDENTITY. With no
|
|
23
|
+
* `permissions` the result carries none — a subject built from one that did is
|
|
24
|
+
* stripped rather than left answering from a stale map.
|
|
25
|
+
*/
|
|
26
|
+
export function bindSubjectPermissions(subject, permissions) {
|
|
27
|
+
if (permissions === undefined) {
|
|
28
|
+
if (!Object.prototype.hasOwnProperty.call(subject, SUBJECT_PERMISSIONS))
|
|
29
|
+
return subject;
|
|
30
|
+
const stripped = { ...subject };
|
|
31
|
+
delete stripped[SUBJECT_PERMISSIONS];
|
|
32
|
+
return stripped;
|
|
33
|
+
}
|
|
34
|
+
return { ...subject, [SUBJECT_PERMISSIONS]: permissions };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The effective object permissions a subject carries, or `undefined` when it
|
|
38
|
+
* carries none (see "Absent is not empty" above).
|
|
39
|
+
*/
|
|
40
|
+
export function subjectPermissionsOf(subject) {
|
|
41
|
+
if (subject === null || typeof subject !== 'object')
|
|
42
|
+
return undefined;
|
|
43
|
+
return subject[SUBJECT_PERMISSIONS];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The key the subject carries its held system capabilities under — the
|
|
47
|
+
* `systemPermissions` list of the `/auth/me/permissions` response, the one
|
|
48
|
+
* input `current_user.hasCapability(name)` is answered from (objectui#13006).
|
|
49
|
+
*
|
|
50
|
+
* It rides beside {@link SUBJECT_PERMISSIONS} for the same reasons: the engine
|
|
51
|
+
* reads it from `EvalContext.systemPermissions`, ⛔ never from a CEL variable,
|
|
52
|
+
* so it can be neither a scope key (every scope key is a root) nor a string
|
|
53
|
+
* member of `current_user` (`current_user.systemPermissions` would be a
|
|
54
|
+
* readable member reaching around the function). A symbol is neither.
|
|
55
|
+
*
|
|
56
|
+
* Absent is not empty, exactly as for the permissions map: a subject carrying
|
|
57
|
+
* no list hands the engine none, and `hasCapability` refuses loudly; an empty
|
|
58
|
+
* list is a real answer ("holds no capability") and evaluates to `false`.
|
|
59
|
+
*/
|
|
60
|
+
export const SUBJECT_CAPABILITIES = Symbol.for('@object-ui/core:subject-capabilities');
|
|
61
|
+
/**
|
|
62
|
+
* The subject to publish as `current_user`, carrying `systemPermissions` when
|
|
63
|
+
* there is a list — the sibling of {@link bindSubjectPermissions}, on the same
|
|
64
|
+
* terms: a shallow copy (the caller's object is never mutated, and every alias
|
|
65
|
+
* must be the returned object), and with no list a subject built from one that
|
|
66
|
+
* had a list is stripped rather than left answering from a stale one.
|
|
67
|
+
*/
|
|
68
|
+
export function bindSubjectCapabilities(subject, systemPermissions) {
|
|
69
|
+
if (systemPermissions === undefined) {
|
|
70
|
+
if (!Object.prototype.hasOwnProperty.call(subject, SUBJECT_CAPABILITIES))
|
|
71
|
+
return subject;
|
|
72
|
+
const stripped = { ...subject };
|
|
73
|
+
delete stripped[SUBJECT_CAPABILITIES];
|
|
74
|
+
return stripped;
|
|
75
|
+
}
|
|
76
|
+
return { ...subject, [SUBJECT_CAPABILITIES]: systemPermissions };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The held system capabilities a subject carries, or `undefined` when it
|
|
80
|
+
* carries none (absent is not empty — see {@link SUBJECT_CAPABILITIES}).
|
|
81
|
+
*/
|
|
82
|
+
export function subjectCapabilitiesOf(subject) {
|
|
83
|
+
if (subject === null || typeof subject !== 'object')
|
|
84
|
+
return undefined;
|
|
85
|
+
return subject[SUBJECT_CAPABILITIES];
|
|
86
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Is this value a gate the author DECLARED but the client cannot EVALUATE?
|
|
10
|
+
* (objectui#11358)
|
|
11
|
+
*
|
|
12
|
+
* A predicate gate (`visible` / `hidden` / `enabled` / `disabled` /
|
|
13
|
+
* `condition`) is in one of three states, and this is the one place the third
|
|
14
|
+
* is told apart from the other two:
|
|
15
|
+
*
|
|
16
|
+
* - **absent** — `null` / `undefined` / `''` / blank predicate text. Nothing
|
|
17
|
+
* was declared; {@link hasDeclaredPredicate} answers "not declared" (and
|
|
18
|
+
* reports a blank one, objectui#8069).
|
|
19
|
+
* - **evaluable** — a boolean, a predicate string, or an envelope whose
|
|
20
|
+
* `source` is a non-blank string.
|
|
21
|
+
* - **declared but not evaluable** — anything else that is present: an
|
|
22
|
+
* envelope with no string `source` (the `ast`-only `{ dialect: 'cel', ast }`
|
|
23
|
+
* an authoring door can admit), a number (`0`, `42`), an object with no
|
|
24
|
+
* `source` (`{}`), an array. This function answers `true` for exactly
|
|
25
|
+
* these.
|
|
26
|
+
*
|
|
27
|
+
* Triage's ruling on objectui#11358 makes the third state "declared and
|
|
28
|
+
* faulting": it is a gate, so it is never "no gate", and it cannot be
|
|
29
|
+
* evaluated, so it takes the path every other faulting predicate takes —
|
|
30
|
+
* reported, and answered with the fail direction its key already has. Before
|
|
31
|
+
* that ruling the normalizer folded it to `undefined`, the same answer as
|
|
32
|
+
* "absent", so every reader showed / enabled / ran it in silence: ADR-0137 D4's
|
|
33
|
+
* "a gate predicate that is blank or faulting is diagnosed, never a silent
|
|
34
|
+
* `true`", broken by a fold.
|
|
35
|
+
*
|
|
36
|
+
* Read by the normalizer ({@link toPredicateInput}, which now keeps the state
|
|
37
|
+
* instead of folding it), the evaluation step
|
|
38
|
+
* (`ExpressionEvaluator.evaluateCondition`, which reports it and answers it as a
|
|
39
|
+
* fault), the canonical predicate helper (`evalFieldPredicate`, which tags its
|
|
40
|
+
* reason) and the dev-mode schema validator (which still refuses it). ⛔ Not
|
|
41
|
+
* re-exported from the package entry: it is the internal definition those four
|
|
42
|
+
* share, not public API.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ No client-side reading of what an `ast` MEANT: the client evaluates
|
|
45
|
+
* `source` text only, so an `ast` with no `source` is not evaluable here,
|
|
46
|
+
* whatever it says.
|
|
47
|
+
*/
|
|
48
|
+
export declare function isUnevaluablePredicate(value: unknown): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The `reason` reported for a gate that is declared but cannot be evaluated,
|
|
51
|
+
* tagged like an engine reason (`[parse]`, `[type]`, `[blank]`) because it
|
|
52
|
+
* travels the same two channels — `evalFieldPredicate`'s one-time warning and
|
|
53
|
+
* its `onFault` passback — and a caller that routes on the tag must tell it
|
|
54
|
+
* apart from a predicate that faulted with text in it: the fix is to give the
|
|
55
|
+
* gate a `source` (or delete it), never to debug CEL syntax.
|
|
56
|
+
*/
|
|
57
|
+
export declare const UNEVALUABLE_PREDICATE_REASON: string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Is this value a gate the author DECLARED but the client cannot EVALUATE?
|
|
10
|
+
* (objectui#11358)
|
|
11
|
+
*
|
|
12
|
+
* A predicate gate (`visible` / `hidden` / `enabled` / `disabled` /
|
|
13
|
+
* `condition`) is in one of three states, and this is the one place the third
|
|
14
|
+
* is told apart from the other two:
|
|
15
|
+
*
|
|
16
|
+
* - **absent** — `null` / `undefined` / `''` / blank predicate text. Nothing
|
|
17
|
+
* was declared; {@link hasDeclaredPredicate} answers "not declared" (and
|
|
18
|
+
* reports a blank one, objectui#8069).
|
|
19
|
+
* - **evaluable** — a boolean, a predicate string, or an envelope whose
|
|
20
|
+
* `source` is a non-blank string.
|
|
21
|
+
* - **declared but not evaluable** — anything else that is present: an
|
|
22
|
+
* envelope with no string `source` (the `ast`-only `{ dialect: 'cel', ast }`
|
|
23
|
+
* an authoring door can admit), a number (`0`, `42`), an object with no
|
|
24
|
+
* `source` (`{}`), an array. This function answers `true` for exactly
|
|
25
|
+
* these.
|
|
26
|
+
*
|
|
27
|
+
* Triage's ruling on objectui#11358 makes the third state "declared and
|
|
28
|
+
* faulting": it is a gate, so it is never "no gate", and it cannot be
|
|
29
|
+
* evaluated, so it takes the path every other faulting predicate takes —
|
|
30
|
+
* reported, and answered with the fail direction its key already has. Before
|
|
31
|
+
* that ruling the normalizer folded it to `undefined`, the same answer as
|
|
32
|
+
* "absent", so every reader showed / enabled / ran it in silence: ADR-0137 D4's
|
|
33
|
+
* "a gate predicate that is blank or faulting is diagnosed, never a silent
|
|
34
|
+
* `true`", broken by a fold.
|
|
35
|
+
*
|
|
36
|
+
* Read by the normalizer ({@link toPredicateInput}, which now keeps the state
|
|
37
|
+
* instead of folding it), the evaluation step
|
|
38
|
+
* (`ExpressionEvaluator.evaluateCondition`, which reports it and answers it as a
|
|
39
|
+
* fault), the canonical predicate helper (`evalFieldPredicate`, which tags its
|
|
40
|
+
* reason) and the dev-mode schema validator (which still refuses it). ⛔ Not
|
|
41
|
+
* re-exported from the package entry: it is the internal definition those four
|
|
42
|
+
* share, not public API.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ No client-side reading of what an `ast` MEANT: the client evaluates
|
|
45
|
+
* `source` text only, so an `ast` with no `source` is not evaluable here,
|
|
46
|
+
* whatever it says.
|
|
47
|
+
*/
|
|
48
|
+
export function isUnevaluablePredicate(value) {
|
|
49
|
+
if (value === null || value === undefined)
|
|
50
|
+
return false;
|
|
51
|
+
if (typeof value === 'boolean' || typeof value === 'string')
|
|
52
|
+
return false;
|
|
53
|
+
if (typeof value === 'object') {
|
|
54
|
+
return typeof value.source !== 'string';
|
|
55
|
+
}
|
|
56
|
+
// A number (`0` included — it is present, not absent), a function, a symbol
|
|
57
|
+
// or a bigint: nothing a predicate is spelled as.
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The `reason` reported for a gate that is declared but cannot be evaluated,
|
|
62
|
+
* tagged like an engine reason (`[parse]`, `[type]`, `[blank]`) because it
|
|
63
|
+
* travels the same two channels — `evalFieldPredicate`'s one-time warning and
|
|
64
|
+
* its `onFault` passback — and a caller that routes on the tag must tell it
|
|
65
|
+
* apart from a predicate that faulted with text in it: the fix is to give the
|
|
66
|
+
* gate a `source` (or delete it), never to debug CEL syntax.
|
|
67
|
+
*/
|
|
68
|
+
export const UNEVALUABLE_PREDICATE_REASON = '[unevaluable] the gate is declared but has no `source` to evaluate (an ' +
|
|
69
|
+
'`ast`-only envelope, a number, an object or an array) — it fails in the ' +
|
|
70
|
+
'direction its key fails on any fault';
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
export type { SchemaNode, ComponentRendererProps } from './types/index.js';
|
|
9
9
|
export * from './registry/Registry.js';
|
|
10
10
|
export * from './registry/public-blocks.js';
|
|
11
|
+
export * from './registry/html-tier-intrinsics.js';
|
|
11
12
|
export * from './registry/PluginSystem.js';
|
|
12
13
|
export * from './registry/PluginScopeImpl.js';
|
|
13
14
|
export * from './registry/WidgetRegistry.js';
|
|
@@ -16,10 +17,14 @@ export * from './builder/schema-builder.js';
|
|
|
16
17
|
export * from './utils/dom-props.js';
|
|
17
18
|
export * from './utils/filter-converter.js';
|
|
18
19
|
export * from './utils/managedBy.js';
|
|
20
|
+
export * from './utils/affordanceGrants.js';
|
|
19
21
|
export * from './utils/extract-records.js';
|
|
22
|
+
export * from './utils/non-grid-row-ceiling.js';
|
|
23
|
+
export * from './utils/emptiness.js';
|
|
20
24
|
export * from './utils/expand-fields.js';
|
|
21
25
|
export * from './utils/retired-field-types.js';
|
|
22
26
|
export * from './utils/unmaterialized-fields.js';
|
|
27
|
+
export * from './utils/column-sortability.js';
|
|
23
28
|
export * from './utils/column-identity.js';
|
|
24
29
|
export * from './utils/sort-values.js';
|
|
25
30
|
export * from './utils/sort-query.js';
|
|
@@ -51,14 +56,24 @@ export * from './utils/dashboard-filters.js';
|
|
|
51
56
|
export * from './utils/merge-filters.js';
|
|
52
57
|
export * from './utils/compare-to.js';
|
|
53
58
|
export * from './utils/chart-series.js';
|
|
59
|
+
export * from './utils/chart-measure-key.js';
|
|
60
|
+
export * from './utils/chart-category-key.js';
|
|
61
|
+
export * from './utils/object-aggregate-query.js';
|
|
54
62
|
export * from './utils/chart-presentation.js';
|
|
55
63
|
export * from './utils/number-display.js';
|
|
64
|
+
export * from './utils/date-display.js';
|
|
65
|
+
export * from './utils/native-date-value.js';
|
|
56
66
|
export * from './utils/dataset-format.js';
|
|
57
67
|
export * from './utils/dataset-pivot.js';
|
|
58
68
|
export * from './utils/record-title.js';
|
|
69
|
+
export * from './utils/record-color.js';
|
|
59
70
|
export * from './utils/export-filename.js';
|
|
60
71
|
export * from './utils/reference-keys.js';
|
|
61
72
|
export * from './utils/predicate-record.js';
|
|
73
|
+
export * from './utils/without-denied-fields.js';
|
|
74
|
+
export * from './utils/parent-scope.js';
|
|
62
75
|
export * from './utils/predicate-fields.js';
|
|
76
|
+
export * from './utils/grouping-fields.js';
|
|
63
77
|
export * from './utils/normalize-list-view.js';
|
|
78
|
+
export * from './utils/record-source.js';
|
|
64
79
|
export * from './utils/humanize-label.js';
|