@object-ui/core 16.1.0 → 17.2.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 (60) hide show
  1. package/CHANGELOG.md +1881 -4
  2. package/dist/actions/ActionEngine.d.ts +11 -1
  3. package/dist/actions/ActionEngine.js +14 -3
  4. package/dist/actions/ActionRunner.d.ts +199 -24
  5. package/dist/actions/ActionRunner.js +189 -84
  6. package/dist/actions/actionKeys.d.ts +135 -0
  7. package/dist/actions/actionKeys.js +283 -0
  8. package/dist/actions/index.d.ts +1 -0
  9. package/dist/actions/index.js +1 -0
  10. package/dist/adapters/ApiDataSource.js +10 -1
  11. package/dist/adapters/ValueDataSource.d.ts +2 -2
  12. package/dist/evaluator/fieldRules.d.ts +0 -2
  13. package/dist/evaluator/fieldRules.js +3 -2
  14. package/dist/evaluator/optionLint.js +5 -2
  15. package/dist/evaluator/optionRules.d.ts +13 -6
  16. package/dist/evaluator/optionRules.js +0 -23
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.js +7 -0
  19. package/dist/query/query-ast.d.ts +6 -6
  20. package/dist/query/query-ast.js +2 -2
  21. package/dist/registry/PluginSystem.d.ts +15 -5
  22. package/dist/registry/PluginSystem.js +1 -1
  23. package/dist/registry/Registry.d.ts +81 -2
  24. package/dist/registry/Registry.js +102 -2
  25. package/dist/registry/WidgetRegistry.d.ts +7 -7
  26. package/dist/registry/WidgetRegistry.js +1 -1
  27. package/dist/registry/public-blocks.js +52 -1
  28. package/dist/styling/scoped-styles.d.ts +14 -8
  29. package/dist/theme/ThemeEngine.js +0 -12
  30. package/dist/utils/chart-series.d.ts +42 -2
  31. package/dist/utils/chart-series.js +55 -0
  32. package/dist/utils/column-identity.d.ts +110 -0
  33. package/dist/utils/column-identity.js +210 -0
  34. package/dist/utils/dashboard-filters.d.ts +11 -2
  35. package/dist/utils/dashboard-filters.js +99 -4
  36. package/dist/utils/dataset-format.d.ts +13 -1
  37. package/dist/utils/dataset-format.js +10 -2
  38. package/dist/utils/expand-fields.js +8 -8
  39. package/dist/utils/filter-converter.d.ts +47 -0
  40. package/dist/utils/filter-converter.js +94 -7
  41. package/dist/utils/filter-tokens.d.ts +108 -0
  42. package/dist/utils/filter-tokens.js +180 -0
  43. package/dist/utils/freeze-schema.d.ts +14 -6
  44. package/dist/utils/freeze-schema.js +14 -6
  45. package/dist/utils/managedBy.d.ts +97 -51
  46. package/dist/utils/managedBy.js +117 -53
  47. package/dist/utils/normalize-list-view.d.ts +109 -0
  48. package/dist/utils/normalize-list-view.js +255 -0
  49. package/dist/utils/sort-values.d.ts +52 -0
  50. package/dist/utils/sort-values.js +125 -0
  51. package/dist/validation/index.d.ts +10 -1
  52. package/dist/validation/index.js +10 -1
  53. package/dist/validation/required-presence.d.ts +31 -0
  54. package/dist/validation/required-presence.js +36 -0
  55. package/dist/validation/schema-validator.d.ts +24 -8
  56. package/dist/validation/validators/index.d.ts +7 -1
  57. package/dist/validation/validators/index.js +7 -1
  58. package/dist/validation/validators/object-validation-engine.d.ts +151 -31
  59. package/dist/validation/validators/object-validation-engine.js +275 -142
  60. package/package.json +4 -4
@@ -78,7 +78,17 @@ export declare class ActionEngine {
78
78
  bulkEnabled?: boolean;
79
79
  priority?: number;
80
80
  }): void;
81
- /** Register multiple actions from an ActionSchema array */
81
+ /**
82
+ * Register multiple actions from an ActionSchema array.
83
+ *
84
+ * Only `locations` is harvested from the metadata. `shortcut` and
85
+ * `bulkEnabled` used to be read here too, but spec 17 retired both as
86
+ * `retiredKey()` tombstones — a declaration carrying either one no longer
87
+ * parses, so harvesting them read a key that cannot exist and made two dead
88
+ * options look load-bearing. Both remain accepted on the single-action
89
+ * `registerAction(action, options)` overload, where a HOST passes them
90
+ * explicitly; they are simply no longer sourced from authored metadata.
91
+ */
82
92
  registerActions(actions: ActionDef[]): void;
83
93
  /** Unregister an action by name */
84
94
  unregisterAction(name: string): void;
@@ -110,13 +110,24 @@ export class ActionEngine {
110
110
  this.normalizedShortcutMap.set(normalizeShortcut(options.shortcut), name);
111
111
  }
112
112
  }
113
- /** Register multiple actions from an ActionSchema array */
113
+ /**
114
+ * Register multiple actions from an ActionSchema array.
115
+ *
116
+ * Only `locations` is harvested from the metadata. `shortcut` and
117
+ * `bulkEnabled` used to be read here too, but spec 17 retired both as
118
+ * `retiredKey()` tombstones — a declaration carrying either one no longer
119
+ * parses, so harvesting them read a key that cannot exist and made two dead
120
+ * options look load-bearing. Both remain accepted on the single-action
121
+ * `registerAction(action, options)` overload, where a HOST passes them
122
+ * explicitly; they are simply no longer sourced from authored metadata.
123
+ */
114
124
  registerActions(actions) {
115
125
  for (const action of actions) {
116
126
  this.registerAction(action, {
127
+ // No cast: `locations` is a declared `ActionDef` field as of
128
+ // objectstack#4075 step 2. The `as any` it replaces existed only
129
+ // because the key reached this reader through the index signature.
117
130
  locations: action.locations,
118
- shortcut: action.shortcut,
119
- bulkEnabled: action.bulkEnabled,
120
131
  });
121
132
  }
122
133
  }
@@ -5,14 +5,7 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- /**
9
- * @object-ui/core - Action Runner
10
- *
11
- * Executes actions defined in ActionSchema and EventHandler.
12
- * Supports all spec v2.0.1 action types: script, url, modal, flow, api.
13
- * Features: conditional execution, confirmation, toast notifications,
14
- * redirect handling, action chaining, custom handler registration.
15
- */
8
+ import type { ActionInput as SpecActionInput } from '@objectstack/spec/ui';
16
9
  import { ExpressionEvaluator } from '../evaluator/ExpressionEvaluator';
17
10
  import { type UndoableOperation } from './UndoManager';
18
11
  export interface ActionResult {
@@ -70,9 +63,15 @@ export interface ApiConfig {
70
63
  * Compatible with both UIActionSchema (spec v2.0.1) and legacy crud.ts ActionSchema.
71
64
  */
72
65
  export interface ActionDef {
73
- /** Action type identifier: 'script' | 'url' | 'modal' | 'flow' | 'api' | 'navigation' | custom */
66
+ /** Action type identifier — a `RunnableActionType` (the spec's six plus the
67
+ * `navigation` alias of `url`) or a custom type with a registered handler. */
74
68
  type?: string;
75
- /** Legacy action type field */
69
+ /**
70
+ * Legacy action type field.
71
+ *
72
+ * @deprecated objectui dialect — use the spec spelling `type`. Both are read,
73
+ * `type` first (objectstack#4075 step 2).
74
+ */
76
75
  actionType?: string;
77
76
  /** Action name (from UIActionSchema) */
78
77
  name?: string;
@@ -91,13 +90,36 @@ export interface ActionDef {
91
90
  condition?: string;
92
91
  /** Disabled expression — if truthy, skip action */
93
92
  disabled?: string | boolean;
94
- /** API endpoint (string URL or complex config) */
93
+ /**
94
+ * API endpoint (string URL or complex config).
95
+ *
96
+ * @deprecated objectui dialect — the spec spells an `api` action's endpoint
97
+ * `target` (with the verb in `method`). `executeAPI` resolves
98
+ * `api || endpoint || target`, so `target` alone is sufficient. The
99
+ * `ApiConfig` object form has no spec counterpart and is objectui-only
100
+ * (objectstack#4075 step 2).
101
+ */
95
102
  api?: string | ApiConfig;
96
- /** API endpoint URL (spec v2.0.1 alias) */
103
+ /**
104
+ * API endpoint URL.
105
+ *
106
+ * @deprecated objectui dialect — this was the "spec v2.0.1 alias", but spec 17
107
+ * spells it `target`. `executeAPI` resolves `api || endpoint || target`
108
+ * (objectstack#4075 step 2).
109
+ */
97
110
  endpoint?: string;
98
111
  /** HTTP method */
99
112
  method?: string;
100
- /** Navigation target */
113
+ /**
114
+ * Navigation target — a nested envelope carrying the `navigation` alias's own
115
+ * spelling (`to` / `external` / `newTab` / `replace`).
116
+ *
117
+ * @deprecated objectui dialect — the spec has no nested navigation envelope;
118
+ * it spells a navigation action `{ type: 'url', target, openIn }`.
119
+ * `executeNavigation` reads `navigate.to || navigate.target || navigate.redirect`,
120
+ * falling back to the same keys on the action itself, so a flat `target` works
121
+ * today (objectstack#4075 step 2).
122
+ */
101
123
  navigate?: any;
102
124
  /** onClick callback (legacy) */
103
125
  onClick?: () => void | Promise<void>;
@@ -131,10 +153,33 @@ export interface ActionDef {
131
153
  * reveal dialog rendering `result.data` (see ResultDialogSpec).
132
154
  */
133
155
  resultDialog?: ResultDialogSpec;
134
- /** Script/expression to execute (for type: 'script') */
135
- execute?: string;
136
- /** Target URL or identifier (for type: 'url', 'modal', 'flow') */
156
+ /**
157
+ * The one handler slot: script name/expression for `type: 'script'`, URL for
158
+ * `'url'`, flow name for `'flow'`, modal/page for `'modal'`, endpoint for
159
+ * `'api'`, FormView name for `'form'`.
160
+ *
161
+ * The `execute` alias was REMOVED in @objectstack/spec 17 (#3855) and is not
162
+ * read here (#3856) — don't re-add it. Two handler slots is how one action ran
163
+ * one script server-side and a different one client-side (#3713).
164
+ */
137
165
  target?: string;
166
+ /**
167
+ * Action body (spec `ActionSchema.body` — `HookBodySchema`). Opaque here:
168
+ * bodies are a SERVER-side execution surface and this runner never
169
+ * interprets one.
170
+ *
171
+ * L2 (`language: 'js'`) is defined as a function body run inside an isolated
172
+ * VM enforcing declared capabilities, `timeoutMs` and `memoryMb`. A browser
173
+ * has no such isolate, so "enforcing" those client-side would be decoration.
174
+ * L1 (`language: 'expression'`) is formula-engine (CEL) source, a different
175
+ * dialect from this package's `${…}` ExpressionEvaluator — running it here
176
+ * would diverge silently rather than fail.
177
+ *
178
+ * Consumers dispatch bodies by registering a `script` handler that POSTs to
179
+ * `/api/v1/actions/{object}/{action}` (see app-shell's
180
+ * `useConsoleActionRuntime`); the server runs the body through its sandbox.
181
+ */
182
+ body?: unknown;
138
183
  /** For type: 'url' — where to open `target`. `'new-tab'` forces a new
139
184
  * browser tab/window, `'self'` forces same-page navigation. When omitted,
140
185
  * external URLs open in a new tab and relative URLs navigate in place.
@@ -161,10 +206,82 @@ export interface ActionDef {
161
206
  * must perform all auth/authz itself (e.g. the cloud `/sso-open` endpoint,
162
207
  * which re-runs every check the POST half would have done). */
163
208
  newTabUrl?: string;
209
+ /** Where this action renders (`record_header`, `list_toolbar`, …). Read by
210
+ * `ActionEngine.registerActions` to seed the location filter. */
211
+ locations?: SpecActionInput['locations'];
212
+ /**
213
+ * Visibility predicate, evaluated against the runner's context.
214
+ *
215
+ * The `boolean` arm is objectui's own, deliberately wider than the spec:
216
+ * `ActionSchema.visible` admits only a CEL string or a `{ dialect, source }`
217
+ * envelope, while `ActionEngine.getActionsForLocation` also honours a literal
218
+ * `visible: true` / `false` (pinned by `ActionEngine.visibility.test.ts`).
219
+ * Declared rather than left to the index signature so the tolerance is
220
+ * auditable instead of invisible — but it IS a dialect, and objectstack#4075
221
+ * step 3 has to decide whether the spec adopts the boolean or objectui drops
222
+ * it. Do not widen this further.
223
+ */
224
+ visible?: SpecActionInput['visible'] | boolean;
225
+ /** System permissions the caller must ALL hold for the action to be offered
226
+ * (ADR-0066 D4 UI half — the server enforces the source of truth). */
227
+ requiredPermissions?: SpecActionInput['requiredPermissions'];
228
+ /** Icon name for the rendered control. */
229
+ icon?: SpecActionInput['icon'];
230
+ /** Visual emphasis of the rendered control (`primary`, `danger`, …). */
231
+ variant?: SpecActionInput['variant'];
232
+ /** Sort weight among sibling actions. */
233
+ order?: SpecActionInput['order'];
234
+ /** Which renderer surfaces the action (`action:button`, `action:menu`, …). */
235
+ component?: SpecActionInput['component'];
236
+ /** Object this action is declared against. */
237
+ objectName?: SpecActionInput['objectName'];
238
+ /** AI affordance metadata (tool exposure, prompts). */
239
+ ai?: SpecActionInput['ai'];
240
+ /** Accessibility overrides for the rendered control. */
241
+ aria?: SpecActionInput['aria'];
242
+ /** Extra properties merged into the request body alongside the collected params. */
243
+ bodyExtra?: SpecActionInput['bodyExtra'];
244
+ /** How collected params are shaped into the request body (`flat` or nested). */
245
+ bodyShape?: SpecActionInput['bodyShape'];
246
+ /** Execution mode of the action. */
247
+ mode?: SpecActionInput['mode'];
248
+ /** Field on the record supplying the record id sent to the endpoint. */
249
+ recordIdField?: SpecActionInput['recordIdField'];
250
+ /** Request-parameter name carrying the record id. */
251
+ recordIdParam?: SpecActionInput['recordIdParam'];
252
+ /** Auth/tenancy feature the action requires before it is offered. */
253
+ requiresFeature?: SpecActionInput['requiresFeature'];
254
+ /**
255
+ * @deprecated Retired in `@objectstack/spec` 17 as a `retiredKey()` tombstone —
256
+ * authoring it is a hard parse rejection, so this resolves to `undefined` and
257
+ * assigning a value is a compile error. A HOST may still pass a shortcut
258
+ * explicitly via `ActionEngine.registerAction(action, { shortcut })`; it is
259
+ * only no longer sourced from authored metadata. No replacement key.
260
+ */
261
+ shortcut?: SpecActionInput['shortcut'];
262
+ /**
263
+ * @deprecated Retired in `@objectstack/spec` 17 as a `retiredKey()` tombstone —
264
+ * authoring it is a hard parse rejection, so this resolves to `undefined` and
265
+ * assigning a value is a compile error. Use the list view's `bulkActions` /
266
+ * `bulkActionDefs` instead; `ActionEngine.registerAction(action, { bulkEnabled })`
267
+ * remains available to a HOST passing it explicitly.
268
+ */
269
+ bulkEnabled?: SpecActionInput['bulkEnabled'];
164
270
  /** Any additional properties */
165
271
  [key: string]: any;
166
272
  }
167
- export type ActionHandler = (action: ActionDef, context: ActionContext) => Promise<ActionResult> | ActionResult;
273
+ /**
274
+ * A handler registered with {@link ActionRunner} — the client-side action
275
+ * dispatch seam: given an authored `ActionDef` and the invocation context,
276
+ * produce an `ActionResult`.
277
+ *
278
+ * NOT the spec's `ActionHandler` (`@objectstack/spec/ui`), whose name this type
279
+ * carried until objectstack#4115. That one is the SERVER-side objectql action
280
+ * handler — a different calling convention entirely (`(ctx) => unknown`, with
281
+ * validated params and a trusted engine facade on `ctx`) and a different
282
+ * execution site.
283
+ */
284
+ export type ActionRunnerHandler = (action: ActionDef, context: ActionContext) => Promise<ActionResult> | ActionResult;
168
285
  /**
169
286
  * Confirmation handler — replaces window.confirm.
170
287
  * Consumers can provide an async implementation (e.g., Shadcn AlertDialog).
@@ -294,6 +411,21 @@ export declare class ActionRunner {
294
411
  private navigationHandler;
295
412
  private paramCollectionHandler;
296
413
  private resultDialogHandler;
414
+ /**
415
+ * Built-in dispatch, one entry per runnable action type.
416
+ *
417
+ * A table rather than a `switch` because the type annotation is the guard:
418
+ * `Record<RunnableActionType, …>` stops compiling the moment
419
+ * `@objectstack/spec` adds an `ActionType` this runner has no executor for.
420
+ * That is the whole Tier-2 failure class for actions — a spec name that
421
+ * validates at save and then does nothing at run time (#2942) — converted
422
+ * into a build error. It also rejects the reverse: an executor keyed by a
423
+ * name that is neither in the spec nor a declared local alias.
424
+ *
425
+ * `navigation` is that declared alias (see `ObjectUiLocalActionType`); it
426
+ * shares `url`'s navigator.
427
+ */
428
+ private readonly builtinExecutors;
297
429
  constructor(context?: ActionContext);
298
430
  /**
299
431
  * Set a custom confirmation handler (e.g., Shadcn AlertDialog).
@@ -322,7 +454,7 @@ export declare class ActionRunner {
322
454
  * values the user must copy now (2FA codes, OAuth client_secret).
323
455
  */
324
456
  setResultDialogHandler(handler: ResultDialogHandler): void;
325
- registerHandler(actionName: string, handler: ActionHandler): void;
457
+ registerHandler(actionName: string, handler: ActionRunnerHandler): void;
326
458
  unregisterHandler(actionName: string): void;
327
459
  /**
328
460
  * Register a named script handler. When a `script` action's
@@ -330,7 +462,7 @@ export declare class ActionRunner {
330
462
  * instead of the expression evaluator. Lets dashboards/views wire
331
463
  * symbolic action names (e.g. 'export_dashboard_pdf') to JS callbacks.
332
464
  */
333
- registerScript(scriptName: string, handler: ActionHandler): void;
465
+ registerScript(scriptName: string, handler: ActionRunnerHandler): void;
334
466
  unregisterScript(scriptName: string): void;
335
467
  execute(action: ActionDef): Promise<ActionResult>;
336
468
  /**
@@ -351,6 +483,21 @@ export declare class ActionRunner {
351
483
  * Uses navigationHandler for SPA routing, falls back to window.location.
352
484
  */
353
485
  private executeUrl;
486
+ /**
487
+ * The one navigator behind `type: 'url'` and its `navigation` alias.
488
+ *
489
+ * Both names mean "go to a location", and they had two implementations that
490
+ * drifted: only this one interpolated `${param.X}`, promoted `/api/…` to the
491
+ * `apiBase`, short-circuited a redirect-dance URL to a full-page load, and
492
+ * honoured `openIn`. A `navigation` action was quietly the weaker of the two
493
+ * (#2944 item 3). Sharing the body is what keeps the alias honest.
494
+ *
495
+ * `action` supplies the interpolation context (`params`) and `openIn`;
496
+ * `source` supplies the navigation modifiers, which live on the nested
497
+ * `action.navigate` block for the legacy shape and on the action itself
498
+ * otherwise.
499
+ */
500
+ private navigateTo;
354
501
  /**
355
502
  * Execute modal action — open a dialog.
356
503
  * Delegates to the registered modalHandler; returns modal schema if no handler.
@@ -361,10 +508,22 @@ export declare class ActionRunner {
361
508
  * Delegates to a registered 'flow' handler; otherwise returns not-implemented.
362
509
  */
363
510
  private executeFlow;
364
- private executeActionSchema;
365
511
  /**
366
- * Execute navigation action
512
+ * Terminal fallback for a dispatch the switch above did not recognize: no
513
+ * known `type`, no registered handler, no `navigate` / `api` / `endpoint` /
514
+ * `onClick`. Some schema-only shapes are still runnable here — an action
515
+ * whose whole payload is a `redirect`, or an explicit `reload` / `close`
516
+ * directive — so this stays a real execution path.
517
+ *
518
+ * What it must never do is report success for an action it did not run. It
519
+ * used to return `{ success: true, reload: true, close: true }` for a
520
+ * COMPLETELY EMPTY schema, which is how a legacy string row action —
521
+ * dispatched as `{ type: '<action name>' }`, the name landing in the `type`
522
+ * slot — reached the user as a green "Action completed successfully" toast
523
+ * having issued zero requests (objectui#2960). An unresolvable dispatch is a
524
+ * wiring bug; surface it instead of laundering it into a success.
367
525
  */
526
+ private executeActionSchema;
368
527
  /**
369
528
  * `form` action — open a FormView as a routed page (`/forms/:name`, per the
370
529
  * spec). `target` is the FormView name. Without this case the action fell
@@ -373,6 +532,16 @@ export declare class ActionRunner {
373
532
  * hosts that support it; the form route ignores unknown query params.
374
533
  */
375
534
  private executeForm;
535
+ /**
536
+ * `navigation` — objectui's alias of the spec's `url` (`ObjectUiLocalActionType`).
537
+ *
538
+ * Kept because `{ type: 'navigation', to: … }` is authored today
539
+ * (`element:button` CTAs) and because deleting the case would not fail loudly:
540
+ * the action would land in `executeActionSchema` and report success while
541
+ * navigating nowhere. All it does now is resolve the alias's own spelling of
542
+ * the target and hand off to the shared navigator, so the two names cannot
543
+ * drift apart again. Prefer `type: 'url'` + `openIn` in new metadata.
544
+ */
376
545
  private executeNavigation;
377
546
  /**
378
547
  * Execute API action — supports both simple string endpoint and complex ApiConfig.
@@ -399,9 +568,15 @@ export declare class ActionRunner {
399
568
  * fragment.
400
569
  *
401
570
  * `ctx.origin`, `ctx.user.*`, `ctx.org.*`, `ctx.recordId` are surfaced
402
- * implicitly from the runner's ActionContext + window. Consumers can
403
- * extend by stuffing extra keys under `context.ctx = {...}` before
404
- * calling `runner.execute()`.
571
+ * implicitly from the runner's ActionContext + window, and
572
+ * `ctx.selection.ids` / `ctx.selection.count` reflect the grid's current
573
+ * checkbox selection (`context.selectedRecords`) — `${ctx.selection.ids}`
574
+ * comma-joins the ids via the standard `String(array)` behaviour, so a
575
+ * list_toolbar url/api action can carry the selection without any bulk
576
+ * plumbing (objectui#3139). In an aggregate bulk dispatch,
577
+ * `${param._selectedIds}` interpolates the same ids from the injected
578
+ * param. Consumers can extend by stuffing extra keys under
579
+ * `context.ctx = {...}` before calling `runner.execute()`.
405
580
  */
406
581
  private interpolateTarget;
407
582
  /**