@mintplayer/ng-spark 22.22.0 → 22.24.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 (68) hide show
  1. package/README.md +181 -0
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +100 -47
  3. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs.map +1 -1
  4. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +308 -329
  5. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-column-filter.mjs +175 -135
  7. package/fesm2022/mintplayer-ng-spark-column-filter.mjs.map +1 -1
  8. package/fesm2022/mintplayer-ng-spark-grid.mjs +2259 -1117
  9. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  10. package/fesm2022/mintplayer-ng-spark-history.mjs +611 -0
  11. package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -0
  12. package/fesm2022/mintplayer-ng-spark-icon.mjs +64 -27
  13. package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -1
  14. package/fesm2022/mintplayer-ng-spark-models.mjs +365 -753
  15. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  16. package/fesm2022/mintplayer-ng-spark-moderation.mjs +1212 -0
  17. package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -0
  18. package/fesm2022/mintplayer-ng-spark-panels.mjs +52 -0
  19. package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -0
  20. package/fesm2022/mintplayer-ng-spark-pipes.mjs +1122 -765
  21. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  22. package/fesm2022/mintplayer-ng-spark-po-create.mjs +273 -169
  23. package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
  24. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +786 -410
  25. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  26. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +292 -284
  27. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  28. package/fesm2022/mintplayer-ng-spark-po-form.mjs +2187 -1225
  29. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  30. package/fesm2022/mintplayer-ng-spark-query-list.mjs +516 -303
  31. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  32. package/fesm2022/mintplayer-ng-spark-renderers.mjs +17 -55
  33. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  34. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +216 -177
  35. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
  36. package/fesm2022/mintplayer-ng-spark-routes.mjs +24 -28
  37. package/fesm2022/mintplayer-ng-spark-routes.mjs.map +1 -1
  38. package/fesm2022/mintplayer-ng-spark-services.mjs +625 -640
  39. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  40. package/fesm2022/mintplayer-ng-spark-shell.mjs +1013 -397
  41. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  42. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +294 -0
  43. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs.map +1 -0
  44. package/fesm2022/mintplayer-ng-spark.mjs +13 -50
  45. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  46. package/package.json +17 -1
  47. package/types/mintplayer-ng-spark-attribute-description.d.ts +25 -18
  48. package/types/mintplayer-ng-spark-client-operations.d.ts +133 -160
  49. package/types/mintplayer-ng-spark-column-filter.d.ts +73 -55
  50. package/types/mintplayer-ng-spark-grid.d.ts +885 -448
  51. package/types/mintplayer-ng-spark-history.d.ts +223 -0
  52. package/types/mintplayer-ng-spark-icon.d.ts +15 -13
  53. package/types/mintplayer-ng-spark-models.d.ts +624 -573
  54. package/types/mintplayer-ng-spark-moderation.d.ts +402 -0
  55. package/types/mintplayer-ng-spark-panels.d.ts +108 -0
  56. package/types/mintplayer-ng-spark-pipes.d.ts +120 -149
  57. package/types/mintplayer-ng-spark-po-create.d.ts +46 -37
  58. package/types/mintplayer-ng-spark-po-detail.d.ts +177 -117
  59. package/types/mintplayer-ng-spark-po-edit.d.ts +64 -65
  60. package/types/mintplayer-ng-spark-po-form.d.ts +507 -371
  61. package/types/mintplayer-ng-spark-query-list.d.ts +118 -82
  62. package/types/mintplayer-ng-spark-renderers.d.ts +79 -85
  63. package/types/mintplayer-ng-spark-retry-action-modal.d.ts +31 -34
  64. package/types/mintplayer-ng-spark-routes.d.ts +7 -11
  65. package/types/mintplayer-ng-spark-services.d.ts +377 -261
  66. package/types/mintplayer-ng-spark-shell.d.ts +209 -128
  67. package/types/mintplayer-ng-spark-soft-delete.d.ts +90 -0
  68. package/types/mintplayer-ng-spark.d.ts +7 -12
@@ -1,40 +1,35 @@
1
- import { WritableSignal } from '@angular/core';
2
-
3
- type TranslatedString = Record<string, string>;
1
+ import { WritableSignal } from "@angular/core";
2
+ export type TranslatedString = Record<string, string>;
4
3
  /** Global reactive language state — shared across library boundaries via globalThis */
5
- declare const currentLanguage: WritableSignal<string>;
6
- declare function resolveTranslation(ts: TranslatedString | undefined, lang?: string): string;
7
-
8
- interface ValidationRule {
9
- type: string;
10
- value?: any;
11
- min?: number;
12
- max?: number;
13
- message?: TranslatedString;
14
- }
15
-
16
- interface ValidationError {
17
- attributeName: string;
18
- errorMessage: TranslatedString;
19
- ruleType: string;
20
- }
21
- interface ValidationErrorResponse {
22
- errors: ValidationError[];
23
- }
24
-
4
+ export declare const currentLanguage: WritableSignal<string>;
5
+ export declare function resolveTranslation(ts: TranslatedString | undefined, lang?: string): string;
6
+ export interface ValidationRule {
7
+ type: string;
8
+ value?: any;
9
+ min?: number;
10
+ max?: number;
11
+ message?: TranslatedString;
12
+ }
13
+ export interface ValidationError {
14
+ attributeName: string;
15
+ errorMessage: TranslatedString;
16
+ ruleType: string;
17
+ }
18
+ export interface ValidationErrorResponse {
19
+ errors: ValidationError[];
20
+ }
25
21
  /**
26
22
  * Flags enum controlling on which pages an attribute should be displayed.
27
23
  * Values can be combined: ShowedOn.Query | ShowedOn.PersistentObject
28
24
  */
29
- declare enum ShowedOn {
30
- Query = 1,
31
- PersistentObject = 2
25
+ export declare enum ShowedOn {
26
+ Query = 1,
27
+ PersistentObject = 2
32
28
  }
33
29
  /**
34
30
  * Helper function to check if a ShowedOn value includes a specific flag.
35
31
  */
36
- declare function hasShowedOnFlag(value: ShowedOn | string | undefined, flag: ShowedOn): boolean;
37
-
32
+ export declare function hasShowedOnFlag(value: ShowedOn | string | undefined, flag: ShowedOn): boolean;
38
33
  /**
39
34
  * Per-row action affordances (#236 G5). Present only when the entity type has a row-level rule;
40
35
  * absent otherwise, so clients fall back to the type-level permissions from
@@ -44,262 +39,345 @@ declare function hasShowedOnFlag(value: ShowedOn | string | undefined, flag: Sho
44
39
  * row rule (#243) — a present block never claims more than the permissions endpoint would, so
45
40
  * letting it override the type-level answer is safe.
46
41
  */
47
- interface PersistentObjectPermissions {
48
- edit: boolean;
49
- delete: boolean;
50
- }
51
- interface PersistentObject {
52
- id: string;
53
- name: string;
54
- objectTypeId: string;
55
- breadcrumb?: string;
56
- /**
57
- * Optimistic-concurrency token -- RavenDB's change vector for the document behind this object.
58
- * The server stamps it on every `/po/load` and expects it back on update.
59
- *
60
- * ⚠️ The check is opt-in BY PRESENCE of this field. Dropping it does not fail loudly; it makes
61
- * the save unconditional, so two people editing the same record both succeed and the second
62
- * silently overwrites the first. That is what used to happen here: `SparkClient` round-tripped
63
- * the token and got a 409, the browser did not and got last-write-wins, on the same endpoint.
64
- */
65
- etag?: string;
66
- attributes: PersistentObjectAttribute[];
67
- /** Per-row edit/delete affordances; undefined = fall back to type-level permissions. */
68
- can?: PersistentObjectPermissions;
69
- /**
70
- * Custom actions withheld for THIS object, by name. Absent or empty means every action the
71
- * caller has the right to is offered.
72
- *
73
- * The action catalogue at `/spark/actions/{objectTypeId}` is per TYPE -- it is never told which
74
- * row is open -- so an action that applies to only some rows cannot be filtered there. The
75
- * entity actions hook decides while it has the entity in hand, and the answer arrives here.
76
- *
77
- * An affordance, not a permission: the action endpoint is still reachable and its handler still
78
- * refuses on its own terms. What this prevents is offering a destructive action where it cannot
79
- * apply.
80
- */
81
- disabledActions?: string[];
82
- }
83
-
84
- interface PersistentObjectAttribute {
85
- id: string;
86
- name: string;
87
- label?: TranslatedString;
88
- value?: any;
89
- dataType: string;
90
- isArray?: boolean;
91
- isRequired: boolean;
92
- isVisible: boolean;
93
- isReadOnly: boolean;
94
- order: number;
95
- query?: string;
96
- breadcrumb?: string;
97
- /**
98
- * For `dataType === 'Reference'` with `isArray === true`: resolved display label per
99
- * referenced id (id → breadcrumb), so the UI can render a chip per selected reference.
100
- * The single-reference counterpart is `breadcrumb`.
101
- */
102
- breadcrumbs?: Record<string, string | null>;
103
- /**
104
- * Controls on which pages the attribute should be displayed.
105
- * Query = shown in list views, PersistentObject = shown in detail/edit views.
106
- * Can be a numeric flag value or a string like "Query, PersistentObject".
107
- */
108
- showedOn?: ShowedOn | string;
109
- isValueChanged?: boolean;
110
- rules: ValidationRule[];
111
- group?: string;
112
- /** Renderer component name for custom display in detail/list views */
113
- renderer?: string;
114
- /** Options passed to the renderer component */
115
- rendererOptions?: Record<string, any>;
116
- /**
117
- * When `dataType === 'AsDetail'` and `isArray === false`: the single nested
118
- * PersistentObject carrying the detail entity's values. `null` when the field is unset.
119
- * Mirrors the server's PersistentObjectAttributeAsDetail.Object.
120
- */
121
- object?: PersistentObject | null;
122
- /**
123
- * When `dataType === 'AsDetail'` and `isArray === true`: the nested PO collection, one
124
- * per array element. Mirrors PersistentObjectAttributeAsDetail.Objects.
125
- */
126
- objects?: PersistentObject[] | null;
127
- /**
128
- * CLR type name of the detail entity for AsDetail attributes (e.g. `HR.Entities.Address`).
129
- * Emitted by the server so the frontend can look up the matching EntityType metadata
130
- * without re-resolving through `asDetailType` on the schema definition.
131
- */
132
- asDetailType?: string;
133
- }
134
-
42
+ export interface PersistentObjectPermissions {
43
+ edit: boolean;
44
+ delete: boolean;
45
+ }
46
+ export interface PersistentObject {
47
+ id: string;
48
+ name: string;
49
+ objectTypeId: string;
50
+ breadcrumb?: string;
51
+ /**
52
+ * Optimistic-concurrency token -- RavenDB's change vector for the document behind this object.
53
+ * The server stamps it on every `/po/load` and expects it back on update.
54
+ *
55
+ * ⚠️ The check is opt-in BY PRESENCE of this field. Dropping it does not fail loudly; it makes
56
+ * the save unconditional, so two people editing the same record both succeed and the second
57
+ * silently overwrites the first. That is what used to happen here: `SparkClient` round-tripped
58
+ * the token and got a 409, the browser did not and got last-write-wins, on the same endpoint.
59
+ */
60
+ etag?: string;
61
+ attributes: PersistentObjectAttribute[];
62
+ /** Per-row edit/delete affordances; undefined = fall back to type-level permissions. */
63
+ can?: PersistentObjectPermissions;
64
+ /**
65
+ * Custom actions withheld for THIS object, by name. Absent or empty means every action the
66
+ * caller has the right to is offered.
67
+ *
68
+ * The action catalogue at `/spark/actions/{objectTypeId}` is per TYPE -- it is never told which
69
+ * row is open -- so an action that applies to only some rows cannot be filtered there. The
70
+ * entity's `OnDisableActionsAsync` hook decides on the stored entity, and the answer arrives here
71
+ * -- built-in actions (`Edit`, `Save`, `Delete`) included (#460, D13). The server asks the same
72
+ * hook again at submit and refuses a disabled action with 403.
73
+ */
74
+ disabledActions?: string[];
75
+ }
76
+ export interface PersistentObjectAttribute {
77
+ id: string;
78
+ name: string;
79
+ label?: TranslatedString;
80
+ value?: any;
81
+ dataType: string;
82
+ isArray?: boolean;
83
+ isRequired: boolean;
84
+ isVisible: boolean;
85
+ isReadOnly: boolean;
86
+ order: number;
87
+ query?: string;
88
+ breadcrumb?: string;
89
+ /**
90
+ * For `dataType === 'Reference'` with `isArray === true`: resolved display label per
91
+ * referenced id (id → breadcrumb), so the UI can render a chip per selected reference.
92
+ * The single-reference counterpart is `breadcrumb`.
93
+ */
94
+ breadcrumbs?: Record<string, string | null>;
95
+ /**
96
+ * Controls on which pages the attribute should be displayed.
97
+ * Query = shown in list views, PersistentObject = shown in detail/edit views.
98
+ * Can be a numeric flag value or a string like "Query, PersistentObject".
99
+ */
100
+ showedOn?: ShowedOn | string;
101
+ isValueChanged?: boolean;
102
+ rules: ValidationRule[];
103
+ group?: string;
104
+ /** Renderer component name for custom display in detail/list views */
105
+ renderer?: string;
106
+ /** Options passed to the renderer component */
107
+ rendererOptions?: Record<string, any>;
108
+ /**
109
+ * When `dataType === 'AsDetail'` and `isArray === false`: the single nested
110
+ * PersistentObject carrying the detail entity's values. `null` when the field is unset.
111
+ * Mirrors the server's PersistentObjectAttributeAsDetail.Object.
112
+ */
113
+ object?: PersistentObject | null;
114
+ /**
115
+ * When `dataType === 'AsDetail'` and `isArray === true`: the nested PO collection, one
116
+ * per array element. Mirrors PersistentObjectAttributeAsDetail.Objects.
117
+ */
118
+ objects?: PersistentObject[] | null;
119
+ /**
120
+ * CLR type name of the detail entity for AsDetail attributes (e.g. `HR.Entities.Address`).
121
+ * Emitted by the server so the frontend can look up the matching EntityType metadata
122
+ * without re-resolving through `asDetailType` on the schema definition.
123
+ */
124
+ asDetailType?: string;
125
+ }
126
+ export interface CustomActionDefinition {
127
+ name: string;
128
+ displayName: TranslatedString;
129
+ icon?: string;
130
+ description?: string;
131
+ showedOn: string;
132
+ selectionRule?: string;
133
+ refreshOnCompleted: boolean;
134
+ confirmationMessageKey?: string;
135
+ /**
136
+ * How prominently, and how warily, to present the action: 'primary', 'secondary', 'danger',
137
+ * 'warning'. Undefined renders the neutral default.
138
+ *
139
+ * Presentation only — the server decides who may see and run an action. A 'danger' variant just
140
+ * asks the button to look like what it does; pair it with `confirmationMessageKey` for anything
141
+ * irreversible, because the colour warns and the prompt is what actually prevents the accident.
142
+ */
143
+ variant?: string;
144
+ offset: number;
145
+ /**
146
+ * True for the framework's built-in `New` and `Delete` (#460, D18): catalogue entries with a
147
+ * `showedOn` and a `selectionRule` like any custom action (defaults: New has no rule, Delete is
148
+ * `>0`; an app overrides them in `customActions.json`), but run through `/po/new` and
149
+ * `/po/delete-many`, never `/actions/execute`. Absent for a custom action.
150
+ */
151
+ isDefault?: boolean;
152
+ }
153
+ /** How many rows a grid lets the user select — what `<bs-datatable>` renders. */
154
+ export type SparkSelectionMode = 'none' | 'single' | 'multiple';
155
+ /**
156
+ * What a query definition (or a sub-query entry) declares (#460, D17): a fixed mode, or `'auto'` —
157
+ * the default, and what an absent field means — to derive it from the actions offered.
158
+ */
159
+ export type SparkSelectionModeSetting = 'auto' | SparkSelectionMode;
160
+ /**
161
+ * The selection mode a grid renders.
162
+ *
163
+ * A declared `'none'`, `'single'` or `'multiple'` wins outright. `'auto'` (or nothing) derives it,
164
+ * so a grid gains a checkbox column exactly when an action needs one and is otherwise
165
+ * pixel-identical to a grid with no selection at all — as Vidyano's query grid does. The offered
166
+ * actions include the default Delete (`>0`) when the caller may delete, so such a grid is
167
+ * multi-select.
168
+ *
169
+ * Derived: `'single'` when every gated action is satisfied by one row and refused by two; anything
170
+ * else that cares about the count gets `'multiple'`.
171
+ */
172
+ export declare function selectionModeFor(actions: CustomActionDefinition[], declared?: SparkSelectionModeSetting | null): SparkSelectionMode;
173
+ /**
174
+ * One sub-query of a type, with the overrides the parent's type states for it (#460, D17).
175
+ */
176
+ export interface SparkSubQuery {
177
+ /** The query's alias or id. */
178
+ query: string;
179
+ /** Overrides the query's own `selectionMode` on this parent's detail page. */
180
+ selectionMode?: SparkSelectionModeSetting;
181
+ /** The row attribute pointing at the parent, when the row type has several (server-side). */
182
+ parentReference?: string;
183
+ }
184
+ /**
185
+ * An entry of `EntityType.queries` as it arrives on the wire: a bare alias — the shape every model
186
+ * had before #460 M15, and still what the server sends for an entry without overrides — or the
187
+ * object form.
188
+ */
189
+ export type SparkSubQueryEntry = string | SparkSubQuery;
190
+ /** One entry in its object form, whichever shape it arrived in. */
191
+ export declare function normalizeSubQuery(entry: SparkSubQueryEntry): SparkSubQuery;
192
+ /** A type's sub-queries in object form; empty when it declares none. */
193
+ export declare function subQueriesOf(type: {
194
+ queries?: SparkSubQueryEntry[];
195
+ } | null | undefined): SparkSubQuery[];
135
196
  /**
136
197
  * Controls how a Reference attribute is picked in the PO-edit form.
137
198
  * Serialized as a string by the server (mirrors the .NET EReferenceDisplayType).
138
199
  */
139
- declare enum EReferenceDisplayType {
140
- /** Renders as a `<bs-select>` listing every referenced item. */
141
- Dropdown = "Dropdown",
142
- /** Renders a readonly textbox + "…" button that opens a searchable modal grid picker. */
143
- Modal = "Modal"
144
- }
145
- interface EntityAttributeDefinition {
146
- id: string;
147
- name: string;
148
- label?: TranslatedString;
149
- /** Help text rendered as an [i] tooltip beside the label. Absent when the model declares none. */
150
- description?: TranslatedString;
151
- dataType: string;
152
- isRequired: boolean;
153
- isVisible: boolean;
154
- isReadOnly: boolean;
155
- order: number;
156
- query?: string;
157
- /** For reference attributes, specifies the target entity type's CLR type name */
158
- referenceType?: string;
159
- /** For AsDetail attributes, specifies the nested entity type's CLR type name */
160
- asDetailType?: string;
161
- /** When true, the attribute represents an array/collection of AsDetail objects */
162
- isArray?: boolean;
163
- /** For array AsDetail attributes: "modal" (default) or "inline" */
164
- editMode?: 'inline' | 'modal';
165
- /**
166
- * For Reference attributes: 'Modal' renders the "…" + modal query-grid picker;
167
- * 'Dropdown'/absent (default) renders a `<bs-select>`. Hand-set in the model JSON.
168
- */
169
- referenceDisplayType?: EReferenceDisplayType;
170
- /** For array AsDetail attributes: when true, rows can be drag-reordered (order = array position) */
171
- isSortable?: boolean;
172
- /**
173
- * When true, changing this attribute's value posts the in-progress object to
174
- * `/spark/po/{objectTypeId}/refresh` and applies the reshaped result as an overlay.
175
- * Schema-only by design — it never travels on a PersistentObjectAttribute, so a client
176
- * cannot claim a trigger the model did not declare.
177
- */
178
- triggersRefresh?: boolean;
179
- /** For LookupReference attributes, specifies the lookup reference type name */
180
- lookupReferenceType?: string;
181
- /**
182
- * Controls on which pages the attribute should be displayed.
183
- * Query = shown in list views, PersistentObject = shown in detail/edit views.
184
- * Can be a numeric flag value or a string like "Query, PersistentObject".
185
- */
186
- showedOn?: ShowedOn | string;
187
- rules: ValidationRule[];
188
- /** References an AttributeGroup.id to assign this attribute to a group */
189
- group?: string;
190
- /** Number of grid columns this attribute spans within a tab's column layout */
191
- columnSpan?: number;
192
- /** Renderer component name for custom display in detail/list views */
193
- renderer?: string;
194
- /** Options passed to the renderer component */
195
- rendererOptions?: Record<string, any>;
196
- }
197
- interface AttributeTab {
198
- id: string;
199
- name: string;
200
- label?: TranslatedString;
201
- order: number;
202
- /** Number of columns for the grid layout within this tab */
203
- columnCount?: number;
204
- }
205
- interface AttributeGroup {
206
- id: string;
207
- name: string;
208
- label?: TranslatedString;
209
- /** References an AttributeTab.id to assign this group to a tab */
210
- tab?: string;
211
- order: number;
212
- }
213
- interface EntityType {
214
- id: string;
215
- name: string;
216
- description?: TranslatedString;
217
- /**
218
- * The backing CLR type, or absent for a JSON-only virtual type (#325).
219
- *
220
- * Optional on purpose: the server sends null for a virtual type, and this was declared
221
- * non-nullable, so any unguarded `.endsWith(...)` on it threw a TypeError naming an innocent
222
- * query. Types are data from the wire, not a promise about it.
223
- */
224
- clrType?: string;
225
- /**
226
- * Whether this caller may OPEN one of these, as opposed to list them.
227
- *
228
- * The catalogue is gated on Query, so its presence here means "you may list this type" and says
229
- * nothing about opening one. A reference must not render as a link when this is false: it used
230
- * to, and the click refused on arrival.
231
- */
232
- canRead?: boolean;
233
- alias?: string;
234
- /**
235
- * Breadcrumb template: literal text plus `{AttributeName}` placeholders. A scalar placeholder
236
- * renders its value; a reference placeholder renders the referenced entity's breadcrumb.
237
- * The server resolves this — clients only read the resulting strings. Example: "{Street}, {City}".
238
- */
239
- breadcrumb?: string;
240
- /**
241
- * When false, the breadcrumb needs the collection document (a placeholder field is not on the
242
- * projection). null/absent means renderable from the projection. Informational on the client.
243
- */
244
- breadcrumbProjectionSatisfiable?: boolean;
245
- tabs?: AttributeTab[];
246
- groups?: AttributeGroup[];
247
- attributes: EntityAttributeDefinition[];
248
- /** Query aliases or IDs to display as related query tables on the detail page. */
249
- queries?: string[];
250
- /**
251
- * The definitions of this type's AsDetail row types, sent alongside it so a detail table can draw
252
- * its columns.
253
- *
254
- * Needed because a row type usually has no rights of its own — a row is edited through its parent
255
- * — and the entity-type catalogue is gated on `Query`. Resolving a row type from the catalogue
256
- * therefore failed silently, and the table rendered with **no columns at all**.
257
- *
258
- * Per-caller and gated on the *parent's* right, which is the same gate that already ships each
259
- * row's attribute schema inside `attr.objects`. Pruned server-side: no `queryType`, `indexName`,
260
- * `queries` or `alias`.
261
- */
262
- detailTypes?: EntityType[];
263
- /**
264
- * Whether adding or removing a row of this type asks the server first.
265
- *
266
- * Absent or false — the default — keeps the purely client-side behaviour: New pushes a blank row,
267
- * Delete splices it out, and the parent's save is the first the server hears of either. On, the
268
- * grid calls `newObject` before showing a row and `deleteRow` before removing one, so
269
- * `OnNewAsync` can default it and the delete hook can refuse.
270
- *
271
- * ⚠️ Read it off the **row type**, never the parent: the type that owns the hooks owns the
272
- * decision. For a row type resolved out of `detailTypes` the flag survives the server-side
273
- * pruning, so both sources agree.
274
- *
275
- * ⚠️ It governs the round trip and nothing else. It is not a permission, and it is outside the
276
- * model hash, so nothing that gates a write may be conditional on it — the save path enforces the
277
- * row type's New/Edit/Delete rights on every embedded collection whether this is on or off.
278
- */
279
- serverSideRowLifecycle?: boolean;
280
- }
281
-
282
- type SparkQueryRenderMode = 'Pagination' | 'VirtualScrolling';
283
- interface SparkQuerySortColumn {
284
- property: string;
285
- direction: string;
286
- }
287
- interface SparkQuery {
288
- id: string;
289
- name: string;
290
- description?: TranslatedString;
291
- source: string;
292
- alias?: string;
293
- sortColumns: SparkQuerySortColumn[];
294
- renderMode?: SparkQueryRenderMode;
295
- /** The RavenDB index this query runs against, resolved by name server-side. */
296
- indexName?: string;
297
- /** Optional entity type name (e.g., "Person"). When set, used for entity type resolution. */
298
- entityType?: string;
299
- /** When true, this query uses WebSocket streaming with snapshot + patch updates. */
300
- isStreamingQuery?: boolean;
301
- }
302
-
200
+ export declare enum EReferenceDisplayType {
201
+ /** Renders as a `<bs-select>` listing every referenced item. */
202
+ Dropdown = "Dropdown",
203
+ /** Renders a readonly textbox + "…" button that opens a searchable modal grid picker. */
204
+ Modal = "Modal"
205
+ }
206
+ export interface EntityAttributeDefinition {
207
+ id: string;
208
+ name: string;
209
+ label?: TranslatedString;
210
+ /** Help text rendered as an [i] tooltip beside the label. Absent when the model declares none. */
211
+ description?: TranslatedString;
212
+ dataType: string;
213
+ isRequired: boolean;
214
+ isVisible: boolean;
215
+ isReadOnly: boolean;
216
+ order: number;
217
+ query?: string;
218
+ /** For reference attributes, specifies the target entity type's CLR type name */
219
+ referenceType?: string;
220
+ /** For AsDetail attributes, specifies the nested entity type's CLR type name */
221
+ asDetailType?: string;
222
+ /** When true, the attribute represents an array/collection of AsDetail objects */
223
+ isArray?: boolean;
224
+ /** For array AsDetail attributes: "modal" (default) or "inline" */
225
+ editMode?: 'inline' | 'modal';
226
+ /**
227
+ * For Reference attributes: 'Modal' renders the "…" + modal query-grid picker;
228
+ * 'Dropdown'/absent (default) renders a `<bs-select>`. Hand-set in the model JSON.
229
+ */
230
+ referenceDisplayType?: EReferenceDisplayType;
231
+ /** For array AsDetail attributes: when true, rows can be drag-reordered (order = array position) */
232
+ isSortable?: boolean;
233
+ /**
234
+ * When true, changing this attribute's value posts the in-progress object to
235
+ * `/spark/po/{objectTypeId}/refresh` and applies the reshaped result as an overlay.
236
+ * Schema-only by design — it never travels on a PersistentObjectAttribute, so a client
237
+ * cannot claim a trigger the model did not declare.
238
+ */
239
+ triggersRefresh?: boolean;
240
+ /** For LookupReference attributes, specifies the lookup reference type name */
241
+ lookupReferenceType?: string;
242
+ /**
243
+ * Controls on which pages the attribute should be displayed.
244
+ * Query = shown in list views, PersistentObject = shown in detail/edit views.
245
+ * Can be a numeric flag value or a string like "Query, PersistentObject".
246
+ */
247
+ showedOn?: ShowedOn | string;
248
+ rules: ValidationRule[];
249
+ /** References an AttributeGroup.id to assign this attribute to a group */
250
+ group?: string;
251
+ /** Number of grid columns this attribute spans within a tab's column layout */
252
+ columnSpan?: number;
253
+ /** Renderer component name for custom display in detail/list views */
254
+ renderer?: string;
255
+ /** Options passed to the renderer component */
256
+ rendererOptions?: Record<string, any>;
257
+ }
258
+ export interface AttributeTab {
259
+ id: string;
260
+ name: string;
261
+ label?: TranslatedString;
262
+ order: number;
263
+ /** Number of columns for the grid layout within this tab */
264
+ columnCount?: number;
265
+ }
266
+ export interface AttributeGroup {
267
+ id: string;
268
+ name: string;
269
+ label?: TranslatedString;
270
+ /** References an AttributeTab.id to assign this group to a tab */
271
+ tab?: string;
272
+ order: number;
273
+ }
274
+ export interface EntityType {
275
+ id: string;
276
+ name: string;
277
+ description?: TranslatedString;
278
+ /**
279
+ * The backing CLR type, or absent for a JSON-only virtual type (#325).
280
+ *
281
+ * Optional on purpose: the server sends null for a virtual type, and this was declared
282
+ * non-nullable, so any unguarded `.endsWith(...)` on it threw a TypeError naming an innocent
283
+ * query. Types are data from the wire, not a promise about it.
284
+ */
285
+ clrType?: string;
286
+ /**
287
+ * Whether this caller may OPEN one of these, as opposed to list them.
288
+ *
289
+ * The catalogue is gated on Query, so its presence here means "you may list this type" and says
290
+ * nothing about opening one. A reference must not render as a link when this is false: it used
291
+ * to, and the click refused on arrival.
292
+ */
293
+ canRead?: boolean;
294
+ alias?: string;
295
+ /**
296
+ * Breadcrumb template: literal text plus `{AttributeName}` placeholders. A scalar placeholder
297
+ * renders its value; a reference placeholder renders the referenced entity's breadcrumb.
298
+ * The server resolves this — clients only read the resulting strings. Example: "{Street}, {City}".
299
+ */
300
+ breadcrumb?: string;
301
+ /**
302
+ * When false, the breadcrumb needs the collection document (a placeholder field is not on the
303
+ * projection). null/absent means renderable from the projection. Informational on the client.
304
+ */
305
+ breadcrumbProjectionSatisfiable?: boolean;
306
+ tabs?: AttributeTab[];
307
+ groups?: AttributeGroup[];
308
+ attributes: EntityAttributeDefinition[];
309
+ /**
310
+ * The sub-queries displayed as related query tables on the detail page: a bare alias or id, or
311
+ * `{ query, selectionMode?, parentReference? }` (#460, D17). Read them with `subQueriesOf(type)`.
312
+ */
313
+ queries?: SparkSubQueryEntry[];
314
+ /**
315
+ * The definitions of this type's AsDetail row types, sent alongside it so a detail table can draw
316
+ * its columns.
317
+ *
318
+ * Needed because a row type usually has no rights of its own — a row is edited through its parent
319
+ * — and the entity-type catalogue is gated on `Query`. Resolving a row type from the catalogue
320
+ * therefore failed silently, and the table rendered with **no columns at all**.
321
+ *
322
+ * Per-caller and gated on the *parent's* right, which is the same gate that already ships each
323
+ * row's attribute schema inside `attr.objects`. Pruned server-side: no `queryType`, `indexName`,
324
+ * `queries` or `alias`.
325
+ */
326
+ detailTypes?: EntityType[];
327
+ /**
328
+ * Whether adding or removing a row of this type asks the server first.
329
+ *
330
+ * Absent or false — the default — keeps the purely client-side behaviour: New pushes a blank row,
331
+ * Delete splices it out, and the parent's save is the first the server hears of either. On, the
332
+ * grid calls `newObject` before showing a row and `deleteRow` before removing one, so
333
+ * `OnNewAsync` can default it and the delete hook can refuse.
334
+ *
335
+ * ⚠️ Read it off the **row type**, never the parent: the type that owns the hooks owns the
336
+ * decision. For a row type resolved out of `detailTypes` the flag survives the server-side
337
+ * pruning, so both sources agree.
338
+ *
339
+ * ⚠️ It governs the round trip and nothing else. It is not a permission, and it is outside the
340
+ * model hash, so nothing that gates a write may be conditional on it — the save path enforces the
341
+ * row type's New/Edit/Delete rights on every embedded collection whether this is on or off.
342
+ */
343
+ serverSideRowLifecycle?: boolean;
344
+ }
345
+ export type SparkQueryRenderMode = 'Pagination' | 'VirtualScrolling';
346
+ export interface SparkQuerySortColumn {
347
+ property: string;
348
+ direction: string;
349
+ }
350
+ export interface SparkQuery {
351
+ id: string;
352
+ name: string;
353
+ description?: TranslatedString;
354
+ source: string;
355
+ alias?: string;
356
+ sortColumns: SparkQuerySortColumn[];
357
+ renderMode?: SparkQueryRenderMode;
358
+ /** The RavenDB index this query runs against, resolved by name server-side. */
359
+ indexName?: string;
360
+ /** Optional entity type name (e.g., "Person"). When set, used for entity type resolution. */
361
+ entityType?: string;
362
+ /** When true, this query uses WebSocket streaming with snapshot + patch updates. */
363
+ isStreamingQuery?: boolean;
364
+ /**
365
+ * Whether the grid offers row selection (#460, D17). Absent is `'auto'`: derived from the offered
366
+ * actions. A sub-query entry on the parent's type may override it.
367
+ */
368
+ selectionMode?: SparkSelectionModeSetting;
369
+ /**
370
+ * The row type's attribute that points at the parent when the query is a sub-query — only needed
371
+ * when the row type references the parent's type more than once (#460, D19). Server-side use.
372
+ */
373
+ parentReference?: string;
374
+ }
375
+ /**
376
+ * Which rows a query asks for with respect to soft deletion (#460). Core carries it to every row
377
+ * policy; the SoftDelete package's policy honours it only for holders of `ViewDeleted` on the type.
378
+ * Omitted means `'exclude'`.
379
+ */
380
+ export type SparkDeletedFilter = 'exclude' | 'include' | 'only';
303
381
  /**
304
382
  * What a query returns: the column metadata once, then one lightweight row per result.
305
383
  *
@@ -309,25 +387,23 @@ interface SparkQuery {
309
387
  * **persistent object is a document**. Nothing here is entitled to act on a row — every
310
388
  * mutating path re-loads by id and re-applies security server-side.
311
389
  */
312
- interface QueryResult {
313
- columns: QueryColumn[];
314
- items: QueryResultItem[];
315
- /** Rows matching the query after filtering, before paging. */
316
- totalItems: number;
317
- skip: number;
318
- take: number;
319
- typeHints?: Record<string, string>;
320
- /**
321
- * Custom actions withheld for THIS result, by name. Absent or empty means every action the
322
- * caller has the right to is offered.
323
- *
324
- * The mirror of `PersistentObject.disabledActions`, set server-side via
325
- * `CustomQueryArgs.DisableActions(...)`. The action catalogue is per type and is never told what
326
- * an execution returned, so this is where a per-result answer travels.
327
- *
328
- * An affordance, not a permission -- the action handler still refuses on its own terms.
329
- */
330
- disabledActions?: string[];
390
+ export interface QueryResult {
391
+ columns: QueryColumn[];
392
+ items: QueryResultItem[];
393
+ /** Rows matching the query after filtering, before paging. */
394
+ totalItems: number;
395
+ skip: number;
396
+ take: number;
397
+ typeHints?: Record<string, string>;
398
+ /**
399
+ * Actions withheld for THIS query, by name (custom, and `New`). Absent or empty means every
400
+ * action the caller has the right to is offered.
401
+ *
402
+ * The mirror of `PersistentObject.disabledActions`, set server-side by the entity's
403
+ * `OnDisableActionsAsync` hook with a query target (#460, D13). The server asks the same hook when
404
+ * an action is submitted from this query and refuses a disabled one with 403.
405
+ */
406
+ disabledActions?: string[];
331
407
  }
332
408
  /**
333
409
  * The subset of column metadata a cell needs in order to render.
@@ -336,50 +412,50 @@ interface QueryResult {
336
412
  * page's attribute) satisfy this, which is what lets one cell component serve the query grid and
337
413
  * the AsDetail sub-table without either side knowing about the other's shape.
338
414
  */
339
- interface SparkCellColumn {
340
- name: string;
341
- label?: TranslatedString;
342
- /** Help text rendered as an [i] tooltip in the column header. Absent when the model declares none. */
343
- description?: TranslatedString;
344
- dataType: string;
345
- isArray?: boolean;
346
- renderer?: string;
347
- rendererOptions?: Record<string, any>;
348
- referenceType?: string;
349
- lookupReferenceType?: string;
350
- asDetailType?: string;
415
+ export interface SparkCellColumn {
416
+ name: string;
417
+ label?: TranslatedString;
418
+ /** Help text rendered as an [i] tooltip in the column header. Absent when the model declares none. */
419
+ description?: TranslatedString;
420
+ dataType: string;
421
+ isArray?: boolean;
422
+ renderer?: string;
423
+ rendererOptions?: Record<string, any>;
424
+ referenceType?: string;
425
+ lookupReferenceType?: string;
426
+ asDetailType?: string;
351
427
  }
352
428
  /** One column of a query result, sent once rather than repeated on every row. */
353
- interface QueryColumn extends SparkCellColumn {
354
- order: number;
355
- /**
356
- * Whether the grid offers a sort affordance for this column. Resolved server-side from the query's
357
- * override then the attribute, so it arrives already decided.
358
- *
359
- * This replaced an `isSortable` that carried the server's AsDetail *drag-reorder* flag — a
360
- * different concept that was `false` for every scalar column while the grid hard-coded every
361
- * header sortable anyway. Neither side read it. The attribute-level `isSortable` on `EntityType`
362
- * is the drag-reorder flag and stays.
363
- *
364
- * Absent means sortable, matching `isVisible`: a server predating the field still draws arrows.
365
- */
366
- canSort?: boolean;
367
- /** Whether the grid draws a filter cell for this column. Absent means filterable. */
368
- canFilter?: boolean;
369
- /**
370
- * Whether this column's distinct values may be listed. When `false` and `canFilter` is true the
371
- * panel offers a free-text filter instead of a value list — listing a column's values is a much
372
- * stronger disclosure than filtering by one already known.
373
- */
374
- canListDistincts?: boolean;
375
- /**
376
- * Whether the grid draws this column. `false` means the row carries the value but no column is
377
- * rendered — for a renderer that needs a sibling value (a lock glyph beside a name) without
378
- * spending a column on it. `showedOn` decides what ships; this decides what is drawn.
379
- */
380
- isVisible?: boolean;
381
- /** The query backing a Reference column, by name — the client's option source. */
382
- query?: string;
429
+ export interface QueryColumn extends SparkCellColumn {
430
+ order: number;
431
+ /**
432
+ * Whether the grid offers a sort affordance for this column. Resolved server-side from the query's
433
+ * override then the attribute, so it arrives already decided.
434
+ *
435
+ * This replaced an `isSortable` that carried the server's AsDetail *drag-reorder* flag — a
436
+ * different concept that was `false` for every scalar column while the grid hard-coded every
437
+ * header sortable anyway. Neither side read it. The attribute-level `isSortable` on `EntityType`
438
+ * is the drag-reorder flag and stays.
439
+ *
440
+ * Absent means sortable, matching `isVisible`: a server predating the field still draws arrows.
441
+ */
442
+ canSort?: boolean;
443
+ /** Whether the grid draws a filter cell for this column. Absent means filterable. */
444
+ canFilter?: boolean;
445
+ /**
446
+ * Whether this column's distinct values may be listed. When `false` and `canFilter` is true the
447
+ * panel offers a free-text filter instead of a value list — listing a column's values is a much
448
+ * stronger disclosure than filtering by one already known.
449
+ */
450
+ canListDistincts?: boolean;
451
+ /**
452
+ * Whether the grid draws this column. `false` means the row carries the value but no column is
453
+ * rendered — for a renderer that needs a sibling value (a lock glyph beside a name) without
454
+ * spending a column on it. `showedOn` decides what ships; this decides what is drawn.
455
+ */
456
+ isVisible?: boolean;
457
+ /** The query backing a Reference column, by name — the client's option source. */
458
+ query?: string;
383
459
  }
384
460
  /**
385
461
  * One row: an id, a display string, and a value per column.
@@ -387,28 +463,28 @@ interface QueryColumn extends SparkCellColumn {
387
463
  * Deliberately too weak to act on — no attribute metadata, no `can` block, no etag, because none
388
464
  * of those can be trusted from a projection.
389
465
  */
390
- interface QueryResultItem {
391
- /** Never null and unique within a result; the server refuses anything else. */
392
- id: string;
393
- /** What to show when the row is named rather than tabulated (a reference picker's value). */
394
- breadcrumb?: string;
395
- values: QueryResultItemValue[];
396
- /** Presentation hints for the whole row. Keys arrive lower-cased. */
397
- typeHints?: Record<string, string>;
466
+ export interface QueryResultItem {
467
+ /** Never null and unique within a result; the server refuses anything else. */
468
+ id: string;
469
+ /** What to show when the row is named rather than tabulated (a reference picker's value). */
470
+ breadcrumb?: string;
471
+ values: QueryResultItemValue[];
472
+ /** Presentation hints for the whole row. Keys arrive lower-cased. */
473
+ typeHints?: Record<string, string>;
398
474
  }
399
475
  /** One cell. */
400
- interface QueryResultItemValue {
401
- /** The {@link QueryColumn.name} this value belongs to. */
402
- key: string;
403
- /** Typed JSON, not a string — the server converts on the way out. */
404
- value?: any;
405
- /** For a reference cell: the target document's id, so a link needs no second lookup. */
406
- objectId?: string;
407
- /** For a reference cell: the target's display string. */
408
- breadcrumb?: string;
409
- /** Resolved display labels per referenced id, for a multi-reference cell. */
410
- breadcrumbs?: Record<string, string | null>;
411
- typeHints?: Record<string, string>;
476
+ export interface QueryResultItemValue {
477
+ /** The {@link QueryColumn.name} this value belongs to. */
478
+ key: string;
479
+ /** Typed JSON, not a string — the server converts on the way out. */
480
+ value?: any;
481
+ /** For a reference cell: the target document's id, so a link needs no second lookup. */
482
+ objectId?: string;
483
+ /** For a reference cell: the target's display string. */
484
+ breadcrumb?: string;
485
+ /** Resolved display labels per referenced id, for a multi-reference cell. */
486
+ breadcrumbs?: Record<string, string | null>;
487
+ typeHints?: Record<string, string>;
412
488
  }
413
489
  /**
414
490
  * Every shape a renderer's `item` can arrive in.
@@ -419,7 +495,7 @@ interface QueryResultItemValue {
419
495
  * grid and an AsDetail table therefore sees two of them, which is why {@link valueFor} reads all
420
496
  * three rather than making every app write the branch.
421
497
  */
422
- type SparkRow = QueryResultItem | PersistentObject | Record<string, unknown>;
498
+ export type SparkRow = QueryResultItem | PersistentObject | Record<string, unknown>;
423
499
  /**
424
500
  * The value a row carries for a column, whatever shape the row is in — or `undefined` when it
425
501
  * carries none.
@@ -437,7 +513,7 @@ type SparkRow = QueryResultItem | PersistentObject | Record<string, unknown>;
437
513
  * attribute marked `"showedOn": "PersistentObject"` is absent from a row by design, and this returns
438
514
  * `undefined` for it; that is the first thing to check when a sibling read comes back empty.
439
515
  */
440
- declare function valueFor(item: SparkRow | null | undefined, columnName: string): QueryResultItemValue | undefined;
516
+ export declare function valueFor(item: SparkRow | null | undefined, columnName: string): QueryResultItemValue | undefined;
441
517
  /**
442
518
  * Whether this row is a query result item.
443
519
  *
@@ -445,9 +521,9 @@ declare function valueFor(item: SparkRow | null | undefined, columnName: string)
445
521
  * legitimately have a column named `values` holding an array, and mistaking one for the other would
446
522
  * silently read the wrong thing.
447
523
  */
448
- declare function isQueryRow(row: SparkRow | null | undefined): row is QueryResultItem;
524
+ export declare function isQueryRow(row: SparkRow | null | undefined): row is QueryResultItem;
449
525
  /** Whether this row is a full persistent object. Discriminated on elements, as above. */
450
- declare function isPersistentObject(row: SparkRow | null | undefined): row is PersistentObject;
526
+ export declare function isPersistentObject(row: SparkRow | null | undefined): row is PersistentObject;
451
527
  /**
452
528
  * One column's value filter (#431): the values a caller picked, or the values they excluded.
453
529
  *
@@ -460,10 +536,10 @@ declare function isPersistentObject(row: SparkRow | null | undefined): row is Pe
460
536
  * moves values between them. `null` is a real, selectable value meaning "no value"; an empty or
461
537
  * absent array is the absent filter.
462
538
  */
463
- interface QueryColumnFilter {
464
- name: string;
465
- includes?: unknown[];
466
- excludes?: unknown[];
539
+ export interface QueryColumnFilter {
540
+ name: string;
541
+ includes?: unknown[];
542
+ excludes?: unknown[];
467
543
  }
468
544
  /**
469
545
  * One selectable entry in a column's filter panel.
@@ -471,11 +547,11 @@ interface QueryColumnFilter {
471
547
  * Structurally identical to the datatable's own `DistinctValue`, deliberately — it is what lets the
472
548
  * HTTP response be handed straight to `[distincts]` with no mapper in between.
473
549
  */
474
- interface DistinctValue {
475
- /** The raw stored value. `null` is a real entry meaning "no value", not an absent one. */
476
- value: unknown;
477
- /** What the user reads. Never matched on. */
478
- label: string;
550
+ export interface DistinctValue {
551
+ /** The raw stored value. `null` is a real entry meaning "no value", not an absent one. */
552
+ value: unknown;
553
+ /** What the user reads. Never matched on. */
554
+ label: string;
479
555
  }
480
556
  /**
481
557
  * A column's distinct values.
@@ -486,144 +562,128 @@ interface DistinctValue {
486
562
  * `hasMore` reports that the cap truncated the list, and the panel re-queries only when it is set or
487
563
  * the search term widens — so a dishonest `false` strands a user typing past the cap.
488
564
  */
489
- interface DistinctValuesResult {
490
- matching: DistinctValue[];
491
- remaining: DistinctValue[];
492
- hasMore: boolean;
493
- }
494
-
495
- interface ProgramUnit {
496
- id: string;
497
- name: TranslatedString;
498
- icon?: string;
499
- /**
500
- * Canonical unit type, exact-cased by the server's loader: 'query' | 'persistentObject' | 'url'.
501
- * Kept as string rather than a union so an older client tolerates a newer server.
502
- */
503
- type: string;
504
- queryId?: string;
505
- persistentObjectId?: string;
506
- /**
507
- * For a persistentObject unit: the specific object to open — the menu entry deep-links to
508
- * `/po/{type}/{objectId}`. Absent means the type's default list. For a composed page this is
509
- * whatever stable string the app declared; the server's Actions class may ignore it.
510
- */
511
- objectId?: string;
512
- /** For a url unit: the external address, rendered as a plain anchor (never a router link). */
513
- url?: string;
514
- order: number;
515
- alias?: string;
516
- }
517
- interface ProgramUnitGroup {
518
- id: string;
519
- name: TranslatedString;
520
- icon?: string;
521
- order: number;
522
- programUnits: ProgramUnit[];
523
- }
524
- interface ProgramUnitsConfiguration {
525
- programUnitGroups: ProgramUnitGroup[];
526
- }
527
-
528
- declare enum ELookupDisplayType {
529
- Dropdown = 0,
530
- Modal = 1
531
- }
532
- interface LookupReferenceListItem {
533
- name: string;
534
- isTransient: boolean;
535
- valueCount: number;
536
- displayType: ELookupDisplayType;
537
- }
538
- interface LookupReference {
539
- name: string;
540
- isTransient: boolean;
541
- displayType: ELookupDisplayType;
542
- values: LookupReferenceValue[];
543
- }
544
- interface LookupReferenceValue {
545
- key: string;
546
- values: TranslatedString;
547
- isActive: boolean;
548
- extra?: Record<string, unknown>;
549
- }
550
-
551
- interface RetryActionPayload {
552
- type: 'retry-action';
553
- step: number;
554
- title: string;
555
- message?: string;
556
- options: string[];
557
- defaultOption?: string;
558
- persistentObject?: PersistentObject;
559
- }
560
- interface RetryActionResult {
561
- step: number;
562
- option: string;
563
- persistentObject?: PersistentObject;
564
- }
565
-
566
- interface EntityPermissions {
567
- /**
568
- * Whether the caller may list this type. Independently grantable from `canRead` — `Query/Person`
569
- * alone lists rows while refusing a by-id load — and reported since preview.60. The combined
570
- * `QueryRead` right bundles the two invisibly, which is why this was the one action introspection
571
- * never mentioned.
572
- */
573
- canQuery: boolean;
574
- canRead: boolean;
575
- canCreate: boolean;
576
- canEdit: boolean;
577
- canDelete: boolean;
578
- }
579
-
580
- interface CustomActionDefinition {
581
- name: string;
582
- displayName: TranslatedString;
583
- icon?: string;
584
- description?: string;
585
- showedOn: string;
586
- selectionRule?: string;
587
- refreshOnCompleted: boolean;
588
- confirmationMessageKey?: string;
589
- /**
590
- * How prominently, and how warily, to present the action: 'primary', 'secondary', 'danger',
591
- * 'warning'. Undefined renders the neutral default.
592
- *
593
- * Presentation only — the server decides who may see and run an action. A 'danger' variant just
594
- * asks the button to look like what it does; pair it with `confirmationMessageKey` for anything
595
- * irreversible, because the colour warns and the prompt is what actually prevents the accident.
596
- */
597
- variant?: string;
598
- offset: number;
599
- }
600
-
565
+ export interface DistinctValuesResult {
566
+ matching: DistinctValue[];
567
+ remaining: DistinctValue[];
568
+ hasMore: boolean;
569
+ }
570
+ export interface ProgramUnit {
571
+ id: string;
572
+ name: TranslatedString;
573
+ icon?: string;
574
+ /**
575
+ * Canonical unit type, exact-cased by the server's loader: 'query' | 'persistentObject' | 'url'.
576
+ * Kept as string rather than a union so an older client tolerates a newer server.
577
+ */
578
+ type: string;
579
+ queryId?: string;
580
+ persistentObjectId?: string;
581
+ /**
582
+ * For a persistentObject unit: the specific object to open — the menu entry deep-links to
583
+ * `/po/{type}/{objectId}`. Absent means the type's default list. For a composed page this is
584
+ * whatever stable string the app declared; the server's Actions class may ignore it.
585
+ */
586
+ objectId?: string;
587
+ /** For a url unit: the external address, rendered as a plain anchor (never a router link). */
588
+ url?: string;
589
+ order: number;
590
+ alias?: string;
591
+ }
592
+ export interface ProgramUnitGroup {
593
+ id: string;
594
+ name: TranslatedString;
595
+ icon?: string;
596
+ order: number;
597
+ programUnits: ProgramUnit[];
598
+ }
599
+ export interface ProgramUnitsConfiguration {
600
+ programUnitGroups: ProgramUnitGroup[];
601
+ }
602
+ export declare enum ELookupDisplayType {
603
+ Dropdown = 0,
604
+ Modal = 1
605
+ }
606
+ export interface LookupReferenceListItem {
607
+ name: string;
608
+ isTransient: boolean;
609
+ valueCount: number;
610
+ displayType: ELookupDisplayType;
611
+ }
612
+ export interface LookupReference {
613
+ name: string;
614
+ isTransient: boolean;
615
+ displayType: ELookupDisplayType;
616
+ values: LookupReferenceValue[];
617
+ }
618
+ export interface LookupReferenceValue {
619
+ key: string;
620
+ values: TranslatedString;
621
+ isActive: boolean;
622
+ extra?: Record<string, unknown>;
623
+ }
624
+ export interface RetryActionPayload {
625
+ type: 'retry-action';
626
+ step: number;
627
+ title: string;
628
+ message?: string;
629
+ options: string[];
630
+ defaultOption?: string;
631
+ persistentObject?: PersistentObject;
632
+ }
633
+ export interface RetryActionResult {
634
+ step: number;
635
+ option: string;
636
+ persistentObject?: PersistentObject;
637
+ }
638
+ export interface EntityPermissions {
639
+ /**
640
+ * Whether the caller may list this type. Independently grantable from `canRead` — `Query/Person`
641
+ * alone lists rows while refusing a by-id load — and reported since preview.60. The combined
642
+ * `QueryRead` right bundles the two invisibly, which is why this was the one action introspection
643
+ * never mentioned.
644
+ */
645
+ canQuery: boolean;
646
+ canRead: boolean;
647
+ canCreate: boolean;
648
+ canEdit: boolean;
649
+ canDelete: boolean;
650
+ /**
651
+ * SoftDelete (#460): `Restore/T`, `Purge/T` and `ViewDeleted/T` (the recycle bin — query and load
652
+ * with `deleted: 'include' | 'only'`). Type-level only; a row may still refuse. Optional because a
653
+ * server older than 11.0.0-preview.91 does not send them — treat absent as `false`.
654
+ */
655
+ canRestore?: boolean;
656
+ canPurge?: boolean;
657
+ canViewDeleted?: boolean;
658
+ /** History (#460): `History/T` (list and read revisions) and `Revert/T` together with `Edit/T`. */
659
+ canViewHistory?: boolean;
660
+ canRevert?: boolean;
661
+ }
601
662
  /**
602
663
  * The opening message of a stream: the column metadata plus the rows so far.
603
664
  *
604
665
  * Columns arrive once, here. A stream is one result whose rows arrive over time, so its shape is
605
666
  * fixed when it opens — patches carry values only.
606
667
  */
607
- interface StreamingSnapshotMessage {
608
- type: 'snapshot';
609
- columns: QueryColumn[];
610
- data: QueryResultItem[];
668
+ export interface StreamingSnapshotMessage {
669
+ type: 'snapshot';
670
+ columns: QueryColumn[];
671
+ data: QueryResultItem[];
611
672
  }
612
- interface StreamingPatchItem {
613
- id: string;
614
- /** Changed cell values, keyed by column name. Never metadata — the snapshot fixed that. */
615
- values: Record<string, any>;
673
+ export interface StreamingPatchItem {
674
+ id: string;
675
+ /** Changed cell values, keyed by column name. Never metadata — the snapshot fixed that. */
676
+ values: Record<string, any>;
616
677
  }
617
- interface StreamingPatchMessage {
618
- type: 'patch';
619
- updated: StreamingPatchItem[];
678
+ export interface StreamingPatchMessage {
679
+ type: 'patch';
680
+ updated: StreamingPatchItem[];
620
681
  }
621
- interface StreamingErrorMessage {
622
- type: 'error';
623
- message: string;
682
+ export interface StreamingErrorMessage {
683
+ type: 'error';
684
+ message: string;
624
685
  }
625
- type StreamingMessage = StreamingSnapshotMessage | StreamingPatchMessage | StreamingErrorMessage;
626
-
686
+ export type StreamingMessage = StreamingSnapshotMessage | StreamingPatchMessage | StreamingErrorMessage;
627
687
  /**
628
688
  * Conversions between the wire format for a timestamp and what an `<input type="datetime-local">`
629
689
  * or `<input type="date">` will accept.
@@ -641,7 +701,7 @@ type StreamingMessage = StreamingSnapshotMessage | StreamingPatchMessage | Strea
641
701
  * Parses a wire timestamp. Returns `null` for anything unusable, matching the contract of
642
702
  * `parsedDate` / `queryCellValue` -- a bad value renders as empty rather than as `Invalid Date`.
643
703
  */
644
- declare function parseWireDate(value: unknown): Date | null;
704
+ export declare function parseWireDate(value: unknown): Date | null;
645
705
  /**
646
706
  * Wire value -> the `yyyy-MM-ddTHH:mm` a `datetime-local` input needs, in the **viewer's** zone.
647
707
  *
@@ -649,9 +709,9 @@ declare function parseWireDate(value: unknown): Date | null;
649
709
  * *on that value's own date*, so a timestamp in July and one in January convert differently in a
650
710
  * zone that observes DST.
651
711
  */
652
- declare function toDateTimeLocalInput(value: unknown): string;
712
+ export declare function toDateTimeLocalInput(value: unknown): string;
653
713
  /** Wire value -> the `yyyy-MM-dd` a `date` input needs, in the viewer's zone. */
654
- declare function toDateInput(value: unknown): string;
714
+ export declare function toDateInput(value: unknown): string;
655
715
  /**
656
716
  * `yyyy-MM-ddTHH:mm` from the control -> a complete ISO-8601 string carrying the viewer's offset
657
717
  * **for the entered date**.
@@ -665,9 +725,9 @@ declare function toDateInput(value: unknown): string;
665
725
  * before a fall-back and after a spring-forward. That is specified ECMAScript behaviour, and the
666
726
  * server's conversion helper deliberately reproduces the same choice so the two cannot drift.
667
727
  */
668
- declare function fromDateTimeLocalInput(local: unknown): string | null;
728
+ export declare function fromDateTimeLocalInput(local: unknown): string | null;
669
729
  /** `yyyy-MM-dd` from a `date` control -> an ISO string at local midnight with the viewer's offset. */
670
- declare function fromDateInput(local: unknown): string | null;
730
+ export declare function fromDateInput(local: unknown): string | null;
671
731
  /**
672
732
  * Compares two wire timestamps **by instant**, which is what a `DateTimeOffset` means in Spark.
673
733
  *
@@ -680,21 +740,20 @@ declare function fromDateInput(local: unknown): string | null;
680
740
  * the instant and silently hides a destroyed offset. Here comparing by instant is the correct thing:
681
741
  * we are asking "did the user change this value", not "is the offset intact".
682
742
  */
683
- declare function wireDatesEqual(a: unknown, b: unknown): boolean;
743
+ export declare function wireDatesEqual(a: unknown, b: unknown): boolean;
684
744
  /** True for the attribute data types that are edited through a native date/time control. */
685
- declare function isDateDataType(dataType: string | null | undefined): dataType is 'date' | 'datetime';
745
+ export declare function isDateDataType(dataType: string | null | undefined): dataType is 'date' | 'datetime';
686
746
  /** Wire -> control, picking the right shape for the data type. */
687
- declare function toDateInputValue(dataType: string | null | undefined, value: unknown): string;
747
+ export declare function toDateInputValue(dataType: string | null | undefined, value: unknown): string;
688
748
  /** Control -> wire, picking the right shape for the data type. */
689
- declare function fromDateInputValue(dataType: string | null | undefined, value: unknown): string | null;
690
-
749
+ export declare function fromDateInputValue(dataType: string | null | undefined, value: unknown): string | null;
691
750
  /**
692
751
  * Resolves an `EntityType` by its CLR type name (e.g. `"HR.Entities.Address"`).
693
752
  * Callers typically close over a list they already hold. `getEntityTypes()` is NOT cached --
694
753
  * it issues a request per call -- so resolve against a list you fetched once rather than
695
754
  * calling it inside the resolver.
696
755
  */
697
- type EntityTypeResolver = (clrTypeName: string) => EntityType | undefined;
756
+ export type EntityTypeResolver = (clrTypeName: string) => EntityType | undefined;
698
757
  /**
699
758
  * Flattens a nested `PersistentObject` into the plain `Record<string, any>` shape the
700
759
  * form state uses throughout ng-spark. Primitive / reference attributes contribute their
@@ -704,7 +763,7 @@ type EntityTypeResolver = (clrTypeName: string) => EntityType | undefined;
704
763
  * This is the ONE place that reads the server's new AsDetail wire shape and collapses it
705
764
  * back to the flat dict the form components already handle.
706
765
  */
707
- declare function nestedPoToDict(po: PersistentObject | null | undefined): Record<string, any>;
766
+ export declare function nestedPoToDict(po: PersistentObject | null | undefined): Record<string, any>;
708
767
  /**
709
768
  * Reserved key under which a flattened nested object keeps the breadcrumb the SERVER resolved for
710
769
  * that object itself (as opposed to {@link AS_DETAIL_BREADCRUMBS_KEY}, which keys the breadcrumbs
@@ -717,7 +776,7 @@ declare function nestedPoToDict(po: PersistentObject | null | undefined): Record
717
776
  * used to discard the resolved string, leaving the form to substitute `{Crumb}` against a dict that
718
777
  * can never contain it.
719
778
  */
720
- declare const AS_DETAIL_SELF_BREADCRUMB_KEY = "__sparkBreadcrumb";
779
+ export declare const AS_DETAIL_SELF_BREADCRUMB_KEY = "__sparkBreadcrumb";
721
780
  /**
722
781
  * Reserved key under which a flattened nested object keeps its own row key — the value of whatever
723
782
  * property the server registered as that type's `[ValueKey]`.
@@ -737,7 +796,7 @@ declare const AS_DETAIL_SELF_BREADCRUMB_KEY = "__sparkBreadcrumb";
737
796
  * Safe to carry, for the same reason the breadcrumb is: `dictToNestedPo` walks the entity type's
738
797
  * attributes and never the dict's keys, so a reserved key is never sent back as an attribute.
739
798
  */
740
- declare const AS_DETAIL_ROW_KEY = "__sparkRowKey";
799
+ export declare const AS_DETAIL_ROW_KEY = "__sparkRowKey";
741
800
  /**
742
801
  * The breadcrumb the server resolved for a flattened object, or null when it resolved to nothing.
743
802
  *
@@ -746,7 +805,7 @@ declare const AS_DETAIL_ROW_KEY = "__sparkRowKey";
746
805
  * (EntityMapper.cs:209-211). That is a placeholder, not data, and rendering it would be worse than
747
806
  * rendering nothing — it reads as a real value. `typeName` lets a caller filter it back out.
748
807
  */
749
- declare function selfBreadcrumb(row: Record<string, any> | null | undefined, typeName?: string): string | null;
808
+ export declare function selfBreadcrumb(row: Record<string, any> | null | undefined, typeName?: string): string | null;
750
809
  /**
751
810
  * The same filter as {@link selfBreadcrumb}, for a caller holding the server's breadcrumb string
752
811
  * directly rather than a flattened row dict — `attr.object.breadcrumb` on a detail page, or a query
@@ -757,14 +816,14 @@ declare function selfBreadcrumb(row: Record<string, any> | null | undefined, typ
757
816
  * first; the two that read the string straight off the wire kept printing `BuildFeedback` and
758
817
  * `GateSettings` at users (#384). A second copy of the comparison is how that happens again.
759
818
  */
760
- declare function resolvedBreadcrumb(value: unknown, typeName?: string): string | null;
819
+ export declare function resolvedBreadcrumb(value: unknown, typeName?: string): string | null;
761
820
  /**
762
821
  * Reserved key under which {@link nestedPoToDisplayRow} stashes the server-resolved breadcrumb of
763
822
  * each reference attribute (keyed by attribute name). Lets an AsDetail reference cell render the
764
823
  * label the server already resolved by id — page-independent — instead of guessing from a single
765
824
  * reference-query options page. Prefixed to avoid colliding with a real attribute name.
766
825
  */
767
- declare const AS_DETAIL_BREADCRUMBS_KEY = "__sparkBreadcrumbs";
826
+ export declare const AS_DETAIL_BREADCRUMBS_KEY = "__sparkBreadcrumbs";
768
827
  /**
769
828
  * Reserved key under which a flattened row keeps the **wire** values of its date cells, keyed by
770
829
  * attribute name.
@@ -780,7 +839,7 @@ declare const AS_DETAIL_BREADCRUMBS_KEY = "__sparkBreadcrumbs";
780
839
  * instant is unchanged. Safe to carry for the same reason as the other reserved keys: the rebuild
781
840
  * walks the entity type's attributes, never the dict's keys.
782
841
  */
783
- declare const AS_DETAIL_ORIGINAL_DATES_KEY = "__sparkOriginalDates";
842
+ export declare const AS_DETAIL_ORIGINAL_DATES_KEY = "__sparkOriginalDates";
784
843
  /**
785
844
  * Whether a flattened-row key is one this module reserved rather than a model attribute.
786
845
  *
@@ -790,7 +849,7 @@ declare const AS_DETAIL_ORIGINAL_DATES_KEY = "__sparkOriginalDates";
790
849
  * dict to build display text: a caller joining `Object.values(dict)` prints the row's guid and its
791
850
  * own breadcrumb as though they were fields.
792
851
  */
793
- declare function isReservedAsDetailKey(key: string): boolean;
852
+ export declare function isReservedAsDetailKey(key: string): boolean;
794
853
  /**
795
854
  * Like {@link nestedPoToDict}, but for the read-only detail display path. In addition to each
796
855
  * attribute's value it preserves the server-resolved per-reference `breadcrumb` under
@@ -798,7 +857,7 @@ declare function isReservedAsDetailKey(key: string): boolean;
798
857
  * regardless of whether the referenced document fits on the reference query's first options page.
799
858
  * The form/edit path keeps using {@link nestedPoToDict}, which never carries breadcrumbs.
800
859
  */
801
- declare function nestedPoToDisplayRow(po: PersistentObject | null | undefined): Record<string, any>;
860
+ export declare function nestedPoToDisplayRow(po: PersistentObject | null | undefined): Record<string, any>;
802
861
  /**
803
862
  * Builds a nested `PersistentObject` from a flat dict against the schema in
804
863
  * <paramref name="entityType"/>. Used when the form is about to save — AsDetail attributes
@@ -809,22 +868,33 @@ declare function nestedPoToDisplayRow(po: PersistentObject | null | undefined):
809
868
  * `getEntityTypes()` list, keyed by CLR type name). Nested AsDetail inside AsDetail is
810
869
  * handled recursively.
811
870
  */
812
- declare function dictToNestedPo(dict: Record<string, any> | null | undefined, entityType: EntityType, resolve: EntityTypeResolver): PersistentObject;
813
-
871
+ export declare function dictToNestedPo(dict: Record<string, any> | null | undefined, entityType: EntityType, resolve: EntityTypeResolver): PersistentObject;
872
+ /** Whether `showedOn` offers the action on a query: `"query"` or `"both"`, in any casing. */
873
+ export declare function isShowedOnQuery(showedOn: string | undefined | null): boolean;
874
+ /** Whether `showedOn` offers the action on a detail page: `"detail"` or `"both"`, in any casing. */
875
+ export declare function isShowedOnDetail(showedOn: string | undefined | null): boolean;
814
876
  /**
815
877
  * The custom actions a query should offer, from the entity type's full set.
816
878
  *
817
879
  * `showedOn` must include the query side. The accepted values are `"detail"`, `"query"`
818
880
  * and `"both"` — as the server model and the custom-actions guide have always
819
- * documented. Both grids previously tested for `"list"`, a value nothing emits, so an
820
- * action authored per the documentation rendered nowhere at all.
881
+ * documented — compared case-insensitively (#460 M15: `"Both"` used to render nowhere).
882
+ *
883
+ * The built-in New and Delete (`isDefault`, #460 D18) are left out: they are not executed as
884
+ * custom actions, and the grid offers them itself (see {@link defaultQueryActions}).
821
885
  *
822
886
  * ⚠️ This narrows what is DISPLAYED. It is NOT an authorization boundary: the grant
823
887
  * is, and it is enforced independently in `ExecuteCustomAction` regardless of which
824
888
  * query the caller clicked from — a caller can always POST directly.
825
889
  */
826
- declare function filterQueryActions(actions: CustomActionDefinition[]): CustomActionDefinition[];
827
-
890
+ export declare function filterQueryActions(actions: CustomActionDefinition[]): CustomActionDefinition[];
891
+ /** The custom actions a detail page should offer — the detail-side counterpart of {@link filterQueryActions}. */
892
+ export declare function filterDetailActions(actions: CustomActionDefinition[]): CustomActionDefinition[];
893
+ /**
894
+ * The built-in New and Delete the server listed for this caller (#460, D18), when their `showedOn`
895
+ * includes the query side. Present only when the caller holds `New/T` or `Delete/T`.
896
+ */
897
+ export declare function defaultQueryActions(actions: CustomActionDefinition[]): CustomActionDefinition[];
828
898
  /**
829
899
  * Parses a custom action's `selectionRule` — a cardinality expression over the number
830
900
  * of selected rows — into a predicate.
@@ -842,29 +912,14 @@ declare function filterQueryActions(actions: CustomActionDefinition[]): CustomAc
842
912
  * Client-side this only drives whether a button is disabled. The server enforces the same
843
913
  * rule independently — and neither is an authorization boundary: the action's grant is.
844
914
  */
845
- declare function parseSelectionRule(rule?: string | null): (count: number) => boolean;
846
-
847
- type SparkSelectionMode = 'none' | 'single' | 'multiple';
848
- /**
849
- * The selection mode a grid needs in order to satisfy the actions offered on it.
850
- *
851
- * Derived rather than configured, so a grid gains a checkbox column exactly when an
852
- * action needs one and is otherwise pixel-identical to a grid with no selection at all.
853
- * Vidyano's query grid does the same thing — it renders the checkbox column only if some
854
- * action is selection-gated.
855
- *
856
- * `'single'` when every gated action is satisfied by one row and refused by two; anything
857
- * else that cares about the count gets `'multiple'`.
858
- */
859
- declare function selectionModeFor(actions: CustomActionDefinition[]): SparkSelectionMode;
860
-
915
+ export declare function parseSelectionRule(rule?: string | null): (count: number) => boolean;
861
916
  /**
862
917
  * One selectable value, as replaced by a refresh hook. Mirrors the server's
863
918
  * `PersistentObjectAttributeOption`.
864
919
  */
865
- interface RefreshedOption {
866
- key: string;
867
- label?: Record<string, string>;
920
+ export interface RefreshedOption {
921
+ key: string;
922
+ label?: Record<string, string>;
868
923
  }
869
924
  /**
870
925
  * What a refresh changed about one attribute's *presentation*.
@@ -876,25 +931,25 @@ interface RefreshedOption {
876
931
  * Mutating the existing object instead is inert, because the rendering computed would not re-run.
877
932
  * An overlay is the only shape that is both reactive and free.
878
933
  */
879
- interface AttributeOverlay {
880
- isRequired?: boolean;
881
- isReadOnly?: boolean;
882
- isVisible?: boolean;
883
- rules?: ValidationRule[];
884
- query?: string;
885
- /** `undefined` means the hook did not touch the options; an empty array means there are none. */
886
- options?: RefreshedOption[];
934
+ export interface AttributeOverlay {
935
+ isRequired?: boolean;
936
+ isReadOnly?: boolean;
937
+ isVisible?: boolean;
938
+ rules?: ValidationRule[];
939
+ query?: string;
940
+ /** `undefined` means the hook did not touch the options; an empty array means there are none. */
941
+ options?: RefreshedOption[];
887
942
  }
888
- type RefreshOverlay = Record<string, AttributeOverlay>;
943
+ export type RefreshOverlay = Record<string, AttributeOverlay>;
889
944
  /** Applies an overlay to one attribute definition, returning a new object when anything changed. */
890
- declare function applyOverlay(attr: EntityAttributeDefinition, overlay: AttributeOverlay | undefined): EntityAttributeDefinition;
945
+ export declare function applyOverlay(attr: EntityAttributeDefinition, overlay: AttributeOverlay | undefined): EntityAttributeDefinition;
891
946
  /**
892
947
  * Reads a refresh response into an overlay.
893
948
  *
894
949
  * Everything here is presentation the server owns outright, so it is taken verbatim — there is no
895
950
  * merging to do on this half, only on values.
896
951
  */
897
- declare function overlayFromResponse(response: PersistentObject): RefreshOverlay;
952
+ export declare function overlayFromResponse(response: PersistentObject): RefreshOverlay;
898
953
  /**
899
954
  * Merges a refresh response's values into the live form.
900
955
  *
@@ -915,19 +970,18 @@ declare function overlayFromResponse(response: PersistentObject): RefreshOverlay
915
970
  * @param current values as they are now, which may have moved on
916
971
  * @param response the reshaped object
917
972
  */
918
- declare function mergeRefreshValues(sent: Record<string, any>, current: Record<string, any>, response: PersistentObject): Record<string, any>;
919
-
973
+ export declare function mergeRefreshValues(sent: Record<string, any>, current: Record<string, any>, response: PersistentObject): Record<string, any>;
920
974
  /** One rule failure, in the shape the form already renders per field. */
921
- interface RuleFailure {
922
- attributeName: string;
923
- ruleType: string;
924
- message: string;
975
+ export interface RuleFailure {
976
+ attributeName: string;
977
+ ruleType: string;
978
+ message: string;
925
979
  }
926
- interface EvaluableAttribute {
927
- name: string;
928
- label?: TranslatedString;
929
- isRequired?: boolean;
930
- rules?: ValidationRule[];
980
+ export interface EvaluableAttribute {
981
+ name: string;
982
+ label?: TranslatedString;
983
+ isRequired?: boolean;
984
+ rules?: ValidationRule[];
931
985
  }
932
986
  /**
933
987
  * Evaluates an attribute's rules against a value, mirroring the server's `ValidationService`.
@@ -941,7 +995,4 @@ interface EvaluableAttribute {
941
995
  * This exists because a refresh hook that imposes a rule needs it to bite before Save — previously
942
996
  * `rules` was carried on the wire and never evaluated in the browser at all.
943
997
  */
944
- declare function evaluateRules(attr: EvaluableAttribute, value: any): RuleFailure[];
945
-
946
- export { AS_DETAIL_BREADCRUMBS_KEY, AS_DETAIL_ORIGINAL_DATES_KEY, AS_DETAIL_ROW_KEY, AS_DETAIL_SELF_BREADCRUMB_KEY, ELookupDisplayType, EReferenceDisplayType, ShowedOn, applyOverlay, currentLanguage, dictToNestedPo, evaluateRules, filterQueryActions, fromDateInput, fromDateInputValue, fromDateTimeLocalInput, hasShowedOnFlag, isDateDataType, isPersistentObject, isQueryRow, isReservedAsDetailKey, mergeRefreshValues, nestedPoToDict, nestedPoToDisplayRow, overlayFromResponse, parseSelectionRule, parseWireDate, resolveTranslation, resolvedBreadcrumb, selectionModeFor, selfBreadcrumb, toDateInput, toDateInputValue, toDateTimeLocalInput, valueFor, wireDatesEqual };
947
- export type { AttributeGroup, AttributeOverlay, AttributeTab, CustomActionDefinition, DistinctValue, DistinctValuesResult, EntityAttributeDefinition, EntityPermissions, EntityType, EntityTypeResolver, EvaluableAttribute, LookupReference, LookupReferenceListItem, LookupReferenceValue, PersistentObject, PersistentObjectAttribute, PersistentObjectPermissions, ProgramUnit, ProgramUnitGroup, ProgramUnitsConfiguration, QueryColumn, QueryColumnFilter, QueryResult, QueryResultItem, QueryResultItemValue, RefreshOverlay, RefreshedOption, RetryActionPayload, RetryActionResult, RuleFailure, SparkCellColumn, SparkQuery, SparkQueryRenderMode, SparkQuerySortColumn, SparkRow, SparkSelectionMode, StreamingErrorMessage, StreamingMessage, StreamingPatchItem, StreamingPatchMessage, StreamingSnapshotMessage, TranslatedString, ValidationError, ValidationErrorResponse, ValidationRule };
998
+ export declare function evaluateRules(attr: EvaluableAttribute, value: any): RuleFailure[];