@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
@@ -24,8 +24,13 @@ export interface TransactionOperation {
24
24
  type: 'create' | 'update' | 'delete';
25
25
  /** Target resource name */
26
26
  resource: string;
27
- /** Record ID (for update/delete) */
28
- id?: string | number;
27
+ /**
28
+ * Record ID (for update/delete). A `string`, as `@objectstack/spec` declares
29
+ * every record door and as this type's own sibling `BatchTransactionOperation.id`
30
+ * already did; the rollback path hands it straight to `DataSource.update`
31
+ * (objectui#9333).
32
+ */
33
+ id?: string;
29
34
  /** Data payload */
30
35
  data?: Record<string, any>;
31
36
  /** Previous state (for rollback) */
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * That asymmetry was the issue. An open key set on a DECLARED METADATA CONTRACT
20
20
  * is what lets a typo (`targt`, `exectue`) and a tombstoned key (`execute`) both
21
- * sail through to a runner that then silently does nothing — the #2169 "Mark
21
+ * sail through to a runner that then silently does nothing — the objectstack-ai/objectstack#2169 "Mark
22
22
  * Done does nothing" shape.
23
23
  *
24
24
  * This module was step 1 of the staged narrowing: make the key set VISIBLE and
@@ -74,10 +74,12 @@
74
74
  * rejection" into a compile error at no cost. Hand-copying would have quietly
75
75
  * re-legitimized two dead keys — which is why the types are derived.
76
76
  *
77
- * 17 keys `ActionDef` declares that the spec does not own — `actionType`, `api`,
77
+ * 16 keys `ActionDef` declares that the spec does not own — `actionType`, `api`,
78
78
  * `chain`, `chainMode`, `close`, `condition`, `confirm`, `endpoint`, `modal`,
79
- * `navigate`, `onClick`, `onFailure`, `onSuccess`, `redirect`, `reload`, `toast`,
80
- * `actionParams`. Step 2 marked `@deprecated`, with the spec spelling to use
79
+ * `navigate`, `onClick`, `onFailure`, `redirect`, `reload`, `toast`,
80
+ * `actionParams`. (`onSuccess` was the 17th until objectui#5934 retired the
81
+ * runner's chained-callback meaning; the key is now spec-owned and derived,
82
+ * like the 18 below.) Step 2 marked `@deprecated`, with the spec spelling to use
81
83
  * instead, ONLY the four the runner itself proves are aliases: `actionType` (→
82
84
  * `type`), `api` and `endpoint` (→ `target`; `executeAPI` resolves
83
85
  * `api || endpoint || target`), and `navigate` (→ flat `target`/`openIn`;
@@ -111,7 +113,7 @@
111
113
  * classifier a list of anything. `actionKeys.pin.test.ts` re-derives this one
112
114
  * from the interface's AST, which is what keeps the data true.
113
115
  */
114
- export declare const ACTION_DEF_KEYS: readonly ["type", "actionType", "name", "label", "description", "confirmText", "confirm", "condition", "disabled", "api", "endpoint", "method", "navigate", "to", "external", "newTab", "replace", "onClick", "reload", "close", "redirect", "toast", "successMessage", "errorMessage", "refreshAfter", "undoable", "params", "actionParams", "resultDialog", "target", "body", "openIn", "modal", "chain", "chainMode", "onSuccess", "onFailure", "opensInNewTab", "newTabUrl", "locations", "visible", "requiredPermissions", "icon", "variant", "order", "component", "objectName", "ai", "aria", "bodyExtra", "bodyShape", "mode", "recordIdField", "recordIdParam", "requiresFeature", "shortcut", "bulkEnabled"];
116
+ export declare const ACTION_DEF_KEYS: readonly ["type", "actionType", "name", "label", "description", "confirmText", "confirm", "condition", "disabled", "api", "endpoint", "method", "navigate", "to", "external", "newTab", "replace", "onClick", "reload", "close", "redirect", "toast", "successMessage", "errorMessage", "refreshAfter", "undoable", "params", "actionParams", "resultDialog", "target", "body", "openIn", "modal", "chain", "chainMode", "onFailure", "opensInNewTab", "newTabUrl", "locations", "visible", "requiredPermissions", "icon", "variant", "order", "component", "objectName", "ai", "bodyExtra", "bodyShape", "operation", "patch", "mode", "recordIdField", "recordIdParam", "requiresFeature", "onSuccess", "outcomeMessages", "shortcut", "bulkEnabled"];
115
117
  /**
116
118
  * Every property `@objectstack/spec`'s `ActionSchema` declares.
117
119
  *
@@ -126,7 +128,7 @@ export declare const ACTION_DEF_KEYS: readonly ["type", "actionType", "name", "l
126
128
  * test and wrong in shipped code — `@object-ui/types` made the same call for
127
129
  * `ActionComponent`. The pin test does the walk and fails the day this drifts.
128
130
  */
129
- export declare const SPEC_ACTION_KEYS: readonly ["_lock", "_lockDocsUrl", "_lockReason", "_lockSource", "_packageId", "_packageVersion", "_provenance", "ai", "aria", "body", "bodyExtra", "bodyShape", "bulkEnabled", "component", "confirmText", "description", "disabled", "errorMessage", "execute", "icon", "label", "locations", "method", "mode", "name", "newTabUrl", "objectName", "onSuccess", "openIn", "opensInNewTab", "order", "params", "recordIdField", "recordIdParam", "refreshAfter", "requiredPermissions", "requiresFeature", "resultDialog", "shortcut", "successMessage", "target", "type", "undoable", "variant", "visible"];
131
+ export declare const SPEC_ACTION_KEYS: readonly ["_lock", "_lockDocsUrl", "_lockReason", "_lockSource", "_packageId", "_packageVersion", "_provenance", "ai", "aria", "body", "bodyExtra", "bodyShape", "bulkEnabled", "component", "confirmText", "description", "disabled", "errorMessage", "execute", "execution", "icon", "label", "locations", "method", "mode", "name", "newTabUrl", "objectName", "onSuccess", "openIn", "opensInNewTab", "operation", "order", "outcomeMessages", "params", "patch", "recordIdField", "recordIdParam", "refreshAfter", "requiredPermissions", "requiresFeature", "resultDialog", "shortcut", "successMessage", "target", "type", "undoable", "variant", "visible"];
130
132
  /**
131
133
  * The `navigation` alias's own spelling of a target, read off the action itself
132
134
  * when no nested `navigate` object is present (`ActionRunner.executeNavigation`).
@@ -227,7 +229,7 @@ export declare function resetActionKeyWarnings(): void;
227
229
  * Dev-mode only: name the keys an action carries that no reader will ever look
228
230
  * at. Non-breaking by construction — it changes no types and rejects nothing.
229
231
  *
230
- * Silently binding no handler is the #2169 "Mark Done does nothing" shape. The
232
+ * Silently binding no handler is the objectstack-ai/objectstack#2169 "Mark Done does nothing" shape. The
231
233
  * compiler now catches it in action literals AUTHORED IN CODE — objectstack#4075
232
234
  * step 3 closed `ActionDef` — but not in the actions that arrive as DATA, which
233
235
  * reach the runner unparsed and which no compiler ever looked at. This is the
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * That asymmetry was the issue. An open key set on a DECLARED METADATA CONTRACT
20
20
  * is what lets a typo (`targt`, `exectue`) and a tombstoned key (`execute`) both
21
- * sail through to a runner that then silently does nothing — the #2169 "Mark
21
+ * sail through to a runner that then silently does nothing — the objectstack-ai/objectstack#2169 "Mark
22
22
  * Done does nothing" shape.
23
23
  *
24
24
  * This module was step 1 of the staged narrowing: make the key set VISIBLE and
@@ -74,10 +74,12 @@
74
74
  * rejection" into a compile error at no cost. Hand-copying would have quietly
75
75
  * re-legitimized two dead keys — which is why the types are derived.
76
76
  *
77
- * 17 keys `ActionDef` declares that the spec does not own — `actionType`, `api`,
77
+ * 16 keys `ActionDef` declares that the spec does not own — `actionType`, `api`,
78
78
  * `chain`, `chainMode`, `close`, `condition`, `confirm`, `endpoint`, `modal`,
79
- * `navigate`, `onClick`, `onFailure`, `onSuccess`, `redirect`, `reload`, `toast`,
80
- * `actionParams`. Step 2 marked `@deprecated`, with the spec spelling to use
79
+ * `navigate`, `onClick`, `onFailure`, `redirect`, `reload`, `toast`,
80
+ * `actionParams`. (`onSuccess` was the 17th until objectui#5934 retired the
81
+ * runner's chained-callback meaning; the key is now spec-owned and derived,
82
+ * like the 18 below.) Step 2 marked `@deprecated`, with the spec spelling to use
81
83
  * instead, ONLY the four the runner itself proves are aliases: `actionType` (→
82
84
  * `type`), `api` and `endpoint` (→ `target`; `executeAPI` resolves
83
85
  * `api || endpoint || target`), and `navigate` (→ flat `target`/`openIn`;
@@ -154,7 +156,6 @@ export const ACTION_DEF_KEYS = [
154
156
  'modal',
155
157
  'chain',
156
158
  'chainMode',
157
- 'onSuccess',
158
159
  'onFailure',
159
160
  'opensInNewTab',
160
161
  'newTabUrl',
@@ -172,13 +173,30 @@ export const ACTION_DEF_KEYS = [
172
173
  'component',
173
174
  'objectName',
174
175
  'ai',
175
- 'aria',
176
+ // `aria` left this list, with its `ActionDef` field, when `@objectstack/spec`
177
+ // 17.5.0 retired `action.aria` as a `retiredKey()` tombstone (objectui#10929):
178
+ // the derived type had become `undefined`, so the field mirrored nothing. It
179
+ // stays in `SPEC_ACTION_KEYS` below, because `ActionSchema` still declares the
180
+ // tombstone.
176
181
  'bodyExtra',
177
182
  'bodyShape',
183
+ // The declarative single-record field write (spec 17.3.0). Promoted from
184
+ // inventory-only to declared `ActionDef` fields when the runner learned to
185
+ // read them — the branch that dispatches `operation: 'update'` on the
186
+ // platform action route, ahead of `type`.
187
+ 'operation',
188
+ 'patch',
178
189
  'mode',
179
190
  'recordIdField',
180
191
  'recordIdParam',
181
192
  'requiresFeature',
193
+ // Moved from the runner-native cluster above by objectui#5934: the legacy
194
+ // chained-callback meaning is retired and the key's type now derives the
195
+ // spec's `{ navigate, openIn }` block.
196
+ 'onSuccess',
197
+ // Spec 17.6.0's success copy per handler outcome (objectui#11344), declared
198
+ // when the runner learned to read it: the success toast's first rung.
199
+ 'outcomeMessages',
182
200
  'shortcut',
183
201
  'bulkEnabled',
184
202
  ];
@@ -228,6 +246,14 @@ export const SPEC_ACTION_KEYS = [
228
246
  'disabled',
229
247
  'errorMessage',
230
248
  'execute',
249
+ // Added to `ActionSchema` in @objectstack/spec 17.5.0 (objectui#11073): the
250
+ // bulk dispatch contract the action's body is written for, in
251
+ // `bulkActionDefs`' own `'perRecord' | 'aggregate'` vocabulary. Listed for the
252
+ // reason `description` is — this array restates the spec's declared keys, so
253
+ // an action carrying it is not reported as having an unknown key. `ActionDef`
254
+ // does NOT declare it and the runner does not read it: the spec's own
255
+ // enforcement is `@objectstack/lint`'s authoring-time check.
256
+ 'execution',
231
257
  'icon',
232
258
  'label',
233
259
  'locations',
@@ -237,17 +263,34 @@ export const SPEC_ACTION_KEYS = [
237
263
  'newTabUrl',
238
264
  'objectName',
239
265
  // Declared by `ActionSchema` as of @objectstack/spec 17.1.0 (objectui#5328).
240
- // Listing it here is a DIAGNOSTIC statement only — `KNOWN_ACTION_KEYS` feeds
241
- // `warnOnUnknownActionKeys`, so without this row an author writing the key the
242
- // spec now accepts would be warned it is unknown. It says nothing about the
243
- // key being forwarded: the four declared action surfaces still drop it before
244
- // the runner, tracked as KNOWN_GAPS in check-action-forward-parity.mjs and
245
- // filed as objectui#5493.
266
+ // All four declared action surfaces forward it since objectui#5493/#6304, and
267
+ // `ActionDef` derives its type from the spec since objectui#5934 retired the
268
+ // runner's legacy chained-callback meaning for the same key.
246
269
  'onSuccess',
247
270
  'openIn',
248
271
  'opensInNewTab',
272
+ // Added to `ActionSchema` in @objectstack/spec 17.3.0 (objectui#7122), the
273
+ // declarative single-record field write that mirrors a list view's
274
+ // `bulkActionDefs`: `operation: 'update'` applies `patch` (merged UNDER the
275
+ // collected `params`) to the current record on the data plane AS THE CALLER.
276
+ //
277
+ // Both keys were inventory-ONLY when that bump landed, under the maintainer's
278
+ // 2026-09-05 ruling on it ("record both as justified omissions now; forward
279
+ // only the key a runtime actually reads, once its semantics are read from
280
+ // upstream — no speculative forwarding"). That condition is now met: the
281
+ // runner reads both (`executeUpdateOperation`, dispatched ahead of `type`),
282
+ // `ActionDef` declares both, and every action surface forwards them — which
283
+ // is what `check:action-forward-parity` now requires rather than excuses.
284
+ 'operation',
249
285
  'order',
286
+ // Added to `ActionSchema` in @objectstack/spec 17.6.0 (objectstack#21095,
287
+ // objectui#11344): success copy per handler outcome, keyed by the snake_case
288
+ // `outcome` the handler returns. `ActionDef` declares it (derived) and the
289
+ // runner reads it as the success toast's first rung, ahead of
290
+ // `successMessage`.
291
+ 'outcomeMessages',
250
292
  'params',
293
+ 'patch',
251
294
  'recordIdField',
252
295
  'recordIdParam',
253
296
  'refreshAfter',
@@ -340,7 +383,7 @@ export const HOST_DISPATCH_ACTION_KEYS = ['overrideNotice'];
340
383
  * a tombstone the spec keeps only in order to reject it by name.
341
384
  */
342
385
  export const RETIRED_ACTION_KEYS = {
343
- execute: '`execute` was removed in @objectstack/spec 17 (#3855) — rename the key to `target`. ' +
386
+ execute: '`execute` was removed in @objectstack/spec 17 (objectstack-ai/objectstack#3855) — rename the key to `target`. ' +
344
387
  'The value (a script name or expression) is unchanged. ' +
345
388
  'Run `os migrate meta --from 16` to rewrite it automatically.',
346
389
  };
@@ -393,7 +436,7 @@ const isDev = () => globalThis.process?.env?.NODE_ENV !==
393
436
  * Dev-mode only: name the keys an action carries that no reader will ever look
394
437
  * at. Non-breaking by construction — it changes no types and rejects nothing.
395
438
  *
396
- * Silently binding no handler is the #2169 "Mark Done does nothing" shape. The
439
+ * Silently binding no handler is the objectstack-ai/objectstack#2169 "Mark Done does nothing" shape. The
397
440
  * compiler now catches it in action literals AUTHORED IN CODE — objectstack#4075
398
441
  * step 3 closed `ActionDef` — but not in the actions that arrive as DATA, which
399
442
  * reach the runner unparsed and which no compiler ever looked at. This is the
@@ -59,7 +59,7 @@ import { actionErrorDetail } from './actionErrorDetail.js';
59
59
  export function readActionPayload(envelope) {
60
60
  // objectstack#3962 servers single-wrap: `body.data` IS the handler value,
61
61
  // so most of the time this is the identity function. Only the exact LEGACY
62
- // action envelope (pre-#3962: a boolean `success` and no keys beyond the
62
+ // action envelope (pre-objectstack-ai/objectstack#3962: a boolean `success` and no keys beyond the
63
63
  // envelope's own) is unwrapped one more level — a handler value that
64
64
  // merely contains a `success` key passes through untouched.
65
65
  if (isLegacyActionEnvelope(envelope)) {
@@ -82,7 +82,7 @@ export function isLegacyActionEnvelope(v) {
82
82
  */
83
83
  export function interpretActionResponse(res, json, label) {
84
84
  const envelope = json?.data;
85
- // Pre-#3962 servers reported a rejection as HTTP 200 with the inner
85
+ // Pre-objectstack-ai/objectstack#3962 servers reported a rejection as HTTP 200 with the inner
86
86
  // `{success:false, error}`; current servers answer a real status, which
87
87
  // `res.ok` catches first. Detected narrowly so a handler value that merely
88
88
  // contains `success: false` is not misread as a failure.
@@ -15,3 +15,4 @@ export * from './actionErrorDetail.js';
15
15
  export * from './actionResponse.js';
16
16
  export * from './serverActionHandler.js';
17
17
  export * from './recordIdParam.js';
18
+ export * from './recordDelete.js';
@@ -15,3 +15,4 @@ export * from './actionErrorDetail.js';
15
15
  export * from './actionResponse.js';
16
16
  export * from './serverActionHandler.js';
17
17
  export * from './recordIdParam.js';
18
+ export * from './recordDelete.js';
@@ -0,0 +1,118 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ /**
9
+ * The ONE record-delete core — the confirm question and the delete itself —
10
+ * that every host's list Delete binds to (objectui#10383).
11
+ *
12
+ * ## Why this lives here, and why there is only one
13
+ *
14
+ * The console's object list deleted through `useObjectActions` (`app-shell`):
15
+ * the confirm question, the single delete with its ADR-0094 detection, the bulk
16
+ * delete and their toasts were written inline in that hook. The registered
17
+ * `object-view` renderer (`plugin-view`) had no delete at all — its grid
18
+ * handlers only refreshed — and the first repair COPIED those steps into a
19
+ * second flow. The copy already diverged on consent: it asked every row the
20
+ * plain "delete this record?" question, while the console asks a package-owned
21
+ * permission set the honest reset question. One operation with two
22
+ * implementations drifts on exactly the part that matters, so the rule is: the
23
+ * side with the governance keeps it, the other side re-binds to it, and there
24
+ * is one copy. This module is that copy, and both hosts call it.
25
+ *
26
+ * `@object-ui/core` is the lowest package both hosts already depend on. The
27
+ * host-specific pieces are INJECTED — the translator, the toast sink, the data
28
+ * source and the refresh — so this package gains no i18n or toast dependency.
29
+ * The host keeps its own confirm UI (`app-shell` runs the question through the
30
+ * action runner's confirm handler; `plugin-view` renders it in its own
31
+ * AlertDialog); the QUESTION comes from {@link recordDelete}.confirmText.
32
+ *
33
+ * ## ADR-0094 — a package-owned permission set is RESET, not deleted
34
+ *
35
+ * "Deleting" a `sys_permission_set` row whose `managed_by` is `'package'` does
36
+ * not remove it: the backend drops the environment overlay and resets the set
37
+ * to its shipped baseline. So the confirm asks the reset question and the
38
+ * success toast says "reset" — a promise of an irreversible delete the user can
39
+ * then see did not happen would be the dishonest copy. The row passed in is
40
+ * read first; when the caller has only an id (an SDUI header delete), the row
41
+ * is looked up with `findOne`, best-effort — a failed lookup falls back to the
42
+ * generic copy rather than blocking the delete.
43
+ *
44
+ * ## The result shape is the action runner's, on purpose
45
+ *
46
+ * `run` returns what a registered `delete` handler returns, because the console
47
+ * registers it as exactly that. The toasts here are the AUTHORITATIVE feedback,
48
+ * so every path returns WITHOUT an `error` key (and success returns `silent`) —
49
+ * otherwise the runner's post-execution hook, which has its own toast handler,
50
+ * would toast the same outcome a second time. The single exception is a
51
+ * request with no record id at all, which toasts nothing here and returns the
52
+ * `error` for the runner to report.
53
+ *
54
+ * Record ids are forwarded exactly as the caller supplied them; nothing here
55
+ * converts them.
56
+ */
57
+ import type { ActionResult } from './ActionRunner.js';
58
+ type Row = Record<string, unknown>;
59
+ /** Translate a key — the host's `t`, injected so core takes no i18n dependency. */
60
+ type Translate = (key: string, options?: Record<string, unknown>) => string;
61
+ /** Everything `run` needs from its host. `confirmText` needs only the first two. */
62
+ interface RecordDeleteDeps {
63
+ objectName: string;
64
+ t: Translate;
65
+ /** The object's display label, used by the toasts. */
66
+ label: string;
67
+ dataSource: {
68
+ delete(resource: string, id: string): Promise<unknown>;
69
+ findOne(resource: string, id: string): Promise<unknown>;
70
+ };
71
+ /** The host's toast sink (sonner's `toast` in both hosts today). */
72
+ toast: {
73
+ success(message: string): unknown;
74
+ error(message: string, data?: {
75
+ description?: string;
76
+ }): unknown;
77
+ };
78
+ /** Re-read the list after a delete. */
79
+ onRefresh?: () => void;
80
+ }
81
+ /**
82
+ * The request, in the action runner's `delete` shape — the console registers
83
+ * `run` as its `delete` handler, so the runner's action arrives here verbatim:
84
+ * `params.records` (several), `params.recordId` + optional `params.record`
85
+ * (one), and the legacy top-level `recordId`.
86
+ */
87
+ interface RecordDeleteRequest {
88
+ params?: {
89
+ records?: unknown;
90
+ recordId?: unknown;
91
+ record?: Row | null;
92
+ };
93
+ recordId?: unknown;
94
+ }
95
+ /**
96
+ * The record-delete core: `confirmText` is the question to ask before deleting
97
+ * one record, `run` performs the delete (one record, or several settled
98
+ * together) and reports it. See the module docblock for objectui#10383 and
99
+ * ADR-0094.
100
+ */
101
+ export declare const recordDelete: Readonly<{
102
+ /**
103
+ * The question to ask before deleting ONE record: the reset question for a
104
+ * package-owned permission set (ADR-0094), the plain delete question
105
+ * otherwise. Reads only the row it is handed — no lookup.
106
+ */
107
+ confirmText(deps: Pick<RecordDeleteDeps, "objectName" | "t">, record?: Row | null): string;
108
+ /**
109
+ * Delete, refresh and report. Several records (`params.records`, more than
110
+ * one with an `id`) are deleted one call each and settled together, then the
111
+ * list refreshes and ONE summary toast reports the batch. Otherwise one
112
+ * record is deleted; on success the list refreshes and a success toast shows
113
+ * (the reset copy for a package-owned permission set), on failure an error
114
+ * toast carries the error message and the list is left as it is.
115
+ */
116
+ run(deps: RecordDeleteDeps, request: RecordDeleteRequest): Promise<ActionResult>;
117
+ }>;
118
+ export {};
@@ -0,0 +1,112 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ const PERMISSION_SET_OBJECT = 'sys_permission_set';
9
+ /** ADR-0094: is this permission-set row owned by an installed package? */
10
+ function isPackageOwned(row) {
11
+ return row?.managed_by === 'package';
12
+ }
13
+ /**
14
+ * The record-delete core: `confirmText` is the question to ask before deleting
15
+ * one record, `run` performs the delete (one record, or several settled
16
+ * together) and reports it. See the module docblock for objectui#10383 and
17
+ * ADR-0094.
18
+ */
19
+ export const recordDelete = Object.freeze({
20
+ /**
21
+ * The question to ask before deleting ONE record: the reset question for a
22
+ * package-owned permission set (ADR-0094), the plain delete question
23
+ * otherwise. Reads only the row it is handed — no lookup.
24
+ */
25
+ confirmText(deps, record) {
26
+ const { objectName, t } = deps;
27
+ return objectName === PERMISSION_SET_OBJECT && isPackageOwned(record)
28
+ ? t('objectActions.resetPackageSetConfirm', {
29
+ defaultValue: 'This permission set ships with an installed package and cannot be removed. ' +
30
+ 'Deleting resets it to the shipped baseline and discards your environment customization. Continue?',
31
+ })
32
+ : t('objectActions.deleteConfirm');
33
+ },
34
+ /**
35
+ * Delete, refresh and report. Several records (`params.records`, more than
36
+ * one with an `id`) are deleted one call each and settled together, then the
37
+ * list refreshes and ONE summary toast reports the batch. Otherwise one
38
+ * record is deleted; on success the list refreshes and a success toast shows
39
+ * (the reset copy for a package-owned permission set), on failure an error
40
+ * toast carries the error message and the list is left as it is.
41
+ */
42
+ async run(deps, request) {
43
+ const { objectName, t, label, dataSource, toast, onRefresh } = deps;
44
+ const params = request.params;
45
+ const records = Array.isArray(params?.records)
46
+ ? params.records.filter((r) => r?.id != null)
47
+ : null;
48
+ // Bulk path — delete every record in parallel and report a summary.
49
+ if (records && records.length > 1) {
50
+ const results = await Promise.allSettled(records.map((r) => dataSource.delete(objectName, r.id)));
51
+ const failed = results.filter((r) => r.status === 'rejected').length;
52
+ const succeeded = results.length - failed;
53
+ onRefresh?.();
54
+ if (failed === 0) {
55
+ toast.success(t('objectActions.bulkDeleteSuccess', {
56
+ count: succeeded,
57
+ label,
58
+ defaultValue: `Deleted ${succeeded} ${label} records`,
59
+ }));
60
+ // `silent`: the summary above is the feedback — without it the runner's
61
+ // post-execution hook adds a second, generic success toast.
62
+ return { success: true, reload: true, silent: true };
63
+ }
64
+ toast.error(t('objectActions.bulkDeletePartial', {
65
+ succeeded,
66
+ failed,
67
+ defaultValue: `${succeeded} deleted, ${failed} failed`,
68
+ }));
69
+ // The summary above is authoritative; returning WITHOUT `error` keeps the
70
+ // runner from toasting the failure a second time.
71
+ return { success: false };
72
+ }
73
+ const recordId = params?.recordId ?? params?.record?.id ?? records?.[0]?.id ?? request.recordId;
74
+ if (!recordId)
75
+ return { success: false, error: t('objectActions.noRecordId') };
76
+ // [ADR-0094] Detect the reset BEFORE the delete (the row still exists), so
77
+ // the success toast tells the truth. A caller that passed the row spares
78
+ // the lookup.
79
+ let packagedSetReset = false;
80
+ if (objectName === PERMISSION_SET_OBJECT) {
81
+ try {
82
+ const row = params?.record?.managed_by !== undefined
83
+ ? params.record
84
+ : await dataSource.findOne(objectName, recordId);
85
+ packagedSetReset = isPackageOwned(row);
86
+ }
87
+ catch { /* best-effort — fall back to the generic delete copy */ }
88
+ }
89
+ try {
90
+ await dataSource.delete(objectName, recordId);
91
+ onRefresh?.();
92
+ toast.success(packagedSetReset
93
+ // No `label` argument, unlike `deleteSuccess` below: this sentence
94
+ // names the permission set by kind rather than by label, so none of
95
+ // the ten packs has a `{{label}}` hole and i18next would drop the
96
+ // argument in silence (objectui#3845).
97
+ ? t('objectActions.resetPackageSetSuccess', {
98
+ defaultValue: 'Permission set reset to its shipped baseline',
99
+ })
100
+ : t('objectActions.deleteSuccess', { label }));
101
+ return { success: true, reload: true, silent: true };
102
+ }
103
+ catch (err) {
104
+ toast.error(t('objectActions.deleteFailed', { label }), {
105
+ description: err.message,
106
+ });
107
+ // The toast above (label + error message) is the feedback; returning
108
+ // WITHOUT `error` keeps the runner from toasting the raw message again.
109
+ return { success: false };
110
+ }
111
+ },
112
+ });
@@ -63,6 +63,11 @@ export declare class ValueDataSource<T = any> implements DataSource<T> {
63
63
  onMutation(callback: (event: DataSourceMutationEvent<T>) => void): () => void;
64
64
  /** Get the current number of items */
65
65
  get count(): number;
66
- /** Get a snapshot of all items (cloned) */
66
+ /**
67
+ * Get a snapshot of all items (cloned).
68
+ *
69
+ * Same clone as the constructor and for the same reason — see the note
70
+ * there: `structuredClone`, never a JSON round-trip (objectui#9175).
71
+ */
67
72
  getAll(): T[];
68
73
  }