@object-ui/core 17.6.0 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +7260 -0
  2. package/README.md +228 -14
  3. package/dist/actions/ActionEngine.d.ts +4 -2
  4. package/dist/actions/ActionEngine.js +13 -8
  5. package/dist/actions/ActionRunner.d.ts +473 -69
  6. package/dist/actions/ActionRunner.js +792 -125
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/UndoManager.d.ts +8 -13
  9. package/dist/actions/actionKeys.d.ts +9 -7
  10. package/dist/actions/actionKeys.js +67 -14
  11. package/dist/actions/actionResponse.js +2 -2
  12. package/dist/actions/index.d.ts +2 -0
  13. package/dist/actions/index.js +2 -0
  14. package/dist/actions/navigationTarget.d.ts +41 -0
  15. package/dist/actions/navigationTarget.js +20 -0
  16. package/dist/actions/recordDelete.d.ts +154 -0
  17. package/dist/actions/recordDelete.js +110 -0
  18. package/dist/adapters/ApiDataSource.d.ts +8 -4
  19. package/dist/adapters/ApiDataSource.js +19 -7
  20. package/dist/adapters/ValueDataSource.d.ts +6 -1
  21. package/dist/adapters/ValueDataSource.js +1090 -98
  22. package/dist/adapters/batchTransaction.d.ts +1 -1
  23. package/dist/adapters/batchTransaction.js +1 -1
  24. package/dist/adapters/resolveDataSource.d.ts +9 -6
  25. package/dist/adapters/resolveDataSource.js +9 -12
  26. package/dist/builder/schema-builder.d.ts +38 -81
  27. package/dist/builder/schema-builder.js +33 -152
  28. package/dist/data-scope/ViewDataProvider.d.ts +8 -25
  29. package/dist/data-scope/ViewDataProvider.js +27 -54
  30. package/dist/data-scope/element-data-source.d.ts +261 -7
  31. package/dist/data-scope/element-data-source.js +342 -52
  32. package/dist/data-scope/index.d.ts +9 -4
  33. package/dist/data-scope/index.js +9 -4
  34. package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
  35. package/dist/evaluator/ExpressionEvaluator.js +184 -12
  36. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  37. package/dist/evaluator/FormulaFunctions.js +166 -27
  38. package/dist/evaluator/deadlineSettled.d.ts +40 -0
  39. package/dist/evaluator/deadlineSettled.js +45 -0
  40. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  41. package/dist/evaluator/declaredPredicate.js +117 -16
  42. package/dist/evaluator/fieldRules.d.ts +88 -4
  43. package/dist/evaluator/fieldRules.js +208 -31
  44. package/dist/evaluator/index.d.ts +2 -0
  45. package/dist/evaluator/index.js +2 -0
  46. package/dist/evaluator/listConditional.d.ts +103 -34
  47. package/dist/evaluator/listConditional.js +127 -50
  48. package/dist/evaluator/optionRules.d.ts +77 -6
  49. package/dist/evaluator/optionRules.js +87 -10
  50. package/dist/evaluator/predicateInput.d.ts +15 -4
  51. package/dist/evaluator/predicateInput.js +35 -2
  52. package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
  53. package/dist/evaluator/rowPredicateCanon.js +65 -115
  54. package/dist/evaluator/subjectPermissions.d.ts +105 -0
  55. package/dist/evaluator/subjectPermissions.js +86 -0
  56. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  57. package/dist/evaluator/unevaluablePredicate.js +70 -0
  58. package/dist/index.d.ts +15 -0
  59. package/dist/index.js +83 -2
  60. package/dist/protocols/index.d.ts +0 -1
  61. package/dist/protocols/index.js +9 -1
  62. package/dist/registry/Registry.d.ts +382 -34
  63. package/dist/registry/Registry.js +247 -12
  64. package/dist/registry/WidgetRegistry.js +7 -3
  65. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  66. package/dist/registry/html-tier-intrinsics.js +110 -0
  67. package/dist/registry/public-blocks.d.ts +8 -0
  68. package/dist/registry/public-blocks.js +57 -0
  69. package/dist/theme/ThemeEngine.js +1 -1
  70. package/dist/utils/affordanceGrants.d.ts +371 -0
  71. package/dist/utils/affordanceGrants.js +252 -0
  72. package/dist/utils/chart-category-key.d.ts +82 -0
  73. package/dist/utils/chart-category-key.js +84 -0
  74. package/dist/utils/chart-measure-key.d.ts +64 -0
  75. package/dist/utils/chart-measure-key.js +65 -0
  76. package/dist/utils/chart-presentation.d.ts +95 -112
  77. package/dist/utils/chart-presentation.js +169 -104
  78. package/dist/utils/chart-series.d.ts +165 -5
  79. package/dist/utils/chart-series.js +123 -18
  80. package/dist/utils/column-sortability.d.ts +157 -0
  81. package/dist/utils/column-sortability.js +217 -0
  82. package/dist/utils/dashboard-filters.d.ts +38 -6
  83. package/dist/utils/dashboard-filters.js +107 -61
  84. package/dist/utils/dataset-format.d.ts +211 -30
  85. package/dist/utils/dataset-format.js +389 -46
  86. package/dist/utils/date-display.d.ts +346 -0
  87. package/dist/utils/date-display.js +644 -0
  88. package/dist/utils/date-macros.js +7 -0
  89. package/dist/utils/drill-down.d.ts +78 -0
  90. package/dist/utils/drill-down.js +93 -0
  91. package/dist/utils/emptiness.d.ts +96 -0
  92. package/dist/utils/emptiness.js +101 -0
  93. package/dist/utils/expand-fields.d.ts +81 -9
  94. package/dist/utils/expand-fields.js +81 -9
  95. package/dist/utils/extract-records.d.ts +65 -5
  96. package/dist/utils/extract-records.js +65 -8
  97. package/dist/utils/filter-converter.d.ts +158 -6
  98. package/dist/utils/filter-converter.js +1098 -24
  99. package/dist/utils/filter-tokens.d.ts +49 -5
  100. package/dist/utils/filter-tokens.js +105 -33
  101. package/dist/utils/grouping-fields.d.ts +61 -0
  102. package/dist/utils/grouping-fields.js +84 -0
  103. package/dist/utils/managedBy.d.ts +3 -3
  104. package/dist/utils/managedBy.js +5 -5
  105. package/dist/utils/native-date-value.d.ts +48 -0
  106. package/dist/utils/native-date-value.js +167 -0
  107. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  108. package/dist/utils/non-grid-row-ceiling.js +141 -0
  109. package/dist/utils/normalize-list-view.d.ts +135 -11
  110. package/dist/utils/normalize-list-view.js +243 -64
  111. package/dist/utils/number-display.d.ts +63 -14
  112. package/dist/utils/number-display.js +34 -12
  113. package/dist/utils/object-aggregate-query.d.ts +113 -0
  114. package/dist/utils/object-aggregate-query.js +50 -0
  115. package/dist/utils/parent-scope.d.ts +123 -0
  116. package/dist/utils/parent-scope.js +140 -0
  117. package/dist/utils/predicate-fields.d.ts +45 -12
  118. package/dist/utils/predicate-fields.js +141 -12
  119. package/dist/utils/record-color.d.ts +75 -0
  120. package/dist/utils/record-color.js +65 -0
  121. package/dist/utils/record-source.d.ts +298 -0
  122. package/dist/utils/record-source.js +331 -0
  123. package/dist/utils/record-title.d.ts +148 -8
  124. package/dist/utils/record-title.js +220 -26
  125. package/dist/utils/reference-keys.d.ts +21 -22
  126. package/dist/utils/reference-keys.js +532 -29
  127. package/dist/utils/sort-query.d.ts +115 -15
  128. package/dist/utils/sort-query.js +240 -13
  129. package/dist/utils/text-comparand.d.ts +92 -0
  130. package/dist/utils/text-comparand.js +99 -0
  131. package/dist/utils/without-denied-fields.d.ts +49 -0
  132. package/dist/utils/without-denied-fields.js +52 -0
  133. package/dist/validation/index.d.ts +8 -4
  134. package/dist/validation/index.js +8 -4
  135. package/dist/validation/schema-validator.d.ts +1 -1
  136. package/dist/validation/schema-validator.js +225 -83
  137. package/dist/validation/server-owned-value.d.ts +5 -1
  138. package/dist/validation/server-owned-value.js +5 -1
  139. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  140. package/dist/validation/validators/object-validation-engine.js +14 -3
  141. package/package.json +8 -9
  142. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  143. package/dist/data-scope/DataScopeManager.js +0 -229
  144. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  145. package/dist/protocols/ResponsiveProtocol.js +0 -158
  146. package/dist/validation/validation-engine.d.ts +0 -88
  147. package/dist/validation/validation-engine.js +0 -434
@@ -6,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, and the Phase-1 deprecation warning for the
10
- * two spellings that are not it (objectui#5330).
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 (maintainer ruling, 2026-08-20 — option B)
13
+ * ## The canon
13
14
  *
14
15
  * A row predicate — `visible` / `disabled` / `enabled` on an action renderer, a
15
- * row scope, a `record:alert` — binds the row THREE ways today: canonical
16
- * `record.status`, bare shorthand `status`, and legacy `data.status`
17
- * (objectui#4075 / PR #4079 bound all four action renderers all three ways to
18
- * restore consistency, deliberately without deciding which of them is
19
- * CONTRACT). This module is where that decision now lives:
20
- *
21
- * > **The canon is `record.*`.** The bare shorthand and `data.*` are
22
- * > client-side tolerances in a deprecation window, kept because stored
23
- * > metadata carries them, warned about here, and removable only after a
24
- * > stored-metadata survey sizes the window (⛔ no removal before the survey).
25
- *
26
- * It mirrors the objectstack#7917 option-② precedent for the identical shape (a
27
- * renderer tolerance quietly becoming a second de-facto contract — AGENTS.md
28
- * #0.1), whose objectui half is `utils/dashboard-filters.ts`' bare-string
29
- * `options` shorthand: same three phases, same reason the warning is not
30
- * decoration (ADR-0078 — nothing silently inert; a tolerance nothing ever
31
- * reports can never be retired, because nothing would ever show that the last
32
- * document carrying it is gone).
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` | ⚠️ **silently accepted** |
46
- *
47
- * `buildScope({ record })` mounts exactly `['record']` — `data` is never bound
48
- * and the row's fields are never flattened to top level. So **the server
49
- * accepts `record.*` and nothing else**; both other spellings fault there. The
50
- * three-way binding is a client tolerance with no server counterpart, which is
51
- * precisely why it is the client's job to warn.
52
- *
53
- * ⚠️ The `data.*` row is the dangerous one and the reason this warning exists
54
- * at all. `data` IS in `@objectstack/formula`'s `SCOPE_ROOTS`, so the server's
55
- * bare-identifier oracle waves `data.status` through — that list is a
56
- * deliberately generous "never faults" LINT BASELINE, not the runtime accept
57
- * set. A `data.*` row predicate therefore passes every authoring gate the
58
- * platform has and then binds nothing at runtime: it is not an error, it is a
59
- * constant `false`, and a `visible` that is constantly false is a button that
60
- * silently never appears. That is the #4075 fail-closed family's exact
61
- * signature, and this client is the only layer positioned to catch it.
62
- *
63
- * ## `data.*` is DEPRECATED HERE, not everywhere — the canon is layer-scoped
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. So
69
- * `data.*` is not a legacy alias to be deprecated platform-wide — it is a
70
- * WRONG-LAYER paste on a runtime record surface, and only that is what this
71
- * module reports. Stating the deprecation unqualified would contradict
72
- * ADR-0089 D3 and break the metadata-editing layer's own contract.
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 deprecation warning sends
98
- * an author to rewrite a predicate that was correct:
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. Callers must have already
117
- * routed legacy `${…}`-dialect strings elsewhere — in that
118
- * dialect `data.*` is the NORMAL spelling, and reporting it
119
- * here would be a false positive on every legacy predicate.
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';