@object-ui/core 17.2.0 → 17.5.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 +1436 -0
- package/README.md +31 -12
- package/dist/actions/ActionEngine.d.ts +53 -31
- package/dist/actions/ActionEngine.js +87 -66
- package/dist/actions/ActionRunner.d.ts +215 -24
- package/dist/actions/ActionRunner.js +334 -17
- package/dist/actions/actionErrorDetail.d.ts +26 -0
- package/dist/actions/actionErrorDetail.js +37 -0
- package/dist/actions/actionKeys.d.ts +96 -20
- package/dist/actions/actionKeys.js +168 -18
- package/dist/actions/actionResponse.d.ts +42 -0
- package/dist/actions/actionResponse.js +104 -0
- package/dist/actions/index.d.ts +3 -0
- package/dist/actions/index.js +3 -0
- package/dist/actions/serverActionHandler.d.ts +143 -0
- package/dist/actions/serverActionHandler.js +137 -0
- package/dist/data-scope/ViewDataProvider.d.ts +37 -12
- package/dist/data-scope/ViewDataProvider.js +83 -5
- package/dist/data-scope/element-data-source.d.ts +113 -0
- package/dist/data-scope/element-data-source.js +216 -0
- package/dist/data-scope/index.d.ts +2 -1
- package/dist/data-scope/index.js +1 -0
- package/dist/evaluator/ExpressionEvaluator.js +4 -2
- package/dist/evaluator/declaredPredicate.d.ts +119 -0
- package/dist/evaluator/declaredPredicate.js +152 -0
- package/dist/evaluator/fieldRules.d.ts +42 -2
- package/dist/evaluator/fieldRules.js +101 -11
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -0
- package/dist/evaluator/listConditional.d.ts +18 -3
- package/dist/evaluator/listConditional.js +0 -0
- package/dist/evaluator/optionRules.js +5 -1
- package/dist/evaluator/predicateInput.d.ts +132 -0
- package/dist/evaluator/predicateInput.js +129 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +26 -1
- package/dist/protocols/DndProtocol.d.ts +42 -1
- package/dist/protocols/KeyboardProtocol.d.ts +31 -1
- package/dist/protocols/SharingProtocol.d.ts +11 -1
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +8 -1
- package/dist/registry/Registry.d.ts +24 -0
- package/dist/theme/ThemeEngine.d.ts +0 -8
- package/dist/theme/ThemeEngine.js +22 -103
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +7 -1
- package/dist/types/index.d.ts +42 -8
- package/dist/utils/chart-series.d.ts +258 -5
- package/dist/utils/chart-series.js +423 -20
- package/dist/utils/compare-to.d.ts +40 -13
- package/dist/utils/compare-to.js +4 -32
- package/dist/utils/dashboard-filters.d.ts +54 -8
- package/dist/utils/dashboard-filters.js +261 -12
- package/dist/utils/dataset-format.d.ts +79 -15
- package/dist/utils/dataset-format.js +130 -9
- package/dist/utils/dataset-pivot.d.ts +78 -0
- package/dist/utils/dataset-pivot.js +78 -0
- package/dist/utils/dom-props.d.ts +137 -0
- package/dist/utils/dom-props.js +153 -0
- package/dist/utils/filter-converter.d.ts +39 -11
- package/dist/utils/filter-converter.js +87 -7
- package/dist/utils/normalize-list-view.d.ts +26 -6
- package/dist/utils/normalize-list-view.js +30 -8
- package/dist/utils/number-display.d.ts +151 -0
- package/dist/utils/number-display.js +71 -0
- package/dist/utils/predicate-fields.d.ts +96 -0
- package/dist/utils/predicate-fields.js +163 -0
- package/dist/utils/predicate-record.d.ts +32 -0
- package/dist/utils/predicate-record.js +120 -0
- package/dist/utils/record-title.js +0 -0
- package/dist/utils/resolve-view-id.d.ts +31 -0
- package/dist/utils/resolve-view-id.js +45 -0
- package/dist/utils/sort-query.d.ts +55 -0
- package/dist/utils/sort-query.js +35 -0
- package/package.json +5 -5
- package/dist/protocols/NotificationProtocol.d.ts +0 -71
- package/dist/protocols/NotificationProtocol.js +0 -89
- package/dist/utils/merge-views-into-objects.d.ts +0 -19
- package/dist/utils/merge-views-into-objects.js +0 -39
package/README.md
CHANGED
|
@@ -58,6 +58,35 @@ const userName = scope.get('user.name') // 'John'
|
|
|
58
58
|
const isAdmin = scope.evaluate('${user.role === "admin"}') // true
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
### Server Action Dispatch (`createServerActionHandler`)
|
|
62
|
+
|
|
63
|
+
`ActionSchema.body` (L1 expression / L2 sandboxed JS) executes **server-side**
|
|
64
|
+
— `POST /api/v1/actions/{object}/{action}` → the runtime sandbox. The client
|
|
65
|
+
dispatches; it never interprets a body. Build the dispatch handler with the
|
|
66
|
+
factory and register it — core stays opinion-free about auth, origin and
|
|
67
|
+
object scope, which are injected:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { createServerActionHandler } from '@object-ui/core'
|
|
71
|
+
|
|
72
|
+
const script = createServerActionHandler({
|
|
73
|
+
fetch: myAuthenticatedFetch, // your auth wrapper (Bearer/cookies/...)
|
|
74
|
+
baseUrl: 'https://api.example.com', // '' or omitted = same-origin
|
|
75
|
+
resolveObject: () => currentObject, // fallback when the action has no objectName
|
|
76
|
+
onRefresh: () => refetchData(), // called per the action's refreshAfter
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Registered handlers beat the built-in executors:
|
|
80
|
+
runner.registerHandler('script', script)
|
|
81
|
+
// (React hosts: <ActionProvider handlers={{ script }} ... />)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The factory owns the protocol so consumers cannot drift on it: name-based
|
|
85
|
+
action identity (ADR-0110), the record-id resolution dance (`_rowRecord`,
|
|
86
|
+
`recordIdField`, selection fallback, aggregate `_selectedIds`), a re-entrancy
|
|
87
|
+
guard, and the `/actions` response-envelope rule (`interpretActionResponse` /
|
|
88
|
+
`readActionPayload`, also exported).
|
|
89
|
+
|
|
61
90
|
### System Views (`defineView`)
|
|
62
91
|
|
|
63
92
|
Schemas authored in source code are part of the product contract and must
|
|
@@ -112,21 +141,11 @@ This allows the core types and logic to be used in:
|
|
|
112
141
|
|
|
113
142
|
## API Reference
|
|
114
143
|
|
|
115
|
-
See [full documentation](https://objectui.org/api
|
|
116
|
-
|
|
117
|
-
<!-- release-metadata:v3.3.0 -->
|
|
118
|
-
|
|
119
|
-
## Compatibility
|
|
120
|
-
|
|
121
|
-
- **Node.js:** ≥ 18
|
|
122
|
-
- **TypeScript:** ≥ 5.0 (strict mode)
|
|
123
|
-
- **`@objectstack/spec`:** ^3.3.0
|
|
124
|
-
- **`@objectstack/client`:** ^3.3.0
|
|
125
|
-
- **Tailwind CSS:** ≥ 3.4 (for packages with UI)
|
|
144
|
+
See [full documentation](https://objectui.org/docs/api) for detailed API reference.
|
|
126
145
|
|
|
127
146
|
## Links
|
|
128
147
|
|
|
129
|
-
- 📚 [Documentation](https://www.objectui.org/docs/
|
|
148
|
+
- 📚 [Documentation](https://www.objectui.org/docs/guide/architecture)
|
|
130
149
|
- 📦 [npm package](https://www.npmjs.com/package/@object-ui/core)
|
|
131
150
|
- 📝 [Changelog](./CHANGELOG.md)
|
|
132
151
|
- 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
|
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* @object-ui/core - Action Engine
|
|
10
10
|
*
|
|
11
|
-
* Declarative action
|
|
12
|
-
*
|
|
13
|
-
* and bulk operation support.
|
|
11
|
+
* Declarative action engine. Manages action registration, location-based
|
|
12
|
+
* filtering, keyboard shortcuts, and bulk operation support.
|
|
14
13
|
*
|
|
15
14
|
* Replaces callback-based patterns with a schema-driven pipeline:
|
|
16
15
|
* ActionSchema[] → ActionEngine → ActionRunner → ActionResult
|
|
@@ -37,15 +36,6 @@ export interface RegisteredAction {
|
|
|
37
36
|
/** Priority for ordering (lower = first) */
|
|
38
37
|
priority?: number;
|
|
39
38
|
}
|
|
40
|
-
/** Event-to-action mapping */
|
|
41
|
-
export interface ActionMapping {
|
|
42
|
-
/** Event name (e.g., 'row:click', 'toolbar:save', 'keyboard:ctrl+s') */
|
|
43
|
-
event: string;
|
|
44
|
-
/** Action name to execute */
|
|
45
|
-
actionName: string;
|
|
46
|
-
/** Optional condition expression */
|
|
47
|
-
condition?: string;
|
|
48
|
-
}
|
|
49
39
|
/** Keyboard shortcut handler */
|
|
50
40
|
export interface ShortcutBinding {
|
|
51
41
|
/** Key combination (e.g., 'ctrl+s', 'meta+k', 'shift+n') */
|
|
@@ -53,9 +43,35 @@ export interface ShortcutBinding {
|
|
|
53
43
|
/** Action name to trigger */
|
|
54
44
|
actionName: string;
|
|
55
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Registration + filtering + execution for declared actions.
|
|
48
|
+
*
|
|
49
|
+
* Entry points are by NAME (`executeAction`), by LOCATION
|
|
50
|
+
* (`getActionsForLocation`), by SHORTCUT (`handleShortcut`) and in BULK
|
|
51
|
+
* (`executeBulk`). There is deliberately no entry point by arbitrary EVENT
|
|
52
|
+
* name: this class used to carry a second, parallel one — `addMapping()`
|
|
53
|
+
* registered `{ event, actionName, condition }` triples into a private
|
|
54
|
+
* `mappings` array and `dispatch(event)` ran every triple matching an event
|
|
55
|
+
* string. It was retired under enforce-or-remove (objectui#3368) because it
|
|
56
|
+
* was a public export of `@object-ui/core` with zero production callers: in
|
|
57
|
+
* its whole lifetime nothing ever registered a mapping, so `dispatch()` had
|
|
58
|
+
* no reachable caller either, and its four call sites were all in this
|
|
59
|
+
* class's own test file.
|
|
60
|
+
*
|
|
61
|
+
* Its condition gate had drifted three ways from the `visible` contract
|
|
62
|
+
* `getActionsForLocation` below implements — it entered on a raw truthy check
|
|
63
|
+
* (so `condition: false` dispatched anyway), typed `condition` as `string`
|
|
64
|
+
* only (so a `{ dialect: 'cel', source }` envelope could not reach the
|
|
65
|
+
* canonical engine), and evaluated without `throwOnError` (so a throwing
|
|
66
|
+
* predicate failed OPEN, the opposite of `visible`'s fail-closed posture).
|
|
67
|
+
* All three were retired WITH the path rather than fixed on it: aligning the
|
|
68
|
+
* contract of an API nobody calls only widens behaviour nobody uses. If an
|
|
69
|
+
* event-keyed entry point is ever genuinely needed, it must be built on the
|
|
70
|
+
* shared predicate definitions (`hasDeclaredPredicate` + `toPredicateInput`)
|
|
71
|
+
* that the surviving gate below already uses, not on a fourth spelling.
|
|
72
|
+
*/
|
|
56
73
|
export declare class ActionEngine {
|
|
57
74
|
private actions;
|
|
58
|
-
private mappings;
|
|
59
75
|
private shortcuts;
|
|
60
76
|
private normalizedShortcutMap;
|
|
61
77
|
private runner;
|
|
@@ -92,29 +108,37 @@ export declare class ActionEngine {
|
|
|
92
108
|
registerActions(actions: ActionDef[]): void;
|
|
93
109
|
/** Unregister an action by name */
|
|
94
110
|
unregisterAction(name: string): void;
|
|
95
|
-
/** Add an event-to-action mapping */
|
|
96
|
-
addMapping(mapping: ActionMapping): void;
|
|
97
111
|
/**
|
|
98
112
|
* Get actions available at a specific location, sorted by priority.
|
|
99
113
|
*
|
|
100
114
|
* Filtering applied (in order):
|
|
101
115
|
* 1. `locations.includes(location)` — location/region match
|
|
102
116
|
* 2. `action.visible` — evaluated against the runner's current context
|
|
103
|
-
* ({ record, recordId, objectName, user, … })
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
117
|
+
* ({ record, recordId, objectName, user, … }) whenever a gate is
|
|
118
|
+
* DECLARED, which is asked through core's one definition
|
|
119
|
+
* `hasDeclaredPredicate` (objectui#3850's ruling, adopted here by
|
|
120
|
+
* objectui#3957). No gate — absent, `''`, blank predicate text in either
|
|
121
|
+
* spelling, an envelope with no evaluable `source`, or a value that is
|
|
122
|
+
* not a predicate at all — means visible. Evaluator errors hide the
|
|
123
|
+
* action (fail-closed) rather than throwing — this matches the contract
|
|
124
|
+
* used by every individual action renderer (`action-button`,
|
|
125
|
+
* `action-menu`, `action-bar`, …) so the same action behaves identically
|
|
126
|
+
* whether surfaced via the engine or consumed standalone, which is now
|
|
127
|
+
* true of the gate in front of the verdict as well as the verdict
|
|
128
|
+
* (objectui#3314: one value, one answer, whichever entry reads it).
|
|
110
129
|
*
|
|
111
|
-
* Predicate normalization
|
|
130
|
+
* Predicate normalization goes through the shared `toPredicateInput`
|
|
131
|
+
* (`@object-ui/core`'s canonical helper — semantically identical to
|
|
132
|
+
* `@object-ui/react`'s renderer-side twin, pinned by a parity test):
|
|
112
133
|
* raw strings (`'record.foo == true'`) are wrapped as `${...}` so they
|
|
113
|
-
* evaluate as expressions, not as truthy string literals
|
|
114
|
-
* `{ dialect, source }`
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
134
|
+
* evaluate as expressions, not as truthy string literals, and a spec-style
|
|
135
|
+
* `{ dialect: 'cel', source }` envelope is passed through INTACT so
|
|
136
|
+
* `evaluateCondition` routes it to the canonical `@objectstack/formula`
|
|
137
|
+
* engine (#2661). Unwrapping the envelope here — as this method used to —
|
|
138
|
+
* dropped the dialect and silently demoted every CEL predicate to the
|
|
139
|
+
* legacy JS evaluator, so the same `visible:` reached a different verdict
|
|
140
|
+
* via the engine than via a renderer (#3314): CEL faults on `null < null`
|
|
141
|
+
* and fail-closed hides, JS quietly returns `false`.
|
|
118
142
|
*/
|
|
119
143
|
getActionsForLocation(location: ActionLocation): ActionDef[];
|
|
120
144
|
/** Get actions that support bulk operations */
|
|
@@ -125,8 +149,6 @@ export declare class ActionEngine {
|
|
|
125
149
|
getAction(name: string): ActionDef | undefined;
|
|
126
150
|
/** Execute an action by name */
|
|
127
151
|
executeAction(name: string, contextOverride?: Partial<ActionContext>): Promise<ActionResult>;
|
|
128
|
-
/** Dispatch an event — finds mapped actions and executes them */
|
|
129
|
-
dispatch(event: string, contextOverride?: Partial<ActionContext>): Promise<ActionResult[]>;
|
|
130
152
|
/** Handle a keyboard shortcut event — returns true if handled */
|
|
131
153
|
handleShortcut(keys: string, contextOverride?: Partial<ActionContext>): Promise<ActionResult | null>;
|
|
132
154
|
/** Execute a bulk operation on multiple records */
|
|
@@ -141,6 +163,6 @@ export declare class ActionEngine {
|
|
|
141
163
|
}>;
|
|
142
164
|
/** Update the action context */
|
|
143
165
|
updateContext(context: Partial<ActionContext>): void;
|
|
144
|
-
/** Clear all registered actions and
|
|
166
|
+
/** Clear all registered actions and shortcuts */
|
|
145
167
|
clear(): void;
|
|
146
168
|
}
|
|
@@ -8,14 +8,15 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* @object-ui/core - Action Engine
|
|
10
10
|
*
|
|
11
|
-
* Declarative action
|
|
12
|
-
*
|
|
13
|
-
* and bulk operation support.
|
|
11
|
+
* Declarative action engine. Manages action registration, location-based
|
|
12
|
+
* filtering, keyboard shortcuts, and bulk operation support.
|
|
14
13
|
*
|
|
15
14
|
* Replaces callback-based patterns with a schema-driven pipeline:
|
|
16
15
|
* ActionSchema[] → ActionEngine → ActionRunner → ActionResult
|
|
17
16
|
*/
|
|
18
17
|
import { ActionRunner } from './ActionRunner';
|
|
18
|
+
import { toPredicateInput } from '../evaluator/predicateInput.js';
|
|
19
|
+
import { hasDeclaredPredicate } from '../evaluator/declaredPredicate.js';
|
|
19
20
|
export { ACTION_LOCATIONS } from '@object-ui/types';
|
|
20
21
|
/** Normalize a shortcut string for comparison (lowercase, trimmed, sorted parts) */
|
|
21
22
|
function normalizeShortcut(keys) {
|
|
@@ -42,6 +43,33 @@ function warnHiddenPredicate(name, raw, err) {
|
|
|
42
43
|
`Predicate: ${src}. Action predicates evaluate against { record, recordId, objectName, user, os.user, … }; ` +
|
|
43
44
|
`use \`record.<field>\` (not a bare field).`);
|
|
44
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Registration + filtering + execution for declared actions.
|
|
48
|
+
*
|
|
49
|
+
* Entry points are by NAME (`executeAction`), by LOCATION
|
|
50
|
+
* (`getActionsForLocation`), by SHORTCUT (`handleShortcut`) and in BULK
|
|
51
|
+
* (`executeBulk`). There is deliberately no entry point by arbitrary EVENT
|
|
52
|
+
* name: this class used to carry a second, parallel one — `addMapping()`
|
|
53
|
+
* registered `{ event, actionName, condition }` triples into a private
|
|
54
|
+
* `mappings` array and `dispatch(event)` ran every triple matching an event
|
|
55
|
+
* string. It was retired under enforce-or-remove (objectui#3368) because it
|
|
56
|
+
* was a public export of `@object-ui/core` with zero production callers: in
|
|
57
|
+
* its whole lifetime nothing ever registered a mapping, so `dispatch()` had
|
|
58
|
+
* no reachable caller either, and its four call sites were all in this
|
|
59
|
+
* class's own test file.
|
|
60
|
+
*
|
|
61
|
+
* Its condition gate had drifted three ways from the `visible` contract
|
|
62
|
+
* `getActionsForLocation` below implements — it entered on a raw truthy check
|
|
63
|
+
* (so `condition: false` dispatched anyway), typed `condition` as `string`
|
|
64
|
+
* only (so a `{ dialect: 'cel', source }` envelope could not reach the
|
|
65
|
+
* canonical engine), and evaluated without `throwOnError` (so a throwing
|
|
66
|
+
* predicate failed OPEN, the opposite of `visible`'s fail-closed posture).
|
|
67
|
+
* All three were retired WITH the path rather than fixed on it: aligning the
|
|
68
|
+
* contract of an API nobody calls only widens behaviour nobody uses. If an
|
|
69
|
+
* event-keyed entry point is ever genuinely needed, it must be built on the
|
|
70
|
+
* shared predicate definitions (`hasDeclaredPredicate` + `toPredicateInput`)
|
|
71
|
+
* that the surviving gate below already uses, not on a fourth spelling.
|
|
72
|
+
*/
|
|
45
73
|
export class ActionEngine {
|
|
46
74
|
/**
|
|
47
75
|
* @param contextOrRunner Either an initial `ActionContext` (constructs a
|
|
@@ -59,12 +87,6 @@ export class ActionEngine {
|
|
|
59
87
|
writable: true,
|
|
60
88
|
value: new Map()
|
|
61
89
|
});
|
|
62
|
-
Object.defineProperty(this, "mappings", {
|
|
63
|
-
enumerable: true,
|
|
64
|
-
configurable: true,
|
|
65
|
-
writable: true,
|
|
66
|
-
value: []
|
|
67
|
-
});
|
|
68
90
|
Object.defineProperty(this, "shortcuts", {
|
|
69
91
|
enumerable: true,
|
|
70
92
|
configurable: true,
|
|
@@ -139,11 +161,6 @@ export class ActionEngine {
|
|
|
139
161
|
}
|
|
140
162
|
this.actions.delete(name);
|
|
141
163
|
this.shortcuts = this.shortcuts.filter(s => s.actionName !== name);
|
|
142
|
-
this.mappings = this.mappings.filter(m => m.actionName !== name);
|
|
143
|
-
}
|
|
144
|
-
/** Add an event-to-action mapping */
|
|
145
|
-
addMapping(mapping) {
|
|
146
|
-
this.mappings.push(mapping);
|
|
147
164
|
}
|
|
148
165
|
/**
|
|
149
166
|
* Get actions available at a specific location, sorted by priority.
|
|
@@ -151,21 +168,31 @@ export class ActionEngine {
|
|
|
151
168
|
* Filtering applied (in order):
|
|
152
169
|
* 1. `locations.includes(location)` — location/region match
|
|
153
170
|
* 2. `action.visible` — evaluated against the runner's current context
|
|
154
|
-
* ({ record, recordId, objectName, user, … })
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
171
|
+
* ({ record, recordId, objectName, user, … }) whenever a gate is
|
|
172
|
+
* DECLARED, which is asked through core's one definition
|
|
173
|
+
* `hasDeclaredPredicate` (objectui#3850's ruling, adopted here by
|
|
174
|
+
* objectui#3957). No gate — absent, `''`, blank predicate text in either
|
|
175
|
+
* spelling, an envelope with no evaluable `source`, or a value that is
|
|
176
|
+
* not a predicate at all — means visible. Evaluator errors hide the
|
|
177
|
+
* action (fail-closed) rather than throwing — this matches the contract
|
|
178
|
+
* used by every individual action renderer (`action-button`,
|
|
179
|
+
* `action-menu`, `action-bar`, …) so the same action behaves identically
|
|
180
|
+
* whether surfaced via the engine or consumed standalone, which is now
|
|
181
|
+
* true of the gate in front of the verdict as well as the verdict
|
|
182
|
+
* (objectui#3314: one value, one answer, whichever entry reads it).
|
|
161
183
|
*
|
|
162
|
-
* Predicate normalization
|
|
184
|
+
* Predicate normalization goes through the shared `toPredicateInput`
|
|
185
|
+
* (`@object-ui/core`'s canonical helper — semantically identical to
|
|
186
|
+
* `@object-ui/react`'s renderer-side twin, pinned by a parity test):
|
|
163
187
|
* raw strings (`'record.foo == true'`) are wrapped as `${...}` so they
|
|
164
|
-
* evaluate as expressions, not as truthy string literals
|
|
165
|
-
* `{ dialect, source }`
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
188
|
+
* evaluate as expressions, not as truthy string literals, and a spec-style
|
|
189
|
+
* `{ dialect: 'cel', source }` envelope is passed through INTACT so
|
|
190
|
+
* `evaluateCondition` routes it to the canonical `@objectstack/formula`
|
|
191
|
+
* engine (#2661). Unwrapping the envelope here — as this method used to —
|
|
192
|
+
* dropped the dialect and silently demoted every CEL predicate to the
|
|
193
|
+
* legacy JS evaluator, so the same `visible:` reached a different verdict
|
|
194
|
+
* via the engine than via a renderer (#3314): CEL faults on `null < null`
|
|
195
|
+
* and fail-closed hides, JS quietly returns `false`.
|
|
169
196
|
*/
|
|
170
197
|
getActionsForLocation(location) {
|
|
171
198
|
const evaluator = this.runner.getEvaluator();
|
|
@@ -188,30 +215,45 @@ export class ActionEngine {
|
|
|
188
215
|
})
|
|
189
216
|
.filter(ra => {
|
|
190
217
|
const raw = ra.action.visible;
|
|
191
|
-
|
|
218
|
+
// Ask "is a `visible` gate DECLARED?" through the repo's ONE definition
|
|
219
|
+
// (`evaluator/declaredPredicate.ts`, objectui#3850's ruling), which is
|
|
220
|
+
// what the renderers, `SchemaRenderer` and `ActionRunner`'s execution
|
|
221
|
+
// gates ask. This filter used to answer it with a range of its own —
|
|
222
|
+
// three empty spellings folded by hand (`null` / `''` / an envelope with
|
|
223
|
+
// an empty `source`) and everything else coerced with `Boolean(raw)` —
|
|
224
|
+
// and it was the LAST consumer to do so, so the same value got two
|
|
225
|
+
// answers depending on which entry read it: `visible: 0` / `NaN` hid the
|
|
226
|
+
// action here and showed it on the renderer face, and `visible: ' '`
|
|
227
|
+
// hid it here because the normalizer wraps a blank string into `'${ }'`
|
|
228
|
+
// whose verdict is falsy. That is the shape objectui#3314's invariant
|
|
229
|
+
// forbids, and it is fixed by deleting a range, not by adding one
|
|
230
|
+
// (objectui#3957). A value that is not a predicate, or a predicate that
|
|
231
|
+
// says nothing, must not be the reason an action is hidden from
|
|
232
|
+
// everyone — the same fail-open posture `ActionRunner` already committed
|
|
233
|
+
// to for `disabled` (`catch { isDisabled = false }`).
|
|
234
|
+
//
|
|
235
|
+
// Booleans need no branch of their own: they are DECLARED (`visible:
|
|
236
|
+
// false` is a verdict, objectui#3812) and `evaluateCondition`
|
|
237
|
+
// short-circuits them.
|
|
238
|
+
if (!hasDeclaredPredicate(raw))
|
|
192
239
|
return true;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!src)
|
|
202
|
-
return true;
|
|
203
|
-
expr = `\${${src}}`;
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
expr = Boolean(raw);
|
|
207
|
-
}
|
|
240
|
+
// #3314 — one shared normalization, not two. Hand-rolling the envelope
|
|
241
|
+
// unwrap here is what dropped `dialect: 'cel'` and demoted the
|
|
242
|
+
// predicate to the legacy JS engine; `toPredicateInput` keeps the
|
|
243
|
+
// envelope so `evaluateCondition` can route it to `@objectstack/formula`
|
|
244
|
+
// (the engine the server enforces with), exactly as the renderers do.
|
|
245
|
+
// Declared ⇒ the normalizer left something to evaluate, so there is no
|
|
246
|
+
// `undefined` case left to handle here.
|
|
247
|
+
const expr = toPredicateInput(raw);
|
|
208
248
|
try {
|
|
209
249
|
// `throwOnError: true` is required for fail-closed semantics: the
|
|
210
250
|
// evaluator's default behavior swallows expression errors and
|
|
211
251
|
// returns the raw template string, which `Boolean(string)` then
|
|
212
252
|
// coerces to `true` — silently leaking actions whose preconditions
|
|
213
253
|
// failed (e.g. `ctx` undefined). Surface the error so our catch
|
|
214
|
-
// below can hide the action.
|
|
254
|
+
// below can hide the action. It is equally load-bearing on the CEL
|
|
255
|
+
// path, where the canonical helper fails SOFT to visible/enabled
|
|
256
|
+
// unless the caller opts in.
|
|
215
257
|
return evaluator.evaluateCondition(expr, { throwOnError: true });
|
|
216
258
|
}
|
|
217
259
|
catch (err) {
|
|
@@ -255,26 +297,6 @@ export class ActionEngine {
|
|
|
255
297
|
}
|
|
256
298
|
return this.runner.execute(registered.action);
|
|
257
299
|
}
|
|
258
|
-
/** Dispatch an event — finds mapped actions and executes them */
|
|
259
|
-
async dispatch(event, contextOverride) {
|
|
260
|
-
const matchingMappings = this.mappings.filter(m => m.event === event);
|
|
261
|
-
if (matchingMappings.length === 0) {
|
|
262
|
-
return [];
|
|
263
|
-
}
|
|
264
|
-
const results = [];
|
|
265
|
-
for (const mapping of matchingMappings) {
|
|
266
|
-
// Check condition if present
|
|
267
|
-
if (mapping.condition) {
|
|
268
|
-
const evaluator = this.runner.getEvaluator();
|
|
269
|
-
const shouldRun = evaluator.evaluateCondition(mapping.condition);
|
|
270
|
-
if (!shouldRun)
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
const result = await this.executeAction(mapping.actionName, contextOverride);
|
|
274
|
-
results.push(result);
|
|
275
|
-
}
|
|
276
|
-
return results;
|
|
277
|
-
}
|
|
278
300
|
/** Handle a keyboard shortcut event — returns true if handled */
|
|
279
301
|
async handleShortcut(keys, contextOverride) {
|
|
280
302
|
const actionName = this.normalizedShortcutMap.get(normalizeShortcut(keys));
|
|
@@ -334,10 +356,9 @@ export class ActionEngine {
|
|
|
334
356
|
updateContext(context) {
|
|
335
357
|
this.runner.updateContext(context);
|
|
336
358
|
}
|
|
337
|
-
/** Clear all registered actions and
|
|
359
|
+
/** Clear all registered actions and shortcuts */
|
|
338
360
|
clear() {
|
|
339
361
|
this.actions.clear();
|
|
340
|
-
this.mappings = [];
|
|
341
362
|
this.shortcuts = [];
|
|
342
363
|
this.normalizedShortcutMap.clear();
|
|
343
364
|
}
|