@object-ui/core 17.5.0 → 17.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7404 -0
- package/README.md +127 -26
- package/dist/actions/ActionEngine.d.ts +5 -3
- package/dist/actions/ActionEngine.js +14 -10
- package/dist/actions/ActionRunner.d.ts +363 -30
- package/dist/actions/ActionRunner.js +544 -58
- package/dist/actions/TransactionManager.d.ts +7 -2
- package/dist/actions/actionKeys.d.ts +75 -13
- package/dist/actions/actionKeys.js +131 -16
- package/dist/actions/actionResponse.js +3 -3
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/recordDelete.d.ts +118 -0
- package/dist/actions/recordDelete.js +112 -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/adapters/ValueDataSource.d.ts +6 -1
- package/dist/adapters/ValueDataSource.js +1090 -98
- package/dist/adapters/batchTransaction.d.ts +1 -1
- package/dist/adapters/batchTransaction.js +1 -1
- package/dist/builder/schema-builder.d.ts +38 -81
- package/dist/builder/schema-builder.js +33 -152
- package/dist/data-scope/ViewDataProvider.d.ts +1 -1
- package/dist/data-scope/ViewDataProvider.js +21 -1
- package/dist/data-scope/element-data-source.d.ts +261 -7
- package/dist/data-scope/element-data-source.js +342 -52
- package/dist/data-scope/index.d.ts +9 -4
- package/dist/data-scope/index.js +9 -4
- package/dist/evaluator/ExpressionEvaluator.d.ts +128 -1
- package/dist/evaluator/ExpressionEvaluator.js +226 -13
- package/dist/evaluator/FormulaFunctions.d.ts +0 -10
- package/dist/evaluator/FormulaFunctions.js +166 -27
- package/dist/evaluator/SafeExpressionParser.js +0 -4
- package/dist/evaluator/declaredPredicate.d.ts +93 -9
- package/dist/evaluator/declaredPredicate.js +118 -17
- package/dist/evaluator/fieldRules.d.ts +107 -4
- package/dist/evaluator/fieldRules.js +231 -35
- package/dist/evaluator/index.d.ts +2 -0
- package/dist/evaluator/index.js +2 -0
- package/dist/evaluator/listConditional.d.ts +125 -9
- package/dist/evaluator/listConditional.js +167 -26
- package/dist/evaluator/optionRules.d.ts +126 -0
- package/dist/evaluator/optionRules.js +141 -4
- package/dist/evaluator/predicateInput.d.ts +15 -4
- package/dist/evaluator/predicateInput.js +35 -2
- package/dist/evaluator/rowPredicateCanon.d.ts +56 -0
- package/dist/evaluator/rowPredicateCanon.js +158 -0
- package/dist/evaluator/subjectPermissions.d.ts +76 -0
- package/dist/evaluator/subjectPermissions.js +44 -0
- package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
- package/dist/evaluator/unevaluablePredicate.js +70 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +97 -2
- package/dist/protocols/index.d.ts +0 -1
- package/dist/protocols/index.js +9 -1
- package/dist/registry/Registry.d.ts +397 -48
- package/dist/registry/Registry.js +263 -11
- package/dist/registry/WidgetRegistry.js +7 -3
- package/dist/registry/html-tier-intrinsics.d.ts +90 -0
- package/dist/registry/html-tier-intrinsics.js +110 -0
- package/dist/registry/public-blocks.d.ts +8 -0
- package/dist/registry/public-blocks.js +57 -0
- package/dist/theme/ThemeEngine.js +1 -1
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/types/index.d.ts +92 -13
- package/dist/utils/chart-category-key.d.ts +82 -0
- package/dist/utils/chart-category-key.js +84 -0
- package/dist/utils/chart-measure-key.d.ts +64 -0
- package/dist/utils/chart-measure-key.js +65 -0
- package/dist/utils/chart-presentation.d.ts +188 -0
- package/dist/utils/chart-presentation.js +345 -0
- package/dist/utils/chart-series.d.ts +271 -31
- package/dist/utils/chart-series.js +478 -55
- package/dist/utils/column-identity.d.ts +43 -0
- package/dist/utils/column-identity.js +47 -1
- package/dist/utils/column-sortability.d.ts +157 -0
- package/dist/utils/column-sortability.js +217 -0
- package/dist/utils/dashboard-filters.d.ts +38 -6
- package/dist/utils/dashboard-filters.js +107 -61
- package/dist/utils/dataset-format.d.ts +211 -30
- package/dist/utils/dataset-format.js +407 -52
- package/dist/utils/date-display.d.ts +354 -0
- package/dist/utils/date-display.js +463 -0
- package/dist/utils/date-macros.js +7 -0
- package/dist/utils/debug.js +7 -0
- package/dist/utils/drill-down.d.ts +78 -0
- package/dist/utils/drill-down.js +93 -0
- package/dist/utils/emptiness.d.ts +96 -0
- package/dist/utils/emptiness.js +101 -0
- package/dist/utils/expand-fields.d.ts +171 -11
- package/dist/utils/expand-fields.js +171 -11
- package/dist/utils/extract-records.d.ts +65 -5
- package/dist/utils/extract-records.js +65 -8
- package/dist/utils/filter-converter.d.ts +152 -4
- package/dist/utils/filter-converter.js +1092 -22
- package/dist/utils/filter-tokens.d.ts +49 -5
- package/dist/utils/filter-tokens.js +105 -34
- package/dist/utils/freeze-schema.d.ts +38 -1
- package/dist/utils/freeze-schema.js +10 -0
- package/dist/utils/grouping-fields.d.ts +61 -0
- package/dist/utils/grouping-fields.js +84 -0
- package/dist/utils/humanize-label.d.ts +60 -0
- package/dist/utils/humanize-label.js +62 -0
- package/dist/utils/managedBy.d.ts +30 -5
- package/dist/utils/managedBy.js +5 -5
- package/dist/utils/native-date-value.d.ts +48 -0
- package/dist/utils/native-date-value.js +167 -0
- package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
- package/dist/utils/non-grid-row-ceiling.js +141 -0
- package/dist/utils/normalize-list-view.d.ts +131 -1
- package/dist/utils/normalize-list-view.js +248 -6
- package/dist/utils/number-display.d.ts +70 -17
- package/dist/utils/number-display.js +34 -12
- package/dist/utils/object-aggregate-query.d.ts +113 -0
- package/dist/utils/object-aggregate-query.js +50 -0
- package/dist/utils/parent-scope.d.ts +123 -0
- package/dist/utils/parent-scope.js +140 -0
- package/dist/utils/predicate-fields.d.ts +62 -4
- package/dist/utils/predicate-fields.js +169 -4
- package/dist/utils/record-color.d.ts +75 -0
- package/dist/utils/record-color.js +65 -0
- package/dist/utils/record-source.d.ts +298 -0
- package/dist/utils/record-source.js +331 -0
- package/dist/utils/record-title.d.ts +148 -8
- package/dist/utils/record-title.js +220 -26
- package/dist/utils/reference-keys.d.ts +21 -22
- package/dist/utils/reference-keys.js +532 -29
- 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 +128 -26
- package/dist/utils/sort-query.js +240 -13
- package/dist/utils/text-comparand.d.ts +92 -0
- package/dist/utils/text-comparand.js +99 -0
- package/dist/utils/unmaterialized-fields.d.ts +52 -0
- package/dist/utils/unmaterialized-fields.js +57 -0
- package/dist/utils/without-denied-fields.d.ts +49 -0
- package/dist/utils/without-denied-fields.js +52 -0
- package/dist/validation/index.d.ts +9 -4
- package/dist/validation/index.js +9 -4
- package/dist/validation/schema-validator.d.ts +1 -1
- package/dist/validation/schema-validator.js +305 -82
- 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.d.ts +1 -1
- package/dist/validation/validators/object-validation-engine.js +14 -5
- package/package.json +8 -9
- package/dist/__benchmarks__/core.bench.d.ts +0 -8
- package/dist/__benchmarks__/core.bench.js +0 -53
- package/dist/data-scope/DataScopeManager.d.ts +0 -127
- package/dist/data-scope/DataScopeManager.js +0 -229
- package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
- package/dist/protocols/ResponsiveProtocol.js +0 -158
- package/dist/validation/validation-engine.d.ts +0 -88
- package/dist/validation/validation-engine.js +0 -434
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { UIActionSchema } from '@object-ui/types';
|
|
23
23
|
import type { Action as SpecActionInput } from '@objectstack/spec/ui';
|
|
24
|
-
import { ExpressionEvaluator } from '../evaluator/ExpressionEvaluator';
|
|
25
|
-
import { type UndoableOperation } from './UndoManager';
|
|
24
|
+
import { ExpressionEvaluator } from '../evaluator/ExpressionEvaluator.js';
|
|
25
|
+
import { type UndoableOperation } from './UndoManager.js';
|
|
26
|
+
import { type FieldContainerLike } from '../utils/predicate-record.js';
|
|
26
27
|
export interface ActionResult {
|
|
27
28
|
success: boolean;
|
|
28
29
|
data?: any;
|
|
@@ -134,15 +135,22 @@ export interface ActionDef {
|
|
|
134
135
|
* it — the two TS2353s the deletion produced repo-wide were both this key.
|
|
135
136
|
*/
|
|
136
137
|
description?: UIActionSchema['description'];
|
|
137
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Confirmation text — shows a confirm dialog before executing. The ONE
|
|
140
|
+
* confirm spelling: `@objectstack/spec`'s action surface and the
|
|
141
|
+
* TranslationBundle key (`{ns}.objects.{obj}._actions.{name}.confirmText`)
|
|
142
|
+
* both stand on it.
|
|
143
|
+
*/
|
|
138
144
|
confirmText?: string;
|
|
139
|
-
/**
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
145
|
+
/**
|
|
146
|
+
* RETIRED (objectui#4314): the structured confirm object used to sit here
|
|
147
|
+
* and its `message` OUTRANKED `confirmText` — untranslated, since no bundle
|
|
148
|
+
* key exists for it. `?: never` so constructing an action with it is a tsc
|
|
149
|
+
* error rather than a silently-ignored key; the authorable twin
|
|
150
|
+
* (`@object-ui/types` `crud.ts`) carries the same tombstone.
|
|
151
|
+
* @deprecated Retired — author `confirmText` instead.
|
|
152
|
+
*/
|
|
153
|
+
confirm?: never;
|
|
146
154
|
/**
|
|
147
155
|
* Condition predicate — the action executes only while it holds.
|
|
148
156
|
*
|
|
@@ -161,8 +169,8 @@ export interface ActionDef {
|
|
|
161
169
|
* field, because the 2026-08-06 maintainer ruling on objectstack#4075 gave
|
|
162
170
|
* both keys ONE shape: `boolean | string(CEL) | { dialect, source }` —
|
|
163
171
|
* "boolean = 条件的退化形字面量,string = CEL 简写,信封 = 完整形". The
|
|
164
|
-
* envelope arm arrived in `@objectstack/spec` 17.0.0-rc.6 (objectstack
|
|
165
|
-
*
|
|
172
|
+
* envelope arm arrived in `@objectstack/spec` 17.0.0-rc.6 (objectstack
|
|
173
|
+
* `97e7e3caa`); until then this was a hand-written `string | boolean`
|
|
166
174
|
* that could not describe the envelope, which is why `DeclaredActionsBar`
|
|
167
175
|
* read it through an `(action as any).disabled` cast. Derived, not restated,
|
|
168
176
|
* so the two keys cannot drift apart again.
|
|
@@ -226,7 +234,8 @@ export interface ActionDef {
|
|
|
226
234
|
showOnError?: boolean;
|
|
227
235
|
duration?: number;
|
|
228
236
|
};
|
|
229
|
-
/** Success message (from UIActionSchema)
|
|
237
|
+
/** Success message (from UIActionSchema). The success toast's second rung,
|
|
238
|
+
* after {@link outcomeMessages}; may interpolate `${result.*}`. */
|
|
230
239
|
successMessage?: string;
|
|
231
240
|
/** Error message (from UIActionSchema) */
|
|
232
241
|
errorMessage?: string;
|
|
@@ -249,9 +258,9 @@ export interface ActionDef {
|
|
|
249
258
|
* `'url'`, flow name for `'flow'`, modal/page for `'modal'`, endpoint for
|
|
250
259
|
* `'api'`, FormView name for `'form'`.
|
|
251
260
|
*
|
|
252
|
-
* The `execute` alias was REMOVED in @objectstack/spec 17 (#3855) and is not
|
|
253
|
-
* read here (#3856) — don't re-add it. Two handler slots is how one action ran
|
|
254
|
-
* one script server-side and a different one client-side (#3713).
|
|
261
|
+
* The `execute` alias was REMOVED in @objectstack/spec 17 (objectstack-ai/objectstack#3855) and is not
|
|
262
|
+
* read here (objectstack-ai/objectstack#3856) — don't re-add it. Two handler slots is how one action ran
|
|
263
|
+
* one script server-side and a different one client-side (objectstack-ai/objectstack#3713).
|
|
255
264
|
*/
|
|
256
265
|
target?: string;
|
|
257
266
|
/**
|
|
@@ -284,8 +293,6 @@ export interface ActionDef {
|
|
|
284
293
|
chain?: ActionDef[];
|
|
285
294
|
/** Chain execution mode */
|
|
286
295
|
chainMode?: 'sequential' | 'parallel';
|
|
287
|
-
/** Callback on success */
|
|
288
|
-
onSuccess?: ActionDef | ActionDef[];
|
|
289
296
|
/** Callback on failure */
|
|
290
297
|
onFailure?: ActionDef | ActionDef[];
|
|
291
298
|
/** When true, the runner pre-opens about:blank synchronously on click so the
|
|
@@ -316,7 +323,7 @@ export interface ActionDef {
|
|
|
316
323
|
* spec 采纳 —— `visible` / `disabled` 两键在 spec 侧统一收敛为
|
|
317
324
|
* `boolean | string(CEL) | {dialect, source}`(boolean = 条件的退化形字面量,
|
|
318
325
|
* string = CEL 简写,信封 = 完整形)". `@objectstack/spec` 17.0.0-rc.6 carries
|
|
319
|
-
* it (objectstack
|
|
326
|
+
* it (objectstack `97e7e3caa`), so the local `| boolean` is no
|
|
320
327
|
* longer a tolerance to declare — it is part of the derived type, and adding
|
|
321
328
|
* it back would restate a spec arm rather than widen anything.
|
|
322
329
|
*/
|
|
@@ -336,12 +343,31 @@ export interface ActionDef {
|
|
|
336
343
|
objectName?: SpecActionInput['objectName'];
|
|
337
344
|
/** AI affordance metadata (tool exposure, prompts). */
|
|
338
345
|
ai?: SpecActionInput['ai'];
|
|
339
|
-
/** Accessibility overrides for the rendered control. */
|
|
340
|
-
aria?: SpecActionInput['aria'];
|
|
341
346
|
/** Extra properties merged into the request body alongside the collected params. */
|
|
342
347
|
bodyExtra?: SpecActionInput['bodyExtra'];
|
|
343
348
|
/** How collected params are shaped into the request body (`flat` or nested). */
|
|
344
349
|
bodyShape?: SpecActionInput['bodyShape'];
|
|
350
|
+
/**
|
|
351
|
+
* The declarative single-record field write — a parallel key BESIDE `type`,
|
|
352
|
+
* never a member of it.
|
|
353
|
+
*
|
|
354
|
+
* `ActionType` did not gain a member: the spec materializes `type: 'script'`
|
|
355
|
+
* on an `operation: 'update'` action, because the write is performed on the
|
|
356
|
+
* platform action route — `POST /api/v1/actions/{object}/{action}`, the route
|
|
357
|
+
* the registered `script` dispatch already posts — as the CALLER, so the
|
|
358
|
+
* caller's permissions, the object's hooks and its validations all fire as
|
|
359
|
+
* for a user edit. That is why {@link execute} reads this key BEFORE `type`:
|
|
360
|
+
* by the time `type` is consulted an update action is indistinguishable from
|
|
361
|
+
* any other script action, and its `patch` would never reach the request.
|
|
362
|
+
*/
|
|
363
|
+
operation?: SpecActionInput['operation'];
|
|
364
|
+
/**
|
|
365
|
+
* For `operation: 'update'` — static field values written to the current
|
|
366
|
+
* record, merged UNDER the collected `params` so a fixed value can be
|
|
367
|
+
* declared without exposing it in the dialog. The spec refuses it on an
|
|
368
|
+
* action without `operation: 'update'`, where it would be silently dropped.
|
|
369
|
+
*/
|
|
370
|
+
patch?: SpecActionInput['patch'];
|
|
345
371
|
/** Execution mode of the action. */
|
|
346
372
|
mode?: SpecActionInput['mode'];
|
|
347
373
|
/** Field on the record supplying the record id sent to the endpoint. */
|
|
@@ -350,6 +376,41 @@ export interface ActionDef {
|
|
|
350
376
|
recordIdParam?: SpecActionInput['recordIdParam'];
|
|
351
377
|
/** Auth/tenancy feature the action requires before it is offered. */
|
|
352
378
|
requiresFeature?: SpecActionInput['requiresFeature'];
|
|
379
|
+
/**
|
|
380
|
+
* Declared post-success navigation — the spec's closed strict
|
|
381
|
+
* `{ navigate, openIn }` block (`ActionSchema.onSuccess`, authorable since
|
|
382
|
+
* `@objectstack/spec` 17.1.0, objectui#5328). Read by `handlePostExecution`
|
|
383
|
+
* → `readOnSuccessNavigation` → `navigateOnSuccess`.
|
|
384
|
+
*
|
|
385
|
+
* This key carried a SECOND, older meaning until objectui#5934: the runner's
|
|
386
|
+
* own chained-callback channel, `ActionDef | ActionDef[]`, dispatched through
|
|
387
|
+
* `executeChain`. The maintainer retired that channel on 2026-08-31 — the
|
|
388
|
+
* spec strict-refuses a callback shape here (`{ type: … }` fails parse with
|
|
389
|
+
* `unrecognized_keys`, so no validated metadata could ever reach it), and the
|
|
390
|
+
* census found zero producers outside the channel's own pins. `onSuccess`
|
|
391
|
+
* now means exactly what the contract declares, nothing else; a callback
|
|
392
|
+
* shape gets NO reading (not a fallback, not an error — the same "a shape
|
|
393
|
+
* the spec refuses gets no new reading here" rule the discrimination branch
|
|
394
|
+
* used to apply, now with nothing left to discriminate). `onFailure`, in the
|
|
395
|
+
* runner-native section above, is untouched: the spec declares no such key,
|
|
396
|
+
* so it has only ever had its one runner-native meaning.
|
|
397
|
+
*/
|
|
398
|
+
onSuccess?: SpecActionInput['onSuccess'];
|
|
399
|
+
/**
|
|
400
|
+
* Success copy per handler outcome (`ActionSchema.outcomeMessages`, ruling A
|
|
401
|
+
* on objectstack-ai/cloud#2315): the server answers with a closed `outcome`
|
|
402
|
+
* fact and this map holds the sentence for each, keyed by the snake_case
|
|
403
|
+
* outcome name. Read by `handlePostExecution` → `composeSuccessMessage`, the
|
|
404
|
+
* success toast's FIRST rung: the entry named by the payload's top-level
|
|
405
|
+
* `outcome` (the payload `${result.*}` reads), then {@link successMessage},
|
|
406
|
+
* then the runner's own default text.
|
|
407
|
+
*
|
|
408
|
+
* Derived, so an entry is an `I18nLabel` — a string or an inline per-locale
|
|
409
|
+
* map. The runner composes from strings only: the host's localizer
|
|
410
|
+
* (`useActionTextLocalizer` in `@object-ui/react`) resolves each entry to the
|
|
411
|
+
* active language before dispatch, because the runner has no language.
|
|
412
|
+
*/
|
|
413
|
+
outcomeMessages?: SpecActionInput['outcomeMessages'];
|
|
353
414
|
/**
|
|
354
415
|
* @deprecated Retired in `@objectstack/spec` 17 as a `retiredKey()` tombstone —
|
|
355
416
|
* authoring it is a hard parse rejection, so this resolves to `undefined` and
|
|
@@ -382,6 +443,29 @@ export type ActionRunnerHandler = (action: ActionDef, context: ActionContext) =>
|
|
|
382
443
|
/**
|
|
383
444
|
* Confirmation handler — replaces window.confirm.
|
|
384
445
|
* Consumers can provide an async implementation (e.g., Shadcn AlertDialog).
|
|
446
|
+
*
|
|
447
|
+
* `options` is LIVE, and its producer is not in this file — searching the
|
|
448
|
+
* runner alone will tell you otherwise. The only call site here passes one
|
|
449
|
+
* argument (the structured `confirm` arm that used to forward a bag was
|
|
450
|
+
* retired, objectui#4314), so a runner-scoped sweep reads this parameter as
|
|
451
|
+
* declared-but-never-produced and files it for removal. That has already
|
|
452
|
+
* happened once: objectui#5205, ruled KEEP on 2026-08-22.
|
|
453
|
+
*
|
|
454
|
+
* The producer bypasses the runner entirely. `handleDeleteView` in
|
|
455
|
+
* `packages/app-shell/src/views/ObjectView.tsx` — the admin "delete saved
|
|
456
|
+
* view" confirmation — invokes a `ConfirmationHandler` with all three fields
|
|
457
|
+
* populated and localized (`console.objectView.deleteViewTitle` / `.delete` /
|
|
458
|
+
* `.cancel`), and `app-shell`'s `ActionConfirmDialog.tsx` renders them in
|
|
459
|
+
* place of its generic `actionConfirm.*` fallbacks. Line numbers are left out
|
|
460
|
+
* on purpose: #5205's pointer had already drifted by the time it was executed.
|
|
461
|
+
*
|
|
462
|
+
* So re-measure across the repo, not this package: grep for two-argument
|
|
463
|
+
* calls of `ConfirmationHandler`-typed values (`confirmHandler(`, `onConfirm(`)
|
|
464
|
+
* in `app-shell` as well as here. Dropping `options` costs that dialog its
|
|
465
|
+
* title and both button labels, and narrows a published `.d.ts` against the
|
|
466
|
+
* natural `(message, options) => …` spelling — which TypeScript rejects
|
|
467
|
+
* against a one-parameter target unless the second parameter is written
|
|
468
|
+
* optional.
|
|
385
469
|
*/
|
|
386
470
|
export type ConfirmationHandler = (message: string, options?: {
|
|
387
471
|
title?: string;
|
|
@@ -418,10 +502,29 @@ export type NavigationHandler = (url: string, options?: {
|
|
|
418
502
|
* Returns collected values, or null if cancelled.
|
|
419
503
|
*/
|
|
420
504
|
export type ParamCollectionHandler = (params: ActionParamDef[], action?: ActionDef) => Promise<Record<string, any> | null>;
|
|
505
|
+
/**
|
|
506
|
+
* The contract's own result-dialog block, and one entry of its field list.
|
|
507
|
+
*
|
|
508
|
+
* ⭐ The two interfaces below DERIVE their label members from these instead of
|
|
509
|
+
* restating them, and that is the whole repair of `43c0d1710`: the three
|
|
510
|
+
* label members were hand-written `string` while the producer declares each as
|
|
511
|
+
* `I18nLabel` — a plain string **or** an inline per-locale map, both authorized
|
|
512
|
+
* and neither deprecated — so this mirror refused what the platform accepts
|
|
513
|
+
* while its own docblock claimed the alignment. A hand copy can drift again the
|
|
514
|
+
* moment the contract moves; a derived member makes `tsc` re-check the claim on
|
|
515
|
+
* every build instead of trusting a sentence written once.
|
|
516
|
+
*
|
|
517
|
+
* ⛔ Local, deliberately not exported: the two interfaces below stay the only
|
|
518
|
+
* names this module publishes for the block.
|
|
519
|
+
*/
|
|
520
|
+
type SpecResultDialog = NonNullable<SpecActionInput['resultDialog']>;
|
|
521
|
+
type SpecResultDialogField = NonNullable<SpecResultDialog['fields']>[number];
|
|
421
522
|
/**
|
|
422
523
|
* Result dialog spec — declarative description of how to render a
|
|
423
524
|
* one-shot reveal of an action's API response. Mirrors
|
|
424
|
-
* `Action.resultDialog` in @objectstack/spec
|
|
525
|
+
* `Action.resultDialog` in @objectstack/spec — derived from it member by
|
|
526
|
+
* member for the labels, so the claim in this sentence is enforced rather
|
|
527
|
+
* than asserted.
|
|
425
528
|
*
|
|
426
529
|
* When set on an action and the action succeeds, the runner suppresses
|
|
427
530
|
* the success toast and awaits a ResultDialogHandler instead. Used for
|
|
@@ -430,13 +533,22 @@ export type ParamCollectionHandler = (params: ActionParamDef[], action?: ActionD
|
|
|
430
533
|
*/
|
|
431
534
|
export interface ResultDialogFieldSpec {
|
|
432
535
|
path: string;
|
|
433
|
-
|
|
536
|
+
/**
|
|
537
|
+
* Derived `I18nLabel` — a plain string, or an inline per-locale map. A
|
|
538
|
+
* renderer must RESOLVE it (`resolveI18nLabel` in `@objectstack/spec/ui`)
|
|
539
|
+
* before it reaches the DOM; passed through as-is, the map arm is an object
|
|
540
|
+
* and React refuses an object as a child.
|
|
541
|
+
*/
|
|
542
|
+
label?: SpecResultDialogField['label'];
|
|
434
543
|
format?: 'qrcode' | 'code-list' | 'secret' | 'text' | 'json';
|
|
435
544
|
}
|
|
436
545
|
export interface ResultDialogSpec {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
546
|
+
/** Derived `I18nLabel` — see `ResultDialogFieldSpec.label` on resolving it. */
|
|
547
|
+
title?: SpecResultDialog['title'];
|
|
548
|
+
/** Derived `I18nLabel` — see `ResultDialogFieldSpec.label` on resolving it. */
|
|
549
|
+
description?: SpecResultDialog['description'];
|
|
550
|
+
/** Derived `I18nLabel` — see `ResultDialogFieldSpec.label` on resolving it. */
|
|
551
|
+
acknowledge?: SpecResultDialog['acknowledge'];
|
|
440
552
|
format?: 'qrcode' | 'code-list' | 'secret' | 'text' | 'json';
|
|
441
553
|
fields?: ResultDialogFieldSpec[];
|
|
442
554
|
}
|
|
@@ -456,7 +568,7 @@ export type ResultDialogHandler = (spec: ResultDialogSpec, data: unknown, action
|
|
|
456
568
|
* `visibleWhen` (the CEL predicate `resolveVisibleOptions()` filters on —
|
|
457
569
|
* ADR-0058 / #2284), `color`, `icon`, `disabled`.
|
|
458
570
|
*
|
|
459
|
-
* The catch-all is deliberate rather than a closed key list (
|
|
571
|
+
* The catch-all is deliberate rather than a closed key list (`fbc23e094`).
|
|
460
572
|
* A field's option vocabulary is owned by the field metadata (`@objectstack/spec`'s
|
|
461
573
|
* `SelectOptionSchema`) and read by the option widgets; a param's option list is
|
|
462
574
|
* only a CONDUIT between the two. When this type restated that vocabulary as
|
|
@@ -500,19 +612,36 @@ export interface ActionParamDef {
|
|
|
500
612
|
placeholder?: string;
|
|
501
613
|
/**
|
|
502
614
|
* Visibility predicate (CEL) evaluated against the same scope as action
|
|
503
|
-
* `visible` (`current_user` / `
|
|
615
|
+
* `visible` (`current_user` / `data` / `features` — ⛔ not `app`, unbound by
|
|
616
|
+
* objectui#8155). When it evaluates
|
|
504
617
|
* false the param dialog omits this param — used to hide a param the backend
|
|
505
618
|
* only accepts under an opt-in capability (e.g. `create_user.phoneNumber`
|
|
506
619
|
* gated on `features.phoneNumber`). Absent = always visible.
|
|
507
620
|
*/
|
|
508
621
|
visible?: string;
|
|
622
|
+
/**
|
|
623
|
+
* Carry-over declaration — `@objectstack/spec`'s `ActionParamSchema.carryOver`
|
|
624
|
+
* (the 2026-08-25 ruling whose spec half is objectstack `0e4e51b0a`,
|
|
625
|
+
* objectui#6246), passed through unchanged by
|
|
626
|
+
* `resolveActionParams()`. The param's value is carried through the dialog
|
|
627
|
+
* rather than collected from the user: seeded from the row (the spec refuses
|
|
628
|
+
* the key without `defaultFromRow: true`), rendered by `ActionParamDialog` as
|
|
629
|
+
* a collapsed read-only summary with no editing affordance, and submitted
|
|
630
|
+
* verbatim.
|
|
631
|
+
*
|
|
632
|
+
* ⛔ Not a styling hint. The permission-set Clone action declares it on its
|
|
633
|
+
* JSON permission facets because an editable facet lets a hand-edited but
|
|
634
|
+
* valid blob clone a set that grants MORE than its base; a renderer that
|
|
635
|
+
* merely greys the control out still offers that edit.
|
|
636
|
+
*/
|
|
637
|
+
carryOver?: boolean;
|
|
509
638
|
/** Accepted upload types (MIME types / extensions) for `file`/`image` params. */
|
|
510
639
|
accept?: string[];
|
|
511
640
|
/** Max upload size in bytes for `file`/`image` params. */
|
|
512
641
|
maxSize?: number;
|
|
513
642
|
/** Allow multiple values (file/image/lookup/user params → array value). */
|
|
514
643
|
multiple?: boolean;
|
|
515
|
-
/** Object name the lookup picker queries (`
|
|
644
|
+
/** Object name the lookup picker queries (`reference` on the field). */
|
|
516
645
|
referenceTo?: string;
|
|
517
646
|
/** Field on the referenced record used as the human label (default `name`). */
|
|
518
647
|
displayField?: string;
|
|
@@ -530,7 +659,87 @@ export interface ActionParamDef {
|
|
|
530
659
|
lookupPageSize?: number;
|
|
531
660
|
/** Form-field dependencies that gate / parameterise the picker query. */
|
|
532
661
|
dependsOn?: unknown[];
|
|
662
|
+
/**
|
|
663
|
+
* Set by `resolveActionParams()` when a FIELD-BACKED param (`{ field }`)
|
|
664
|
+
* named a field that is not in the object metadata the resolver was given,
|
|
665
|
+
* carrying `<object>.<field>` — the pair that could not be resolved.
|
|
666
|
+
*
|
|
667
|
+
* It exists because the alternative is a SILENT one. Without it the resolver
|
|
668
|
+
* hands back `type: param.type ?? 'text'`, and every downstream reader of the
|
|
669
|
+
* degradation is blind to it: the param is a `text` param by then, so
|
|
670
|
+
* `paramDegradesWithoutTarget()` answers false, `paramToField()` emits no
|
|
671
|
+
* "no reference target" warning, and the objectstack-ai/objectstack#3405 "paste a record id" hints do
|
|
672
|
+
* not apply either. A lookup param that should have rendered a record picker
|
|
673
|
+
* renders an unannotated empty box instead — no options, no dropdown, and no
|
|
674
|
+
* request for the referenced object on the wire, because no picker was ever
|
|
675
|
+
* built (`6cc910b6d`).
|
|
676
|
+
*
|
|
677
|
+
* ⛔ It is not a widget config key and `paramToField()` deliberately does not
|
|
678
|
+
* map it: the whole point is that the param's type is UNKNOWN, so there is no
|
|
679
|
+
* widget to configure. `ActionParamDialog` reads it and refuses.
|
|
680
|
+
*/
|
|
681
|
+
unresolvedField?: string;
|
|
533
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* The fields an `operation: 'update'` action writes, and their prior values.
|
|
685
|
+
*
|
|
686
|
+
* `undoable` promises to restore EXACTLY the fields the action wrote, so the
|
|
687
|
+
* captured set is the effective payload — `patch` merged under the collected
|
|
688
|
+
* `params` — and never `patch` alone: a param-collected field left out of the
|
|
689
|
+
* capture would make Undo restore SOME of the edit and report a full one, which
|
|
690
|
+
* is worse than offering no Undo at all.
|
|
691
|
+
*
|
|
692
|
+
* Prior values come from the row record the invoking surface stashed under
|
|
693
|
+
* `params._rowRecord` (the same client-side stash the record-id dance reads).
|
|
694
|
+
* A field the row carries as `null` is captured as `null`: it was empty, and
|
|
695
|
+
* clearing it back to empty is what restoring it means on the data plane.
|
|
696
|
+
*
|
|
697
|
+
* ⛔ A field the row does not CARRY is never captured as `null`
|
|
698
|
+
* (objectui#10404). Absent is not empty: a list row is projected by `$select`,
|
|
699
|
+
* and a written field no column shows is missing from the row while the server
|
|
700
|
+
* holds a real value for it. Recording `null` there made Undo write `null` over
|
|
701
|
+
* the value it existed to restore. The list harvest (`listViewPredicates`) now
|
|
702
|
+
* asks for the written fields of an `undoable` action, so a projected row
|
|
703
|
+
* carries them; this is the backstop for the ones it cannot ask for (a field
|
|
704
|
+
* the object does not declare or the principal may not read, a row no harvest
|
|
705
|
+
* shaped). "Carries" means an own key whose value is not `undefined`: JSON
|
|
706
|
+
* cannot send `undefined`, so such a value says nothing about what is stored.
|
|
707
|
+
*
|
|
708
|
+
* Answers `undefined` when any written field is not carried, and the caller
|
|
709
|
+
* then offers no Undo at all. Capturing only the carried fields is not an
|
|
710
|
+
* option, for the reason above: a partial restore reported as a full one is
|
|
711
|
+
* worse than no Undo.
|
|
712
|
+
*
|
|
713
|
+
* ⛔ A relation's prior value is its STORED id, never the record `$expand`
|
|
714
|
+
* put in its place (objectui#11122). Surfaces read rows with `$expand` on the
|
|
715
|
+
* relations they show (a grid, its visible and grouping columns; the record
|
|
716
|
+
* page, every relation the reader may read), and the server replaces the id
|
|
717
|
+
* in place with the related record. Copied verbatim, that record became the
|
|
718
|
+
* Undo value and Undo wrote it into the reference slot, which stores an id:
|
|
719
|
+
* refused under a strict value-shape posture, stored as corruption under the
|
|
720
|
+
* lenient one. So each carried value goes through `toPredicateRecord`, the one
|
|
721
|
+
* rule that binds a fetched record the way the server stores it: a field the
|
|
722
|
+
* object DECLARES relational (`EXPANDABLE_FIELD_TYPES`, the set that decides
|
|
723
|
+
* what is expanded in the first place) collapses to its id, element-wise for a
|
|
724
|
+
* `multiple` relation, and every other field is left exactly as the row
|
|
725
|
+
* carries it. It is read from `fields`, never from the value's shape: a `json`
|
|
726
|
+
* field may hold an object with an `id`, and that object is its stored value.
|
|
727
|
+
*
|
|
728
|
+
* `fields` is the written object's field definitions (`objectSchema.fields`,
|
|
729
|
+
* either served shape), and it is REQUIRED so that a caller without them has
|
|
730
|
+
* to say so: `undefined` collapses nothing, because a caller that cannot name
|
|
731
|
+
* the relations must not guess them.
|
|
732
|
+
*
|
|
733
|
+
* Exported (objectui#11082) because it is THE rule, not this runner's: every
|
|
734
|
+
* surface that builds an `update` Undo snapshot itself calls it rather than
|
|
735
|
+
* restating it. Today that is the console runtime's `api` handler
|
|
736
|
+
* (`useConsoleActionRuntime`) and the record page's own `api` handler
|
|
737
|
+
* (`RecordDetailView`), which reads prior values off the page's loaded record;
|
|
738
|
+
* that record lacks a written field when field-level security hides it from
|
|
739
|
+
* the reader. To name the fields that blocked a capture, ask per field:
|
|
740
|
+
* `captureUpdateUndoData([field], rowRecord, fields) === undefined`.
|
|
741
|
+
*/
|
|
742
|
+
export declare function captureUpdateUndoData(writtenFields: readonly string[], rowRecord: Record<string, unknown>, fields: FieldContainerLike): Record<string, unknown> | undefined;
|
|
534
743
|
export declare class ActionRunner {
|
|
535
744
|
private handlers;
|
|
536
745
|
private scripts;
|
|
@@ -542,6 +751,7 @@ export declare class ActionRunner {
|
|
|
542
751
|
private navigationHandler;
|
|
543
752
|
private paramCollectionHandler;
|
|
544
753
|
private resultDialogHandler;
|
|
754
|
+
private translate;
|
|
545
755
|
/**
|
|
546
756
|
* Built-in dispatch, one entry per runnable action type.
|
|
547
757
|
*
|
|
@@ -558,6 +768,20 @@ export declare class ActionRunner {
|
|
|
558
768
|
*/
|
|
559
769
|
private readonly builtinExecutors;
|
|
560
770
|
constructor(context?: ActionContext);
|
|
771
|
+
/**
|
|
772
|
+
* Set the translator for the text the runner supplies itself — the host's
|
|
773
|
+
* `t`, injected so this package takes no i18n dependency. That text is the
|
|
774
|
+
* `RUNNER_TEXT` table in this file: the generic success toast shown when an
|
|
775
|
+
* action declares neither an `outcomeMessages` entry for the answer nor a
|
|
776
|
+
* `successMessage`, the error fallbacks when no readable error message
|
|
777
|
+
* reached the runner, and the Undo label of an undoable success toast. An
|
|
778
|
+
* author's `outcomeMessages` / `successMessage` / `errorMessage` and an
|
|
779
|
+
* action's own error never go through this translator: the host localizes
|
|
780
|
+
* authored copy before dispatch. With no translator the text stays English.
|
|
781
|
+
*/
|
|
782
|
+
setTranslator(translate: (key: string, options: {
|
|
783
|
+
defaultValue: string;
|
|
784
|
+
}) => string): void;
|
|
561
785
|
/**
|
|
562
786
|
* Set a custom confirmation handler (e.g., Shadcn AlertDialog).
|
|
563
787
|
*/
|
|
@@ -600,10 +824,101 @@ export declare class ActionRunner {
|
|
|
600
824
|
* Execute multiple actions in sequence or parallel.
|
|
601
825
|
*/
|
|
602
826
|
executeChain(actions: ActionDef[], mode?: 'sequential' | 'parallel'): Promise<ActionResult>;
|
|
827
|
+
/**
|
|
828
|
+
* One entry of `RUNNER_TEXT`, in the installed translator's language. An
|
|
829
|
+
* empty answer from the translator falls back to the English source rather
|
|
830
|
+
* than raising an empty toast or an empty label.
|
|
831
|
+
*/
|
|
832
|
+
private runnerText;
|
|
833
|
+
/**
|
|
834
|
+
* The success toast's text, by the contract's three rungs
|
|
835
|
+
* (`ActionSchema.outcomeMessages`, objectui#11344):
|
|
836
|
+
*
|
|
837
|
+
* 1. `outcomeMessages[outcome]`, where `outcome` is the top-level `outcome`
|
|
838
|
+
* of the handler's return value — the payload `${result.*}` reads, so it
|
|
839
|
+
* is the same fact an author writes as `${result.outcome}`;
|
|
840
|
+
* 2. `successMessage`;
|
|
841
|
+
* 3. the runner's own default text, in its translator's language.
|
|
842
|
+
*
|
|
843
|
+
* Rungs 1 and 2 are the author's copy. Both arrive already localized — the
|
|
844
|
+
* host's localizer resolves the bundle entry or the inline `I18nLabel` map
|
|
845
|
+
* before dispatch — and both have their `${result.*}` tokens filled in here,
|
|
846
|
+
* through the one token grammar and scope `onSuccess.navigate` reads
|
|
847
|
+
* (`substituteScopeTokens` over `readActionPayload(result.data)`). Only the
|
|
848
|
+
* sink differs: a toast is text, so a value is inserted as-is, where a URL
|
|
849
|
+
* position percent-encodes it.
|
|
850
|
+
*
|
|
851
|
+
* ⛔ `result.data.message` is not a rung. It used to outrank both rungs of
|
|
852
|
+
* author copy, which put whatever language the server happened to write in
|
|
853
|
+
* front of the user's locale; the contract names no server-message rung, and
|
|
854
|
+
* an author who wants the server's sentence can still write
|
|
855
|
+
* `${result.message}` as the copy.
|
|
856
|
+
*
|
|
857
|
+
* A rung counts only when it is a non-empty string. An `I18nLabel` map that
|
|
858
|
+
* reached the runner without passing through a localizer has no language to
|
|
859
|
+
* resolve against here, so it is skipped rather than handed to the toast as
|
|
860
|
+
* an object — the React #31 crash the error branch below guards against.
|
|
861
|
+
*/
|
|
862
|
+
private composeSuccessMessage;
|
|
603
863
|
/**
|
|
604
864
|
* Post-execution: emit toast notifications, handle chaining, callbacks.
|
|
605
865
|
*/
|
|
606
866
|
private handlePostExecution;
|
|
867
|
+
/**
|
|
868
|
+
* Perform the `ActionSchema.onSuccess` hop for an action that just succeeded.
|
|
869
|
+
*
|
|
870
|
+
* **The router is the app's own.** `navigationHandler` is the seam every
|
|
871
|
+
* navigator in this file already goes through, and the console wires it to
|
|
872
|
+
* react-router's `navigate` — so `openIn: 'self'` is a real SPA route hop,
|
|
873
|
+
* in place, immune to popup blocking. No `window.location` / `window.open`
|
|
874
|
+
* mechanism is introduced here; the no-handler fallback is the same
|
|
875
|
+
* `result.redirect` hand-off `navigateTo` already uses.
|
|
876
|
+
*
|
|
877
|
+
* **No default is written here.** `openIn` is a materialised
|
|
878
|
+
* `.default('self')` in the spec, so parse output always carries a resolved
|
|
879
|
+
* member; this reads the ONE member that changes the branch and leaves the
|
|
880
|
+
* other implicit. A `?? 'self'` would be a second source of truth for a
|
|
881
|
+
* default the producer already resolved.
|
|
882
|
+
*
|
|
883
|
+
* **The two `openIn` spellings never cross.** This reads
|
|
884
|
+
* `onSuccess.openIn` (`'self' | 'newTab'`) and nothing else — never the
|
|
885
|
+
* top-level `type: 'url'` switch, which spells its new-tab member
|
|
886
|
+
* `'new-tab'`. Spec refuses each crossover spelling with a keyed error; a
|
|
887
|
+
* renderer that accepted either would be a second, looser contract than the
|
|
888
|
+
* one authors are validated against.
|
|
889
|
+
*/
|
|
890
|
+
private navigateOnSuccess;
|
|
891
|
+
/**
|
|
892
|
+
* `operation: 'update'` — the declarative single-record field write.
|
|
893
|
+
*
|
|
894
|
+
* ## The write is the ROUTE's, never this client's
|
|
895
|
+
*
|
|
896
|
+
* This method composes a request and hands it to the registered `script`
|
|
897
|
+
* dispatch; it does not touch a `DataSource`. That is a security boundary,
|
|
898
|
+
* not an implementation preference. `POST /api/v1/actions/{object}/{action}`
|
|
899
|
+
* runs the write AS THE CALLER — the caller's permissions, the object's hooks
|
|
900
|
+
* and its validations all fire as for a user edit — while a client-side
|
|
901
|
+
* `dataSource.update()` reaches the data plane having consulted none of them.
|
|
902
|
+
* The spec says the same thing from its end by refusing `target`, `body`,
|
|
903
|
+
* `method` and `bodyExtra` beside this key and pinning `type` to `'script'`:
|
|
904
|
+
* there is exactly one execution surface for an update action, and it is the
|
|
905
|
+
* route.
|
|
906
|
+
*
|
|
907
|
+
* Consequently, a consumer that has registered NO server-action dispatch gets
|
|
908
|
+
* a loud refusal naming the remedy. The two silent alternatives were both
|
|
909
|
+
* available and both wrong: falling through to `executeScript` ends at "No
|
|
910
|
+
* script provided" (an update action declares no `target` — the author is
|
|
911
|
+
* sent hunting for a field they were right not to write), and falling through
|
|
912
|
+
* to `executeActionSchema` reports on an action that never ran.
|
|
913
|
+
*
|
|
914
|
+
* ## `patch` rides UNDER the collected params
|
|
915
|
+
*
|
|
916
|
+
* The spec's words: static field values "merged UNDER the user-supplied
|
|
917
|
+
* `params` so a fixed value can be declared without exposing it in the
|
|
918
|
+
* dialog". So a collected param of the same name WINS — the author declared a
|
|
919
|
+
* default, and the user answered it.
|
|
920
|
+
*/
|
|
921
|
+
private executeUpdateOperation;
|
|
607
922
|
/**
|
|
608
923
|
* Execute script action — evaluates client-side expression via ExpressionEvaluator.
|
|
609
924
|
* Supports ${} template expressions referencing data, record, user context.
|
|
@@ -718,7 +1033,7 @@ export declare class ActionRunner {
|
|
|
718
1033
|
* old object-form `params` page — POSTed an empty body here.
|
|
719
1034
|
*
|
|
720
1035
|
* `params` contributes only in its DEPRECATED non-array form (the compat window
|
|
721
|
-
* #5777 opened; see `warnOnDeprecatedObjectParams`). An ARRAY `params` is a
|
|
1036
|
+
* objectstack-ai/objectstack#5777 opened; see `warnOnDeprecatedObjectParams`). An ARRAY `params` is a
|
|
722
1037
|
* parameter DEFINITION list, not a payload: it reaches this method unconsumed
|
|
723
1038
|
* only when no `paramCollectionHandler` is mounted, and POSTing the definitions
|
|
724
1039
|
* as the request body was never a payload any endpoint wanted. It now falls
|
|
@@ -787,3 +1102,21 @@ export declare class ActionRunner {
|
|
|
787
1102
|
* Convenience function to execute a single action with a one-off runner.
|
|
788
1103
|
*/
|
|
789
1104
|
export declare function executeAction(action: ActionDef, context?: ActionContext): Promise<ActionResult>;
|
|
1105
|
+
/** The `ActionSchema.onSuccess` block, as the pinned spec declares it. */
|
|
1106
|
+
export interface OnSuccessNavigation {
|
|
1107
|
+
navigate: string;
|
|
1108
|
+
/** `'self' | 'newTab'` — read as data, so an off-contract value cannot widen the branch. */
|
|
1109
|
+
openIn?: unknown;
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Is this `onSuccess` the spec's navigation block?
|
|
1113
|
+
*
|
|
1114
|
+
* The test IS the spec's declaration: a non-array object carrying a STRING
|
|
1115
|
+
* `navigate`. Stored rows are rehydrated UNPARSED (objectstack-ai/objectstack#3903), so the runner reads
|
|
1116
|
+
* the value as data and anything else gets NO reading — since objectui#5934
|
|
1117
|
+
* retired the legacy chained-callback channel (`ActionDef | ActionDef[]`),
|
|
1118
|
+
* there is no other channel for an off-contract shape to fall into. This is a
|
|
1119
|
+
* shape GUARD on unparsed data, not a discriminator between two meanings.
|
|
1120
|
+
*/
|
|
1121
|
+
export declare function readOnSuccessNavigation(value: unknown): OnSuccessNavigation | null;
|
|
1122
|
+
export {};
|