@object-ui/core 17.7.0 → 18.0.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 +650 -0
- package/README.md +127 -0
- package/dist/actions/ActionRunner.d.ts +192 -49
- package/dist/actions/ActionRunner.js +389 -102
- package/dist/actions/UndoManager.d.ts +8 -13
- package/dist/actions/actionKeys.d.ts +1 -1
- package/dist/actions/actionKeys.js +10 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/navigationTarget.d.ts +41 -0
- package/dist/actions/navigationTarget.js +20 -0
- package/dist/actions/recordDelete.d.ts +59 -23
- package/dist/actions/recordDelete.js +40 -42
- package/dist/adapters/ApiDataSource.d.ts +8 -4
- package/dist/adapters/ApiDataSource.js +19 -7
- package/dist/adapters/resolveDataSource.d.ts +9 -6
- package/dist/adapters/resolveDataSource.js +9 -12
- package/dist/data-scope/ViewDataProvider.d.ts +7 -24
- package/dist/data-scope/ViewDataProvider.js +6 -53
- package/dist/evaluator/deadlineSettled.d.ts +40 -0
- package/dist/evaluator/deadlineSettled.js +45 -0
- package/dist/evaluator/fieldRules.js +6 -1
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/evaluator/optionRules.d.ts +1 -1
- package/dist/evaluator/optionRules.js +1 -1
- package/dist/evaluator/subjectPermissions.d.ts +30 -1
- package/dist/evaluator/subjectPermissions.js +42 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/registry/Registry.d.ts +34 -13
- package/dist/registry/Registry.js +5 -22
- package/dist/utils/affordanceGrants.d.ts +371 -0
- package/dist/utils/affordanceGrants.js +252 -0
- package/dist/utils/chart-series.d.ts +34 -0
- package/dist/utils/chart-series.js +25 -2
- package/dist/utils/date-display.d.ts +42 -50
- package/dist/utils/date-display.js +192 -11
- package/dist/utils/expand-fields.d.ts +6 -4
- package/dist/utils/expand-fields.js +6 -4
- package/dist/utils/filter-converter.d.ts +6 -2
- package/dist/utils/filter-converter.js +6 -2
- package/dist/utils/normalize-list-view.d.ts +16 -12
- package/dist/utils/normalize-list-view.js +66 -67
- package/dist/utils/predicate-fields.js +6 -2
- package/dist/utils/record-source.d.ts +1 -1
- package/dist/utils/record-source.js +1 -1
- package/dist/utils/reference-keys.js +1 -1
- package/dist/validation/schema-validator.js +29 -5
- package/dist/validation/server-owned-value.d.ts +5 -1
- package/dist/validation/server-owned-value.js +5 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -179,6 +179,75 @@ withoutDeniedFields({ account_code: 'A-1', salary: 100 }, policy, 'account', ['a
|
|
|
179
179
|
- When nothing is withheld the SAME object comes back, so callers can tell the
|
|
180
180
|
two cases apart by identity.
|
|
181
181
|
|
|
182
|
+
### Affordance-to-grant map (`AFFORDANCE_GRANTS`, `resolveAffordance`)
|
|
183
|
+
|
|
184
|
+
Every console affordance that offers a write — a create form's fields, the
|
|
185
|
+
record header's Edit, a list's New and Import, a lookup's "Create new", a grid
|
|
186
|
+
row's Delete, and the rest — reads the grant it exercises from ONE table,
|
|
187
|
+
`AFFORDANCE_GRANTS` (objectui#12082). A row names the CRUD-affordance bit the
|
|
188
|
+
affordance needs, the object grant it exercises (`create`, `update` or
|
|
189
|
+
`delete`) and, for an affordance that offers fields, the field question it asks
|
|
190
|
+
(`create` for an insert, `write` for an update). The fields of a form, of the
|
|
191
|
+
record page's in-place edit (`recordEdit`) and of a list's in-cell edit
|
|
192
|
+
(`listInlineEdit`) each ask that question, so a field the caller's permission
|
|
193
|
+
set marks `editable: false` is offered read-only on every one of them
|
|
194
|
+
(objectui#12103). The `write` question is the server's whole update rule for a
|
|
195
|
+
field, so for the record's owner field it also needs the transfer grant
|
|
196
|
+
(`allowTransfer`, or `modifyAllRecords`).
|
|
197
|
+
|
|
198
|
+
`resolveAffordance` is the one verdict: the object's managed-object policy, the
|
|
199
|
+
server's effective API operation set and the caller's grant must all allow it,
|
|
200
|
+
and the row's `userActions` predicates are surfaced only then.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { resolveAffordance, resolveFieldAffordance } from '@object-ui/core'
|
|
204
|
+
|
|
205
|
+
// `perms` is structural: `usePermissions()` from `@object-ui/permissions`
|
|
206
|
+
// satisfies it. With no provider mounted every grant reads open.
|
|
207
|
+
const perms = {
|
|
208
|
+
isLoaded: true,
|
|
209
|
+
can: (_object: string, grant: 'create' | 'update' | 'delete') => grant === 'create',
|
|
210
|
+
checkField: (_object: string, _field: string, action: 'create' | 'write') => action === 'create',
|
|
211
|
+
getObjectApiOperations: () => undefined,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
resolveAffordance('listNew', { objectSchema: null, objectName: 'orders', perms })
|
|
215
|
+
// { allowed: true } — the create grant is held
|
|
216
|
+
resolveAffordance('recordEdit', { objectSchema: null, objectName: 'orders', perms })
|
|
217
|
+
// { allowed: false } — Edit reads the update grant, which is not
|
|
218
|
+
|
|
219
|
+
resolveFieldAffordance('createFormFields', perms, 'orders', 'title') // true
|
|
220
|
+
resolveFieldAffordance('editFormFields', perms, 'orders', 'title') // false
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
An affordance added later gets a row here and reads it through these functions;
|
|
224
|
+
it does not spell its own `can(object, verb)`. That includes an affordance that
|
|
225
|
+
reads NO grant at all — the calendar's quick-create and drag-to-reschedule
|
|
226
|
+
(`calendarQuickCreate`, `calendarReschedule`) and the kanban card move
|
|
227
|
+
(`kanbanCardMove`) were such affordances until they gained their rows. An
|
|
228
|
+
affordance that writes with the same verb on the same object reuses that row:
|
|
229
|
+
the record overlay's inline edit reads `recordEdit`, a gantt's bar edits and a
|
|
230
|
+
grid's bulk field update read `rowEdit`, a gantt row's Delete reads `rowDelete`,
|
|
231
|
+
the AI build panel's spreadsheet import reads `listImport`, a related list's
|
|
232
|
+
Add existing reads `relatedNew` (a link row) or `relatedRowEdit` (a re-parent),
|
|
233
|
+
and Setup's permission-set and position holders read `relatedNew` (a holder
|
|
234
|
+
added) and `relatedRowDelete` (a holder removed), each asked of its assignment
|
|
235
|
+
object (`sys_user_permission_set`, `sys_user_position`). Reaching Setup is not
|
|
236
|
+
that grant: the Setup app asks `setup.access`, which a caller can hold while the
|
|
237
|
+
server refuses their write of an assignment row.
|
|
238
|
+
The record feed's writes have rows of their own, each asked of the object it
|
|
239
|
+
writes: `feedComment` (a comment or reply, `sys_comment`), `feedReactionAdd` and
|
|
240
|
+
`feedReactionRemove` (the caller's own `sys_comment_reaction` row), and
|
|
241
|
+
`feedReactionColumn` (a reaction on a deployment that stores them in a
|
|
242
|
+
`sys_comment` column). The map's
|
|
243
|
+
enumeration pin in `@object-ui/plugin-form` (`affordanceGrantMap-12082.test.tsx`)
|
|
244
|
+
also counts every write call site in the console tree and holds each to the row
|
|
245
|
+
it sits behind, or to the reason it has none — and, for a component whose write
|
|
246
|
+
its host gates (the import wizard), holds every mount to reading the row. The
|
|
247
|
+
sites it found with no row yet formed a frozen backlog, which the pin now holds
|
|
248
|
+
empty: a new one cannot be added, so a write affordance gets its row when it
|
|
249
|
+
lands.
|
|
250
|
+
|
|
182
251
|
### Undo snapshot for an update (`captureUpdateUndoData`)
|
|
183
252
|
|
|
184
253
|
An `undoable` update restores exactly the fields it wrote, from their values
|
|
@@ -223,6 +292,64 @@ captureUpdateUndoData(['config'], { id: 't1', config: { id: 'c1', mode: 'strict'
|
|
|
223
292
|
- `fields` is required. Pass `undefined` only when the caller has no field
|
|
224
293
|
definitions for the object; nothing is then treated as a relation.
|
|
225
294
|
|
|
295
|
+
### Which fields an `operation: 'update'` Undo covers
|
|
296
|
+
|
|
297
|
+
The platform action route writes the `patch` of the declared action the
|
|
298
|
+
action's `name` names, under the collected params. So `ActionRunner`'s Undo
|
|
299
|
+
snapshot covers that declaration's `patch` keys plus the collected params. The
|
|
300
|
+
runner reads the declaration from its context: a host publishes the object's
|
|
301
|
+
declared actions as `objectActions`, beside `objectName` and `objectFields`.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { ActionRunner } from '@object-ui/core'
|
|
305
|
+
|
|
306
|
+
// The object definition the host already holds, as the metadata API serves it.
|
|
307
|
+
declare const taskDef: { fields: Record<string, unknown>; actions: unknown[] }
|
|
308
|
+
|
|
309
|
+
// The same context `ActionProvider` takes. `objectFields` and `objectActions`
|
|
310
|
+
// are read for `objectName`'s object only.
|
|
311
|
+
new ActionRunner({
|
|
312
|
+
objectName: 'task',
|
|
313
|
+
objectFields: taskDef.fields,
|
|
314
|
+
objectActions: taskDef.actions,
|
|
315
|
+
})
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
- The list is read only for the context's own object. A dispatch to another
|
|
319
|
+
object, or a host that publishes no list, reads the dispatched action's own
|
|
320
|
+
`patch`: a host that dispatches an object's action whole hands it in.
|
|
321
|
+
- A page action row's own `patch` is never covered, and no action renderer
|
|
322
|
+
forwards one: `@objectstack/spec` 18 retires the key on the row. A row that
|
|
323
|
+
names an action whose declaration has no `patch` snapshots the collected
|
|
324
|
+
params alone.
|
|
325
|
+
|
|
326
|
+
### Display time zone (`setDisplayTimeZone`)
|
|
327
|
+
|
|
328
|
+
The date and datetime faces this package exports (`formatDate`,
|
|
329
|
+
`formatDateTime`, `formatDateTimeCompactParts`, `formatRelativeDate`) render
|
|
330
|
+
an instant in one display zone. A host declares it once; no caller passes a
|
|
331
|
+
zone, so every face on the page agrees.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { formatDateTime, setDisplayTimeZone } from '@object-ui/core'
|
|
335
|
+
|
|
336
|
+
formatDateTime('2026-09-02T03:00:00Z', { locale: 'en-US' }) // the viewer's zone
|
|
337
|
+
setDisplayTimeZone('America/Los_Angeles')
|
|
338
|
+
formatDateTime('2026-09-02T03:00:00Z', { locale: 'en-US' }) // "Sep 1, 2026, 08:00 PM"
|
|
339
|
+
formatDateTime('2026-09-01', { locale: 'en-US' }) // "Sep 1, 2026, 12:00 AM", in every zone
|
|
340
|
+
setDisplayTimeZone(undefined) // back to the viewer's zone
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
- A date-only value names a calendar day and keeps it in every zone, including
|
|
344
|
+
the `Date` that `toDisplayDate` builds for one and a caller hands on.
|
|
345
|
+
- "Today" for the relative face is the display zone's today.
|
|
346
|
+
- An IANA name the runtime's `Intl` does not know clears the zone, with a
|
|
347
|
+
console warning.
|
|
348
|
+
- `getDisplayTimeZone()` reads it back, and `subscribeDisplayTimeZone(listener)`
|
|
349
|
+
reports changes in React's `useSyncExternalStore` shape. In a React tree,
|
|
350
|
+
`LocalizationProvider` from `@object-ui/i18n` sets it from its `timezone`
|
|
351
|
+
value; a renderer never calls the setter.
|
|
352
|
+
|
|
226
353
|
## Philosophy
|
|
227
354
|
|
|
228
355
|
This package is designed to be **framework-agnostic**. It contains:
|
|
@@ -19,44 +19,37 @@
|
|
|
19
19
|
* Features: conditional execution, confirmation, toast notifications,
|
|
20
20
|
* redirect handling, action chaining, custom handler registration.
|
|
21
21
|
*/
|
|
22
|
-
import type { UIActionSchema } from '@object-ui/types';
|
|
22
|
+
import type { ActionContext, ActionResult, 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
|
-
import { type UndoableOperation } from './UndoManager.js';
|
|
26
25
|
import { type FieldContainerLike } from '../utils/predicate-record.js';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
selectedRecords?: Record<string, any>[];
|
|
55
|
-
/** Live page-variable snapshot (ADR-0049), published by PageVariableActionBridge. */
|
|
56
|
-
pageVariables?: Record<string, any>;
|
|
57
|
-
user?: any;
|
|
58
|
-
[key: string]: any;
|
|
59
|
-
}
|
|
26
|
+
/**
|
|
27
|
+
* What an action handler returns and the runner reads — ONE authority, in
|
|
28
|
+
* `@object-ui/types` (objectui#6349, batch 4), re-exported here exactly as
|
|
29
|
+
* {@link ActionContext} is below. The members this module used to declare
|
|
30
|
+
* (`reload`, `redirect`, `modal`, `silent`, `undo`) moved down with it, and
|
|
31
|
+
* `UndoableOperation`, which types `undo`, moved down beside it
|
|
32
|
+
* (`./UndoManager.js` re-exports that one). The gate that counts authorities is
|
|
33
|
+
* `scripts/__tests__/one-authority-per-exported-name-6273.test.ts`; a re-export
|
|
34
|
+
* is not one.
|
|
35
|
+
*/
|
|
36
|
+
export type { ActionResult } from '@object-ui/types';
|
|
37
|
+
/**
|
|
38
|
+
* The context a runner executes actions in — ONE authority, in
|
|
39
|
+
* `@object-ui/types` (objectui#6349, batch 4). This module RE-EXPORTS that
|
|
40
|
+
* declaration instead of declaring a second one, so an import of
|
|
41
|
+
* `ActionContext` from `@object-ui/core` and one from `@object-ui/types` are
|
|
42
|
+
* the same type. `@object-ui/types` is the dependency-legal side: it depends on
|
|
43
|
+
* no `@object-ui/*` package, and this package depends on it.
|
|
44
|
+
*
|
|
45
|
+
* A re-export is not a second authority —
|
|
46
|
+
* `scripts/__tests__/one-authority-per-exported-name-6273.test.ts` counts
|
|
47
|
+
* declarations and ALIASING re-exports, never `export type { X } from …` —
|
|
48
|
+
* which is why this convergence takes `ActionContext` off that gate's
|
|
49
|
+
* `KNOWN_COLLISIONS` baseline. The member drift it removed is recorded on the
|
|
50
|
+
* declaration itself.
|
|
51
|
+
*/
|
|
52
|
+
export type { ActionContext } from '@object-ui/types';
|
|
60
53
|
/**
|
|
61
54
|
* API configuration for complex requests.
|
|
62
55
|
*/
|
|
@@ -358,7 +351,8 @@ export interface ActionDef {
|
|
|
358
351
|
* caller's permissions, the object's hooks and its validations all fire as
|
|
359
352
|
* for a user edit. That is why {@link execute} reads this key BEFORE `type`:
|
|
360
353
|
* by the time `type` is consulted an update action is indistinguishable from
|
|
361
|
-
* any other script action, and
|
|
354
|
+
* any other script action, and it would run without the update path's Undo
|
|
355
|
+
* capture and its no-route refusal.
|
|
362
356
|
*/
|
|
363
357
|
operation?: SpecActionInput['operation'];
|
|
364
358
|
/**
|
|
@@ -366,6 +360,16 @@ export interface ActionDef {
|
|
|
366
360
|
* record, merged UNDER the collected `params` so a fixed value can be
|
|
367
361
|
* declared without exposing it in the dialog. The spec refuses it on an
|
|
368
362
|
* action without `operation: 'update'`, where it would be silently dropped.
|
|
363
|
+
*
|
|
364
|
+
* The ROUTE applies it, from the declaration it resolves by name; this
|
|
365
|
+
* runner never sends it (objectui#12941) and reads it only to know which
|
|
366
|
+
* fields an Undo must restore.
|
|
367
|
+
*
|
|
368
|
+
* It is a DECLARATION's key here: a host that dispatches an object's action
|
|
369
|
+
* whole hands it along. No action renderer forwards a page row's own `patch`
|
|
370
|
+
* (objectui#13022, following `@objectstack/spec` 18's retirement of the row
|
|
371
|
+
* key), and the runner reads the host-published declaration ahead of this
|
|
372
|
+
* one (`declaredUpdatePatch`).
|
|
369
373
|
*/
|
|
370
374
|
patch?: SpecActionInput['patch'];
|
|
371
375
|
/** Execution mode of the action. */
|
|
@@ -458,6 +462,11 @@ export type ActionRunnerHandler = (action: ActionDef, context: ActionContext) =>
|
|
|
458
462
|
* `.cancel`), and `app-shell`'s `ActionConfirmDialog.tsx` renders them in
|
|
459
463
|
* place of its generic `actionConfirm.*` fallbacks. Line numbers are left out
|
|
460
464
|
* on purpose: #5205's pointer had already drifted by the time it was executed.
|
|
465
|
+
* The record delete is a second producer (objectui#11695): `app-shell`'s
|
|
466
|
+
* `useObjectActions` (the list's row and bulk Delete) and `RecordDetailView`
|
|
467
|
+
* (the record page's Delete) hand it `recordDelete.confirmCopy`'s title and
|
|
468
|
+
* confirm label with `destructive: true`, which paints the confirm button in
|
|
469
|
+
* the destructive button style.
|
|
461
470
|
*
|
|
462
471
|
* So re-measure across the repo, not this package: grep for two-argument
|
|
463
472
|
* calls of `ConfirmationHandler`-typed values (`confirmHandler(`, `onConfirm(`)
|
|
@@ -471,6 +480,8 @@ export type ConfirmationHandler = (message: string, options?: {
|
|
|
471
480
|
title?: string;
|
|
472
481
|
confirmText?: string;
|
|
473
482
|
cancelText?: string;
|
|
483
|
+
/** The confirmed action destroys data: paint the confirm button destructive. */
|
|
484
|
+
destructive?: boolean;
|
|
474
485
|
}) => Promise<boolean>;
|
|
475
486
|
/**
|
|
476
487
|
* Toast handler — consumers can wire to Sonner or any toast library.
|
|
@@ -500,8 +511,35 @@ export type NavigationHandler = (url: string, options?: {
|
|
|
500
511
|
* Param collection handler — consumers provide to show a dialog
|
|
501
512
|
* for collecting ActionParam values before action execution.
|
|
502
513
|
* Returns collected values, or null if cancelled.
|
|
514
|
+
*
|
|
515
|
+
* `interpolate` fills the `${...}` holes of the dialog's authored copy (the
|
|
516
|
+
* action's `description`, each param's `helpText`) in the same template scope
|
|
517
|
+
* the runner reads `confirmText` in — for a row dispatch, the row is `record`
|
|
518
|
+
* (see `templateEvaluatorFor`). The runner cannot fill them itself before the
|
|
519
|
+
* call: the handler localizes that copy AFTER it receives it (a translation
|
|
520
|
+
* bundle entry or an inline per-locale map), so the hole has to be filled on
|
|
521
|
+
* the text the user will actually read (objectui#12967).
|
|
522
|
+
*
|
|
523
|
+
* `run` lets the handler keep its dialog open until the run its values start
|
|
524
|
+
* has an outcome (objectui#13161). Called with the collected values, it
|
|
525
|
+
* dispatches the action with them and resolves with that run's
|
|
526
|
+
* {@link ActionResult} (a dispatch that throws resolves as a refused run, the
|
|
527
|
+
* way `execute` reports one). A refused run has already had its
|
|
528
|
+
* post-execution (the error toast, `onFailure`), so the handler can show the
|
|
529
|
+
* refusal beside the values the user typed and call `run` again with the
|
|
530
|
+
* edited values. A successful run's post-execution (success toast, result
|
|
531
|
+
* dialog, chain, `onSuccess`) waits until the handler settles its promise,
|
|
532
|
+
* so the dialog is closed before any of it shows. A call made while an
|
|
533
|
+
* earlier one is still in flight returns that earlier run, and dispatches
|
|
534
|
+
* nothing new.
|
|
535
|
+
*
|
|
536
|
+
* Once `run` has been called, the handler's own promise only ends the
|
|
537
|
+
* session: the runner never dispatches the values it resolves with. `null`
|
|
538
|
+
* after a refused run is a cancel, reported exactly as a cancel before any
|
|
539
|
+
* run. A handler that never calls `run` keeps the old contract: the runner
|
|
540
|
+
* dispatches once with the values it resolves.
|
|
503
541
|
*/
|
|
504
|
-
export type ParamCollectionHandler = (params: ActionParamDef[], action?: ActionDef) => Promise<Record<string, any> | null>;
|
|
542
|
+
export type ParamCollectionHandler = (params: ActionParamDef[], action?: ActionDef, interpolate?: (text: string) => string, run?: (values: Record<string, any>) => Promise<ActionResult>) => Promise<Record<string, any> | null>;
|
|
505
543
|
/**
|
|
506
544
|
* The contract's own result-dialog block, and one entry of its field list.
|
|
507
545
|
*
|
|
@@ -819,7 +857,32 @@ export declare class ActionRunner {
|
|
|
819
857
|
*/
|
|
820
858
|
registerScript(scriptName: string, handler: ActionRunnerHandler): void;
|
|
821
859
|
unregisterScript(scriptName: string): void;
|
|
822
|
-
|
|
860
|
+
/**
|
|
861
|
+
* Run `action`; with `callContext`, run it against that context for this
|
|
862
|
+
* one dispatch (objectui#13063).
|
|
863
|
+
*
|
|
864
|
+
* The runner's context names the PAGE: its `record` and its `objectName`.
|
|
865
|
+
* A dispatch that runs against some other record, such as a related-list
|
|
866
|
+
* row on a record page, which is a record of the CHILD object, hands that
|
|
867
|
+
* record here as `{ record, objectName }`. Every executor then reads it as
|
|
868
|
+
* the record the action runs against, the way a dispatch from that record's
|
|
869
|
+
* own page does: `executeForm` forwards its id and object, and its
|
|
870
|
+
* boundary check compares the target view against the child object instead
|
|
871
|
+
* of the page's.
|
|
872
|
+
*
|
|
873
|
+
* The runner's own context is not touched: the dispatch runs on a one-shot
|
|
874
|
+
* runner over the merged context that shares this runner's handlers (see
|
|
875
|
+
* {@link ActionRunner.scopedTo}).
|
|
876
|
+
*/
|
|
877
|
+
execute(action: ActionDef, callContext?: Partial<ActionContext>): Promise<ActionResult>;
|
|
878
|
+
/**
|
|
879
|
+
* Dispatch an action whose params are collected, and answer what it
|
|
880
|
+
* returned — WITHOUT post-execution, which each caller runs itself:
|
|
881
|
+
* `execute` right after a lone run, and the param-collection session
|
|
882
|
+
* (`ParamCollectionHandler`'s `run`, objectui#13161) once per refused attempt
|
|
883
|
+
* and once for the successful one when the session ends.
|
|
884
|
+
*/
|
|
885
|
+
private dispatchCollected;
|
|
823
886
|
/**
|
|
824
887
|
* Execute multiple actions in sequence or parallel.
|
|
825
888
|
*/
|
|
@@ -872,7 +935,9 @@ export declare class ActionRunner {
|
|
|
872
935
|
* react-router's `navigate` — so `openIn: 'self'` is a real SPA route hop,
|
|
873
936
|
* in place, immune to popup blocking. No `window.location` / `window.open`
|
|
874
937
|
* mechanism is introduced here; the no-handler fallback is the same
|
|
875
|
-
* `result.redirect` hand-off `navigateTo` already uses.
|
|
938
|
+
* `result.redirect` hand-off `navigateTo` already uses. The one exception is
|
|
939
|
+
* shared with `navigateTo`: a `mailto:` target is not a route, so it is a
|
|
940
|
+
* full-page navigation in place (objectui#12191).
|
|
876
941
|
*
|
|
877
942
|
* **No default is written here.** `openIn` is a materialised
|
|
878
943
|
* `.default('self')` in the spec, so parse output always carries a resolved
|
|
@@ -911,12 +976,34 @@ export declare class ActionRunner {
|
|
|
911
976
|
* sent hunting for a field they were right not to write), and falling through
|
|
912
977
|
* to `executeActionSchema` reports on an action that never ran.
|
|
913
978
|
*
|
|
914
|
-
* ## `patch` rides
|
|
979
|
+
* ## `patch` is the route's: it never rides on the wire (objectui#12941)
|
|
980
|
+
*
|
|
981
|
+
* The request carries the values collected for the action's declared
|
|
982
|
+
* `params`, plus the transport keys the dispatch reads (`recordId`, and the
|
|
983
|
+
* `_rowRecord` stash it strips before POSTing) — and NOTHING from `patch`.
|
|
984
|
+
* The route applies the declared `patch` itself, from the declaration it
|
|
985
|
+
* resolves by the action's name (`@objectstack/runtime`'s
|
|
986
|
+
* `declarativeUpdateWrite`, which writes `{ ...patch, ...params }`). The
|
|
987
|
+
* patch keys are not params the action declares, so posting them makes every
|
|
988
|
+
* declarative-update button a request carrying undeclared params — the very
|
|
989
|
+
* request the ADR-0104 D2 param contract refuses (objectstack#24268). A
|
|
990
|
+
* client copy of `patch` on the wire also adds nothing: the server never
|
|
991
|
+
* reads it.
|
|
992
|
+
*
|
|
993
|
+
* The spec's words for the merge the route performs: static field values
|
|
994
|
+
* "merged UNDER the user-supplied `params` so a fixed value can be declared
|
|
995
|
+
* without exposing it in the dialog". So a collected param of the same name
|
|
996
|
+
* WINS — the author declared a default, and the user answered it. This
|
|
997
|
+
* runner still computes that same merge, but only to know what the route
|
|
998
|
+
* WROTE, which is what an Undo snapshot must cover; it is never sent.
|
|
915
999
|
*
|
|
916
|
-
* The
|
|
917
|
-
*
|
|
918
|
-
*
|
|
919
|
-
*
|
|
1000
|
+
* ## The `patch` in that merge is the DECLARATION's (objectui#13022)
|
|
1001
|
+
*
|
|
1002
|
+
* The route writes the `patch` of the declaration the action's `name` names,
|
|
1003
|
+
* so that is the `patch` the snapshot covers: read by
|
|
1004
|
+
* {@link declaredUpdatePatch} from the declarations the host published, else
|
|
1005
|
+
* off a def that is the declaration itself. A page action row's own `patch`
|
|
1006
|
+
* is never written (`@objectstack/spec` 18 retires it) and never covered.
|
|
920
1007
|
*/
|
|
921
1008
|
private executeUpdateOperation;
|
|
922
1009
|
/**
|
|
@@ -929,6 +1016,24 @@ export declare class ActionRunner {
|
|
|
929
1016
|
* Uses navigationHandler for SPA routing, falls back to window.location.
|
|
930
1017
|
*/
|
|
931
1018
|
private executeUrl;
|
|
1019
|
+
/**
|
|
1020
|
+
* A navigation announces nothing unless its action declares the copy
|
|
1021
|
+
* (objectui#12755).
|
|
1022
|
+
*
|
|
1023
|
+
* `type: 'url'`, its `navigation` alias and `type: 'form'` only change the
|
|
1024
|
+
* route: nothing was written, so there is nothing to confirm. Their
|
|
1025
|
+
* `{ success: true }` used to reach `handlePostExecution` like a write's, and
|
|
1026
|
+
* the generic `completedSuccessfully` toast ("操作已成功完成") told the user an
|
|
1027
|
+
* operation had completed when a page had merely opened (objectstack-ai/cloud#2909:
|
|
1028
|
+
* an "invite member" button that only navigated read as "invitation sent").
|
|
1029
|
+
*
|
|
1030
|
+
* The result keeps `success: true` (callers read it as "the navigation
|
|
1031
|
+
* happened") and is marked `silent`, the flag that already means "no success
|
|
1032
|
+
* toast for this result". An author who wants a toast after the navigation
|
|
1033
|
+
* declares `successMessage`, and that copy still shows: the declaration is the
|
|
1034
|
+
* only signal, so there is no separate switch to forget.
|
|
1035
|
+
*/
|
|
1036
|
+
private announceNavigation;
|
|
932
1037
|
/**
|
|
933
1038
|
* The one navigator behind `type: 'url'` and its `navigation` alias.
|
|
934
1039
|
*
|
|
@@ -1053,10 +1158,6 @@ export declare class ActionRunner {
|
|
|
1053
1158
|
* Execute API action — supports both simple string endpoint and complex ApiConfig.
|
|
1054
1159
|
*/
|
|
1055
1160
|
private executeAPI;
|
|
1056
|
-
/**
|
|
1057
|
-
* Validate URL to prevent javascript: or data: protocol injection.
|
|
1058
|
-
*/
|
|
1059
|
-
private isValidUrl;
|
|
1060
1161
|
/**
|
|
1061
1162
|
* Substitute `${param.X}` and `${ctx.X}` tokens in an action target.
|
|
1062
1163
|
*
|
|
@@ -1091,8 +1192,51 @@ export declare class ActionRunner {
|
|
|
1091
1192
|
* placed under context.ctx is merged on top.
|
|
1092
1193
|
*/
|
|
1093
1194
|
private buildInterpolationContext;
|
|
1195
|
+
/**
|
|
1196
|
+
* The evaluator a dispatch's authored copy is read through: `confirmText`,
|
|
1197
|
+
* and the param dialog's `description` / `helpText` via the handler's
|
|
1198
|
+
* `interpolate` argument.
|
|
1199
|
+
*
|
|
1200
|
+
* One rule for every door (objectui#12967): the record the action runs
|
|
1201
|
+
* against is `record`. The record header binds it in the runner's context
|
|
1202
|
+
* (`RecordDetailView`'s `ActionProvider`). A row surface does not — it
|
|
1203
|
+
* stashes the clicked row under `params._rowRecord` (ObjectGrid's row
|
|
1204
|
+
* actions, a related list's row actions, `DeclaredActionsBar`, a page
|
|
1205
|
+
* header, `action:button`'s update write) and fires through the shared
|
|
1206
|
+
* runner, whose context names the page, not the row. So when the dispatch
|
|
1207
|
+
* carries a row, the row is bound as `record` for this dispatch's templates,
|
|
1208
|
+
* and it wins over a page-level `record` (a related-list row on a record page
|
|
1209
|
+
* is the child, not the page's record). Nothing else in the scope changes,
|
|
1210
|
+
* and the runner's own context is not touched.
|
|
1211
|
+
*
|
|
1212
|
+
* A hole naming a field the record does not carry degrades the way the
|
|
1213
|
+
* header's always has: the evaluator reads `undefined` and the hole prints
|
|
1214
|
+
* empty.
|
|
1215
|
+
*/
|
|
1216
|
+
private templateEvaluatorFor;
|
|
1094
1217
|
updateContext(newContext: Partial<ActionContext>): void;
|
|
1095
1218
|
getContext(): ActionContext;
|
|
1219
|
+
/**
|
|
1220
|
+
* A one-shot runner for {@link ActionRunner.execute}'s `callContext`
|
|
1221
|
+
* (objectui#13063): this runner's context with `callContext` merged over it,
|
|
1222
|
+
* and this runner's handlers, translator and registries, shared rather than
|
|
1223
|
+
* copied, so the dispatch confirms, collects params, toasts and navigates
|
|
1224
|
+
* exactly as this runner does.
|
|
1225
|
+
*
|
|
1226
|
+
* Two keys follow the merge rather than the base:
|
|
1227
|
+
*
|
|
1228
|
+
* - `ctx`, the `ctx.*` mirror `ActionProvider` publishes beside the flat
|
|
1229
|
+
* keys. A `${ctx.record.…}` hole has to read the same record as a
|
|
1230
|
+
* `${record.…}` one.
|
|
1231
|
+
* - `objectFields` / `objectActions`. A host publishes them BESIDE
|
|
1232
|
+
* `objectName`, so the pair names one object (objectui#11122,
|
|
1233
|
+
* objectui#13022). When `callContext` names another object and brings no
|
|
1234
|
+
* definitions of its own, the base's are dropped: the page object's field
|
|
1235
|
+
* types and declarations must not answer for the child's record. That is
|
|
1236
|
+
* what a dispatch with no `callContext` already gets for a retargeted
|
|
1237
|
+
* action, so the update path's capture sees the same input either way.
|
|
1238
|
+
*/
|
|
1239
|
+
private scopedTo;
|
|
1096
1240
|
/**
|
|
1097
1241
|
* Get the expression evaluator (for components that need to evaluate visibility, etc.)
|
|
1098
1242
|
*/
|
|
@@ -1119,4 +1263,3 @@ export interface OnSuccessNavigation {
|
|
|
1119
1263
|
* shape GUARD on unparsed data, not a discriminator between two meanings.
|
|
1120
1264
|
*/
|
|
1121
1265
|
export declare function readOnSuccessNavigation(value: unknown): OnSuccessNavigation | null;
|
|
1122
|
-
export {};
|