@object-ui/core 17.6.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +6610 -0
  2. package/README.md +101 -14
  3. package/dist/actions/ActionEngine.d.ts +4 -2
  4. package/dist/actions/ActionEngine.js +13 -8
  5. package/dist/actions/ActionRunner.d.ts +288 -27
  6. package/dist/actions/ActionRunner.js +443 -63
  7. package/dist/actions/TransactionManager.d.ts +7 -2
  8. package/dist/actions/actionKeys.d.ts +9 -7
  9. package/dist/actions/actionKeys.js +57 -14
  10. package/dist/actions/actionResponse.js +2 -2
  11. package/dist/actions/index.d.ts +1 -0
  12. package/dist/actions/index.js +1 -0
  13. package/dist/actions/recordDelete.d.ts +118 -0
  14. package/dist/actions/recordDelete.js +112 -0
  15. package/dist/adapters/ValueDataSource.d.ts +6 -1
  16. package/dist/adapters/ValueDataSource.js +1090 -98
  17. package/dist/adapters/batchTransaction.d.ts +1 -1
  18. package/dist/adapters/batchTransaction.js +1 -1
  19. package/dist/builder/schema-builder.d.ts +38 -81
  20. package/dist/builder/schema-builder.js +33 -152
  21. package/dist/data-scope/ViewDataProvider.d.ts +1 -1
  22. package/dist/data-scope/ViewDataProvider.js +21 -1
  23. package/dist/data-scope/element-data-source.d.ts +261 -7
  24. package/dist/data-scope/element-data-source.js +342 -52
  25. package/dist/data-scope/index.d.ts +9 -4
  26. package/dist/data-scope/index.js +9 -4
  27. package/dist/evaluator/ExpressionEvaluator.d.ts +86 -0
  28. package/dist/evaluator/ExpressionEvaluator.js +184 -12
  29. package/dist/evaluator/FormulaFunctions.d.ts +0 -10
  30. package/dist/evaluator/FormulaFunctions.js +166 -27
  31. package/dist/evaluator/declaredPredicate.d.ts +93 -9
  32. package/dist/evaluator/declaredPredicate.js +117 -16
  33. package/dist/evaluator/fieldRules.d.ts +88 -4
  34. package/dist/evaluator/fieldRules.js +203 -31
  35. package/dist/evaluator/index.d.ts +1 -0
  36. package/dist/evaluator/index.js +1 -0
  37. package/dist/evaluator/listConditional.d.ts +103 -34
  38. package/dist/evaluator/listConditional.js +127 -50
  39. package/dist/evaluator/optionRules.d.ts +77 -6
  40. package/dist/evaluator/optionRules.js +87 -10
  41. package/dist/evaluator/predicateInput.d.ts +15 -4
  42. package/dist/evaluator/predicateInput.js +35 -2
  43. package/dist/evaluator/rowPredicateCanon.d.ts +7 -18
  44. package/dist/evaluator/rowPredicateCanon.js +65 -115
  45. package/dist/evaluator/subjectPermissions.d.ts +76 -0
  46. package/dist/evaluator/subjectPermissions.js +44 -0
  47. package/dist/evaluator/unevaluablePredicate.d.ts +57 -0
  48. package/dist/evaluator/unevaluablePredicate.js +70 -0
  49. package/dist/index.d.ts +14 -0
  50. package/dist/index.js +80 -2
  51. package/dist/protocols/index.d.ts +0 -1
  52. package/dist/protocols/index.js +9 -1
  53. package/dist/registry/Registry.d.ts +358 -31
  54. package/dist/registry/Registry.js +263 -11
  55. package/dist/registry/WidgetRegistry.js +7 -3
  56. package/dist/registry/html-tier-intrinsics.d.ts +90 -0
  57. package/dist/registry/html-tier-intrinsics.js +110 -0
  58. package/dist/registry/public-blocks.d.ts +8 -0
  59. package/dist/registry/public-blocks.js +57 -0
  60. package/dist/theme/ThemeEngine.js +1 -1
  61. package/dist/utils/chart-category-key.d.ts +82 -0
  62. package/dist/utils/chart-category-key.js +84 -0
  63. package/dist/utils/chart-measure-key.d.ts +64 -0
  64. package/dist/utils/chart-measure-key.js +65 -0
  65. package/dist/utils/chart-presentation.d.ts +95 -112
  66. package/dist/utils/chart-presentation.js +169 -104
  67. package/dist/utils/chart-series.d.ts +131 -5
  68. package/dist/utils/chart-series.js +98 -16
  69. package/dist/utils/column-sortability.d.ts +157 -0
  70. package/dist/utils/column-sortability.js +217 -0
  71. package/dist/utils/dashboard-filters.d.ts +38 -6
  72. package/dist/utils/dashboard-filters.js +107 -61
  73. package/dist/utils/dataset-format.d.ts +211 -30
  74. package/dist/utils/dataset-format.js +389 -46
  75. package/dist/utils/date-display.d.ts +354 -0
  76. package/dist/utils/date-display.js +463 -0
  77. package/dist/utils/date-macros.js +7 -0
  78. package/dist/utils/drill-down.d.ts +78 -0
  79. package/dist/utils/drill-down.js +93 -0
  80. package/dist/utils/emptiness.d.ts +96 -0
  81. package/dist/utils/emptiness.js +101 -0
  82. package/dist/utils/expand-fields.d.ts +76 -6
  83. package/dist/utils/expand-fields.js +76 -6
  84. package/dist/utils/extract-records.d.ts +65 -5
  85. package/dist/utils/extract-records.js +65 -8
  86. package/dist/utils/filter-converter.d.ts +152 -4
  87. package/dist/utils/filter-converter.js +1092 -22
  88. package/dist/utils/filter-tokens.d.ts +49 -5
  89. package/dist/utils/filter-tokens.js +105 -33
  90. package/dist/utils/grouping-fields.d.ts +61 -0
  91. package/dist/utils/grouping-fields.js +84 -0
  92. package/dist/utils/managedBy.d.ts +3 -3
  93. package/dist/utils/managedBy.js +5 -5
  94. package/dist/utils/native-date-value.d.ts +48 -0
  95. package/dist/utils/native-date-value.js +167 -0
  96. package/dist/utils/non-grid-row-ceiling.d.ts +143 -0
  97. package/dist/utils/non-grid-row-ceiling.js +141 -0
  98. package/dist/utils/normalize-list-view.d.ts +121 -1
  99. package/dist/utils/normalize-list-view.js +186 -6
  100. package/dist/utils/number-display.d.ts +63 -14
  101. package/dist/utils/number-display.js +34 -12
  102. package/dist/utils/object-aggregate-query.d.ts +113 -0
  103. package/dist/utils/object-aggregate-query.js +50 -0
  104. package/dist/utils/parent-scope.d.ts +123 -0
  105. package/dist/utils/parent-scope.js +140 -0
  106. package/dist/utils/predicate-fields.d.ts +45 -12
  107. package/dist/utils/predicate-fields.js +137 -12
  108. package/dist/utils/record-color.d.ts +75 -0
  109. package/dist/utils/record-color.js +65 -0
  110. package/dist/utils/record-source.d.ts +298 -0
  111. package/dist/utils/record-source.js +331 -0
  112. package/dist/utils/record-title.d.ts +148 -8
  113. package/dist/utils/record-title.js +220 -26
  114. package/dist/utils/reference-keys.d.ts +21 -22
  115. package/dist/utils/reference-keys.js +532 -29
  116. package/dist/utils/sort-query.d.ts +115 -15
  117. package/dist/utils/sort-query.js +240 -13
  118. package/dist/utils/text-comparand.d.ts +92 -0
  119. package/dist/utils/text-comparand.js +99 -0
  120. package/dist/utils/without-denied-fields.d.ts +49 -0
  121. package/dist/utils/without-denied-fields.js +52 -0
  122. package/dist/validation/index.d.ts +8 -4
  123. package/dist/validation/index.js +8 -4
  124. package/dist/validation/schema-validator.d.ts +1 -1
  125. package/dist/validation/schema-validator.js +200 -82
  126. package/dist/validation/validators/object-validation-engine.d.ts +1 -1
  127. package/dist/validation/validators/object-validation-engine.js +14 -3
  128. package/package.json +8 -9
  129. package/dist/data-scope/DataScopeManager.d.ts +0 -127
  130. package/dist/data-scope/DataScopeManager.js +0 -229
  131. package/dist/protocols/ResponsiveProtocol.d.ts +0 -73
  132. package/dist/protocols/ResponsiveProtocol.js +0 -158
  133. package/dist/validation/validation-engine.d.ts +0 -88
  134. package/dist/validation/validation-engine.js +0 -434
@@ -23,6 +23,7 @@ import type { UIActionSchema } from '@object-ui/types';
23
23
  import type { Action as SpecActionInput } from '@objectstack/spec/ui';
24
24
  import { ExpressionEvaluator } from '../evaluator/ExpressionEvaluator.js';
25
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;
@@ -168,8 +169,8 @@ export interface ActionDef {
168
169
  * field, because the 2026-08-06 maintainer ruling on objectstack#4075 gave
169
170
  * both keys ONE shape: `boolean | string(CEL) | { dialect, source }` —
170
171
  * "boolean = 条件的退化形字面量,string = CEL 简写,信封 = 完整形". The
171
- * envelope arm arrived in `@objectstack/spec` 17.0.0-rc.6 (objectstack#5970,
172
- * PR objectstack#6450); until then this was a hand-written `string | boolean`
172
+ * envelope arm arrived in `@objectstack/spec` 17.0.0-rc.6 (objectstack
173
+ * `97e7e3caa`); until then this was a hand-written `string | boolean`
173
174
  * that could not describe the envelope, which is why `DeclaredActionsBar`
174
175
  * read it through an `(action as any).disabled` cast. Derived, not restated,
175
176
  * so the two keys cannot drift apart again.
@@ -233,7 +234,8 @@ export interface ActionDef {
233
234
  showOnError?: boolean;
234
235
  duration?: number;
235
236
  };
236
- /** Success message (from UIActionSchema) */
237
+ /** Success message (from UIActionSchema). The success toast's second rung,
238
+ * after {@link outcomeMessages}; may interpolate `${result.*}`. */
237
239
  successMessage?: string;
238
240
  /** Error message (from UIActionSchema) */
239
241
  errorMessage?: string;
@@ -256,9 +258,9 @@ export interface ActionDef {
256
258
  * `'url'`, flow name for `'flow'`, modal/page for `'modal'`, endpoint for
257
259
  * `'api'`, FormView name for `'form'`.
258
260
  *
259
- * The `execute` alias was REMOVED in @objectstack/spec 17 (#3855) and is not
260
- * read here (#3856) — don't re-add it. Two handler slots is how one action ran
261
- * 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).
262
264
  */
263
265
  target?: string;
264
266
  /**
@@ -291,8 +293,6 @@ export interface ActionDef {
291
293
  chain?: ActionDef[];
292
294
  /** Chain execution mode */
293
295
  chainMode?: 'sequential' | 'parallel';
294
- /** Callback on success */
295
- onSuccess?: ActionDef | ActionDef[];
296
296
  /** Callback on failure */
297
297
  onFailure?: ActionDef | ActionDef[];
298
298
  /** When true, the runner pre-opens about:blank synchronously on click so the
@@ -323,7 +323,7 @@ export interface ActionDef {
323
323
  * spec 采纳 —— `visible` / `disabled` 两键在 spec 侧统一收敛为
324
324
  * `boolean | string(CEL) | {dialect, source}`(boolean = 条件的退化形字面量,
325
325
  * string = CEL 简写,信封 = 完整形)". `@objectstack/spec` 17.0.0-rc.6 carries
326
- * it (objectstack#5970, PR objectstack#6450), so the local `| boolean` is no
326
+ * it (objectstack `97e7e3caa`), so the local `| boolean` is no
327
327
  * longer a tolerance to declare — it is part of the derived type, and adding
328
328
  * it back would restate a spec arm rather than widen anything.
329
329
  */
@@ -343,12 +343,31 @@ export interface ActionDef {
343
343
  objectName?: SpecActionInput['objectName'];
344
344
  /** AI affordance metadata (tool exposure, prompts). */
345
345
  ai?: SpecActionInput['ai'];
346
- /** Accessibility overrides for the rendered control. */
347
- aria?: SpecActionInput['aria'];
348
346
  /** Extra properties merged into the request body alongside the collected params. */
349
347
  bodyExtra?: SpecActionInput['bodyExtra'];
350
348
  /** How collected params are shaped into the request body (`flat` or nested). */
351
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'];
352
371
  /** Execution mode of the action. */
353
372
  mode?: SpecActionInput['mode'];
354
373
  /** Field on the record supplying the record id sent to the endpoint. */
@@ -357,6 +376,41 @@ export interface ActionDef {
357
376
  recordIdParam?: SpecActionInput['recordIdParam'];
358
377
  /** Auth/tenancy feature the action requires before it is offered. */
359
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'];
360
414
  /**
361
415
  * @deprecated Retired in `@objectstack/spec` 17 as a `retiredKey()` tombstone —
362
416
  * authoring it is a hard parse rejection, so this resolves to `undefined` and
@@ -448,10 +502,29 @@ export type NavigationHandler = (url: string, options?: {
448
502
  * Returns collected values, or null if cancelled.
449
503
  */
450
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];
451
522
  /**
452
523
  * Result dialog spec — declarative description of how to render a
453
524
  * one-shot reveal of an action's API response. Mirrors
454
- * `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.
455
528
  *
456
529
  * When set on an action and the action succeeds, the runner suppresses
457
530
  * the success toast and awaits a ResultDialogHandler instead. Used for
@@ -460,13 +533,22 @@ export type ParamCollectionHandler = (params: ActionParamDef[], action?: ActionD
460
533
  */
461
534
  export interface ResultDialogFieldSpec {
462
535
  path: string;
463
- label?: string;
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'];
464
543
  format?: 'qrcode' | 'code-list' | 'secret' | 'text' | 'json';
465
544
  }
466
545
  export interface ResultDialogSpec {
467
- title?: string;
468
- description?: string;
469
- acknowledge?: string;
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'];
470
552
  format?: 'qrcode' | 'code-list' | 'secret' | 'text' | 'json';
471
553
  fields?: ResultDialogFieldSpec[];
472
554
  }
@@ -486,7 +568,7 @@ export type ResultDialogHandler = (spec: ResultDialogSpec, data: unknown, action
486
568
  * `visibleWhen` (the CEL predicate `resolveVisibleOptions()` filters on —
487
569
  * ADR-0058 / #2284), `color`, `icon`, `disabled`.
488
570
  *
489
- * The catch-all is deliberate rather than a closed key list (objectui#3559).
571
+ * The catch-all is deliberate rather than a closed key list (`fbc23e094`).
490
572
  * A field's option vocabulary is owned by the field metadata (`@objectstack/spec`'s
491
573
  * `SelectOptionSchema`) and read by the option widgets; a param's option list is
492
574
  * only a CONDUIT between the two. When this type restated that vocabulary as
@@ -530,19 +612,36 @@ export interface ActionParamDef {
530
612
  placeholder?: string;
531
613
  /**
532
614
  * Visibility predicate (CEL) evaluated against the same scope as action
533
- * `visible` (`current_user` / `app` / `data` / `features`). When it evaluates
615
+ * `visible` (`current_user` / `data` / `features` — ⛔ not `app`, unbound by
616
+ * objectui#8155). When it evaluates
534
617
  * false the param dialog omits this param — used to hide a param the backend
535
618
  * only accepts under an opt-in capability (e.g. `create_user.phoneNumber`
536
619
  * gated on `features.phoneNumber`). Absent = always visible.
537
620
  */
538
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;
539
638
  /** Accepted upload types (MIME types / extensions) for `file`/`image` params. */
540
639
  accept?: string[];
541
640
  /** Max upload size in bytes for `file`/`image` params. */
542
641
  maxSize?: number;
543
642
  /** Allow multiple values (file/image/lookup/user params → array value). */
544
643
  multiple?: boolean;
545
- /** Object name the lookup picker queries (`reference_to` on the field). */
644
+ /** Object name the lookup picker queries (`reference` on the field). */
546
645
  referenceTo?: string;
547
646
  /** Field on the referenced record used as the human label (default `name`). */
548
647
  displayField?: string;
@@ -560,7 +659,87 @@ export interface ActionParamDef {
560
659
  lookupPageSize?: number;
561
660
  /** Form-field dependencies that gate / parameterise the picker query. */
562
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;
563
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;
564
743
  export declare class ActionRunner {
565
744
  private handlers;
566
745
  private scripts;
@@ -572,6 +751,7 @@ export declare class ActionRunner {
572
751
  private navigationHandler;
573
752
  private paramCollectionHandler;
574
753
  private resultDialogHandler;
754
+ private translate;
575
755
  /**
576
756
  * Built-in dispatch, one entry per runnable action type.
577
757
  *
@@ -588,6 +768,20 @@ export declare class ActionRunner {
588
768
  */
589
769
  private readonly builtinExecutors;
590
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;
591
785
  /**
592
786
  * Set a custom confirmation handler (e.g., Shadcn AlertDialog).
593
787
  */
@@ -630,6 +824,42 @@ export declare class ActionRunner {
630
824
  * Execute multiple actions in sequence or parallel.
631
825
  */
632
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;
633
863
  /**
634
864
  * Post-execution: emit toast notifications, handle chaining, callbacks.
635
865
  */
@@ -658,6 +888,37 @@ export declare class ActionRunner {
658
888
  * one authors are validated against.
659
889
  */
660
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;
661
922
  /**
662
923
  * Execute script action — evaluates client-side expression via ExpressionEvaluator.
663
924
  * Supports ${} template expressions referencing data, record, user context.
@@ -772,7 +1033,7 @@ export declare class ActionRunner {
772
1033
  * old object-form `params` page — POSTed an empty body here.
773
1034
  *
774
1035
  * `params` contributes only in its DEPRECATED non-array form (the compat window
775
- * #5777 opened; see `warnOnDeprecatedObjectParams`). An ARRAY `params` is a
1036
+ * objectstack-ai/objectstack#5777 opened; see `warnOnDeprecatedObjectParams`). An ARRAY `params` is a
776
1037
  * parameter DEFINITION list, not a payload: it reaches this method unconsumed
777
1038
  * only when no `paramCollectionHandler` is mounted, and POSTing the definitions
778
1039
  * as the request body was never a payload any endpoint wanted. It now falls
@@ -848,14 +1109,14 @@ export interface OnSuccessNavigation {
848
1109
  openIn?: unknown;
849
1110
  }
850
1111
  /**
851
- * Is this `onSuccess` the SPEC's navigation block, or this runner's older
852
- * chained-callback channel (`ActionDef | ActionDef[]`)?
1112
+ * Is this `onSuccess` the spec's navigation block?
853
1113
  *
854
1114
  * The test IS the spec's declaration: a non-array object carrying a STRING
855
- * `navigate`. Nothing else can produce that shape — the spec object is strict
856
- * with `navigate: z.string()` required, and on a callback `ActionDef`,
857
- * `navigate` is the deprecated nested navigation ENVELOPE that
858
- * `executeNavigation` reads `to`/`target`/`redirect` off, so a bare string
859
- * there has never been runnable.
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.
860
1120
  */
861
1121
  export declare function readOnSuccessNavigation(value: unknown): OnSuccessNavigation | null;
1122
+ export {};