@mintplayer/ng-spark 22.32.0 → 22.34.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 (81) hide show
  1. package/README.md +101 -1
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +3 -3
  3. package/fesm2022/mintplayer-ng-spark-auth-account.mjs +18 -18
  4. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +16 -8
  5. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +3 -3
  7. package/fesm2022/mintplayer-ng-spark-auth-core.mjs +6 -6
  8. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +3 -3
  9. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +3 -3
  10. package/fesm2022/mintplayer-ng-spark-auth-login.mjs +3 -3
  11. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +3 -3
  12. package/fesm2022/mintplayer-ng-spark-auth-register.mjs +3 -3
  13. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +3 -3
  14. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +3 -3
  15. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +3 -3
  16. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +24 -24
  17. package/fesm2022/mintplayer-ng-spark-column-filter.mjs +3 -3
  18. package/fesm2022/mintplayer-ng-spark-contributions.mjs +9 -9
  19. package/fesm2022/mintplayer-ng-spark-dashboards.mjs +726 -0
  20. package/fesm2022/mintplayer-ng-spark-dashboards.mjs.map +1 -0
  21. package/fesm2022/mintplayer-ng-spark-grid.mjs +220 -49
  22. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  23. package/fesm2022/mintplayer-ng-spark-history.mjs +9 -9
  24. package/fesm2022/mintplayer-ng-spark-icon.mjs +3 -3
  25. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +3 -3
  26. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +9 -9
  27. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +6 -6
  28. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +3 -3
  29. package/fesm2022/mintplayer-ng-spark-models.mjs +44 -1
  30. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  31. package/fesm2022/mintplayer-ng-spark-moderation.mjs +21 -21
  32. package/fesm2022/mintplayer-ng-spark-notifications.mjs +687 -0
  33. package/fesm2022/mintplayer-ng-spark-notifications.mjs.map +1 -0
  34. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs +179 -0
  35. package/fesm2022/mintplayer-ng-spark-organizations-accept.mjs.map +1 -0
  36. package/fesm2022/mintplayer-ng-spark-organizations.mjs +202 -0
  37. package/fesm2022/mintplayer-ng-spark-organizations.mjs.map +1 -0
  38. package/fesm2022/mintplayer-ng-spark-panels.mjs +9 -1
  39. package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -1
  40. package/fesm2022/mintplayer-ng-spark-pipes.mjs +125 -85
  41. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  42. package/fesm2022/mintplayer-ng-spark-po-create.mjs +3 -3
  43. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +95 -29
  44. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  45. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +84 -12
  46. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  47. package/fesm2022/mintplayer-ng-spark-po-form.mjs +9 -9
  48. package/fesm2022/mintplayer-ng-spark-pwa.mjs +74 -6
  49. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -1
  50. package/fesm2022/mintplayer-ng-spark-query-list.mjs +10 -109
  51. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  52. package/fesm2022/mintplayer-ng-spark-renderers.mjs +3 -3
  53. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +3 -3
  54. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs +801 -0
  55. package/fesm2022/mintplayer-ng-spark-score-renderers.mjs.map +1 -0
  56. package/fesm2022/mintplayer-ng-spark-services.mjs +424 -72
  57. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  58. package/fesm2022/mintplayer-ng-spark-share-page.mjs +334 -0
  59. package/fesm2022/mintplayer-ng-spark-share-page.mjs.map +1 -0
  60. package/fesm2022/mintplayer-ng-spark-share.mjs +10 -0
  61. package/fesm2022/mintplayer-ng-spark-share.mjs.map +1 -0
  62. package/fesm2022/mintplayer-ng-spark-shell.mjs +38 -36
  63. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  64. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +9 -9
  65. package/package.json +29 -1
  66. package/types/mintplayer-ng-spark-dashboards.d.ts +226 -0
  67. package/types/mintplayer-ng-spark-grid.d.ts +77 -26
  68. package/types/mintplayer-ng-spark-models.d.ts +150 -17
  69. package/types/mintplayer-ng-spark-notifications.d.ts +188 -0
  70. package/types/mintplayer-ng-spark-organizations-accept.d.ts +40 -0
  71. package/types/mintplayer-ng-spark-organizations.d.ts +72 -0
  72. package/types/mintplayer-ng-spark-panels.d.ts +16 -0
  73. package/types/mintplayer-ng-spark-pipes.d.ts +10 -0
  74. package/types/mintplayer-ng-spark-po-detail.d.ts +29 -0
  75. package/types/mintplayer-ng-spark-po-edit.d.ts +37 -0
  76. package/types/mintplayer-ng-spark-pwa.d.ts +48 -2
  77. package/types/mintplayer-ng-spark-query-list.d.ts +8 -23
  78. package/types/mintplayer-ng-spark-score-renderers.d.ts +340 -0
  79. package/types/mintplayer-ng-spark-services.d.ts +146 -6
  80. package/types/mintplayer-ng-spark-share-page.d.ts +67 -0
  81. package/types/mintplayer-ng-spark-share.d.ts +26 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mintplayer/ng-spark",
3
3
  "private": false,
4
- "version": "22.32.0",
4
+ "version": "22.34.0",
5
5
  "description": "Angular component library for MintPlayer.Spark CRUD applications",
6
6
  "repository": {
7
7
  "type": "git",
@@ -120,6 +120,10 @@
120
120
  "types": "./types/mintplayer-ng-spark-contributions.d.ts",
121
121
  "default": "./fesm2022/mintplayer-ng-spark-contributions.mjs"
122
122
  },
123
+ "./dashboards": {
124
+ "types": "./types/mintplayer-ng-spark-dashboards.d.ts",
125
+ "default": "./fesm2022/mintplayer-ng-spark-dashboards.mjs"
126
+ },
123
127
  "./grid": {
124
128
  "types": "./types/mintplayer-ng-spark-grid.d.ts",
125
129
  "default": "./fesm2022/mintplayer-ng-spark-grid.mjs"
@@ -160,6 +164,18 @@
160
164
  "types": "./types/mintplayer-ng-spark-moderation.d.ts",
161
165
  "default": "./fesm2022/mintplayer-ng-spark-moderation.mjs"
162
166
  },
167
+ "./notifications": {
168
+ "types": "./types/mintplayer-ng-spark-notifications.d.ts",
169
+ "default": "./fesm2022/mintplayer-ng-spark-notifications.mjs"
170
+ },
171
+ "./organizations": {
172
+ "types": "./types/mintplayer-ng-spark-organizations.d.ts",
173
+ "default": "./fesm2022/mintplayer-ng-spark-organizations.mjs"
174
+ },
175
+ "./organizations/accept": {
176
+ "types": "./types/mintplayer-ng-spark-organizations-accept.d.ts",
177
+ "default": "./fesm2022/mintplayer-ng-spark-organizations-accept.mjs"
178
+ },
163
179
  "./panels": {
164
180
  "types": "./types/mintplayer-ng-spark-panels.d.ts",
165
181
  "default": "./fesm2022/mintplayer-ng-spark-panels.mjs"
@@ -204,10 +220,22 @@
204
220
  "types": "./types/mintplayer-ng-spark-routes.d.ts",
205
221
  "default": "./fesm2022/mintplayer-ng-spark-routes.mjs"
206
222
  },
223
+ "./score-renderers": {
224
+ "types": "./types/mintplayer-ng-spark-score-renderers.d.ts",
225
+ "default": "./fesm2022/mintplayer-ng-spark-score-renderers.mjs"
226
+ },
207
227
  "./services": {
208
228
  "types": "./types/mintplayer-ng-spark-services.d.ts",
209
229
  "default": "./fesm2022/mintplayer-ng-spark-services.mjs"
210
230
  },
231
+ "./share": {
232
+ "types": "./types/mintplayer-ng-spark-share.d.ts",
233
+ "default": "./fesm2022/mintplayer-ng-spark-share.mjs"
234
+ },
235
+ "./share/page": {
236
+ "types": "./types/mintplayer-ng-spark-share-page.d.ts",
237
+ "default": "./fesm2022/mintplayer-ng-spark-share-page.mjs"
238
+ },
211
239
  "./shell": {
212
240
  "types": "./types/mintplayer-ng-spark-shell.d.ts",
213
241
  "default": "./fesm2022/mintplayer-ng-spark-shell.mjs"
@@ -0,0 +1,226 @@
1
+ import { SparkAttributeColumnRenderer, SparkAttributeDetailRenderer, SparkAttributeRendererRegistration } from "@mintplayer/ng-spark/renderers";
2
+ import * as i0 from "@angular/core";
3
+ import { Provider, Type } from "@angular/core";
4
+ import { Color } from "@mintplayer/ng-bootstrap";
5
+ import { SparkLanguageService } from "@mintplayer/ng-spark/services";
6
+ import { EntityAttributeDefinition, PersistentObject, TranslatedString } from "@mintplayer/ng-spark/models";
7
+ import { SparkDetailContext } from "@mintplayer/ng-spark/panels";
8
+ /** How a dashboard value is written: a plain number, bytes (binary units), a percentage, a date, or as is. */
9
+ export type SparkDashboardFormat = 'number' | 'bytes' | 'percent' | 'date' | 'datetime' | 'text';
10
+ /** A finite number from a wire value (a number or a numeric string), else null. */
11
+ export declare function toSparkNumber(value: unknown): number | null;
12
+ /** 1536 → "1.5 KB": binary units, one decimal from KB up; "—" for no value. */
13
+ export declare function formatSparkBytes(bytes: number | null | undefined, locale?: string): string;
14
+ /**
15
+ * A value as a dashboard writes it: "—" for none; numbers by `format` (`decimals` places for `number`/`percent`,
16
+ * default 0); dates in the viewer's locale; anything else as text.
17
+ */
18
+ export declare function formatSparkDashboardValue(value: unknown, format: SparkDashboardFormat | undefined, locale?: string, decimals?: number): string;
19
+ /** The colour a threshold gives a value: `danger` at or past `danger`, `warning` at or past `warning`, else none. */
20
+ export declare function sparkThresholdColor(value: number | null, thresholds: {
21
+ warning?: number;
22
+ danger?: number;
23
+ direction?: 'above' | 'below';
24
+ }): 'warning' | 'danger' | null;
25
+ /** `rendererOptions` of the `spark-stat` renderer. Every member is optional. */
26
+ export interface SparkStatOptions {
27
+ /** How the value is written. Default `'number'`. */
28
+ format?: SparkDashboardFormat;
29
+ /** Decimals for `number` (default 0) and `percent` (default 1). */
30
+ decimals?: number;
31
+ /** Text after the value, e.g. `' sites'`. Default none. */
32
+ suffix?: string;
33
+ /** The value turns amber at or past this. */
34
+ warning?: number;
35
+ /** The value turns red at or past this. */
36
+ danger?: number;
37
+ /** `'above'` (default): high is bad, as for disk use. `'below'`: low is bad, as for free space. */
38
+ direction?: 'above' | 'below';
39
+ }
40
+ /**
41
+ * The `spark-stat` renderer: one figure, large on a detail page (a dashboard tile) and plain in a grid cell,
42
+ * coloured amber or red once it passes the model's `warning` / `danger` threshold. No value draws a muted dash.
43
+ */
44
+ export declare class SparkStatComponent implements SparkAttributeColumnRenderer, SparkAttributeDetailRenderer {
45
+ private readonly locale;
46
+ readonly value: import("@angular/core").InputSignal<any>;
47
+ readonly options: import("@angular/core").InputSignal<Record<string, any> | undefined>;
48
+ private readonly settings;
49
+ protected readonly text: import("@angular/core").Signal<string>;
50
+ protected readonly color: import("@angular/core").Signal<"warning" | "danger" | null>;
51
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkStatComponent, never>;
52
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkStatComponent, "spark-stat", never, {
53
+ "value": {
54
+ "alias": "value";
55
+ "required": false;
56
+ "isSignal": true;
57
+ };
58
+ "options": {
59
+ "alias": "options";
60
+ "required": false;
61
+ "isSignal": true;
62
+ };
63
+ }, {}, never, never, true, never>;
64
+ }
65
+ /** `rendererOptions` of the `spark-meter` renderer. Every member is optional. */
66
+ export interface SparkMeterOptions {
67
+ /** The ceiling when the value is a plain number (a value `{ value, max }` brings its own). Default 100. */
68
+ max?: number;
69
+ /** How the value and the ceiling are written. Default `'number'`. */
70
+ format?: SparkDashboardFormat;
71
+ /** Amber from this share of the ceiling, in percent. Default 75. */
72
+ warning?: number;
73
+ /** Red from this share of the ceiling, in percent. Default 90. */
74
+ danger?: number;
75
+ /** The meter's accessible name. Default `'Usage'`. */
76
+ label?: string;
77
+ }
78
+ interface MeterView {
79
+ value: number;
80
+ max: number;
81
+ /** The share of the ceiling, in percent (0–100), one decimal. */
82
+ share: number;
83
+ color: Color;
84
+ text: string;
85
+ }
86
+ /**
87
+ * The `spark-meter` renderer: how much of something is used, as a bar coloured by its share (green, amber from 75 %,
88
+ * red from 90 % by default) with "used of total (share)" next to it. The value is a number against the `max` option, or
89
+ * `{ value, max }` from the server (the dashboards package's `DashboardValues.Meter`), e.g. disk bytes used of the total.
90
+ * No value, or no ceiling, draws a muted dash.
91
+ */
92
+ export declare class SparkMeterComponent implements SparkAttributeColumnRenderer, SparkAttributeDetailRenderer {
93
+ private readonly locale;
94
+ readonly value: import("@angular/core").InputSignal<any>;
95
+ readonly options: import("@angular/core").InputSignal<Record<string, any> | undefined>;
96
+ private readonly settings;
97
+ protected readonly label: import("@angular/core").Signal<string>;
98
+ protected readonly view: import("@angular/core").Signal<MeterView | null>;
99
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkMeterComponent, never>;
100
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkMeterComponent, "spark-meter", never, {
101
+ "value": {
102
+ "alias": "value";
103
+ "required": false;
104
+ "isSignal": true;
105
+ };
106
+ "options": {
107
+ "alias": "options";
108
+ "required": false;
109
+ "isSignal": true;
110
+ };
111
+ }, {}, never, never, true, never>;
112
+ }
113
+ /** One column of a `spark-dashboard-table`. */
114
+ export interface SparkDashboardTableColumn {
115
+ /** The row field. For a value that is a dictionary, its rows are `{ key, value }`. */
116
+ key: string;
117
+ /** A translation key for the header (`translations.json`), shown as is when it has no translation. Default `key`. */
118
+ label?: string;
119
+ /** How the cells are written. Numbers and bytes are right-aligned. Default: as is. */
120
+ format?: SparkDashboardFormat | 'boolean';
121
+ /** Translate each cell as `{translate}{value}` (e.g. `'seoUi.runStatus.'`), falling back to the value. */
122
+ translate?: string;
123
+ }
124
+ /** `rendererOptions` of the `spark-dashboard-table` renderer. */
125
+ export interface SparkDashboardTableOptions {
126
+ /** The columns, in order. Default: the first row's fields. */
127
+ columns?: SparkDashboardTableColumn[];
128
+ /** A translation key shown when there are no rows. Default `'dashboards.empty'`. */
129
+ empty?: string;
130
+ }
131
+ interface TableCell {
132
+ text: string;
133
+ check: boolean;
134
+ }
135
+ /**
136
+ * The `spark-dashboard-table` renderer (detail pages): a dashboard value that is a list of rows, such as the
137
+ * RavenDB statistics' collections (D12: tables are JSON attributes). A dictionary (`{ "Completed": 3 }`) reads as
138
+ * rows `{ key, value }`.
139
+ */
140
+ export declare class SparkDashboardTableComponent implements SparkAttributeDetailRenderer {
141
+ private readonly locale;
142
+ protected readonly lang: SparkLanguageService;
143
+ readonly value: import("@angular/core").InputSignal<any>;
144
+ readonly options: import("@angular/core").InputSignal<Record<string, any> | undefined>;
145
+ protected readonly settings: import("@angular/core").Signal<SparkDashboardTableOptions>;
146
+ protected readonly rows: import("@angular/core").Signal<Record<string, unknown>[]>;
147
+ protected readonly columns: import("@angular/core").Signal<SparkDashboardTableColumn[]>;
148
+ protected readonly cells: import("@angular/core").Signal<TableCell[][]>;
149
+ protected header(column: SparkDashboardTableColumn): string;
150
+ protected isNumeric(column: SparkDashboardTableColumn | undefined): boolean;
151
+ private cell;
152
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkDashboardTableComponent, never>;
153
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkDashboardTableComponent, "spark-dashboard-table", never, {
154
+ "value": {
155
+ "alias": "value";
156
+ "required": false;
157
+ "isSignal": true;
158
+ };
159
+ "options": {
160
+ "alias": "options";
161
+ "required": false;
162
+ "isSignal": true;
163
+ };
164
+ }, {}, never, never, true, never>;
165
+ }
166
+ /** The rows of a table value: a list of objects as is, a dictionary as `{ key, value }` rows, anything else none. */
167
+ export declare function toSparkTableRows(value: unknown): Record<string, unknown>[];
168
+ /** The `metadata` key a dashboard carries (the dashboards package's `SparkDashboardActions.MetadataKey`). */
169
+ export declare const SPARK_DASHBOARD_METADATA_KEY = "dashboard";
170
+ /** Whether `item` is a dashboard: the server marks one with `metadata.dashboard`. */
171
+ export declare function isSparkDashboard(item: PersistentObject | null | undefined): boolean;
172
+ interface DashboardTile {
173
+ attribute: EntityAttributeDefinition;
174
+ /** Columns spanned: 1, 2, or the whole row. */
175
+ span: 1 | 2 | 'row';
176
+ component: Type<any> | null;
177
+ inputs: Record<string, any>;
178
+ /** The value as text, for an attribute without a detail renderer. */
179
+ text: string;
180
+ }
181
+ interface DashboardSection {
182
+ id: string;
183
+ name: string | null;
184
+ label?: TranslatedString;
185
+ tiles: DashboardTile[];
186
+ }
187
+ /**
188
+ * The dashboard tile layout (D12): replaces a dashboard's attribute cards on the routed detail page
189
+ * (`SPARK_DETAIL_LAYOUTS`, registered by `provideSparkDashboards()`). Each group of the model is a section, each
190
+ * attribute a tile: its label above its detail renderer (`spark-stat`, `spark-meter`, `spark-trend`,
191
+ * `spark-dashboard-table`, …) or its plain value. `columnSpan` 2 takes two columns, 3 and more (and a table without one) the
192
+ * whole row. An attribute the caller may not read is not on the object and has no tile. The page follows the object
193
+ * over `/spark/live` like any live type, so the tiles change in place.
194
+ */
195
+ export declare class SparkDashboardComponent {
196
+ private readonly locale;
197
+ private readonly renderers;
198
+ readonly context: import("@angular/core").InputSignal<SparkDetailContext>;
199
+ protected readonly sections: import("@angular/core").Signal<DashboardSection[]>;
200
+ /** When the server read the values, in the viewer's locale. */
201
+ protected readonly generatedAt: import("@angular/core").Signal<string | null>;
202
+ private tile;
203
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkDashboardComponent, never>;
204
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkDashboardComponent, "spark-dashboard", never, {
205
+ "context": {
206
+ "alias": "context";
207
+ "required": true;
208
+ "isSignal": true;
209
+ };
210
+ }, {}, never, never, true, never>;
211
+ }
212
+ /**
213
+ * The dashboard renderers under their model names. Renderers are one list, so add these to the app's own in its
214
+ * single `provideSparkAttributeRenderers` (or `provideSparkScoreRenderers`, which `spark-trend` comes from) call:
215
+ *
216
+ * @example
217
+ * provideSparkScoreRenderers([...appRenderers, ...sparkDashboardRenderers]),
218
+ * provideSparkDashboards(),
219
+ */
220
+ export declare const sparkDashboardRenderers: readonly SparkAttributeRendererRegistration[];
221
+ /**
222
+ * Draws every dashboard (an object the server marks with `metadata.dashboard`, the dashboards package's live virtual
223
+ * types) with the tile layout instead of the attribute cards, on the routed detail page. The renderers are registered
224
+ * separately: see {@link sparkDashboardRenderers}.
225
+ */
226
+ export declare function provideSparkDashboards(): Provider[];
@@ -219,23 +219,22 @@ export declare function sparkActionClass(definition: CustomActionDefinition, siz
219
219
  * The one Spark grid: a `<bs-datatable>` rendering a query or a sub-query.
220
220
  *
221
221
  * This replaces two components that were the same grid written twice — and, counting
222
- * `spark-query-list`'s streaming and paged branches, the `<bs-datatable>` element itself written
223
- * three times. The duplication had already produced four user-visible bugs, each fixed on one
224
- * side and not the other: `[indeterminate]` on null booleans, permission state surviving a failed
222
+ * `spark-query-list`'s former streaming and paged branches, the `<bs-datatable>` element itself
223
+ * written three times. The duplication had already produced four user-visible bugs, each fixed on
224
+ * one side and not the other: `[indeterminate]` on null booleans, permission state surviving a failed
225
225
  * reload, a swallowed fetch failure, and virtual-scroll sizing. `SparkGridRenderers` was
226
226
  * extracted to stop that and could only take the stateless helpers with it; this takes the rest.
227
227
  *
228
- * ## What it deliberately does NOT own: streaming
228
+ * ## Where the rows come from
229
229
  *
230
- * `spark-grid-renderers.ts` records why — a websocket dependency graph must not reach the bundle
231
- * of every PO detail page, none of which stream. So rows can come from **either** side:
232
- *
233
- * - unbound `data` — the grid fetches for itself, paging and sorting server-side;
230
+ * - unbound `data` — the grid fetches for itself, paging and sorting server-side: over HTTP, or over
231
+ * `/spark/live` when the grid is live (see {@link live});
234
232
  * - bound `data` — the grid renders what it is given and never fetches.
235
233
  *
236
- * `spark-query-list` keeps the socket and feeds its snapshot in. This is the line `bs-datatable`
237
- * itself draws: `[data]` and `[fetch]` are mutually exclusive, and binding both makes `[fetch]`
238
- * win silently.
234
+ * This is the line `bs-datatable` itself draws: `[data]` and `[fetch]` are mutually exclusive, and
235
+ * binding both makes `[fetch]` win silently. Live rides on `[fetch]`, so server-side paging, sort,
236
+ * search and filters work the same live or not; every embedded grid, sub-query and card follows a live
237
+ * query, not only the query page.
239
238
  *
240
239
  * ## Reading its state
241
240
  *
@@ -246,6 +245,7 @@ export declare function sparkActionClass(definition: CustomActionDefinition, siz
246
245
  */
247
246
  export declare class SparkQueryGridComponent {
248
247
  private readonly sparkService;
248
+ private readonly liveService;
249
249
  private readonly gridRenderers;
250
250
  private readonly queryRefresh;
251
251
  private readonly injector;
@@ -282,11 +282,31 @@ export declare class SparkQueryGridComponent {
282
282
  /**
283
283
  * Rows supplied from outside. When bound, the grid renders these and runs no fetch.
284
284
  *
285
- * This is how streaming stays out of this component (see the class comment). `null` — the
286
- * default — means "fetch for yourself"; an empty array means "you have been given no rows",
287
- * which is a different statement and renders an empty grid rather than triggering a load.
285
+ * `null` — the default — means "fetch for yourself"; an empty array means "you have been given no
286
+ * rows", which is a different statement and renders an empty grid rather than triggering a load.
287
+ * Bound rows are never live.
288
288
  */
289
289
  data: import("@angular/core").InputSignal<QueryResultItem[] | null>;
290
+ /**
291
+ * Whether this grid follows its query over `/spark/live` (X4): `undefined` — the default — follows the
292
+ * query's own `live` flag; `true`/`false` overrides it (`[live]="false"` on a dashboard tile).
293
+ *
294
+ * Live, the grid's current request (page, sort, search, column filters, parent, deleted modes — exactly
295
+ * the `/execute` body) is ONE server subscription, sent again as an `update` on every page, sort, search
296
+ * or filter change, and every pushed page is rendered as it arrives: rows that change, appear or leave.
297
+ * A subscription `error` clears the rows and shows the message; it is not retried (see {@link isLive}).
298
+ * A virtual-scrolling query is never live: its blocks are separate pages fetched as the user scrolls, and
299
+ * one subscription follows one page.
300
+ */
301
+ live: import("@angular/core").InputSignal<boolean | undefined>;
302
+ /**
303
+ * The live subscription ended with an error for the query loaded now. Reset by a new load; a live grid
304
+ * does not resubscribe by itself after an error (the answer would not change).
305
+ */
306
+ private readonly liveFailed;
307
+ /** Whether rows arrive over `/spark/live` right now: the query page shows its LIVE badge from this. */
308
+ readonly isLive: import("@angular/core").Signal<boolean>;
309
+ private liveWanted;
290
310
  /**
291
311
  * Server-side search term, passed through to the query.
292
312
  *
@@ -539,10 +559,9 @@ export declare class SparkQueryGridComponent {
539
559
  deleteRows(rows: QueryResultItem[], definition?: CustomActionDefinition): Promise<void>;
540
560
  isVirtualScrolling: import("@angular/core").Signal<boolean>;
541
561
  /**
542
- * Columns as sent with the most recent fetched page.
562
+ * Columns as sent with the most recent fetched (or pushed) page.
543
563
  *
544
- * A host binding `data` (streaming) supplies its own via the `columns` input instead — the
545
- * snapshot carries them once, exactly as a paged result does.
564
+ * A host binding `data` supplies its own via the `columns` input instead.
546
565
  */
547
566
  private readonly fetchedColumns;
548
567
  /**
@@ -557,15 +576,7 @@ export declare class SparkQueryGridComponent {
557
576
  * each string names comes from `bsDatatableColumnLabel` in the template. See `sparkDatatableLabels`.
558
577
  */
559
578
  datatableLabels: import("@angular/core").Signal<import("@mintplayer/ng-spark/services").SparkDatatableLabels>;
560
- /**
561
- * True when rows do not come from this component's own fetch.
562
- *
563
- * Either because the host bound `data`, or because the query streams — a streaming query's rows
564
- * arrive over a socket and `/execute` is not a way to get them. The grid recognises the second
565
- * case itself rather than waiting to be told, because it resolves the query before the host can
566
- * see it: left to `data` alone, every streaming grid would fire one pointless fetch on mount,
567
- * before the host had anything to bind.
568
- */
579
+ /** True when rows do not come from this component's own fetch: the host bound `data`. */
569
580
  hasExternalData: import("@angular/core").Signal<boolean>;
570
581
  /**
571
582
  * Whether an action can run right now: its selection rule is satisfied, and the client method it
@@ -578,6 +589,22 @@ export declare class SparkQueryGridComponent {
578
589
  */
579
590
  actionUnavailableReason(action: CustomActionDefinition): string | null;
580
591
  constructor();
592
+ /** The live subscription of the query loaded now, while the grid is live. */
593
+ private liveSession;
594
+ private disposeLive;
595
+ /**
596
+ * A pushed page: re-fetch through the datatable, which lands in the live fetch and is served from
597
+ * memory. Through `reload()` rather than a new `[fetch]` identity, so page, sort and scroll stay.
598
+ */
599
+ private onLivePush;
600
+ /**
601
+ * A live subscription error (defect 4): clear the rows — a stale page must never stay up looking current
602
+ * — and say why. Not retried: `unavailable` (unknown, forbidden, not live, missing parent — one answer),
603
+ * `invalid`, `limit` and `failed` would answer the same again, and a socket-level failure (4429, a
604
+ * repeated 4401) is the socket's to surface. No HTTP fallback either: `unavailable` is the same answer
605
+ * `/execute` gives as a 404, and an app stream's rows exist only on the socket.
606
+ */
607
+ private onLiveError;
581
608
  /**
582
609
  * Re-run the query, keeping the current page, sort and scroll position.
583
610
  *
@@ -689,7 +716,26 @@ export declare class SparkQueryGridComponent {
689
716
  * rendered correctly as a page. Both grids are this one now, so there is one answer.
690
717
  */
691
718
  private resolveEntityType;
719
+ /** The datatable's `[fetch]`: over `/spark/live` when the grid is live (see {@link live}), else HTTP. */
692
720
  private makeFetch;
721
+ /**
722
+ * The `/execute` body for one datatable request — the same for HTTP and live. Read at call time, so
723
+ * what forces a refetch is the fetch identity `reload()` creates, not the value captured when the
724
+ * closure was built.
725
+ */
726
+ private requestFor;
727
+ /**
728
+ * One page as the datatable takes it, recording what the chrome reads off a result: count, columns,
729
+ * the result's withheld actions, the ids on screen.
730
+ */
731
+ private toPage;
732
+ /**
733
+ * Live: the request goes to this grid's {@link LiveGridSession} — subscribed on the first fetch, sent as
734
+ * an `update` when it changes, answered from the latest push when it does not. After an error every fetch
735
+ * is an empty page and the error stays shown.
736
+ */
737
+ private makeLiveFetch;
738
+ private makeHttpFetch;
693
739
  private loadLookupReferenceOptions;
694
740
  /**
695
741
  * The underlying value a custom renderer receives, which is not the printable one.
@@ -730,6 +776,11 @@ export declare class SparkQueryGridComponent {
730
776
  "required": false;
731
777
  "isSignal": true;
732
778
  };
779
+ "live": {
780
+ "alias": "live";
781
+ "required": false;
782
+ "isSignal": true;
783
+ };
733
784
  "search": {
734
785
  "alias": "search";
735
786
  "required": false;
@@ -341,6 +341,12 @@ export interface EntityType {
341
341
  */
342
342
  canRead?: boolean;
343
343
  alias?: string;
344
+ /**
345
+ * Whether an object of this type is followed over `/spark/live` (X4): `spark-po-detail` replaces the
346
+ * shown object when it changes and shows the unavailable state when it is deleted, and `spark-po-edit`
347
+ * adopts a newer version (or offers a merge when the form is dirty). Opt-in per type.
348
+ */
349
+ live?: boolean;
344
350
  /**
345
351
  * Breadcrumb template: literal text plus `{AttributeName}` placeholders. A scalar placeholder
346
352
  * renders its value; a reference placeholder renders the referenced entity's breadcrumb.
@@ -409,8 +415,12 @@ export interface SparkQuery {
409
415
  indexName?: string;
410
416
  /** Optional entity type name (e.g., "Person"). When set, used for entity type resolution. */
411
417
  entityType?: string;
412
- /** When true, this query uses WebSocket streaming with snapshot + patch updates. */
413
- isStreamingQuery?: boolean;
418
+ /**
419
+ * Whether grids showing this query follow it over `/spark/live` (X4): the page re-renders when a row in
420
+ * it changes, appears or disappears, with server-side paging, sort, search and filters intact. Opt-in
421
+ * per query; a grid's `[live]` input overrides it. See `docs/guide-live-updates.md`.
422
+ */
423
+ live?: boolean;
414
424
  /**
415
425
  * Whether the grid offers row selection (#460, D17). Absent is `'auto'`: derived from the offered
416
426
  * actions. A sub-query entry on the parent's type may override it.
@@ -734,30 +744,153 @@ export interface EntityPermissions {
734
744
  canRevertContribution?: boolean;
735
745
  }
736
746
  /**
737
- * The opening message of a stream: the column metadata plus the rows so far.
747
+ * The live-updates protocol (`/spark/live`, X4), as `docs/guide-live-updates.md` "The protocol" fixes it.
738
748
  *
739
- * Columns arrive once, here. A stream is one result whose rows arrive over time, so its shape is
740
- * fixed when it opens — patches carry values only.
749
+ * One WebSocket per browser tab carries every subscription; one JSON object per text frame. Subscriptions
750
+ * are reads only: mutations stay on HTTP.
741
751
  */
742
- export interface StreamingSnapshotMessage {
743
- type: 'snapshot';
744
- columns: QueryColumn[];
745
- data: QueryResultItem[];
746
- }
747
- export interface StreamingPatchItem {
752
+ /** A subscription to one persistent object: exactly the body of `POST /spark/po/load`. */
753
+ export interface LivePoRequest {
754
+ objectTypeId: string;
748
755
  id: string;
749
- /** Changed cell values, keyed by column name. Never metadata — the snapshot fixed that. */
750
- values: Record<string, any>;
756
+ deleted?: SparkDeletedFilter | null;
757
+ }
758
+ /** A sort column as `/spark/queries/execute` takes it. */
759
+ export interface LiveSortColumn {
760
+ property: string;
761
+ direction: 'asc' | 'desc';
762
+ }
763
+ /**
764
+ * A subscription to one page of a query: exactly the body of `POST /spark/queries/execute`
765
+ * (`retryResults` is ignored by the server — a push cannot prompt).
766
+ */
767
+ export interface LiveQueryRequest {
768
+ queryId: string;
769
+ skip?: number | null;
770
+ take?: number | null;
771
+ sortColumns?: LiveSortColumn[] | null;
772
+ search?: string | null;
773
+ columns?: QueryColumnFilter[] | null;
774
+ parentId?: string | null;
775
+ parentType?: string | null;
776
+ deleted?: SparkDeletedFilter | null;
777
+ parentDeleted?: SparkDeletedFilter | null;
778
+ }
779
+ export interface LiveSubscribePoOp {
780
+ op: 'subscribe';
781
+ sub: string;
782
+ po: LivePoRequest;
783
+ }
784
+ export interface LiveSubscribeQueryOp {
785
+ op: 'subscribe';
786
+ sub: string;
787
+ query: LiveQueryRequest;
788
+ }
789
+ /** The same query subscription with a new page, sort, search or filters; the server keeps the sub. */
790
+ export interface LiveUpdateOp {
791
+ op: 'update';
792
+ sub: string;
793
+ query: LiveQueryRequest;
794
+ }
795
+ export interface LiveUnsubscribeOp {
796
+ op: 'unsubscribe';
797
+ sub: string;
751
798
  }
752
- export interface StreamingPatchMessage {
799
+ export type LiveClientOp = LiveSubscribePoOp | LiveSubscribeQueryOp | LiveUpdateOp | LiveUnsubscribeOp;
800
+ /** `error` codes. Every one ends its subscription; the socket stays open. */
801
+ export type LiveErrorCode = 'unavailable' | 'invalid' | 'limit' | 'failed';
802
+ /** The changes to a held persistent object: top-level fields (`null` = removed) and whole attributes, by `name`. */
803
+ export interface LivePoPatch {
804
+ set?: Record<string, unknown>;
805
+ attributes?: PersistentObjectAttribute[];
806
+ }
807
+ /**
808
+ * The changes to a held query page. Apply: index the held rows by `id`; drop `remove`; replace or add each
809
+ * `upsert`; the rows are `order` mapped through the index when present, otherwise the held order minus the
810
+ * removed rows; then assign `set` (`totalItems`, `skip`, `take`, `disabledActions`, `typeHints`, …).
811
+ */
812
+ export interface LiveQueryPatch {
813
+ upsert?: QueryResultItem[];
814
+ remove?: string[];
815
+ order?: string[];
816
+ set?: Record<string, unknown>;
817
+ }
818
+ /** The first state of an object, or one whose set or order of attributes changed. Replace. */
819
+ export interface LivePoFrame {
820
+ type: 'po';
821
+ sub: string;
822
+ po: PersistentObject;
823
+ }
824
+ export interface LivePoPatchFrame {
825
+ type: 'poPatch';
826
+ sub: string;
827
+ patch: LivePoPatch;
828
+ }
829
+ /** Deleted or no longer readable (one answer). The subscription has ended. */
830
+ export interface LiveGoneFrame {
831
+ type: 'gone';
832
+ sub: string;
833
+ }
834
+ /** The first page, a page whose columns changed, or a change a patch would not make smaller. Replace. */
835
+ export interface LiveResultFrame {
836
+ type: 'result';
837
+ sub: string;
838
+ result: QueryResult;
839
+ }
840
+ export interface LivePatchFrame {
753
841
  type: 'patch';
754
- updated: StreamingPatchItem[];
842
+ sub: string;
843
+ patch: LiveQueryPatch;
755
844
  }
756
- export interface StreamingErrorMessage {
845
+ /** The subscription has ended. Without `sub` it answers an unparseable frame. */
846
+ export interface LiveErrorFrame {
757
847
  type: 'error';
848
+ sub?: string;
849
+ code: LiveErrorCode;
758
850
  message: string;
759
851
  }
760
- export type StreamingMessage = StreamingSnapshotMessage | StreamingPatchMessage | StreamingErrorMessage;
852
+ /** Heartbeat. Nothing to answer. */
853
+ export interface LivePingFrame {
854
+ type: 'ping';
855
+ }
856
+ export type LiveServerFrame = LivePoFrame | LivePoPatchFrame | LiveGoneFrame | LiveResultFrame | LivePatchFrame | LiveErrorFrame | LivePingFrame;
857
+ /** Close codes with a meaning of their own; any other code is reconnected with backoff. */
858
+ export declare const LIVE_CLOSE_REAUTHENTICATE = 4401;
859
+ export declare const LIVE_CLOSE_TOO_MANY_SOCKETS = 4429;
860
+ export declare const LIVE_CLOSE_MESSAGE_TOO_LARGE = 1009;
861
+ /**
862
+ * Why a watcher's subscription ended, beyond the server's own {@link LiveErrorCode}s — the socket-level
863
+ * failures the client surfaces instead of retrying:
864
+ * - `tooManySockets`: closed with 4429; not reconnected until a new watcher asks.
865
+ * - `unauthenticated`: closed with 4401 again within seconds of reconnecting for a 4401.
866
+ * - `messageTooLarge`: closed with 1009, a client bug; not retried blindly.
867
+ */
868
+ export type LiveFailureCode = LiveErrorCode | 'tooManySockets' | 'unauthenticated' | 'messageTooLarge';
869
+ export interface LiveErrorEvent {
870
+ type: 'error';
871
+ code: LiveFailureCode;
872
+ message: string;
873
+ }
874
+ /** `po`: the object with every patch applied. `gone` and `error` end the stream (it completes after them). */
875
+ export type LivePoEvent = {
876
+ type: 'po';
877
+ po: PersistentObject;
878
+ } | {
879
+ type: 'gone';
880
+ } | LiveErrorEvent;
881
+ /** `result`: the page with every patch applied. `error` ends the stream (it completes after it). */
882
+ export type LiveQueryEvent = {
883
+ type: 'result';
884
+ result: QueryResult;
885
+ } | LiveErrorEvent;
886
+ /**
887
+ * Applies a `patch` frame to a held page, exactly as the protocol says. A new id in `upsert` that `order`
888
+ * does not place (the server always sends `order` when membership changes, so this is defensive) is
889
+ * appended rather than dropped.
890
+ */
891
+ export declare function applyLiveQueryPatch(held: QueryResult, patch: LiveQueryPatch): QueryResult;
892
+ /** Applies a `poPatch` frame to a held object: `set` assigned, changed attributes replaced by `name`. */
893
+ export declare function applyLivePoPatch(held: PersistentObject, patch: LivePoPatch): PersistentObject;
761
894
  /**
762
895
  * Conversions between the wire format for a timestamp and what an `<input type="datetime-local">`
763
896
  * or `<input type="date">` will accept.