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