@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
|
@@ -6,26 +6,46 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* What an action is allowed to carry — objectstack#4075
|
|
9
|
+
* What an action is allowed to carry — objectstack#4075, all three steps.
|
|
10
10
|
*
|
|
11
|
-
* `ActionDef`
|
|
12
|
-
* Concretely (objectui#2990): deleting `ActionDef.execute` produced
|
|
13
|
-
* errors even though the field had just been removed, and stale
|
|
14
|
-
* authoring `execute: 'markDone'` type-
|
|
15
|
-
* `@object-ui/types`' `ActionSchema` — which has no index signature —
|
|
16
|
-
* produced `TS2353` at the authoring site. One of the two readers
|
|
17
|
-
* retired key; the other
|
|
11
|
+
* `ActionDef` used to end with `[key: string]: any`, so it accepted any key of
|
|
12
|
+
* any type. Concretely (objectui#2990): deleting `ActionDef.execute` produced
|
|
13
|
+
* ZERO compile errors even though the field had just been removed, and stale
|
|
14
|
+
* metadata authoring `execute: 'markDone'` type-checked. The same deletion
|
|
15
|
+
* against `@object-ui/types`' `ActionSchema` — which has no index signature —
|
|
16
|
+
* correctly produced `TS2353` at the authoring site. One of the two readers
|
|
17
|
+
* could catch a retired key; the other was structurally incapable.
|
|
18
18
|
*
|
|
19
|
-
* That asymmetry
|
|
19
|
+
* That asymmetry was the issue. An open key set on a DECLARED METADATA CONTRACT
|
|
20
20
|
* is what lets a typo (`targt`, `exectue`) and a tombstoned key (`execute`) both
|
|
21
21
|
* sail through to a runner that then silently does nothing — the #2169 "Mark
|
|
22
22
|
* Done does nothing" shape.
|
|
23
23
|
*
|
|
24
|
-
* This module
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* This module was step 1 of the staged narrowing: make the key set VISIBLE and
|
|
25
|
+
* warn on anything outside it, without changing a single type. Step 2 promoted
|
|
26
|
+
* the legitimate keys to explicit optional fields; **step 3 removed the index
|
|
27
|
+
* signature**, so `tsc` now rejects a typo or a retired key outright.
|
|
28
|
+
*
|
|
29
|
+
* ── Why this module SURVIVES step 3 ──────────────────────────────────────────
|
|
30
|
+
* Step 1 wrote that the dev-mode warning "can retire" once the index signature
|
|
31
|
+
* came down. Step 3 measured that and kept it, because the two mechanisms cover
|
|
32
|
+
* DISJOINT populations rather than the same one twice:
|
|
33
|
+
*
|
|
34
|
+
* - `tsc` sees action literals AUTHORED IN CODE. It rejects an unknown key at
|
|
35
|
+
* the construction site, which is the strongest possible signal — but only
|
|
36
|
+
* for actions that exist as TypeScript.
|
|
37
|
+
* - the warning below sees actions that arrive as DATA. objectstack#3903
|
|
38
|
+
* established that stored `sys_metadata` rows are rehydrated UNPARSED, so
|
|
39
|
+
* authored metadata reaches `runner.execute()` as a plain object no
|
|
40
|
+
* compiler ever looked at. That is the population `execute: 'markDone'`
|
|
41
|
+
* actually lives in, and no type can reach it.
|
|
42
|
+
*
|
|
43
|
+
* Retiring the warning would therefore have re-opened the runtime half of the
|
|
44
|
+
* gap while closing the compile-time half — which is why `executeScript`'s
|
|
45
|
+
* rename-prescription branch also survives, for the same rows and the same
|
|
46
|
+
* reason. What DID retire with the index signature is the inverted pin in
|
|
47
|
+
* `actionKeys.pin.test.ts` that asserted the signature was still there; it now
|
|
48
|
+
* asserts the opposite, which was always its stated completion condition.
|
|
29
49
|
*
|
|
30
50
|
* The lists are pinned by `__tests__/actionKeys.pin.test.ts`, which re-derives
|
|
31
51
|
* each one from its actual source. A hand-maintained list that drifts from the
|
|
@@ -66,8 +86,22 @@
|
|
|
66
86
|
* deprecating them toward a spelling that does not exist would be worse than
|
|
67
87
|
* leaving them declared.
|
|
68
88
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
89
|
+
* ── What step 3 found the first two steps had missed ─────────────────────────
|
|
90
|
+
* Deleting the index signature is the only thing that can name the keys still
|
|
91
|
+
* hiding behind it, and it named five, in two groups:
|
|
92
|
+
*
|
|
93
|
+
* `to` / `external` / `newTab` / `replace` — the `navigation` alias's own
|
|
94
|
+
* spelling. Step 1 had already ruled these legitimate and listed them below in
|
|
95
|
+
* {@link NAVIGATION_ALIAS_KEYS}, but step 2's scope was the SPEC-owned keys, so
|
|
96
|
+
* they were declared as data and never as fields; `executeNavigation` read them
|
|
97
|
+
* through the index signature. Promoted, and deliberately not `@deprecated` —
|
|
98
|
+
* step 2's acceptance ruled on exactly that point.
|
|
99
|
+
*
|
|
100
|
+
* `description` — objectui vocabulary from `@object-ui/types`' renderer view of
|
|
101
|
+
* an action, forwarded by all four action renderers (`check:action-forward-parity`
|
|
102
|
+
* requires it) and read by the param-collection dialog for its subtitle
|
|
103
|
+
* (objectui#4192). Authorable, forwarded, read — and undeclared. These were the
|
|
104
|
+
* only two `TS2353`s the deletion produced across the whole workspace.
|
|
71
105
|
*/
|
|
72
106
|
/**
|
|
73
107
|
* Every property `ActionDef` declares, in declaration order.
|
|
@@ -76,7 +110,7 @@
|
|
|
76
110
|
* `keyof ActionDef` cannot substitute for this list: the index signature widens
|
|
77
111
|
* it to `string | number`, which is precisely the problem being worked around.
|
|
78
112
|
*/
|
|
79
|
-
export declare const ACTION_DEF_KEYS: readonly ["type", "actionType", "name", "label", "confirmText", "confirm", "condition", "disabled", "api", "endpoint", "method", "navigate", "onClick", "reload", "close", "redirect", "toast", "successMessage", "errorMessage", "refreshAfter", "undoable", "params", "actionParams", "resultDialog", "target", "body", "openIn", "modal", "chain", "chainMode", "onSuccess", "onFailure", "opensInNewTab", "newTabUrl", "locations", "visible", "requiredPermissions", "icon", "variant", "order", "component", "objectName", "ai", "aria", "bodyExtra", "bodyShape", "mode", "recordIdField", "recordIdParam", "requiresFeature", "shortcut", "bulkEnabled"];
|
|
113
|
+
export declare const ACTION_DEF_KEYS: readonly ["type", "actionType", "name", "label", "description", "confirmText", "confirm", "condition", "disabled", "api", "endpoint", "method", "navigate", "to", "external", "newTab", "replace", "onClick", "reload", "close", "redirect", "toast", "successMessage", "errorMessage", "refreshAfter", "undoable", "params", "actionParams", "resultDialog", "target", "body", "openIn", "modal", "chain", "chainMode", "onSuccess", "onFailure", "opensInNewTab", "newTabUrl", "locations", "visible", "requiredPermissions", "icon", "variant", "order", "component", "objectName", "ai", "aria", "bodyExtra", "bodyShape", "mode", "recordIdField", "recordIdParam", "requiresFeature", "shortcut", "bulkEnabled"];
|
|
80
114
|
/**
|
|
81
115
|
* Every property `@objectstack/spec`'s `ActionSchema` declares.
|
|
82
116
|
*
|
|
@@ -91,7 +125,7 @@ export declare const ACTION_DEF_KEYS: readonly ["type", "actionType", "name", "l
|
|
|
91
125
|
* test and wrong in shipped code — `@object-ui/types` made the same call for
|
|
92
126
|
* `ActionComponent`. The pin test does the walk and fails the day this drifts.
|
|
93
127
|
*/
|
|
94
|
-
export declare const SPEC_ACTION_KEYS: readonly ["ai", "aria", "body", "bodyExtra", "bodyShape", "bulkEnabled", "component", "confirmText", "disabled", "errorMessage", "execute", "icon", "label", "locations", "method", "mode", "name", "newTabUrl", "objectName", "openIn", "opensInNewTab", "order", "params", "recordIdField", "recordIdParam", "refreshAfter", "requiredPermissions", "requiresFeature", "resultDialog", "shortcut", "successMessage", "target", "type", "undoable", "variant", "visible"];
|
|
128
|
+
export declare const SPEC_ACTION_KEYS: readonly ["_lock", "_lockDocsUrl", "_lockReason", "_lockSource", "_packageId", "_packageVersion", "_provenance", "ai", "aria", "body", "bodyExtra", "bodyShape", "bulkEnabled", "component", "confirmText", "description", "disabled", "errorMessage", "execute", "icon", "label", "locations", "method", "mode", "name", "newTabUrl", "objectName", "openIn", "opensInNewTab", "order", "params", "recordIdField", "recordIdParam", "refreshAfter", "requiredPermissions", "requiresFeature", "resultDialog", "shortcut", "successMessage", "target", "type", "undoable", "variant", "visible"];
|
|
95
129
|
/**
|
|
96
130
|
* The `navigation` alias's own spelling of a target, read off the action itself
|
|
97
131
|
* when no nested `navigate` object is present (`ActionRunner.executeNavigation`).
|
|
@@ -111,8 +145,15 @@ export declare const NAVIGATION_ALIAS_KEYS: readonly ["to", "external", "newTab"
|
|
|
111
145
|
* Warned about separately from unknown keys, and more loudly: an unknown key is
|
|
112
146
|
* probably a typo, while a retired key is metadata that used to work. That
|
|
113
147
|
* distinction is why `executeScript` carries a runtime branch returning the
|
|
114
|
-
* rename prescription
|
|
115
|
-
*
|
|
148
|
+
* rename prescription.
|
|
149
|
+
*
|
|
150
|
+
* Step 1 expected that branch to retire with the index signature. It does not,
|
|
151
|
+
* and the reason is the one in this module's header: `tsc` closed the
|
|
152
|
+
* code-authored half of the gap, while `execute: 'markDone'` lives in stored
|
|
153
|
+
* rows that reach the runner UNPARSED (objectstack#3903). The branch reads the
|
|
154
|
+
* key off an untyped view of the action precisely so that `ActionDef` can go on
|
|
155
|
+
* NOT declaring it — declaring it to make the read compile would re-legitimize
|
|
156
|
+
* a tombstone the spec keeps only in order to reject it by name.
|
|
116
157
|
*/
|
|
117
158
|
export declare const RETIRED_ACTION_KEYS: Readonly<Record<string, string>>;
|
|
118
159
|
/** Every key an action may legitimately carry today. */
|
|
@@ -133,3 +174,38 @@ export declare function resetActionKeyWarnings(): void;
|
|
|
133
174
|
* down, this is the audible half.
|
|
134
175
|
*/
|
|
135
176
|
export declare function warnOnUnknownActionKeys(action: object | null | undefined, where?: string): void;
|
|
177
|
+
/**
|
|
178
|
+
* Does this action carry the DEPRECATED object-form `params` — a static request
|
|
179
|
+
* payload written under the key that means "parameter definitions"?
|
|
180
|
+
*
|
|
181
|
+
* The discriminator is `Array.isArray` and the type guard is `api`, mirroring
|
|
182
|
+
* `@objectstack/spec`'s `inline-action-api-params-to-body-extra` conversion
|
|
183
|
+
* (ADR-0087 D2) key for key. Contract parity with the producer is the point: the
|
|
184
|
+
* conversion rewrites exactly this shape to `bodyExtra` at load, so the runner
|
|
185
|
+
* must recognize exactly the same shape as "the thing that got rewritten".
|
|
186
|
+
*
|
|
187
|
+
* The `api` guard is also what keeps the deprecation out of objectstack#6828's
|
|
188
|
+
* territory — object-form `params` on a `type: 'url'` action is a THIRD meaning
|
|
189
|
+
* (`interpolateTarget`'s `${param.X}` scope, `executeUrl`'s `params.newTab`) and
|
|
190
|
+
* `bodyExtra` is NOT its replacement. Do not widen this guard without that
|
|
191
|
+
* ruling.
|
|
192
|
+
*/
|
|
193
|
+
export declare function hasDeprecatedObjectParams(action: object | null | undefined): boolean;
|
|
194
|
+
/**
|
|
195
|
+
* Dev-mode only: name the object-form `params` that `@objectstack/spec` 17 already
|
|
196
|
+
* refuses at the authoring door, and prescribe `bodyExtra`.
|
|
197
|
+
*
|
|
198
|
+
* This is the audible half of the compat window the maintainer's 2026-08-06
|
|
199
|
+
* ruling on objectstack#5777 ordered (direction A — a separate key, no same-name
|
|
200
|
+
* union): the runner keeps READING a non-array `params` as the static payload for
|
|
201
|
+
* one version window, says so, and the arm is deleted at 18. The loud half lives
|
|
202
|
+
* at the producer, where it belongs — spec's `params` field refuses the object
|
|
203
|
+
* form outright with a message naming `bodyExtra`, and the ADR-0087 conversion
|
|
204
|
+
* rewrites sources that still carry it. So a warning is the correct severity
|
|
205
|
+
* HERE: by the time metadata reaches this runner the producer has already had its
|
|
206
|
+
* say, and what is left to catch is a host composing an ActionDef in code.
|
|
207
|
+
*
|
|
208
|
+
* Dev-only and warn-once, matching {@link warnOnUnknownActionKeys} — a warning
|
|
209
|
+
* that floods a production console is a warning that gets muted.
|
|
210
|
+
*/
|
|
211
|
+
export declare function warnOnDeprecatedObjectParams(action: object | null | undefined, where?: string): void;
|
|
@@ -6,26 +6,46 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* What an action is allowed to carry — objectstack#4075
|
|
9
|
+
* What an action is allowed to carry — objectstack#4075, all three steps.
|
|
10
10
|
*
|
|
11
|
-
* `ActionDef`
|
|
12
|
-
* Concretely (objectui#2990): deleting `ActionDef.execute` produced
|
|
13
|
-
* errors even though the field had just been removed, and stale
|
|
14
|
-
* authoring `execute: 'markDone'` type-
|
|
15
|
-
* `@object-ui/types`' `ActionSchema` — which has no index signature —
|
|
16
|
-
* produced `TS2353` at the authoring site. One of the two readers
|
|
17
|
-
* retired key; the other
|
|
11
|
+
* `ActionDef` used to end with `[key: string]: any`, so it accepted any key of
|
|
12
|
+
* any type. Concretely (objectui#2990): deleting `ActionDef.execute` produced
|
|
13
|
+
* ZERO compile errors even though the field had just been removed, and stale
|
|
14
|
+
* metadata authoring `execute: 'markDone'` type-checked. The same deletion
|
|
15
|
+
* against `@object-ui/types`' `ActionSchema` — which has no index signature —
|
|
16
|
+
* correctly produced `TS2353` at the authoring site. One of the two readers
|
|
17
|
+
* could catch a retired key; the other was structurally incapable.
|
|
18
18
|
*
|
|
19
|
-
* That asymmetry
|
|
19
|
+
* That asymmetry was the issue. An open key set on a DECLARED METADATA CONTRACT
|
|
20
20
|
* is what lets a typo (`targt`, `exectue`) and a tombstoned key (`execute`) both
|
|
21
21
|
* sail through to a runner that then silently does nothing — the #2169 "Mark
|
|
22
22
|
* Done does nothing" shape.
|
|
23
23
|
*
|
|
24
|
-
* This module
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* This module was step 1 of the staged narrowing: make the key set VISIBLE and
|
|
25
|
+
* warn on anything outside it, without changing a single type. Step 2 promoted
|
|
26
|
+
* the legitimate keys to explicit optional fields; **step 3 removed the index
|
|
27
|
+
* signature**, so `tsc` now rejects a typo or a retired key outright.
|
|
28
|
+
*
|
|
29
|
+
* ── Why this module SURVIVES step 3 ──────────────────────────────────────────
|
|
30
|
+
* Step 1 wrote that the dev-mode warning "can retire" once the index signature
|
|
31
|
+
* came down. Step 3 measured that and kept it, because the two mechanisms cover
|
|
32
|
+
* DISJOINT populations rather than the same one twice:
|
|
33
|
+
*
|
|
34
|
+
* - `tsc` sees action literals AUTHORED IN CODE. It rejects an unknown key at
|
|
35
|
+
* the construction site, which is the strongest possible signal — but only
|
|
36
|
+
* for actions that exist as TypeScript.
|
|
37
|
+
* - the warning below sees actions that arrive as DATA. objectstack#3903
|
|
38
|
+
* established that stored `sys_metadata` rows are rehydrated UNPARSED, so
|
|
39
|
+
* authored metadata reaches `runner.execute()` as a plain object no
|
|
40
|
+
* compiler ever looked at. That is the population `execute: 'markDone'`
|
|
41
|
+
* actually lives in, and no type can reach it.
|
|
42
|
+
*
|
|
43
|
+
* Retiring the warning would therefore have re-opened the runtime half of the
|
|
44
|
+
* gap while closing the compile-time half — which is why `executeScript`'s
|
|
45
|
+
* rename-prescription branch also survives, for the same rows and the same
|
|
46
|
+
* reason. What DID retire with the index signature is the inverted pin in
|
|
47
|
+
* `actionKeys.pin.test.ts` that asserted the signature was still there; it now
|
|
48
|
+
* asserts the opposite, which was always its stated completion condition.
|
|
29
49
|
*
|
|
30
50
|
* The lists are pinned by `__tests__/actionKeys.pin.test.ts`, which re-derives
|
|
31
51
|
* each one from its actual source. A hand-maintained list that drifts from the
|
|
@@ -66,8 +86,22 @@
|
|
|
66
86
|
* deprecating them toward a spelling that does not exist would be worse than
|
|
67
87
|
* leaving them declared.
|
|
68
88
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
89
|
+
* ── What step 3 found the first two steps had missed ─────────────────────────
|
|
90
|
+
* Deleting the index signature is the only thing that can name the keys still
|
|
91
|
+
* hiding behind it, and it named five, in two groups:
|
|
92
|
+
*
|
|
93
|
+
* `to` / `external` / `newTab` / `replace` — the `navigation` alias's own
|
|
94
|
+
* spelling. Step 1 had already ruled these legitimate and listed them below in
|
|
95
|
+
* {@link NAVIGATION_ALIAS_KEYS}, but step 2's scope was the SPEC-owned keys, so
|
|
96
|
+
* they were declared as data and never as fields; `executeNavigation` read them
|
|
97
|
+
* through the index signature. Promoted, and deliberately not `@deprecated` —
|
|
98
|
+
* step 2's acceptance ruled on exactly that point.
|
|
99
|
+
*
|
|
100
|
+
* `description` — objectui vocabulary from `@object-ui/types`' renderer view of
|
|
101
|
+
* an action, forwarded by all four action renderers (`check:action-forward-parity`
|
|
102
|
+
* requires it) and read by the param-collection dialog for its subtitle
|
|
103
|
+
* (objectui#4192). Authorable, forwarded, read — and undeclared. These were the
|
|
104
|
+
* only two `TS2353`s the deletion produced across the whole workspace.
|
|
71
105
|
*/
|
|
72
106
|
/**
|
|
73
107
|
* Every property `ActionDef` declares, in declaration order.
|
|
@@ -81,6 +115,10 @@ export const ACTION_DEF_KEYS = [
|
|
|
81
115
|
'actionType',
|
|
82
116
|
'name',
|
|
83
117
|
'label',
|
|
118
|
+
// Promoted by step 3 — objectui vocabulary (`@object-ui/types`' renderer view
|
|
119
|
+
// of an action), forwarded by all four action renderers and read by the
|
|
120
|
+
// param-collection dialog, but never declared on `ActionDef`.
|
|
121
|
+
'description',
|
|
84
122
|
'confirmText',
|
|
85
123
|
'confirm',
|
|
86
124
|
'condition',
|
|
@@ -89,6 +127,14 @@ export const ACTION_DEF_KEYS = [
|
|
|
89
127
|
'endpoint',
|
|
90
128
|
'method',
|
|
91
129
|
'navigate',
|
|
130
|
+
// The `navigation` alias's own spelling, promoted to real fields by step 3.
|
|
131
|
+
// Listed a second time in `NAVIGATION_ALIAS_KEYS` below, which keeps the
|
|
132
|
+
// separate fact that these four are objectui dialect with a spec-adoption
|
|
133
|
+
// tripwire; this list is only "what the interface declares".
|
|
134
|
+
'to',
|
|
135
|
+
'external',
|
|
136
|
+
'newTab',
|
|
137
|
+
'replace',
|
|
92
138
|
'onClick',
|
|
93
139
|
'reload',
|
|
94
140
|
'close',
|
|
@@ -150,6 +196,17 @@ export const ACTION_DEF_KEYS = [
|
|
|
150
196
|
* `ActionComponent`. The pin test does the walk and fails the day this drifts.
|
|
151
197
|
*/
|
|
152
198
|
export const SPEC_ACTION_KEYS = [
|
|
199
|
+
// The package-lock / provenance envelope, added to `ActionSchema` in spec
|
|
200
|
+
// 17.0.0-rc.2. Not authorable action vocabulary — it is stamped on a record
|
|
201
|
+
// that came from an installed package — but it IS declared by the schema, so
|
|
202
|
+
// an action carrying it must not be reported as having unknown keys.
|
|
203
|
+
'_lock',
|
|
204
|
+
'_lockDocsUrl',
|
|
205
|
+
'_lockReason',
|
|
206
|
+
'_lockSource',
|
|
207
|
+
'_packageId',
|
|
208
|
+
'_packageVersion',
|
|
209
|
+
'_provenance',
|
|
153
210
|
'ai',
|
|
154
211
|
'aria',
|
|
155
212
|
'body',
|
|
@@ -158,6 +215,15 @@ export const SPEC_ACTION_KEYS = [
|
|
|
158
215
|
'bulkEnabled',
|
|
159
216
|
'component',
|
|
160
217
|
'confirmText',
|
|
218
|
+
// Added to `ActionSchema` in @objectstack/spec 17.0.0-rc.6. Listed here purely
|
|
219
|
+
// because this array's contract is "every property the spec's `ActionSchema`
|
|
220
|
+
// declares" — restating the spec, not adopting a feature. `ActionDef` does NOT
|
|
221
|
+
// declare `description` and is not changed: whether the runner or any renderer
|
|
222
|
+
// should READ it is a separate question, and `actionKeys.pin.test.ts`'s
|
|
223
|
+
// `ActionDef` half stays green without it. What this entry buys is that an
|
|
224
|
+
// action carrying a `description` is no longer reported as having an unknown
|
|
225
|
+
// key, which is the only thing the inventory is consulted for.
|
|
226
|
+
'description',
|
|
161
227
|
'disabled',
|
|
162
228
|
'errorMessage',
|
|
163
229
|
'execute',
|
|
@@ -206,8 +272,15 @@ export const NAVIGATION_ALIAS_KEYS = ['to', 'external', 'newTab', 'replace'];
|
|
|
206
272
|
* Warned about separately from unknown keys, and more loudly: an unknown key is
|
|
207
273
|
* probably a typo, while a retired key is metadata that used to work. That
|
|
208
274
|
* distinction is why `executeScript` carries a runtime branch returning the
|
|
209
|
-
* rename prescription
|
|
210
|
-
*
|
|
275
|
+
* rename prescription.
|
|
276
|
+
*
|
|
277
|
+
* Step 1 expected that branch to retire with the index signature. It does not,
|
|
278
|
+
* and the reason is the one in this module's header: `tsc` closed the
|
|
279
|
+
* code-authored half of the gap, while `execute: 'markDone'` lives in stored
|
|
280
|
+
* rows that reach the runner UNPARSED (objectstack#3903). The branch reads the
|
|
281
|
+
* key off an untyped view of the action precisely so that `ActionDef` can go on
|
|
282
|
+
* NOT declaring it — declaring it to make the read compile would re-legitimize
|
|
283
|
+
* a tombstone the spec keeps only in order to reject it by name.
|
|
211
284
|
*/
|
|
212
285
|
export const RETIRED_ACTION_KEYS = {
|
|
213
286
|
execute: '`execute` was removed in @objectstack/spec 17 (#3855) — rename the key to `target`. ' +
|
|
@@ -281,3 +354,80 @@ export function warnOnUnknownActionKeys(action, where = 'ActionRunner') {
|
|
|
281
354
|
'(packages/core/src/actions/actionKeys.ts). Warned rather than rejected because `ActionDef` ' +
|
|
282
355
|
'still carries `[key: string]: any`, so the compiler cannot see this (objectstack#4075).');
|
|
283
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Keys a HOST stashes into an action's `params` for the runner's own plumbing,
|
|
359
|
+
* rather than an author writing them there as a request payload.
|
|
360
|
+
*
|
|
361
|
+
* {@link warnOnDeprecatedObjectParams} has to tell "the author put the payload in
|
|
362
|
+
* `params`" (the shape objectstack#5777 retires) apart from "a host stashed row
|
|
363
|
+
* context there" (a shape the runner itself asks for, and that is not going
|
|
364
|
+
* away). `DeclaredActionsBar` dispatches `params: { _rowRecord: record }`,
|
|
365
|
+
* `RelatedRecordActionsBridge` does the same, and `ObjectGrid` adds
|
|
366
|
+
* `_selectedIds`; the api handler lifts `recordId` out of the field values
|
|
367
|
+
* before it updates. Warning on those would fire on nearly every declared-action
|
|
368
|
+
* click and prescribe `bodyExtra`, which is the wrong home for all of them.
|
|
369
|
+
*
|
|
370
|
+
* The `_` prefix is the existing convention for the stash — `recordId` is its one
|
|
371
|
+
* unprefixed member, so it is listed rather than inferred.
|
|
372
|
+
*/
|
|
373
|
+
const HOST_STASHED_PARAM_KEYS = new Set(['recordId']);
|
|
374
|
+
const isAuthoredParamKey = (key) => !key.startsWith('_') && !HOST_STASHED_PARAM_KEYS.has(key);
|
|
375
|
+
/**
|
|
376
|
+
* Does this action carry the DEPRECATED object-form `params` — a static request
|
|
377
|
+
* payload written under the key that means "parameter definitions"?
|
|
378
|
+
*
|
|
379
|
+
* The discriminator is `Array.isArray` and the type guard is `api`, mirroring
|
|
380
|
+
* `@objectstack/spec`'s `inline-action-api-params-to-body-extra` conversion
|
|
381
|
+
* (ADR-0087 D2) key for key. Contract parity with the producer is the point: the
|
|
382
|
+
* conversion rewrites exactly this shape to `bodyExtra` at load, so the runner
|
|
383
|
+
* must recognize exactly the same shape as "the thing that got rewritten".
|
|
384
|
+
*
|
|
385
|
+
* The `api` guard is also what keeps the deprecation out of objectstack#6828's
|
|
386
|
+
* territory — object-form `params` on a `type: 'url'` action is a THIRD meaning
|
|
387
|
+
* (`interpolateTarget`'s `${param.X}` scope, `executeUrl`'s `params.newTab`) and
|
|
388
|
+
* `bodyExtra` is NOT its replacement. Do not widen this guard without that
|
|
389
|
+
* ruling.
|
|
390
|
+
*/
|
|
391
|
+
export function hasDeprecatedObjectParams(action) {
|
|
392
|
+
if (!action || typeof action !== 'object')
|
|
393
|
+
return false;
|
|
394
|
+
const { type, actionType, params } = action;
|
|
395
|
+
if ((type ?? actionType) !== 'api')
|
|
396
|
+
return false;
|
|
397
|
+
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
398
|
+
return false;
|
|
399
|
+
return Object.keys(params).some(isAuthoredParamKey);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Dev-mode only: name the object-form `params` that `@objectstack/spec` 17 already
|
|
403
|
+
* refuses at the authoring door, and prescribe `bodyExtra`.
|
|
404
|
+
*
|
|
405
|
+
* This is the audible half of the compat window the maintainer's 2026-08-06
|
|
406
|
+
* ruling on objectstack#5777 ordered (direction A — a separate key, no same-name
|
|
407
|
+
* union): the runner keeps READING a non-array `params` as the static payload for
|
|
408
|
+
* one version window, says so, and the arm is deleted at 18. The loud half lives
|
|
409
|
+
* at the producer, where it belongs — spec's `params` field refuses the object
|
|
410
|
+
* form outright with a message naming `bodyExtra`, and the ADR-0087 conversion
|
|
411
|
+
* rewrites sources that still carry it. So a warning is the correct severity
|
|
412
|
+
* HERE: by the time metadata reaches this runner the producer has already had its
|
|
413
|
+
* say, and what is left to catch is a host composing an ActionDef in code.
|
|
414
|
+
*
|
|
415
|
+
* Dev-only and warn-once, matching {@link warnOnUnknownActionKeys} — a warning
|
|
416
|
+
* that floods a production console is a warning that gets muted.
|
|
417
|
+
*/
|
|
418
|
+
export function warnOnDeprecatedObjectParams(action, where = 'ActionRunner') {
|
|
419
|
+
if (!isDev())
|
|
420
|
+
return;
|
|
421
|
+
if (!hasDeprecatedObjectParams(action))
|
|
422
|
+
return;
|
|
423
|
+
const name = action?.name ?? '(unnamed)';
|
|
424
|
+
const memo = `deprecated-object-params:${String(name)}`;
|
|
425
|
+
if (warned.has(memo))
|
|
426
|
+
return;
|
|
427
|
+
warned.add(memo);
|
|
428
|
+
console.warn(`[${where}] action "${String(name)}" carries a static request payload in \`params\`. ` +
|
|
429
|
+
'`params` is the parameter DEFINITION array (fields collected from the user before the ' +
|
|
430
|
+
'action runs), never a payload map — put the payload in `bodyExtra` instead ' +
|
|
431
|
+
'(objectstack#5777). Still read as the payload for one version window; the arm is ' +
|
|
432
|
+
'removed at 18. `bodyExtra` is merged LAST, so it overrides anything left here.');
|
|
433
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
export interface ActionResponseOutcome {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
/** Human-readable failure text. Always a string when `ok` is false. */
|
|
11
|
+
error?: string;
|
|
12
|
+
/**
|
|
13
|
+
* The ACTION ENVELOPE (`body.data`) — `{ success, data }`. Kept as-is
|
|
14
|
+
* because `ActionResult.data` has always carried this level and
|
|
15
|
+
* `resultDialog` field paths in the wild may compensate for it; see the
|
|
16
|
+
* note in {@link readActionPayload}.
|
|
17
|
+
*/
|
|
18
|
+
envelope?: unknown;
|
|
19
|
+
/** What the handler actually returned (`body.data.data`). */
|
|
20
|
+
payload?: unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The handler's own return value — one level deeper than the action envelope.
|
|
24
|
+
*
|
|
25
|
+
* `body.data` is `{ success, data }`; the handler's value is `body.data.data`.
|
|
26
|
+
* Reading the shallower level is why an action returning `{ redirectUrl }` was
|
|
27
|
+
* silently ignored: `body.data.redirectUrl` is never set by anything, because
|
|
28
|
+
* that level is constructed by the server and only ever holds `success`/`data`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readActionPayload(envelope: unknown): unknown;
|
|
31
|
+
/** The pre-objectstack#3962 inner envelope, detected narrowly. */
|
|
32
|
+
export declare function isLegacyActionEnvelope(v: unknown): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Classify an `/actions` response. `label` names the action for the fallback
|
|
35
|
+
* message (e.g. `Action "log_call" failed`).
|
|
36
|
+
*
|
|
37
|
+
* `json` is the parsed body, or `null` when it could not be parsed.
|
|
38
|
+
*/
|
|
39
|
+
export declare function interpretActionResponse(res: {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
status: number;
|
|
42
|
+
}, json: any, label: string): ActionResponseOutcome;
|
|
@@ -0,0 +1,104 @@
|
|
|
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 ONE place a `POST /api/v1/actions/...` response is interpreted.
|
|
10
|
+
*
|
|
11
|
+
* This existed twice — `useConsoleActionRuntime.serverActionHandler` and
|
|
12
|
+
* `RecordDetailView`'s copy of the same handler — and the two drifted, which is
|
|
13
|
+
* the whole reason objectstack#3913 was filed against the console: one copy
|
|
14
|
+
* learned to inspect the inner envelope and the other did not, so a failed
|
|
15
|
+
* action fired a green "completed" toast on every list/page surface. Two copies
|
|
16
|
+
* of a subtle envelope rule is a bug generator; this is the rule, once.
|
|
17
|
+
*
|
|
18
|
+
* Moved from `@object-ui/app-shell` (`utils/actionResponse`) into core by
|
|
19
|
+
* objectui#2904: `createServerActionHandler` — the dispatch every consumer
|
|
20
|
+
* registers as its `script` handler — lives here and must apply the rule, so
|
|
21
|
+
* the rule lives beside it.
|
|
22
|
+
*
|
|
23
|
+
* ## The legacy envelope was DOUBLE, and that was the trap (pre-objectstack#3962)
|
|
24
|
+
*
|
|
25
|
+
* ```
|
|
26
|
+
* { ← transport envelope
|
|
27
|
+
* success: true,
|
|
28
|
+
* data: { ← action envelope
|
|
29
|
+
* success: true,
|
|
30
|
+
* data: { … } ← what the handler actually returned
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Unlike `/api/v1/<anything-else>`, which wraps ONCE, `/actions` wraps twice —
|
|
36
|
+
* the route's own `{success, data}` sits inside the dispatcher's. Reading one
|
|
37
|
+
* level (the natural instinct, and what the other handlers correctly do) lands
|
|
38
|
+
* on the action envelope, not the payload.
|
|
39
|
+
*
|
|
40
|
+
* ## Failure has three shapes, and `res.ok` alone catches only one
|
|
41
|
+
*
|
|
42
|
+
* - **dispatch failure** — 404 no such action, 403 denied, 400 wrong action
|
|
43
|
+
* type, 503 unavailable, 500 the handler crashed. `res.ok` is false.
|
|
44
|
+
* - **business rejection** — the handler ran and said no. HTTP **200** with
|
|
45
|
+
* `data.success === false`. `res.ok` is TRUE; only the inner envelope shows
|
|
46
|
+
* it. This is the one that got missed.
|
|
47
|
+
* - **transport-level `success: false`** — the outer envelope reporting a
|
|
48
|
+
* failure directly.
|
|
49
|
+
*/
|
|
50
|
+
import { actionErrorDetail } from './actionErrorDetail';
|
|
51
|
+
/**
|
|
52
|
+
* The handler's own return value — one level deeper than the action envelope.
|
|
53
|
+
*
|
|
54
|
+
* `body.data` is `{ success, data }`; the handler's value is `body.data.data`.
|
|
55
|
+
* Reading the shallower level is why an action returning `{ redirectUrl }` was
|
|
56
|
+
* silently ignored: `body.data.redirectUrl` is never set by anything, because
|
|
57
|
+
* that level is constructed by the server and only ever holds `success`/`data`.
|
|
58
|
+
*/
|
|
59
|
+
export function readActionPayload(envelope) {
|
|
60
|
+
// objectstack#3962 servers single-wrap: `body.data` IS the handler value,
|
|
61
|
+
// so most of the time this is the identity function. Only the exact LEGACY
|
|
62
|
+
// action envelope (pre-#3962: a boolean `success` and no keys beyond the
|
|
63
|
+
// envelope's own) is unwrapped one more level — a handler value that
|
|
64
|
+
// merely contains a `success` key passes through untouched.
|
|
65
|
+
if (isLegacyActionEnvelope(envelope)) {
|
|
66
|
+
return envelope.data;
|
|
67
|
+
}
|
|
68
|
+
return envelope;
|
|
69
|
+
}
|
|
70
|
+
const LEGACY_ENVELOPE_KEYS = new Set(['success', 'data', 'error', 'code', 'fields']);
|
|
71
|
+
/** The pre-objectstack#3962 inner envelope, detected narrowly. */
|
|
72
|
+
export function isLegacyActionEnvelope(v) {
|
|
73
|
+
return !!v && typeof v === 'object' && !Array.isArray(v)
|
|
74
|
+
&& typeof v.success === 'boolean'
|
|
75
|
+
&& Object.keys(v).every((k) => LEGACY_ENVELOPE_KEYS.has(k));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Classify an `/actions` response. `label` names the action for the fallback
|
|
79
|
+
* message (e.g. `Action "log_call" failed`).
|
|
80
|
+
*
|
|
81
|
+
* `json` is the parsed body, or `null` when it could not be parsed.
|
|
82
|
+
*/
|
|
83
|
+
export function interpretActionResponse(res, json, label) {
|
|
84
|
+
const envelope = json?.data;
|
|
85
|
+
// Pre-#3962 servers reported a rejection as HTTP 200 with the inner
|
|
86
|
+
// `{success:false, error}`; current servers answer a real status, which
|
|
87
|
+
// `res.ok` catches first. Detected narrowly so a handler value that merely
|
|
88
|
+
// contains `success: false` is not misread as a failure.
|
|
89
|
+
const innerFailed = isLegacyActionEnvelope(envelope) && envelope.success === false;
|
|
90
|
+
if (!res.ok || (json && json.success === false) || innerFailed) {
|
|
91
|
+
// A business rejection carries its message on the INNER envelope; a
|
|
92
|
+
// dispatch failure carries it on the outer one (as a nested
|
|
93
|
+
// `{message, code}` object, which `actionErrorDetail` resolves to a
|
|
94
|
+
// string — handing that object to `toast.error()` as a React child
|
|
95
|
+
// crashes the page, React #31).
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
error: innerFailed
|
|
99
|
+
? actionErrorDetail(envelope, `${label} failed`)
|
|
100
|
+
: actionErrorDetail(json, `${label} failed (HTTP ${res.status})`),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, envelope, payload: readActionPayload(envelope) };
|
|
104
|
+
}
|
package/dist/actions/index.d.ts
CHANGED
|
@@ -11,3 +11,6 @@ export * from './actionKeys.js';
|
|
|
11
11
|
export * from './TransactionManager.js';
|
|
12
12
|
export * from './UndoManager.js';
|
|
13
13
|
export * from './bulkFastPath.js';
|
|
14
|
+
export * from './actionErrorDetail.js';
|
|
15
|
+
export * from './actionResponse.js';
|
|
16
|
+
export * from './serverActionHandler.js';
|
package/dist/actions/index.js
CHANGED
|
@@ -11,3 +11,6 @@ export * from './actionKeys.js';
|
|
|
11
11
|
export * from './TransactionManager.js';
|
|
12
12
|
export * from './UndoManager.js';
|
|
13
13
|
export * from './bulkFastPath.js';
|
|
14
|
+
export * from './actionErrorDetail.js';
|
|
15
|
+
export * from './actionResponse.js';
|
|
16
|
+
export * from './serverActionHandler.js';
|