@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.
Files changed (79) hide show
  1. package/CHANGELOG.md +1436 -0
  2. package/README.md +31 -12
  3. package/dist/actions/ActionEngine.d.ts +53 -31
  4. package/dist/actions/ActionEngine.js +87 -66
  5. package/dist/actions/ActionRunner.d.ts +215 -24
  6. package/dist/actions/ActionRunner.js +334 -17
  7. package/dist/actions/actionErrorDetail.d.ts +26 -0
  8. package/dist/actions/actionErrorDetail.js +37 -0
  9. package/dist/actions/actionKeys.d.ts +96 -20
  10. package/dist/actions/actionKeys.js +168 -18
  11. package/dist/actions/actionResponse.d.ts +42 -0
  12. package/dist/actions/actionResponse.js +104 -0
  13. package/dist/actions/index.d.ts +3 -0
  14. package/dist/actions/index.js +3 -0
  15. package/dist/actions/serverActionHandler.d.ts +143 -0
  16. package/dist/actions/serverActionHandler.js +137 -0
  17. package/dist/data-scope/ViewDataProvider.d.ts +37 -12
  18. package/dist/data-scope/ViewDataProvider.js +83 -5
  19. package/dist/data-scope/element-data-source.d.ts +113 -0
  20. package/dist/data-scope/element-data-source.js +216 -0
  21. package/dist/data-scope/index.d.ts +2 -1
  22. package/dist/data-scope/index.js +1 -0
  23. package/dist/evaluator/ExpressionEvaluator.js +4 -2
  24. package/dist/evaluator/declaredPredicate.d.ts +119 -0
  25. package/dist/evaluator/declaredPredicate.js +152 -0
  26. package/dist/evaluator/fieldRules.d.ts +42 -2
  27. package/dist/evaluator/fieldRules.js +101 -11
  28. package/dist/evaluator/index.d.ts +2 -0
  29. package/dist/evaluator/index.js +2 -0
  30. package/dist/evaluator/listConditional.d.ts +18 -3
  31. package/dist/evaluator/listConditional.js +0 -0
  32. package/dist/evaluator/optionRules.js +5 -1
  33. package/dist/evaluator/predicateInput.d.ts +132 -0
  34. package/dist/evaluator/predicateInput.js +129 -0
  35. package/dist/index.d.ts +7 -1
  36. package/dist/index.js +26 -1
  37. package/dist/protocols/DndProtocol.d.ts +42 -1
  38. package/dist/protocols/KeyboardProtocol.d.ts +31 -1
  39. package/dist/protocols/SharingProtocol.d.ts +11 -1
  40. package/dist/protocols/index.d.ts +0 -1
  41. package/dist/protocols/index.js +8 -1
  42. package/dist/registry/Registry.d.ts +24 -0
  43. package/dist/theme/ThemeEngine.d.ts +0 -8
  44. package/dist/theme/ThemeEngine.js +22 -103
  45. package/dist/theme/index.d.ts +1 -1
  46. package/dist/theme/index.js +7 -1
  47. package/dist/types/index.d.ts +42 -8
  48. package/dist/utils/chart-series.d.ts +258 -5
  49. package/dist/utils/chart-series.js +423 -20
  50. package/dist/utils/compare-to.d.ts +40 -13
  51. package/dist/utils/compare-to.js +4 -32
  52. package/dist/utils/dashboard-filters.d.ts +54 -8
  53. package/dist/utils/dashboard-filters.js +261 -12
  54. package/dist/utils/dataset-format.d.ts +79 -15
  55. package/dist/utils/dataset-format.js +130 -9
  56. package/dist/utils/dataset-pivot.d.ts +78 -0
  57. package/dist/utils/dataset-pivot.js +78 -0
  58. package/dist/utils/dom-props.d.ts +137 -0
  59. package/dist/utils/dom-props.js +153 -0
  60. package/dist/utils/filter-converter.d.ts +39 -11
  61. package/dist/utils/filter-converter.js +87 -7
  62. package/dist/utils/normalize-list-view.d.ts +26 -6
  63. package/dist/utils/normalize-list-view.js +30 -8
  64. package/dist/utils/number-display.d.ts +151 -0
  65. package/dist/utils/number-display.js +71 -0
  66. package/dist/utils/predicate-fields.d.ts +96 -0
  67. package/dist/utils/predicate-fields.js +163 -0
  68. package/dist/utils/predicate-record.d.ts +32 -0
  69. package/dist/utils/predicate-record.js +120 -0
  70. package/dist/utils/record-title.js +0 -0
  71. package/dist/utils/resolve-view-id.d.ts +31 -0
  72. package/dist/utils/resolve-view-id.js +45 -0
  73. package/dist/utils/sort-query.d.ts +55 -0
  74. package/dist/utils/sort-query.js +35 -0
  75. package/package.json +5 -5
  76. package/dist/protocols/NotificationProtocol.d.ts +0 -71
  77. package/dist/protocols/NotificationProtocol.js +0 -89
  78. package/dist/utils/merge-views-into-objects.d.ts +0 -19
  79. 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 step 1.
9
+ * What an action is allowed to carry — objectstack#4075, all three steps.
10
10
  *
11
- * `ActionDef` ends with `[key: string]: any`, so it accepts any key of any type.
12
- * Concretely (objectui#2990): deleting `ActionDef.execute` produced ZERO compile
13
- * errors even though the field had just been removed, and stale metadata still
14
- * authoring `execute: 'markDone'` type-checks today. The same deletion against
15
- * `@object-ui/types`' `ActionSchema` — which has no index signature — correctly
16
- * produced `TS2353` at the authoring site. One of the two readers can catch a
17
- * retired key; the other is structurally incapable.
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 is the issue. An open key set on a DECLARED METADATA CONTRACT
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 is step 1 of the staged narrowing: it makes the key set VISIBLE
25
- * and warns on anything outside it, without changing a single type. Nothing
26
- * breaks, and an invisible failure becomes an audible one. Steps 2 and 3 —
27
- * promoting the legitimate keys to explicit optional fields, then removing the
28
- * index signature — are what finally let `tsc` catch typos and retired keys.
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
- * Step 3 remains: remove the index signature, at which point `tsc` catches both
70
- * typos and retired keys and the dev-mode warning below can retire with it.
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 — a branch that exists solely to compensate for the index
115
- * signature, and that can retire with it in step 3.
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 step 1.
9
+ * What an action is allowed to carry — objectstack#4075, all three steps.
10
10
  *
11
- * `ActionDef` ends with `[key: string]: any`, so it accepts any key of any type.
12
- * Concretely (objectui#2990): deleting `ActionDef.execute` produced ZERO compile
13
- * errors even though the field had just been removed, and stale metadata still
14
- * authoring `execute: 'markDone'` type-checks today. The same deletion against
15
- * `@object-ui/types`' `ActionSchema` — which has no index signature — correctly
16
- * produced `TS2353` at the authoring site. One of the two readers can catch a
17
- * retired key; the other is structurally incapable.
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 is the issue. An open key set on a DECLARED METADATA CONTRACT
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 is step 1 of the staged narrowing: it makes the key set VISIBLE
25
- * and warns on anything outside it, without changing a single type. Nothing
26
- * breaks, and an invisible failure becomes an audible one. Steps 2 and 3 —
27
- * promoting the legitimate keys to explicit optional fields, then removing the
28
- * index signature — are what finally let `tsc` catch typos and retired keys.
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
- * Step 3 remains: remove the index signature, at which point `tsc` catches both
70
- * typos and retired keys and the dev-mode warning below can retire with it.
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 — a branch that exists solely to compensate for the index
210
- * signature, and that can retire with it in step 3.
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
+ }
@@ -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';
@@ -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';