@object-ui/core 17.5.0 → 17.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +7404 -0
  2. package/README.md +127 -26
  3. package/dist/actions/ActionEngine.d.ts +5 -3
  4. package/dist/actions/ActionEngine.js +14 -10
  5. package/dist/actions/ActionRunner.d.ts +363 -30
  6. package/dist/actions/ActionRunner.js +544 -58
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/actionKeys.d.ts +75 -13
  9. package/dist/actions/actionKeys.js +131 -16
  10. package/dist/actions/actionResponse.js +3 -3
  11. package/dist/actions/index.d.ts +2 -0
  12. package/dist/actions/index.js +2 -0
  13. package/dist/actions/recordDelete.d.ts +118 -0
  14. package/dist/actions/recordDelete.js +112 -0
  15. package/dist/actions/recordIdParam.d.ts +77 -0
  16. package/dist/actions/recordIdParam.js +37 -0
  17. package/dist/actions/serverActionHandler.d.ts +1 -1
  18. package/dist/actions/serverActionHandler.js +1 -1
  19. package/dist/adapters/ValueDataSource.d.ts +6 -1
  20. package/dist/adapters/ValueDataSource.js +1090 -98
  21. package/dist/adapters/batchTransaction.d.ts +1 -1
  22. package/dist/adapters/batchTransaction.js +1 -1
  23. package/dist/builder/schema-builder.d.ts +38 -81
  24. package/dist/builder/schema-builder.js +33 -152
  25. package/dist/data-scope/ViewDataProvider.d.ts +1 -1
  26. package/dist/data-scope/ViewDataProvider.js +21 -1
  27. package/dist/data-scope/element-data-source.d.ts +261 -7
  28. package/dist/data-scope/element-data-source.js +342 -52
  29. package/dist/data-scope/index.d.ts +9 -4
  30. package/dist/data-scope/index.js +9 -4
  31. package/dist/evaluator/ExpressionEvaluator.d.ts +128 -1
  32. package/dist/evaluator/ExpressionEvaluator.js +226 -13
  33. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  34. package/dist/evaluator/FormulaFunctions.js +166 -27
  35. package/dist/evaluator/SafeExpressionParser.js +0 -4
  36. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  37. package/dist/evaluator/declaredPredicate.js +118 -17
  38. package/dist/evaluator/fieldRules.d.ts +107 -4
  39. package/dist/evaluator/fieldRules.js +231 -35
  40. package/dist/evaluator/index.d.ts +2 -0
  41. package/dist/evaluator/index.js +2 -0
  42. package/dist/evaluator/listConditional.d.ts +125 -9
  43. package/dist/evaluator/listConditional.js +167 -26
  44. package/dist/evaluator/optionRules.d.ts +126 -0
  45. package/dist/evaluator/optionRules.js +141 -4
  46. package/dist/evaluator/predicateInput.d.ts +15 -4
  47. package/dist/evaluator/predicateInput.js +35 -2
  48. package/dist/evaluator/rowPredicateCanon.d.ts +56 -0
  49. package/dist/evaluator/rowPredicateCanon.js +158 -0
  50. package/dist/evaluator/subjectPermissions.d.ts +76 -0
  51. package/dist/evaluator/subjectPermissions.js +44 -0
  52. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  53. package/dist/evaluator/unevaluablePredicate.js +70 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.js +97 -2
  56. package/dist/protocols/index.d.ts +0 -1
  57. package/dist/protocols/index.js +9 -1
  58. package/dist/registry/Registry.d.ts +397 -48
  59. package/dist/registry/Registry.js +263 -11
  60. package/dist/registry/WidgetRegistry.js +7 -3
  61. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  62. package/dist/registry/html-tier-intrinsics.js +110 -0
  63. package/dist/registry/public-blocks.d.ts +8 -0
  64. package/dist/registry/public-blocks.js +57 -0
  65. package/dist/theme/ThemeEngine.js +1 -1
  66. package/dist/theme/index.d.ts +1 -1
  67. package/dist/theme/index.js +1 -1
  68. package/dist/types/index.d.ts +92 -13
  69. package/dist/utils/chart-category-key.d.ts +82 -0
  70. package/dist/utils/chart-category-key.js +84 -0
  71. package/dist/utils/chart-measure-key.d.ts +64 -0
  72. package/dist/utils/chart-measure-key.js +65 -0
  73. package/dist/utils/chart-presentation.d.ts +188 -0
  74. package/dist/utils/chart-presentation.js +345 -0
  75. package/dist/utils/chart-series.d.ts +271 -31
  76. package/dist/utils/chart-series.js +478 -55
  77. package/dist/utils/column-identity.d.ts +43 -0
  78. package/dist/utils/column-identity.js +47 -1
  79. package/dist/utils/column-sortability.d.ts +157 -0
  80. package/dist/utils/column-sortability.js +217 -0
  81. package/dist/utils/dashboard-filters.d.ts +38 -6
  82. package/dist/utils/dashboard-filters.js +107 -61
  83. package/dist/utils/dataset-format.d.ts +211 -30
  84. package/dist/utils/dataset-format.js +407 -52
  85. package/dist/utils/date-display.d.ts +354 -0
  86. package/dist/utils/date-display.js +463 -0
  87. package/dist/utils/date-macros.js +7 -0
  88. package/dist/utils/debug.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 +171 -11
  94. package/dist/utils/expand-fields.js +171 -11
  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 +152 -4
  98. package/dist/utils/filter-converter.js +1092 -22
  99. package/dist/utils/filter-tokens.d.ts +49 -5
  100. package/dist/utils/filter-tokens.js +105 -34
  101. package/dist/utils/freeze-schema.d.ts +38 -1
  102. package/dist/utils/freeze-schema.js +10 -0
  103. package/dist/utils/grouping-fields.d.ts +61 -0
  104. package/dist/utils/grouping-fields.js +84 -0
  105. package/dist/utils/humanize-label.d.ts +60 -0
  106. package/dist/utils/humanize-label.js +62 -0
  107. package/dist/utils/managedBy.d.ts +30 -5
  108. package/dist/utils/managedBy.js +5 -5
  109. package/dist/utils/native-date-value.d.ts +48 -0
  110. package/dist/utils/native-date-value.js +167 -0
  111. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  112. package/dist/utils/non-grid-row-ceiling.js +141 -0
  113. package/dist/utils/normalize-list-view.d.ts +131 -1
  114. package/dist/utils/normalize-list-view.js +248 -6
  115. package/dist/utils/number-display.d.ts +70 -17
  116. package/dist/utils/number-display.js +34 -12
  117. package/dist/utils/object-aggregate-query.d.ts +113 -0
  118. package/dist/utils/object-aggregate-query.js +50 -0
  119. package/dist/utils/parent-scope.d.ts +123 -0
  120. package/dist/utils/parent-scope.js +140 -0
  121. package/dist/utils/predicate-fields.d.ts +62 -4
  122. package/dist/utils/predicate-fields.js +169 -4
  123. package/dist/utils/record-color.d.ts +75 -0
  124. package/dist/utils/record-color.js +65 -0
  125. package/dist/utils/record-source.d.ts +298 -0
  126. package/dist/utils/record-source.js +331 -0
  127. package/dist/utils/record-title.d.ts +148 -8
  128. package/dist/utils/record-title.js +220 -26
  129. package/dist/utils/reference-keys.d.ts +21 -22
  130. package/dist/utils/reference-keys.js +532 -29
  131. package/dist/utils/retired-field-types.d.ts +90 -0
  132. package/dist/utils/retired-field-types.js +124 -0
  133. package/dist/utils/sort-query.d.ts +128 -26
  134. package/dist/utils/sort-query.js +240 -13
  135. package/dist/utils/text-comparand.d.ts +92 -0
  136. package/dist/utils/text-comparand.js +99 -0
  137. package/dist/utils/unmaterialized-fields.d.ts +52 -0
  138. package/dist/utils/unmaterialized-fields.js +57 -0
  139. package/dist/utils/without-denied-fields.d.ts +49 -0
  140. package/dist/utils/without-denied-fields.js +52 -0
  141. package/dist/validation/index.d.ts +9 -4
  142. package/dist/validation/index.js +9 -4
  143. package/dist/validation/schema-validator.d.ts +1 -1
  144. package/dist/validation/schema-validator.js +305 -82
  145. package/dist/validation/server-owned-value.d.ts +62 -0
  146. package/dist/validation/server-owned-value.js +99 -0
  147. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  148. package/dist/validation/validators/object-validation-engine.js +14 -5
  149. package/package.json +8 -9
  150. package/dist/__benchmarks__/core.bench.d.ts +0 -8
  151. package/dist/__benchmarks__/core.bench.js +0 -53
  152. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  153. package/dist/data-scope/DataScopeManager.js +0 -229
  154. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  155. package/dist/protocols/ResponsiveProtocol.js +0 -158
  156. package/dist/validation/validation-engine.d.ts +0 -88
  157. package/dist/validation/validation-engine.js +0 -434
@@ -5,6 +5,28 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
+ import { isUnevaluablePredicate } from './unevaluablePredicate.js';
9
+ /**
10
+ * The normalized answer for a gate that is DECLARED but has nothing the client
11
+ * can evaluate (objectui#11358) — the fourth member of
12
+ * {@link EvaluatorPredicateInput}, a `cel` envelope with no `source`.
13
+ *
14
+ * It exists so the normalizer can tell three states apart instead of two.
15
+ * `undefined` is "absent" and evaluates to `true` (visible / enabled) by the
16
+ * evaluator's one documented default; this value is "declared and faulting"
17
+ * and `ExpressionEvaluator.evaluateCondition` treats it as a FAULT — reported,
18
+ * thrown to a `throwOnError` caller, and answered with the fail-soft `true`
19
+ * otherwise — so every reader gets the fail direction its key already has for
20
+ * a predicate that cannot be evaluated. Folding it into `undefined`, as this
21
+ * function did, made an `ast`-only envelope, `0`, `{}` or an array read as
22
+ * "no gate" everywhere, in silence (ADR-0137 D4).
23
+ *
24
+ * Shape, not a marker: it says what the evaluator receives — a CEL predicate
25
+ * (a bare predicate is CEL by the spec's contract) whose `source` is absent —
26
+ * and carries nothing of the original value, so no reader can guess what an
27
+ * `ast` meant. Frozen and shared, so a reader's memo sees one value per state.
28
+ */
29
+ const DECLARED_UNEVALUABLE = Object.freeze({ dialect: 'cel' });
8
30
  /**
9
31
  * Wrap a BARE expression as a `${…}` template, and leave a string that already
10
32
  * carries template syntax alone (objectui#3871 — rationale in
@@ -29,8 +51,13 @@ function wrapIfBare(expression) {
29
51
  * authored predicate, including bare strings) → a `cel` dialect keeps
30
52
  * its envelope; every other dialect is unwrapped and wrapped as
31
53
  * `${source}`.
32
- * - `null` / `undefined` / empty / anything else → `undefined`
33
- * (default visible/enabled).
54
+ * - `null` / `undefined` / `''` / an envelope whose `source` is `''` →
55
+ * `undefined` (absent: default visible/enabled).
56
+ * - anything else that is present — an envelope with no string `source`
57
+ * (`{ dialect: 'cel', ast }`), a number, `{}`, an array → the frozen
58
+ * `{ dialect: 'cel' }` with no `source`: declared, and a FAULT to the
59
+ * evaluator (objectui#11358 — see `DECLARED_UNEVALUABLE` above). This used
60
+ * to fold into `undefined` with the absent shapes.
34
61
  *
35
62
  * ## Why the `cel` envelope must survive normalization (#2661 / #3314)
36
63
  *
@@ -113,6 +140,10 @@ export function toPredicateInput(value) {
113
140
  return value;
114
141
  if (typeof value === 'string')
115
142
  return wrapIfBare(value);
143
+ // Declared, but nothing here can be evaluated (objectui#11358): kept as its
144
+ // own state rather than folded into "absent". See `DECLARED_UNEVALUABLE`.
145
+ if (isUnevaluablePredicate(value))
146
+ return DECLARED_UNEVALUABLE;
116
147
  if (typeof value === 'object' && typeof value.source === 'string') {
117
148
  const src = value.source;
118
149
  if (!src)
@@ -125,5 +156,7 @@ export function toPredicateInput(value) {
125
156
  // Every other dialect (template / unset) keeps the legacy `${…}` behavior.
126
157
  return wrapIfBare(src);
127
158
  }
159
+ // Unreachable: every present value is a boolean, a string, an envelope with a
160
+ // string `source`, or unevaluable — all answered above. Kept for the compiler.
128
161
  return undefined;
129
162
  }
@@ -0,0 +1,56 @@
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
+ /** The one canonical row-predicate root. */
9
+ export declare const ROW_PREDICATE_CANONICAL_ROOT = "record";
10
+ /** A non-canonical row-predicate spelling, and the edit that fixes it. */
11
+ export type NonCanonicalRowSpelling = {
12
+ /** Bare shorthand: `status == 'active'` — a field of THIS row, unrooted. */
13
+ kind: 'bare-shorthand';
14
+ /** The offending identifier (`status`). */
15
+ identifier: string;
16
+ /** What the author should write instead (`record.status`). */
17
+ canonical: string;
18
+ } | {
19
+ /** Wrong-layer root: `data.status == 'active'` on a runtime record surface. */
20
+ kind: 'metadata-layer-root';
21
+ identifier: string;
22
+ canonical: string;
23
+ };
24
+ /**
25
+ * Detect a non-canonical spelling in ONE row predicate, or `null` when the
26
+ * predicate is already canonical (or is not this module's verdict to give).
27
+ *
28
+ * Deliberately conservative in both arms — every condition below can only
29
+ * REMOVE a finding, never invent one, because a false finding sends an author
30
+ * to rewrite a predicate that was correct:
31
+ *
32
+ * - **Unparseable source** → `null`. Syntax is another gate's verdict.
33
+ * - **Bare shorthand** is reported only when the undeclared identifier is an own
34
+ * key of THIS row. That is what makes it unambiguously the #4075 shorthand
35
+ * rather than a host-scope global this module cannot see (a deployment key
36
+ * that is simply not in `SCOPE_ROOTS` would otherwise read as a bare field).
37
+ * - **`data.*`** is reported only when the caller confirms `data` names the row
38
+ * (`dataNamesRow`). A surface whose `data` is the host scope's own — a
39
+ * `rowless` dialog, a metadata-editing form — is a legitimate `data.*` site
40
+ * and is left alone.
41
+ *
42
+ * Only the FIRST finding is returned, and the bare-shorthand arm is checked
43
+ * first: it is the arm the server refuses outright (`visibility-bare-identifier`
44
+ * is an `error` there, while a mis-layered root is a `warning`), so when a
45
+ * predicate manages both it is the one the author must fix to have anything
46
+ * evaluate at all.
47
+ *
48
+ * @param source The predicate's CEL text. A legacy `${…}`-dialect string
49
+ * is not CEL and returns `null` here (unparseable); classify
50
+ * it by its own dialect's rules — on a runtime record surface
51
+ * it retired with the CEL spellings (`83fe6e741`), while on
52
+ * the schema/widget tier `data` is a different scope entirely.
53
+ * @param row The row the predicate is bound against.
54
+ * @param dataNamesRow Whether `data` is bound to that same row on this surface.
55
+ */
56
+ export declare function detectNonCanonicalRowSpelling(source: string, row: Record<string, unknown> | null | undefined, dataNamesRow: boolean): NonCanonicalRowSpelling | null;
@@ -0,0 +1,158 @@
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 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`).
12
+ *
13
+ * ## The canon
14
+ *
15
+ * A row predicate — `visible` / `disabled` / `enabled` on an action renderer, a
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:
34
+ *
35
+ * | spelling | server runtime (`buildScope` + `celEngine`) | server authoring oracle |
36
+ * |---|---|---|
37
+ * | `record.status` | ✅ `{ ok: true, value: true }` | ✅ accepted |
38
+ * | bare `status` | ❌ `Unknown variable: status` | ❌ refused (`'status'`) |
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
60
+ *
61
+ * `data` is the CANONICAL root one layer over, in a metadata-editing form (the
62
+ * row under edit): objectstack's `CANONICAL_ROOT_BY_LAYER` reads
63
+ * `{ runtime: 'record', metadata: 'data' }` (ADR-0089 D3), and objectui's own
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.
79
+ *
80
+ * ## Why the detection reuses the server's oracle instead of a regex
81
+ *
82
+ * `collectCelRootIdentifiers` and `firstUndeclaredReference` are
83
+ * `@objectstack/formula` exports — the same two oracles objectstack's
84
+ * `visibility-bare-identifier` gate uses. Reading roots off the canonical AST
85
+ * rather than pattern-matching the source is what keeps this from inventing a
86
+ * second dialect judgement client-side: a string this module cannot parse is
87
+ * NOT this module's verdict to give (syntax belongs to the gates that own it),
88
+ * and it stands down rather than guessing.
89
+ */
90
+ import { collectCelRootIdentifiers, firstUndeclaredReference } from '@objectstack/formula';
91
+ /** The one canonical row-predicate root. */
92
+ export const ROW_PREDICATE_CANONICAL_ROOT = 'record';
93
+ /**
94
+ * The metadata-editing-form root (ADR-0089 D3). Canonical on THAT layer, and a
95
+ * wrong-layer paste on a runtime record surface — see the module note.
96
+ */
97
+ const METADATA_LAYER_ROOT = 'data';
98
+ /**
99
+ * Detect a non-canonical spelling in ONE row predicate, or `null` when the
100
+ * predicate is already canonical (or is not this module's verdict to give).
101
+ *
102
+ * Deliberately conservative in both arms — every condition below can only
103
+ * REMOVE a finding, never invent one, because a false finding sends an author
104
+ * to rewrite a predicate that was correct:
105
+ *
106
+ * - **Unparseable source** → `null`. Syntax is another gate's verdict.
107
+ * - **Bare shorthand** is reported only when the undeclared identifier is an own
108
+ * key of THIS row. That is what makes it unambiguously the #4075 shorthand
109
+ * rather than a host-scope global this module cannot see (a deployment key
110
+ * that is simply not in `SCOPE_ROOTS` would otherwise read as a bare field).
111
+ * - **`data.*`** is reported only when the caller confirms `data` names the row
112
+ * (`dataNamesRow`). A surface whose `data` is the host scope's own — a
113
+ * `rowless` dialog, a metadata-editing form — is a legitimate `data.*` site
114
+ * and is left alone.
115
+ *
116
+ * Only the FIRST finding is returned, and the bare-shorthand arm is checked
117
+ * first: it is the arm the server refuses outright (`visibility-bare-identifier`
118
+ * is an `error` there, while a mis-layered root is a `warning`), so when a
119
+ * predicate manages both it is the one the author must fix to have anything
120
+ * evaluate at all.
121
+ *
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.
127
+ * @param row The row the predicate is bound against.
128
+ * @param dataNamesRow Whether `data` is bound to that same row on this surface.
129
+ */
130
+ export function detectNonCanonicalRowSpelling(source, row, dataNamesRow) {
131
+ if (typeof source !== 'string' || !source.trim())
132
+ return null;
133
+ const roots = collectCelRootIdentifiers(source);
134
+ // Not parseable through the canonical front end — not our verdict.
135
+ if (!roots || roots.ok !== true)
136
+ return null;
137
+ // (1) Bare shorthand — the arm the server refuses outright.
138
+ const bare = firstUndeclaredReference(source);
139
+ if (typeof bare === 'string' &&
140
+ row != null &&
141
+ typeof row === 'object' &&
142
+ Object.prototype.hasOwnProperty.call(row, bare)) {
143
+ return {
144
+ kind: 'bare-shorthand',
145
+ identifier: bare,
146
+ canonical: `${ROW_PREDICATE_CANONICAL_ROOT}.${bare}`,
147
+ };
148
+ }
149
+ // (2) Wrong-layer `data.*` root — silent at runtime on the server.
150
+ if (dataNamesRow && Array.isArray(roots.roots) && roots.roots.includes(METADATA_LAYER_ROOT)) {
151
+ return {
152
+ kind: 'metadata-layer-root',
153
+ identifier: METADATA_LAYER_ROOT,
154
+ canonical: ROW_PREDICATE_CANONICAL_ROOT,
155
+ };
156
+ }
157
+ return null;
158
+ }
@@ -0,0 +1,76 @@
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 } 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;
@@ -0,0 +1,44 @@
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
+ }
@@ -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';
@@ -17,7 +18,12 @@ export * from './utils/dom-props.js';
17
18
  export * from './utils/filter-converter.js';
18
19
  export * from './utils/managedBy.js';
19
20
  export * from './utils/extract-records.js';
21
+ export * from './utils/non-grid-row-ceiling.js';
22
+ export * from './utils/emptiness.js';
20
23
  export * from './utils/expand-fields.js';
24
+ export * from './utils/retired-field-types.js';
25
+ export * from './utils/unmaterialized-fields.js';
26
+ export * from './utils/column-sortability.js';
21
27
  export * from './utils/column-identity.js';
22
28
  export * from './utils/sort-values.js';
23
29
  export * from './utils/sort-query.js';
@@ -49,12 +55,24 @@ export * from './utils/dashboard-filters.js';
49
55
  export * from './utils/merge-filters.js';
50
56
  export * from './utils/compare-to.js';
51
57
  export * from './utils/chart-series.js';
58
+ export * from './utils/chart-measure-key.js';
59
+ export * from './utils/chart-category-key.js';
60
+ export * from './utils/object-aggregate-query.js';
61
+ export * from './utils/chart-presentation.js';
52
62
  export * from './utils/number-display.js';
63
+ export * from './utils/date-display.js';
64
+ export * from './utils/native-date-value.js';
53
65
  export * from './utils/dataset-format.js';
54
66
  export * from './utils/dataset-pivot.js';
55
67
  export * from './utils/record-title.js';
68
+ export * from './utils/record-color.js';
56
69
  export * from './utils/export-filename.js';
57
70
  export * from './utils/reference-keys.js';
58
71
  export * from './utils/predicate-record.js';
72
+ export * from './utils/without-denied-fields.js';
73
+ export * from './utils/parent-scope.js';
59
74
  export * from './utils/predicate-fields.js';
75
+ export * from './utils/grouping-fields.js';
60
76
  export * from './utils/normalize-list-view.js';
77
+ export * from './utils/record-source.js';
78
+ export * from './utils/humanize-label.js';