@object-ui/core 17.3.0 → 17.6.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 +2018 -0
- package/README.md +38 -34
- package/dist/actions/ActionEngine.d.ts +43 -26
- package/dist/actions/ActionEngine.js +67 -54
- package/dist/actions/ActionRunner.d.ts +281 -31
- package/dist/actions/ActionRunner.js +458 -36
- package/dist/actions/actionKeys.d.ts +163 -27
- package/dist/actions/actionKeys.js +238 -27
- package/dist/actions/actionResponse.js +1 -1
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/recordIdParam.d.ts +77 -0
- package/dist/actions/recordIdParam.js +37 -0
- package/dist/actions/serverActionHandler.d.ts +1 -1
- package/dist/actions/serverActionHandler.js +1 -1
- 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.d.ts +42 -1
- package/dist/evaluator/ExpressionEvaluator.js +42 -1
- package/dist/evaluator/SafeExpressionParser.js +0 -4
- package/dist/evaluator/declaredPredicate.d.ts +119 -0
- package/dist/evaluator/declaredPredicate.js +152 -0
- package/dist/evaluator/fieldRules.d.ts +35 -0
- package/dist/evaluator/fieldRules.js +79 -20
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -0
- package/dist/evaluator/listConditional.d.ts +54 -4
- package/dist/evaluator/listConditional.js +125 -19
- package/dist/evaluator/optionRules.d.ts +55 -0
- package/dist/evaluator/optionRules.js +60 -0
- package/dist/evaluator/predicateInput.d.ts +51 -3
- package/dist/evaluator/predicateInput.js +62 -5
- package/dist/evaluator/rowPredicateCanon.d.ts +67 -0
- package/dist/evaluator/rowPredicateCanon.js +208 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +31 -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/registry/Registry.d.ts +62 -16
- 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 +125 -12
- package/dist/utils/chart-presentation.d.ts +205 -0
- package/dist/utils/chart-presentation.js +280 -0
- package/dist/utils/chart-series.d.ts +390 -23
- package/dist/utils/chart-series.js +776 -32
- package/dist/utils/column-identity.d.ts +43 -0
- package/dist/utils/column-identity.js +47 -1
- 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 +73 -14
- package/dist/utils/dataset-format.js +142 -9
- package/dist/utils/dataset-pivot.d.ts +28 -7
- package/dist/utils/dataset-pivot.js +27 -6
- package/dist/utils/debug.js +7 -0
- package/dist/utils/dom-props.d.ts +137 -0
- package/dist/utils/dom-props.js +153 -0
- package/dist/utils/expand-fields.d.ts +97 -7
- package/dist/utils/expand-fields.js +97 -7
- package/dist/utils/filter-tokens.js +0 -1
- package/dist/utils/freeze-schema.d.ts +38 -1
- package/dist/utils/freeze-schema.js +10 -0
- package/dist/utils/humanize-label.d.ts +60 -0
- package/dist/utils/humanize-label.js +62 -0
- package/dist/utils/managedBy.d.ts +27 -2
- package/dist/utils/normalize-list-view.d.ts +36 -6
- package/dist/utils/normalize-list-view.js +93 -9
- package/dist/utils/number-display.d.ts +155 -0
- package/dist/utils/number-display.js +71 -0
- package/dist/utils/predicate-fields.d.ts +29 -4
- package/dist/utils/predicate-fields.js +44 -4
- package/dist/utils/resolve-view-id.d.ts +31 -0
- package/dist/utils/resolve-view-id.js +45 -0
- package/dist/utils/retired-field-types.d.ts +90 -0
- package/dist/utils/retired-field-types.js +124 -0
- package/dist/utils/sort-query.d.ts +57 -0
- package/dist/utils/sort-query.js +35 -0
- package/dist/utils/unmaterialized-fields.d.ts +52 -0
- package/dist/utils/unmaterialized-fields.js +57 -0
- package/dist/validation/index.d.ts +1 -0
- package/dist/validation/index.js +1 -0
- package/dist/validation/schema-validator.js +105 -0
- package/dist/validation/server-owned-value.d.ts +62 -0
- package/dist/validation/server-owned-value.js +99 -0
- package/dist/validation/validators/object-validation-engine.js +0 -2
- package/package.json +5 -5
- package/dist/__benchmarks__/core.bench.d.ts +0 -8
- package/dist/__benchmarks__/core.bench.js +0 -53
- 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
|
@@ -4,7 +4,8 @@ Core logic, types, and validation for Object UI. Zero React dependencies.
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- 🎯 **Type Definitions** -
|
|
7
|
+
- 🎯 **Type Definitions** - Re-exported runtime types; the component schema
|
|
8
|
+
vocabulary itself is `@object-ui/types`
|
|
8
9
|
- 🔍 **Component Registry** - Framework-agnostic component registration system
|
|
9
10
|
- 📊 **Data Scope** - Data scope management and expression evaluation
|
|
10
11
|
- ✅ **Validation** - Zod-based schema validation
|
|
@@ -20,15 +21,20 @@ npm install @object-ui/core
|
|
|
20
21
|
|
|
21
22
|
### Type Definitions
|
|
22
23
|
|
|
24
|
+
The component schema vocabulary lives in `@object-ui/types`. Core depends on
|
|
25
|
+
that package and does not re-export it, so import the types from there. The
|
|
26
|
+
page node type is `PageNodeSchema` — the SDUI node, as distinct from the
|
|
27
|
+
authored page document.
|
|
28
|
+
|
|
23
29
|
```typescript
|
|
24
|
-
import type {
|
|
25
|
-
|
|
26
|
-
FormSchema,
|
|
30
|
+
import type {
|
|
31
|
+
PageNodeSchema,
|
|
32
|
+
FormSchema,
|
|
27
33
|
InputSchema,
|
|
28
|
-
BaseSchema
|
|
29
|
-
} from '@object-ui/
|
|
34
|
+
BaseSchema
|
|
35
|
+
} from '@object-ui/types'
|
|
30
36
|
|
|
31
|
-
const mySchema:
|
|
37
|
+
const mySchema: PageNodeSchema = {
|
|
32
38
|
type: 'page',
|
|
33
39
|
title: 'My Page',
|
|
34
40
|
body: []
|
|
@@ -40,22 +46,30 @@ const mySchema: PageSchema = {
|
|
|
40
46
|
```typescript
|
|
41
47
|
import { ComponentRegistry } from '@object-ui/core'
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const metadata = registry.get('button')
|
|
49
|
+
ComponentRegistry.register('button', buttonMetadata)
|
|
50
|
+
const metadata = ComponentRegistry.get('button')
|
|
46
51
|
```
|
|
47
52
|
|
|
53
|
+
`ComponentRegistry` is a process-level singleton exported by `@object-ui/core`;
|
|
54
|
+
`SchemaRenderer` resolves every `type` against it, so a component registered
|
|
55
|
+
here is renderable from schema anywhere in the app.
|
|
56
|
+
|
|
48
57
|
### Data Scope
|
|
49
58
|
|
|
59
|
+
`DataScopeManager` owns the named scopes a component tree reads from, and
|
|
60
|
+
`evaluateExpression` evaluates a `${...}` expression against a context. They
|
|
61
|
+
are separate exports: a scope holds data, it does not evaluate.
|
|
62
|
+
|
|
50
63
|
```typescript
|
|
51
|
-
import {
|
|
64
|
+
import { DataScopeManager, evaluateExpression } from '@object-ui/core'
|
|
52
65
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
})
|
|
66
|
+
const manager = new DataScopeManager()
|
|
67
|
+
manager.registerScope('user', { data: { name: 'John', role: 'admin' } })
|
|
56
68
|
|
|
57
|
-
const userName =
|
|
58
|
-
const isAdmin =
|
|
69
|
+
const userName = manager.getScope('user')?.data.name // 'John'
|
|
70
|
+
const isAdmin = evaluateExpression('${user.role === "admin"}', {
|
|
71
|
+
user: { name: 'John', role: 'admin' },
|
|
72
|
+
}) // true
|
|
59
73
|
```
|
|
60
74
|
|
|
61
75
|
### Server Action Dispatch (`createServerActionHandler`)
|
|
@@ -87,16 +101,16 @@ action identity (ADR-0110), the record-id resolution dance (`_rowRecord`,
|
|
|
87
101
|
guard, and the `/actions` response-envelope rule (`interpretActionResponse` /
|
|
88
102
|
`readActionPayload`, also exported).
|
|
89
103
|
|
|
90
|
-
### System Views (`
|
|
104
|
+
### System Views (`defineSystemView`)
|
|
91
105
|
|
|
92
106
|
Schemas authored in source code are part of the product contract and must
|
|
93
|
-
not be mutated at runtime. Wrap them with `
|
|
94
|
-
graph and tag it as a *System View*.
|
|
107
|
+
not be mutated at runtime. Wrap them with `defineSystemView()` to deep-freeze
|
|
108
|
+
the graph and tag it as a *System View*.
|
|
95
109
|
|
|
96
110
|
```typescript
|
|
97
|
-
import {
|
|
111
|
+
import { defineSystemView, cloneAsOverride, isSystemView } from '@object-ui/core'
|
|
98
112
|
|
|
99
|
-
export const userListView =
|
|
113
|
+
export const userListView = defineSystemView({
|
|
100
114
|
type: 'list',
|
|
101
115
|
data: { object: 'User' },
|
|
102
116
|
columns: [{ name: 'email' }],
|
|
@@ -115,7 +129,7 @@ isSystemView(draft) // false — clone is no longer Sys
|
|
|
115
129
|
|
|
116
130
|
| Tier | Source | Mutable? | API |
|
|
117
131
|
| ----------- | --------------------- | -------- | --------------------------- |
|
|
118
|
-
| System View | code (`import` / `as const`) | ❌ frozen | `
|
|
132
|
+
| System View | code (`import` / `as const`) | ❌ frozen | `defineSystemView()` |
|
|
119
133
|
| Tenant View | backend / DB | ⚠️ admin only | `cloneAsOverride()` + persist |
|
|
120
134
|
| User View | localStorage / API | ✅ user-editable | `cloneAsOverride()` + persist |
|
|
121
135
|
|
|
@@ -141,21 +155,11 @@ This allows the core types and logic to be used in:
|
|
|
141
155
|
|
|
142
156
|
## API Reference
|
|
143
157
|
|
|
144
|
-
See [full documentation](https://objectui.org/api
|
|
145
|
-
|
|
146
|
-
<!-- release-metadata:v3.3.0 -->
|
|
147
|
-
|
|
148
|
-
## Compatibility
|
|
149
|
-
|
|
150
|
-
- **Node.js:** ≥ 18
|
|
151
|
-
- **TypeScript:** ≥ 5.0 (strict mode)
|
|
152
|
-
- **`@objectstack/spec`:** ^3.3.0
|
|
153
|
-
- **`@objectstack/client`:** ^3.3.0
|
|
154
|
-
- **Tailwind CSS:** ≥ 3.4 (for packages with UI)
|
|
158
|
+
See [full documentation](https://objectui.org/docs/api) for detailed API reference.
|
|
155
159
|
|
|
156
160
|
## Links
|
|
157
161
|
|
|
158
|
-
- 📚 [Documentation](https://www.objectui.org/docs/
|
|
162
|
+
- 📚 [Documentation](https://www.objectui.org/docs/guide/architecture)
|
|
159
163
|
- 📦 [npm package](https://www.npmjs.com/package/@object-ui/core)
|
|
160
164
|
- 📝 [Changelog](./CHANGELOG.md)
|
|
161
165
|
- 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
|
|
@@ -8,14 +8,13 @@
|
|
|
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
|
-
import { ActionRunner, type ActionDef, type ActionContext, type ActionResult } from './ActionRunner';
|
|
17
|
+
import { ActionRunner, type ActionDef, type ActionContext, type ActionResult } from './ActionRunner.js';
|
|
19
18
|
/**
|
|
20
19
|
* Action location types — re-exported from `@objectstack/spec/ui` so the
|
|
21
20
|
* platform has one source of truth (`ACTION_LOCATIONS`). Do NOT redeclare
|
|
@@ -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,21 +108,24 @@ 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
130
|
* Predicate normalization goes through the shared `toPredicateInput`
|
|
112
131
|
* (`@object-ui/core`'s canonical helper — semantically identical to
|
|
@@ -130,8 +149,6 @@ export declare class ActionEngine {
|
|
|
130
149
|
getAction(name: string): ActionDef | undefined;
|
|
131
150
|
/** Execute an action by name */
|
|
132
151
|
executeAction(name: string, contextOverride?: Partial<ActionContext>): Promise<ActionResult>;
|
|
133
|
-
/** Dispatch an event — finds mapped actions and executes them */
|
|
134
|
-
dispatch(event: string, contextOverride?: Partial<ActionContext>): Promise<ActionResult[]>;
|
|
135
152
|
/** Handle a keyboard shortcut event — returns true if handled */
|
|
136
153
|
handleShortcut(keys: string, contextOverride?: Partial<ActionContext>): Promise<ActionResult | null>;
|
|
137
154
|
/** Execute a bulk operation on multiple records */
|
|
@@ -146,6 +163,6 @@ export declare class ActionEngine {
|
|
|
146
163
|
}>;
|
|
147
164
|
/** Update the action context */
|
|
148
165
|
updateContext(context: Partial<ActionContext>): void;
|
|
149
|
-
/** Clear all registered actions and
|
|
166
|
+
/** Clear all registered actions and shortcuts */
|
|
150
167
|
clear(): void;
|
|
151
168
|
}
|
|
@@ -8,15 +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
|
-
import { ActionRunner } from './ActionRunner';
|
|
17
|
+
import { ActionRunner } from './ActionRunner.js';
|
|
19
18
|
import { toPredicateInput } from '../evaluator/predicateInput.js';
|
|
19
|
+
import { hasDeclaredPredicate } from '../evaluator/declaredPredicate.js';
|
|
20
20
|
export { ACTION_LOCATIONS } from '@object-ui/types';
|
|
21
21
|
/** Normalize a shortcut string for comparison (lowercase, trimmed, sorted parts) */
|
|
22
22
|
function normalizeShortcut(keys) {
|
|
@@ -38,11 +38,37 @@ function warnHiddenPredicate(name, raw, err) {
|
|
|
38
38
|
return;
|
|
39
39
|
_warnedPredicates.add(key);
|
|
40
40
|
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
-
// eslint-disable-next-line no-console
|
|
42
41
|
console.warn(`[ActionEngine] action "${String(name)}" hidden: its \`visible\` predicate threw — ${msg}. ` +
|
|
43
42
|
`Predicate: ${src}. Action predicates evaluate against { record, recordId, objectName, user, os.user, … }; ` +
|
|
44
43
|
`use \`record.<field>\` (not a bare field).`);
|
|
45
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Registration + filtering + execution for declared actions.
|
|
47
|
+
*
|
|
48
|
+
* Entry points are by NAME (`executeAction`), by LOCATION
|
|
49
|
+
* (`getActionsForLocation`), by SHORTCUT (`handleShortcut`) and in BULK
|
|
50
|
+
* (`executeBulk`). There is deliberately no entry point by arbitrary EVENT
|
|
51
|
+
* name: this class used to carry a second, parallel one — `addMapping()`
|
|
52
|
+
* registered `{ event, actionName, condition }` triples into a private
|
|
53
|
+
* `mappings` array and `dispatch(event)` ran every triple matching an event
|
|
54
|
+
* string. It was retired under enforce-or-remove (objectui#3368) because it
|
|
55
|
+
* was a public export of `@object-ui/core` with zero production callers: in
|
|
56
|
+
* its whole lifetime nothing ever registered a mapping, so `dispatch()` had
|
|
57
|
+
* no reachable caller either, and its four call sites were all in this
|
|
58
|
+
* class's own test file.
|
|
59
|
+
*
|
|
60
|
+
* Its condition gate had drifted three ways from the `visible` contract
|
|
61
|
+
* `getActionsForLocation` below implements — it entered on a raw truthy check
|
|
62
|
+
* (so `condition: false` dispatched anyway), typed `condition` as `string`
|
|
63
|
+
* only (so a `{ dialect: 'cel', source }` envelope could not reach the
|
|
64
|
+
* canonical engine), and evaluated without `throwOnError` (so a throwing
|
|
65
|
+
* predicate failed OPEN, the opposite of `visible`'s fail-closed posture).
|
|
66
|
+
* All three were retired WITH the path rather than fixed on it: aligning the
|
|
67
|
+
* contract of an API nobody calls only widens behaviour nobody uses. If an
|
|
68
|
+
* event-keyed entry point is ever genuinely needed, it must be built on the
|
|
69
|
+
* shared predicate definitions (`hasDeclaredPredicate` + `toPredicateInput`)
|
|
70
|
+
* that the surviving gate below already uses, not on a fourth spelling.
|
|
71
|
+
*/
|
|
46
72
|
export class ActionEngine {
|
|
47
73
|
/**
|
|
48
74
|
* @param contextOrRunner Either an initial `ActionContext` (constructs a
|
|
@@ -60,12 +86,6 @@ export class ActionEngine {
|
|
|
60
86
|
writable: true,
|
|
61
87
|
value: new Map()
|
|
62
88
|
});
|
|
63
|
-
Object.defineProperty(this, "mappings", {
|
|
64
|
-
enumerable: true,
|
|
65
|
-
configurable: true,
|
|
66
|
-
writable: true,
|
|
67
|
-
value: []
|
|
68
|
-
});
|
|
69
89
|
Object.defineProperty(this, "shortcuts", {
|
|
70
90
|
enumerable: true,
|
|
71
91
|
configurable: true,
|
|
@@ -140,11 +160,6 @@ export class ActionEngine {
|
|
|
140
160
|
}
|
|
141
161
|
this.actions.delete(name);
|
|
142
162
|
this.shortcuts = this.shortcuts.filter(s => s.actionName !== name);
|
|
143
|
-
this.mappings = this.mappings.filter(m => m.actionName !== name);
|
|
144
|
-
}
|
|
145
|
-
/** Add an event-to-action mapping */
|
|
146
|
-
addMapping(mapping) {
|
|
147
|
-
this.mappings.push(mapping);
|
|
148
163
|
}
|
|
149
164
|
/**
|
|
150
165
|
* Get actions available at a specific location, sorted by priority.
|
|
@@ -152,13 +167,18 @@ export class ActionEngine {
|
|
|
152
167
|
* Filtering applied (in order):
|
|
153
168
|
* 1. `locations.includes(location)` — location/region match
|
|
154
169
|
* 2. `action.visible` — evaluated against the runner's current context
|
|
155
|
-
* ({ record, recordId, objectName, user, … })
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
170
|
+
* ({ record, recordId, objectName, user, … }) whenever a gate is
|
|
171
|
+
* DECLARED, which is asked through core's one definition
|
|
172
|
+
* `hasDeclaredPredicate` (objectui#3850's ruling, adopted here by
|
|
173
|
+
* objectui#3957). No gate — absent, `''`, blank predicate text in either
|
|
174
|
+
* spelling, an envelope with no evaluable `source`, or a value that is
|
|
175
|
+
* not a predicate at all — means visible. Evaluator errors hide the
|
|
176
|
+
* action (fail-closed) rather than throwing — this matches the contract
|
|
177
|
+
* used by every individual action renderer (`action-button`,
|
|
178
|
+
* `action-menu`, `action-bar`, …) so the same action behaves identically
|
|
179
|
+
* whether surfaced via the engine or consumed standalone, which is now
|
|
180
|
+
* true of the gate in front of the verdict as well as the verdict
|
|
181
|
+
* (objectui#3314: one value, one answer, whichever entry reads it).
|
|
162
182
|
*
|
|
163
183
|
* Predicate normalization goes through the shared `toPredicateInput`
|
|
164
184
|
* (`@object-ui/core`'s canonical helper — semantically identical to
|
|
@@ -194,22 +214,36 @@ export class ActionEngine {
|
|
|
194
214
|
})
|
|
195
215
|
.filter(ra => {
|
|
196
216
|
const raw = ra.action.visible;
|
|
197
|
-
|
|
217
|
+
// Ask "is a `visible` gate DECLARED?" through the repo's ONE definition
|
|
218
|
+
// (`evaluator/declaredPredicate.ts`, objectui#3850's ruling), which is
|
|
219
|
+
// what the renderers, `SchemaRenderer` and `ActionRunner`'s execution
|
|
220
|
+
// gates ask. This filter used to answer it with a range of its own —
|
|
221
|
+
// three empty spellings folded by hand (`null` / `''` / an envelope with
|
|
222
|
+
// an empty `source`) and everything else coerced with `Boolean(raw)` —
|
|
223
|
+
// and it was the LAST consumer to do so, so the same value got two
|
|
224
|
+
// answers depending on which entry read it: `visible: 0` / `NaN` hid the
|
|
225
|
+
// action here and showed it on the renderer face, and `visible: ' '`
|
|
226
|
+
// hid it here because the normalizer wraps a blank string into `'${ }'`
|
|
227
|
+
// whose verdict is falsy. That is the shape objectui#3314's invariant
|
|
228
|
+
// forbids, and it is fixed by deleting a range, not by adding one
|
|
229
|
+
// (objectui#3957). A value that is not a predicate, or a predicate that
|
|
230
|
+
// says nothing, must not be the reason an action is hidden from
|
|
231
|
+
// everyone — the same fail-open posture `ActionRunner` already committed
|
|
232
|
+
// to for `disabled` (`catch { isDisabled = false }`).
|
|
233
|
+
//
|
|
234
|
+
// Booleans need no branch of their own: they are DECLARED (`visible:
|
|
235
|
+
// false` is a verdict, objectui#3812) and `evaluateCondition`
|
|
236
|
+
// short-circuits them.
|
|
237
|
+
if (!hasDeclaredPredicate(raw))
|
|
198
238
|
return true;
|
|
199
|
-
if (raw === false)
|
|
200
|
-
return false;
|
|
201
239
|
// #3314 — one shared normalization, not two. Hand-rolling the envelope
|
|
202
240
|
// unwrap here is what dropped `dialect: 'cel'` and demoted the
|
|
203
241
|
// predicate to the legacy JS engine; `toPredicateInput` keeps the
|
|
204
242
|
// envelope so `evaluateCondition` can route it to `@objectstack/formula`
|
|
205
243
|
// (the engine the server enforces with), exactly as the renderers do.
|
|
244
|
+
// Declared ⇒ the normalizer left something to evaluate, so there is no
|
|
245
|
+
// `undefined` case left to handle here.
|
|
206
246
|
const expr = toPredicateInput(raw);
|
|
207
|
-
if (expr === undefined) {
|
|
208
|
-
// Not an evaluable predicate: an envelope with an empty `source`
|
|
209
|
-
// (→ nothing declared → visible), or a stray non-predicate value,
|
|
210
|
-
// which keeps the historical `Boolean(raw)` coercion.
|
|
211
|
-
return typeof raw === 'object' ? true : Boolean(raw);
|
|
212
|
-
}
|
|
213
247
|
try {
|
|
214
248
|
// `throwOnError: true` is required for fail-closed semantics: the
|
|
215
249
|
// evaluator's default behavior swallows expression errors and
|
|
@@ -262,26 +296,6 @@ export class ActionEngine {
|
|
|
262
296
|
}
|
|
263
297
|
return this.runner.execute(registered.action);
|
|
264
298
|
}
|
|
265
|
-
/** Dispatch an event — finds mapped actions and executes them */
|
|
266
|
-
async dispatch(event, contextOverride) {
|
|
267
|
-
const matchingMappings = this.mappings.filter(m => m.event === event);
|
|
268
|
-
if (matchingMappings.length === 0) {
|
|
269
|
-
return [];
|
|
270
|
-
}
|
|
271
|
-
const results = [];
|
|
272
|
-
for (const mapping of matchingMappings) {
|
|
273
|
-
// Check condition if present
|
|
274
|
-
if (mapping.condition) {
|
|
275
|
-
const evaluator = this.runner.getEvaluator();
|
|
276
|
-
const shouldRun = evaluator.evaluateCondition(mapping.condition);
|
|
277
|
-
if (!shouldRun)
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
const result = await this.executeAction(mapping.actionName, contextOverride);
|
|
281
|
-
results.push(result);
|
|
282
|
-
}
|
|
283
|
-
return results;
|
|
284
|
-
}
|
|
285
299
|
/** Handle a keyboard shortcut event — returns true if handled */
|
|
286
300
|
async handleShortcut(keys, contextOverride) {
|
|
287
301
|
const actionName = this.normalizedShortcutMap.get(normalizeShortcut(keys));
|
|
@@ -341,10 +355,9 @@ export class ActionEngine {
|
|
|
341
355
|
updateContext(context) {
|
|
342
356
|
this.runner.updateContext(context);
|
|
343
357
|
}
|
|
344
|
-
/** Clear all registered actions and
|
|
358
|
+
/** Clear all registered actions and shortcuts */
|
|
345
359
|
clear() {
|
|
346
360
|
this.actions.clear();
|
|
347
|
-
this.mappings = [];
|
|
348
361
|
this.shortcuts = [];
|
|
349
362
|
this.normalizedShortcutMap.clear();
|
|
350
363
|
}
|