@mintplayer/ng-spark 22.6.0 → 22.8.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 (28) hide show
  1. package/fesm2022/mintplayer-ng-spark-grid.mjs +133 -52
  2. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  3. package/fesm2022/mintplayer-ng-spark-models.mjs +77 -7
  4. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  5. package/fesm2022/mintplayer-ng-spark-pipes.mjs +85 -4
  6. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  7. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +3 -3
  8. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  9. package/fesm2022/mintplayer-ng-spark-po-form.mjs +12 -14
  10. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  11. package/fesm2022/mintplayer-ng-spark-query-list.mjs +21 -7
  12. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  13. package/fesm2022/mintplayer-ng-spark-renderers.mjs +14 -1
  14. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  15. package/fesm2022/mintplayer-ng-spark-services.mjs +92 -5
  16. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  17. package/fesm2022/mintplayer-ng-spark-shell.mjs +41 -4
  18. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  19. package/package.json +1 -1
  20. package/types/mintplayer-ng-spark-grid.d.ts +75 -33
  21. package/types/mintplayer-ng-spark-models.d.ts +132 -7
  22. package/types/mintplayer-ng-spark-pipes.d.ts +33 -5
  23. package/types/mintplayer-ng-spark-po-detail.d.ts +2 -2
  24. package/types/mintplayer-ng-spark-po-form.d.ts +7 -7
  25. package/types/mintplayer-ng-spark-query-list.d.ts +11 -4
  26. package/types/mintplayer-ng-spark-renderers.d.ts +29 -11
  27. package/types/mintplayer-ng-spark-services.d.ts +71 -2
  28. package/types/mintplayer-ng-spark-shell.d.ts +32 -2
@@ -1,6 +1,6 @@
1
1
  import { DatatableSettings, BsDatatableFetch } from '@mintplayer/ng-bootstrap/datatable';
2
2
  import * as _mintplayer_ng_spark_models from '@mintplayer/ng-spark/models';
3
- import { SparkQuery, EntityType, EntityAttributeDefinition, PersistentObject, LookupReference, CustomActionDefinition } from '@mintplayer/ng-spark/models';
3
+ import { SparkQuery, EntityType, EntityAttributeDefinition, SparkCellColumn, QueryResultItem, QueryColumn, LookupReference, CustomActionDefinition, PersistentObject } from '@mintplayer/ng-spark/models';
4
4
  import * as _angular_core from '@angular/core';
5
5
  import { Type, TemplateRef } from '@angular/core';
6
6
  import { HttpErrorResponse } from '@angular/common/http';
@@ -40,37 +40,33 @@ declare function isVirtualScrollingQuery(query: SparkQuery | null): boolean;
40
40
  * There is now one grid, {@link SparkQueryGridComponent}, so the drift cannot recur. This stays
41
41
  * separate because it is stateless service-shaped logic rather than view state, and because it is
42
42
  * what a custom grid would need in order to render Spark cells at all.
43
- *
44
- * The constraint that shaped the split still holds and is why the grid takes rows as an input
45
- * rather than owning a socket: streaming's dependency graph must not reach the bundle of every
46
- * detail page.
47
43
  */
48
44
  declare class SparkGridRenderers {
49
45
  private readonly registry;
50
46
  private readonly sparkService;
51
- /** The registered column component for an attribute, or null to fall back to the default cell. */
52
- columnComponentFor(attr: EntityAttributeDefinition): Type<any> | null;
47
+ /** The registered column component for a column, or null to fall back to the default cell. */
48
+ columnComponentFor(column: SparkCellColumn): Type<any> | null;
53
49
  /**
54
- * Inputs for a column renderer, filtered to what the component actually declares —
50
+ * Inputs for a query-grid cell renderer, filtered to what the component actually declares —
55
51
  * `NgComponentOutlet` throws on an input the target does not have, which is what lets every
56
52
  * member of the renderer contract be optional.
57
53
  */
58
- columnInputsFor(component: Type<any>, item: PersistentObject, attr: EntityAttributeDefinition): Record<string, any>;
54
+ columnInputsFor(component: Type<any>, item: QueryResultItem, column: QueryColumn): Record<string, any>;
59
55
  /**
60
- * The same contract for a row that is not a <see cref="PersistentObject"/>.
56
+ * The same contract for a row that is not a query result — an AsDetail sub-table row, which is
57
+ * a plain dictionary of embedded values with no id and no column metadata of its own, so its
58
+ * value cannot be read the way {@link columnInputsFor} reads one.
61
59
  *
62
- * An AsDetail row is a plain dictionary embedded values with no id and no attribute list so
63
- * its value cannot be read the way {@link columnInputsFor} reads one. Only the extraction
64
- * differs; the renderer contract is identical, and stating it once is what stops the two from
65
- * drifting the way the cell markup already had.
60
+ * Only the extraction differs; the renderer contract is identical, and stating it once is what
61
+ * stops the two from drifting the way the cell markup already had.
66
62
  */
67
- cellInputsFor(component: Type<any>, value: unknown, column: EntityAttributeDefinition, item: unknown): Record<string, any>;
63
+ cellInputsFor(component: Type<any>, value: unknown, column: SparkCellColumn, item: unknown): Record<string, any>;
68
64
  /**
69
- * Loads every lookup reference the visible attributes need, in one pass.
65
+ * Loads every lookup reference the visible columns need, in one pass.
70
66
  *
71
67
  * Returns an empty map rather than throwing when there are none, so callers never branch on it.
72
68
  */
73
- loadLookupOptions(attributes: EntityAttributeDefinition[]): Promise<Record<string, LookupReference>>;
69
+ loadLookupOptions(columns: readonly (SparkCellColumn | EntityAttributeDefinition)[]): Promise<Record<string, LookupReference>>;
74
70
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkGridRenderers, never>;
75
71
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<SparkGridRenderers>;
76
72
  }
@@ -243,7 +239,7 @@ declare class SparkQueryGridComponent {
243
239
  * default — means "fetch for yourself"; an empty array means "you have been given no rows",
244
240
  * which is a different statement and renders an empty grid rather than triggering a load.
245
241
  */
246
- data: _angular_core.InputSignal<PersistentObject[] | null>;
242
+ data: _angular_core.InputSignal<QueryResultItem[] | null>;
247
243
  /**
248
244
  * Server-side search term, passed through to the query.
249
245
  *
@@ -251,6 +247,11 @@ declare class SparkQueryGridComponent {
251
247
  * host with client-side rows filters them itself before binding `data`.
252
248
  */
253
249
  search: _angular_core.InputSignal<string>;
250
+ /**
251
+ * Column metadata for externally supplied rows. Required whenever `data` is bound, because a
252
+ * projection cannot describe itself — there is no per-row metadata left to infer it from.
253
+ */
254
+ columns: _angular_core.InputSignal<QueryColumn[] | null>;
254
255
  /**
255
256
  * Change this to re-run the query. Any value works; only its identity matters.
256
257
  *
@@ -269,14 +270,35 @@ declare class SparkQueryGridComponent {
269
270
  */
270
271
  settings: _angular_core.ModelSignal<DatatableSettings>;
271
272
  /** Rows the user has ticked. Two-way so chrome outside the grid can read and clear them. */
272
- selection: _angular_core.ModelSignal<PersistentObject[]>;
273
+ selection: _angular_core.ModelSignal<QueryResultItem[]>;
274
+ /**
275
+ * Replaces where the first column's link points, per row.
276
+ *
277
+ * Return `null` to suppress the link for that row — useful when a grid mixes rows that have a
278
+ * detail page with rows that do not.
279
+ *
280
+ * ⚠️ **It changes the destination, not the permission.** `canRead()` still gates whether any
281
+ * link is rendered at all, and this function is never consulted when that gate is closed. It
282
+ * cannot be used to reach a row the rights model withheld; it exists for the case where the
283
+ * row's natural destination is not `/po/{type}/{id}` — a composed row that maps to a page of
284
+ * its own, or a grid embedded in a route that owns its own child paths.
285
+ */
286
+ rowRoute: _angular_core.InputSignal<((row: QueryResultItem) => unknown[] | null) | null>;
273
287
  /** Emitted whenever a load or a page fetch fails, for a host in bespoke chrome. */
274
288
  error: _angular_core.OutputEmitterRef<HttpErrorResponse>;
275
- rowClicked: _angular_core.OutputEmitterRef<PersistentObject>;
289
+ rowClicked: _angular_core.OutputEmitterRef<QueryResultItem>;
276
290
  customActionExecuted: _angular_core.OutputEmitterRef<{
277
291
  action: CustomActionDefinition;
278
292
  }>;
279
293
  colors: typeof Color;
294
+ /**
295
+ * Where the first column's link points for this row: the host's `rowRoute` when it supplied one,
296
+ * otherwise the row's own detail page.
297
+ *
298
+ * A method rather than a computed because the answer is per row, and the template already
299
+ * iterates rows — a computed would have to be a map keyed by id, rebuilt on every page.
300
+ */
301
+ protected rowRouteFor(row: QueryResultItem): unknown[] | null;
280
302
  query: _angular_core.WritableSignal<SparkQuery | null>;
281
303
  entityType: _angular_core.WritableSignal<EntityType | null>;
282
304
  allEntityTypes: _angular_core.WritableSignal<EntityType[]>;
@@ -286,7 +308,7 @@ declare class SparkQueryGridComponent {
286
308
  canCreate: _angular_core.WritableSignal<boolean>;
287
309
  resultCount: _angular_core.WritableSignal<number | null>;
288
310
  customActions: _angular_core.WritableSignal<CustomActionDefinition[]>;
289
- fetchFn: _angular_core.WritableSignal<BsDatatableFetch<PersistentObject> | null>;
311
+ fetchFn: _angular_core.WritableSignal<BsDatatableFetch<QueryResultItem> | null>;
290
312
  /**
291
313
  * Why the component renders its own failure instead of only reporting one.
292
314
  *
@@ -299,7 +321,21 @@ declare class SparkQueryGridComponent {
299
321
  /** 'none' unless an action is selection-gated, so unaffected grids gain no checkbox column. */
300
322
  selectionMode: _angular_core.Signal<_mintplayer_ng_spark_models.SparkSelectionMode>;
301
323
  isVirtualScrolling: _angular_core.Signal<boolean>;
302
- visibleAttributes: _angular_core.Signal<EntityAttributeDefinition[]>;
324
+ /**
325
+ * Columns as sent with the most recent fetched page.
326
+ *
327
+ * A host binding `data` (streaming) supplies its own via the `columns` input instead — the
328
+ * snapshot carries them once, exactly as a paged result does.
329
+ */
330
+ private readonly fetchedColumns;
331
+ /** Every column on the wire, drawn or not — what renderers and lookups resolve against. */
332
+ allColumns: _angular_core.Signal<QueryColumn[]>;
333
+ /**
334
+ * The columns the grid draws. `isVisible: false` ships a value without a column, so a renderer
335
+ * can read a sibling the grid does not show; undefined means visible, so a server that predates
336
+ * the field still draws everything.
337
+ */
338
+ visibleColumns: _angular_core.Signal<QueryColumn[]>;
303
339
  /**
304
340
  * True when rows do not come from this component's own fetch.
305
341
  *
@@ -348,14 +384,15 @@ declare class SparkQueryGridComponent {
348
384
  /**
349
385
  * The underlying value a custom renderer receives, which is not the printable one.
350
386
  *
351
- * `rendererValue` unwraps an AsDetail attribute to the nested object (or objects) rather than
352
- * flattening it to text a renderer for a nested type needs the object, not a summary of it.
387
+ * A projection is flat, so this is the cell's own value rather than a display string. It is
388
+ * deliberately not the attribute-shaped `rendererValue`: a query row has no nested object to
389
+ * fall back to, and reaching for one would hand every renderer `undefined`.
353
390
  */
354
- rendererValueFor(item: PersistentObject, attr: EntityAttributeDefinition): unknown;
355
- getColumnRendererComponent(attr: EntityAttributeDefinition): Type<any> | null;
356
- getColumnRendererInputs(component: Type<any>, item: PersistentObject, attr: EntityAttributeDefinition): Record<string, any>;
391
+ rendererValueFor(item: QueryResultItem, column: QueryColumn): unknown;
392
+ getColumnRendererComponent(column: QueryColumn): Type<any> | null;
393
+ getColumnRendererInputs(component: Type<any>, item: QueryResultItem, column: QueryColumn): Record<string, any>;
357
394
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkQueryGridComponent, never>;
358
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkQueryGridComponent, "spark-query-grid", never, { "queryId": { "alias": "queryId"; "required": true; "isSignal": true; }; "parentId": { "alias": "parentId"; "required": false; "isSignal": true; }; "parentType": { "alias": "parentType"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "reloadToken": { "alias": "reloadToken"; "required": false; "isSignal": true; }; "settings": { "alias": "settings"; "required": false; "isSignal": true; }; "selection": { "alias": "selection"; "required": false; "isSignal": true; }; }, { "settings": "settingsChange"; "selection": "selectionChange"; "error": "error"; "rowClicked": "rowClicked"; "customActionExecuted": "customActionExecuted"; }, never, never, true, never>;
395
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkQueryGridComponent, "spark-query-grid", never, { "queryId": { "alias": "queryId"; "required": true; "isSignal": true; }; "parentId": { "alias": "parentId"; "required": false; "isSignal": true; }; "parentType": { "alias": "parentType"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "reloadToken": { "alias": "reloadToken"; "required": false; "isSignal": true; }; "settings": { "alias": "settings"; "required": false; "isSignal": true; }; "selection": { "alias": "selection"; "required": false; "isSignal": true; }; "rowRoute": { "alias": "rowRoute"; "required": false; "isSignal": true; }; }, { "settings": "settingsChange"; "selection": "selectionChange"; "error": "error"; "rowClicked": "rowClicked"; "customActionExecuted": "customActionExecuted"; }, never, never, true, never>;
359
396
  }
360
397
 
361
398
  /**
@@ -386,7 +423,7 @@ declare class SparkQueryCardComponent {
386
423
  parentId: _angular_core.InputSignal<string>;
387
424
  parentType: _angular_core.InputSignal<string>;
388
425
  reloadToken: _angular_core.InputSignal<unknown>;
389
- data: _angular_core.InputSignal<PersistentObject[] | null>;
426
+ data: _angular_core.InputSignal<QueryResultItem[] | null>;
390
427
  search: _angular_core.InputSignal<string>;
391
428
  /**
392
429
  * Slots forwarded from a host that cannot project content — see the class comment. A card
@@ -396,10 +433,15 @@ declare class SparkQueryCardComponent {
396
433
  captionTemplate: _angular_core.InputSignal<TemplateRef<any> | null>;
397
434
  actionsTemplate: _angular_core.InputSignal<TemplateRef<any> | null>;
398
435
  error: _angular_core.OutputEmitterRef<HttpErrorResponse>;
399
- rowClicked: _angular_core.OutputEmitterRef<PersistentObject>;
436
+ rowClicked: _angular_core.OutputEmitterRef<QueryResultItem>;
400
437
  customActionExecuted: _angular_core.OutputEmitterRef<{
401
438
  action: CustomActionDefinition;
402
439
  }>;
440
+ /**
441
+ * Forwarded to the grid. Without this the escape hatch was unreachable from a card — i.e. from
442
+ * every auto-rendered sub-query and program-unit query page, which is most grids.
443
+ */
444
+ rowRoute: _angular_core.InputSignal<((row: QueryResultItem) => unknown[] | null) | null>;
403
445
  colors: typeof Color;
404
446
  private readonly iconSlots;
405
447
  private readonly captionSlots;
@@ -425,9 +467,9 @@ declare class SparkQueryCardComponent {
425
467
  /** The caption the card renders unless a slot replaces it. */
426
468
  protected readonly caption: _angular_core.Signal<string>;
427
469
  protected readonly customActions: _angular_core.Signal<CustomActionDefinition[]>;
428
- protected readonly selection: _angular_core.Signal<PersistentObject[]>;
470
+ protected readonly selection: _angular_core.Signal<QueryResultItem[]>;
429
471
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkQueryCardComponent, never>;
430
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkQueryCardComponent, "spark-query-card", never, { "queryId": { "alias": "queryId"; "required": true; "isSignal": true; }; "parentId": { "alias": "parentId"; "required": false; "isSignal": true; }; "parentType": { "alias": "parentType"; "required": false; "isSignal": true; }; "reloadToken": { "alias": "reloadToken"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "iconTemplate": { "alias": "iconTemplate"; "required": false; "isSignal": true; }; "captionTemplate": { "alias": "captionTemplate"; "required": false; "isSignal": true; }; "actionsTemplate": { "alias": "actionsTemplate"; "required": false; "isSignal": true; }; }, { "error": "error"; "rowClicked": "rowClicked"; "customActionExecuted": "customActionExecuted"; }, ["iconSlots", "captionSlots", "actionSlots"], never, true, never>;
472
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkQueryCardComponent, "spark-query-card", never, { "queryId": { "alias": "queryId"; "required": true; "isSignal": true; }; "parentId": { "alias": "parentId"; "required": false; "isSignal": true; }; "parentType": { "alias": "parentType"; "required": false; "isSignal": true; }; "reloadToken": { "alias": "reloadToken"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "search": { "alias": "search"; "required": false; "isSignal": true; }; "iconTemplate": { "alias": "iconTemplate"; "required": false; "isSignal": true; }; "captionTemplate": { "alias": "captionTemplate"; "required": false; "isSignal": true; }; "actionsTemplate": { "alias": "actionsTemplate"; "required": false; "isSignal": true; }; "rowRoute": { "alias": "rowRoute"; "required": false; "isSignal": true; }; }, { "error": "error"; "rowClicked": "rowClicked"; "customActionExecuted": "customActionExecuted"; }, ["iconSlots", "captionSlots", "actionSlots"], never, true, never>;
431
473
  }
432
474
 
433
475
  /**
@@ -461,7 +503,7 @@ declare class SparkQueryCardComponent {
461
503
  declare class SparkGridCellComponent {
462
504
  private readonly renderers;
463
505
  /** The column being rendered. Its `dataType` and `renderer` decide everything below. */
464
- column: _angular_core.InputSignal<EntityAttributeDefinition>;
506
+ column: _angular_core.InputSignal<SparkCellColumn>;
465
507
  /**
466
508
  * What the caller's value pipe produced: the display text for most columns, and the raw value
467
509
  * for `boolean` and `color`, which need the value rather than a rendering of it.
@@ -189,7 +189,14 @@ interface EntityType {
189
189
  id: string;
190
190
  name: string;
191
191
  description?: TranslatedString;
192
- clrType: string;
192
+ /**
193
+ * The backing CLR type, or absent for a JSON-only virtual type (#325).
194
+ *
195
+ * Optional on purpose: the server sends null for a virtual type, and this was declared
196
+ * non-nullable, so any unguarded `.endsWith(...)` on it threw a TypeError naming an innocent
197
+ * query. Types are data from the wire, not a promise about it.
198
+ */
199
+ clrType?: string;
193
200
  alias?: string;
194
201
  /**
195
202
  * Breadcrumb template: literal text plus `{AttributeName}` placeholders. A scalar placeholder
@@ -230,12 +237,122 @@ interface SparkQuery {
230
237
  isStreamingQuery?: boolean;
231
238
  }
232
239
 
240
+ /**
241
+ * What a query returns: the column metadata once, then one lightweight row per result.
242
+ *
243
+ * Rows used to be full `PersistentObject`s, so every row repeated the complete attribute
244
+ * metadata the client already held from `/spark/types` and never read off the row. More
245
+ * importantly it conflated two things that are not the same: a **row is a projection**, a
246
+ * **persistent object is a document**. Nothing here is entitled to act on a row — every
247
+ * mutating path re-loads by id and re-applies security server-side.
248
+ */
233
249
  interface QueryResult {
234
- data: PersistentObject[];
235
- totalRecords: number;
250
+ columns: QueryColumn[];
251
+ items: QueryResultItem[];
252
+ /** Rows matching the query after filtering, before paging. */
253
+ totalItems: number;
236
254
  skip: number;
237
255
  take: number;
256
+ typeHints?: Record<string, string>;
257
+ }
258
+ /**
259
+ * The subset of column metadata a cell needs in order to render.
260
+ *
261
+ * Both {@link QueryColumn} (a query result's column) and `EntityAttributeDefinition` (a detail
262
+ * page's attribute) satisfy this, which is what lets one cell component serve the query grid and
263
+ * the AsDetail sub-table without either side knowing about the other's shape.
264
+ */
265
+ interface SparkCellColumn {
266
+ name: string;
267
+ label?: TranslatedString;
268
+ dataType: string;
269
+ isArray?: boolean;
270
+ renderer?: string;
271
+ rendererOptions?: Record<string, any>;
272
+ referenceType?: string;
273
+ lookupReferenceType?: string;
274
+ asDetailType?: string;
238
275
  }
276
+ /** One column of a query result, sent once rather than repeated on every row. */
277
+ interface QueryColumn extends SparkCellColumn {
278
+ order: number;
279
+ isSortable?: boolean;
280
+ /**
281
+ * Whether the grid draws this column. `false` means the row carries the value but no column is
282
+ * rendered — for a renderer that needs a sibling value (a lock glyph beside a name) without
283
+ * spending a column on it. `showedOn` decides what ships; this decides what is drawn.
284
+ */
285
+ isVisible?: boolean;
286
+ /** The query backing a Reference column, by name — the client's option source. */
287
+ query?: string;
288
+ }
289
+ /**
290
+ * One row: an id, a display string, and a value per column.
291
+ *
292
+ * Deliberately too weak to act on — no attribute metadata, no `can` block, no etag, because none
293
+ * of those can be trusted from a projection.
294
+ */
295
+ interface QueryResultItem {
296
+ /** Never null and unique within a result; the server refuses anything else. */
297
+ id: string;
298
+ /** What to show when the row is named rather than tabulated (a reference picker's value). */
299
+ breadcrumb?: string;
300
+ values: QueryResultItemValue[];
301
+ /** Presentation hints for the whole row. Keys arrive lower-cased. */
302
+ typeHints?: Record<string, string>;
303
+ }
304
+ /** One cell. */
305
+ interface QueryResultItemValue {
306
+ /** The {@link QueryColumn.name} this value belongs to. */
307
+ key: string;
308
+ /** Typed JSON, not a string — the server converts on the way out. */
309
+ value?: any;
310
+ /** For a reference cell: the target document's id, so a link needs no second lookup. */
311
+ objectId?: string;
312
+ /** For a reference cell: the target's display string. */
313
+ breadcrumb?: string;
314
+ /** Resolved display labels per referenced id, for a multi-reference cell. */
315
+ breadcrumbs?: Record<string, string | null>;
316
+ typeHints?: Record<string, string>;
317
+ }
318
+ /**
319
+ * Every shape a renderer's `item` can arrive in.
320
+ *
321
+ * Three, and they are genuinely different objects rather than variations of one: a query grid hands
322
+ * a {@link QueryResultItem}, an AsDetail sub-table hands the flat record `nestedPoToDisplayRow`
323
+ * built, and a detail or form host hands the `PersistentObject` itself. A renderer reused across a
324
+ * grid and an AsDetail table therefore sees two of them, which is why {@link valueFor} reads all
325
+ * three rather than making every app write the branch.
326
+ */
327
+ type SparkRow = QueryResultItem | PersistentObject | Record<string, unknown>;
328
+ /**
329
+ * The value a row carries for a column, whatever shape the row is in — or `undefined` when it
330
+ * carries none.
331
+ *
332
+ * ```ts
333
+ * const isPrivate = valueFor(this.item(), 'IsPrivate')?.value === true;
334
+ * ```
335
+ *
336
+ * Always returns the **cell** rather than the bare value, because a reference cell is worth as much
337
+ * for its `objectId` as for its text, and a helper that discarded it would have to be replaced the
338
+ * first time a renderer wanted to link somewhere. `?.value` is the cost, once per call site.
339
+ *
340
+ * ⚠️ A value only reaches a grid row if its attribute is on the **query surface**. To read a sibling
341
+ * the grid does not draw, mark it `"showedOn": "Query", "isVisible": false` — shipped, not drawn. An
342
+ * attribute marked `"showedOn": "PersistentObject"` is absent from a row by design, and this returns
343
+ * `undefined` for it; that is the first thing to check when a sibling read comes back empty.
344
+ */
345
+ declare function valueFor(item: SparkRow | null | undefined, columnName: string): QueryResultItemValue | undefined;
346
+ /**
347
+ * Whether this row is a query result item.
348
+ *
349
+ * Discriminated on the *elements*, not just on `Array.isArray(values)`: a flat AsDetail record may
350
+ * legitimately have a column named `values` holding an array, and mistaking one for the other would
351
+ * silently read the wrong thing.
352
+ */
353
+ declare function isQueryRow(row: SparkRow | null | undefined): row is QueryResultItem;
354
+ /** Whether this row is a full persistent object. Discriminated on elements, as above. */
355
+ declare function isPersistentObject(row: SparkRow | null | undefined): row is PersistentObject;
239
356
 
240
357
  interface ProgramUnit {
241
358
  id: string;
@@ -334,13 +451,21 @@ interface CustomActionDefinition {
334
451
  offset: number;
335
452
  }
336
453
 
454
+ /**
455
+ * The opening message of a stream: the column metadata plus the rows so far.
456
+ *
457
+ * Columns arrive once, here. A stream is one result whose rows arrive over time, so its shape is
458
+ * fixed when it opens — patches carry values only.
459
+ */
337
460
  interface StreamingSnapshotMessage {
338
461
  type: 'snapshot';
339
- data: PersistentObject[];
462
+ columns: QueryColumn[];
463
+ data: QueryResultItem[];
340
464
  }
341
465
  interface StreamingPatchItem {
342
466
  id: string;
343
- attributes: Record<string, any>;
467
+ /** Changed cell values, keyed by column name. Never metadata — the snapshot fixed that. */
468
+ values: Record<string, any>;
344
469
  }
345
470
  interface StreamingPatchMessage {
346
471
  type: 'patch';
@@ -550,5 +675,5 @@ interface EvaluableAttribute {
550
675
  */
551
676
  declare function evaluateRules(attr: EvaluableAttribute, value: any): RuleFailure[];
552
677
 
553
- export { AS_DETAIL_BREADCRUMBS_KEY, AS_DETAIL_SELF_BREADCRUMB_KEY, ELookupDisplayType, EReferenceDisplayType, ShowedOn, applyOverlay, currentLanguage, dictToNestedPo, evaluateRules, filterQueryActions, hasShowedOnFlag, mergeRefreshValues, nestedPoToDict, nestedPoToDisplayRow, overlayFromResponse, parseSelectionRule, resolveTranslation, selectionModeFor, selfBreadcrumb };
554
- export type { AttributeGroup, AttributeOverlay, AttributeTab, CustomActionDefinition, EntityAttributeDefinition, EntityPermissions, EntityType, EntityTypeResolver, EvaluableAttribute, LookupReference, LookupReferenceListItem, LookupReferenceValue, PersistentObject, PersistentObjectAttribute, PersistentObjectPermissions, ProgramUnit, ProgramUnitGroup, ProgramUnitsConfiguration, QueryResult, RefreshOverlay, RefreshedOption, RetryActionPayload, RetryActionResult, RuleFailure, SparkQuery, SparkQueryRenderMode, SparkQuerySortColumn, SparkSelectionMode, StreamingErrorMessage, StreamingMessage, StreamingPatchItem, StreamingPatchMessage, StreamingSnapshotMessage, TranslatedString, ValidationError, ValidationErrorResponse, ValidationRule };
678
+ export { AS_DETAIL_BREADCRUMBS_KEY, AS_DETAIL_SELF_BREADCRUMB_KEY, ELookupDisplayType, EReferenceDisplayType, ShowedOn, applyOverlay, currentLanguage, dictToNestedPo, evaluateRules, filterQueryActions, hasShowedOnFlag, isPersistentObject, isQueryRow, mergeRefreshValues, nestedPoToDict, nestedPoToDisplayRow, overlayFromResponse, parseSelectionRule, resolveTranslation, selectionModeFor, selfBreadcrumb, valueFor };
679
+ export type { AttributeGroup, AttributeOverlay, AttributeTab, CustomActionDefinition, EntityAttributeDefinition, EntityPermissions, EntityType, EntityTypeResolver, EvaluableAttribute, LookupReference, LookupReferenceListItem, LookupReferenceValue, PersistentObject, PersistentObjectAttribute, PersistentObjectPermissions, ProgramUnit, ProgramUnitGroup, ProgramUnitsConfiguration, QueryColumn, QueryResult, QueryResultItem, QueryResultItemValue, RefreshOverlay, RefreshedOption, RetryActionPayload, RetryActionResult, RuleFailure, SparkCellColumn, SparkQuery, SparkQueryRenderMode, SparkQuerySortColumn, SparkRow, SparkSelectionMode, StreamingErrorMessage, StreamingMessage, StreamingPatchItem, StreamingPatchMessage, StreamingSnapshotMessage, TranslatedString, ValidationError, ValidationErrorResponse, ValidationRule };
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { PipeTransform } from '@angular/core';
3
- import { TranslatedString, PersistentObject, EntityType, LookupReference, EntityAttributeDefinition, ProgramUnit, EntityPermissions, ELookupDisplayType, LookupReferenceValue, ValidationError } from '@mintplayer/ng-spark/models';
3
+ import { TranslatedString, PersistentObject, EntityType, LookupReference, EntityAttributeDefinition, QueryResultItem, ProgramUnit, EntityPermissions, ELookupDisplayType, LookupReferenceValue, ValidationError, QueryColumn } from '@mintplayer/ng-spark/models';
4
4
 
5
5
  declare class TranslateKeyPipe implements PipeTransform {
6
6
  private readonly lang;
@@ -36,7 +36,7 @@ declare class RawAttributeValuePipe implements PipeTransform {
36
36
 
37
37
  declare class ReferenceDisplayValuePipe implements PipeTransform {
38
38
  private readonly lang;
39
- transform(attr: EntityAttributeDefinition, formData: Record<string, any>, referenceOptions: Record<string, PersistentObject[]>): string;
39
+ transform(attr: EntityAttributeDefinition, formData: Record<string, any>, referenceOptions: Record<string, QueryResultItem[]>): string;
40
40
  static ɵfac: i0.ɵɵFactoryDeclaration<ReferenceDisplayValuePipe, never>;
41
41
  static ɵpipe: i0.ɵɵPipeDeclaration<ReferenceDisplayValuePipe, "referenceDisplayValue", true>;
42
42
  }
@@ -97,7 +97,7 @@ declare class AsDetailColumnsPipe implements PipeTransform {
97
97
  }
98
98
 
99
99
  declare class AsDetailCellValuePipe implements PipeTransform {
100
- transform(row: Record<string, any>, parentAttr: EntityAttributeDefinition, col: EntityAttributeDefinition, asDetailRefOptions: Record<string, Record<string, PersistentObject[]>>): string;
100
+ transform(row: Record<string, any>, parentAttr: EntityAttributeDefinition, col: EntityAttributeDefinition, asDetailRefOptions: Record<string, Record<string, QueryResultItem[]>>): string;
101
101
  static ɵfac: i0.ɵɵFactoryDeclaration<AsDetailCellValuePipe, never>;
102
102
  static ɵpipe: i0.ɵɵPipeDeclaration<AsDetailCellValuePipe, "asDetailCellValue", true>;
103
103
  }
@@ -140,7 +140,7 @@ declare class LookupOptionsPipe implements PipeTransform {
140
140
  }
141
141
 
142
142
  declare class InlineRefOptionsPipe implements PipeTransform {
143
- transform(parentAttr: EntityAttributeDefinition, col: EntityAttributeDefinition, asDetailRefOptions: Record<string, Record<string, PersistentObject[]>>): PersistentObject[];
143
+ transform(parentAttr: EntityAttributeDefinition, col: EntityAttributeDefinition, asDetailRefOptions: Record<string, Record<string, QueryResultItem[]>>): QueryResultItem[];
144
144
  static ɵfac: i0.ɵɵFactoryDeclaration<InlineRefOptionsPipe, never>;
145
145
  static ɵpipe: i0.ɵɵPipeDeclaration<InlineRefOptionsPipe, "inlineRefOptions", true>;
146
146
  }
@@ -171,5 +171,33 @@ declare class ArrayValuePipe implements PipeTransform {
171
171
  static ɵpipe: i0.ɵɵPipeDeclaration<ArrayValuePipe, "arrayValue", true>;
172
172
  }
173
173
 
174
- export { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AttributeValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, ErrorForAttributePipe, IconNamePipe, InlineRefOptionsPipe, InputTypePipe, LookupDisplayTypePipe, LookupDisplayValuePipe, LookupOptionsPipe, RawAttributeValuePipe, ReferenceAttrValuePipe, ReferenceChipsPipe, ReferenceDisplayValuePipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, RouterLinkPipe, TranslateKeyPipe };
174
+ /**
175
+ * The display text of one query-grid cell.
176
+ *
177
+ * The attribute-shaped sibling of this ({@link AttributeValuePipe}) still serves detail and edit
178
+ * pages, which do work in attributes. They are deliberately not one pipe: a query row is a flat
179
+ * projection with no nested objects and no per-row metadata, so the fallbacks that make sense
180
+ * there — reaching into `attr.object`, recomputing a breadcrumb template client-side — are not
181
+ * merely unnecessary here, they are unreachable. Sharing the code would mean carrying branches
182
+ * that can never fire and inviting the next reader to feed one shape into the other.
183
+ */
184
+ declare class QueryCellValuePipe implements PipeTransform {
185
+ transform(column: QueryColumn, item: QueryResultItem | null, lookupRefOptions: Record<string, LookupReference>): any;
186
+ static ɵfac: i0.ɵɵFactoryDeclaration<QueryCellValuePipe, never>;
187
+ static ɵpipe: i0.ɵɵPipeDeclaration<QueryCellValuePipe, "queryCellValue", true>;
188
+ }
189
+ /**
190
+ * Resolves a multi-reference column (`dataType === 'Reference'`, `isArray === true`) to chips.
191
+ *
192
+ * The cell's `value` carries the id array and `breadcrumbs` the server-resolved id → label map,
193
+ * falling back to the id itself when a label is missing — the same display rule as a single
194
+ * reference, applied per id.
195
+ */
196
+ declare class QueryReferenceChipsPipe implements PipeTransform {
197
+ transform(column: QueryColumn, item: QueryResultItem | null): ReferenceChip[];
198
+ static ɵfac: i0.ɵɵFactoryDeclaration<QueryReferenceChipsPipe, never>;
199
+ static ɵpipe: i0.ɵɵPipeDeclaration<QueryReferenceChipsPipe, "queryReferenceChips", true>;
200
+ }
201
+
202
+ export { ArrayValuePipe, AsDetailCellValuePipe, AsDetailColumnsPipe, AsDetailDisplayValuePipe, AsDetailTypePipe, AttributeValuePipe, CanCreateDetailRowPipe, CanDeleteDetailRowPipe, ErrorForAttributePipe, IconNamePipe, InlineRefOptionsPipe, InputTypePipe, LookupDisplayTypePipe, LookupDisplayValuePipe, LookupOptionsPipe, QueryCellValuePipe, QueryReferenceChipsPipe, RawAttributeValuePipe, ReferenceAttrValuePipe, ReferenceChipsPipe, ReferenceDisplayValuePipe, ReferenceLinkRoutePipe, ResolveTranslationPipe, RouterLinkPipe, TranslateKeyPipe };
175
203
  export type { ReferenceChip };
@@ -2,7 +2,7 @@ import * as _angular_core from '@angular/core';
2
2
  import { TemplateRef, Type } from '@angular/core';
3
3
  import { Color } from '@mintplayer/ng-bootstrap';
4
4
  import { SparkLanguageService } from '@mintplayer/ng-spark/services';
5
- import { PersistentObject, EntityType, CustomActionDefinition, LookupReference, EntityAttributeDefinition, AttributeTab, AttributeGroup } from '@mintplayer/ng-spark/models';
5
+ import { PersistentObject, EntityType, CustomActionDefinition, LookupReference, QueryResultItem, EntityAttributeDefinition, AttributeTab, AttributeGroup } from '@mintplayer/ng-spark/models';
6
6
 
7
7
  declare class SparkPoDetailComponent {
8
8
  private readonly route;
@@ -45,7 +45,7 @@ declare class SparkPoDetailComponent {
45
45
  item: _angular_core.WritableSignal<PersistentObject | null>;
46
46
  lookupReferenceOptions: _angular_core.WritableSignal<Record<string, LookupReference>>;
47
47
  asDetailTypes: _angular_core.WritableSignal<Record<string, EntityType>>;
48
- asDetailReferenceOptions: _angular_core.WritableSignal<Record<string, Record<string, PersistentObject[]>>>;
48
+ asDetailReferenceOptions: _angular_core.WritableSignal<Record<string, Record<string, QueryResultItem[]>>>;
49
49
  type: string;
50
50
  id: string;
51
51
  canEdit: _angular_core.WritableSignal<boolean>;
@@ -4,7 +4,7 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop';
4
4
  import { Color } from '@mintplayer/ng-bootstrap';
5
5
  import { InMemoryTreeSelectProvider, TreeNode } from '@mintplayer/ng-bootstrap/tree-select';
6
6
  import * as _mintplayer_ng_spark_models from '@mintplayer/ng-spark/models';
7
- import { PersistentObject, EntityType, ValidationError, RefreshOverlay, LookupReference, EntityAttributeDefinition, EntityPermissions, ELookupDisplayType, EReferenceDisplayType, AttributeTab, AttributeGroup, LookupReferenceValue, RuleFailure } from '@mintplayer/ng-spark/models';
7
+ import { PersistentObject, EntityType, ValidationError, RefreshOverlay, QueryResultItem, LookupReference, EntityAttributeDefinition, EntityPermissions, ELookupDisplayType, EReferenceDisplayType, AttributeTab, AttributeGroup, LookupReferenceValue, RuleFailure } from '@mintplayer/ng-spark/models';
8
8
  import { DatatableSettings } from '@mintplayer/ng-bootstrap/datatable';
9
9
  import { PaginationResponse } from '@mintplayer/pagination';
10
10
 
@@ -87,7 +87,7 @@ declare class SparkPoFormComponent {
87
87
  refreshOverlay: _angular_core.WritableSignal<RefreshOverlay>;
88
88
  isRefreshing: _angular_core.WritableSignal<boolean>;
89
89
  colors: typeof Color;
90
- referenceOptions: _angular_core.WritableSignal<Record<string, PersistentObject[]>>;
90
+ referenceOptions: _angular_core.WritableSignal<Record<string, QueryResultItem[]>>;
91
91
  referenceProviders: _angular_core.WritableSignal<Record<string, InMemoryTreeSelectProvider>>;
92
92
  referenceNodes: _angular_core.WritableSignal<Record<string, Record<string, TreeNode>>>;
93
93
  asDetailTypes: _angular_core.WritableSignal<Record<string, EntityType>>;
@@ -97,7 +97,7 @@ declare class SparkPoFormComponent {
97
97
  showAsDetailModal: _angular_core.WritableSignal<boolean>;
98
98
  editingArrayIndex: _angular_core.WritableSignal<number | null>;
99
99
  asDetailPermissions: _angular_core.WritableSignal<Record<string, EntityPermissions>>;
100
- asDetailReferenceOptions: _angular_core.WritableSignal<Record<string, Record<string, PersistentObject[]>>>;
100
+ asDetailReferenceOptions: _angular_core.WritableSignal<Record<string, Record<string, QueryResultItem[]>>>;
101
101
  ELookupDisplayType: typeof ELookupDisplayType;
102
102
  EReferenceDisplayType: typeof EReferenceDisplayType;
103
103
  /**
@@ -229,7 +229,7 @@ declare class SparkReferencePickerComponent {
229
229
  /** Currently selected referenced id (or null when unset). */
230
230
  value: _angular_core.InputSignal<string | null>;
231
231
  /** Candidate items to pick from (the full query result, pre-loaded by the parent). */
232
- options: _angular_core.InputSignal<PersistentObject[]>;
232
+ options: _angular_core.InputSignal<QueryResultItem[]>;
233
233
  /** Target entity type's CLR type name; used to load the grid's column headers. */
234
234
  referenceType: _angular_core.InputSignal<string | undefined>;
235
235
  /** Renders the field with the Bootstrap is-invalid state. */
@@ -242,17 +242,17 @@ declare class SparkReferencePickerComponent {
242
242
  colors: typeof Color;
243
243
  showModal: _angular_core.WritableSignal<boolean>;
244
244
  entityType: _angular_core.WritableSignal<EntityType | null>;
245
- pagination: _angular_core.WritableSignal<PaginationResponse<PersistentObject> | undefined>;
245
+ pagination: _angular_core.WritableSignal<PaginationResponse<QueryResultItem> | undefined>;
246
246
  settings: _angular_core.WritableSignal<DatatableSettings>;
247
247
  searchTerm: string;
248
248
  visibleAttributes: _angular_core.Signal<_mintplayer_ng_spark_models.EntityAttributeDefinition[]>;
249
- rows: _angular_core.Signal<PersistentObject[]>;
249
+ rows: _angular_core.Signal<QueryResultItem[]>;
250
250
  displayValue: _angular_core.Signal<string>;
251
251
  open(): Promise<void>;
252
252
  onSearchChange(): void;
253
253
  applyFilter(): void;
254
254
  clearSearch(): void;
255
- select(item: PersistentObject): void;
255
+ select(item: QueryResultItem): void;
256
256
  close(): void;
257
257
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkReferencePickerComponent, never>;
258
258
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkReferencePickerComponent, "spark-reference-picker", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "referenceType": { "alias": "referenceType"; "required": false; "isSignal": true; }; "isInvalid": { "alias": "isInvalid"; "required": false; "isSignal": true; }; "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
@@ -1,5 +1,5 @@
1
1
  import * as _mintplayer_ng_spark_models from '@mintplayer/ng-spark/models';
2
- import { PersistentObject, CustomActionDefinition } from '@mintplayer/ng-spark/models';
2
+ import { QueryResultItem, CustomActionDefinition, QueryColumn } from '@mintplayer/ng-spark/models';
3
3
  import * as _angular_core from '@angular/core';
4
4
  import { TemplateRef } from '@angular/core';
5
5
  import { Color } from '@mintplayer/ng-bootstrap';
@@ -31,7 +31,12 @@ declare class SparkQueryListComponent {
31
31
  protected readonly lang: SparkLanguageService;
32
32
  extraActionsTemplate: _angular_core.InputSignal<TemplateRef<void> | null>;
33
33
  showCustomActions: _angular_core.InputSignal<boolean>;
34
- rowClicked: _angular_core.OutputEmitterRef<PersistentObject>;
34
+ /**
35
+ * Forwarded to the grid, so a query PAGE can replace its row links — the same reason the card
36
+ * forwards it. Without this the escape hatch existed only for a directly-embedded grid.
37
+ */
38
+ rowRoute: _angular_core.InputSignal<((row: QueryResultItem) => unknown[] | null) | null>;
39
+ rowClicked: _angular_core.OutputEmitterRef<QueryResultItem>;
35
40
  createClicked: _angular_core.OutputEmitterRef<void>;
36
41
  customActionExecuted: _angular_core.OutputEmitterRef<{
37
42
  action: CustomActionDefinition;
@@ -59,6 +64,8 @@ declare class SparkQueryListComponent {
59
64
  protected isActionEnabled(action: CustomActionDefinition): boolean;
60
65
  isStreaming: _angular_core.WritableSignal<boolean>;
61
66
  private streamingSub;
67
+ /** Columns as sent with the stream's snapshot; empty until it arrives. */
68
+ protected readonly streamColumns: _angular_core.WritableSignal<QueryColumn[]>;
62
69
  private readonly allItems;
63
70
  private readonly streamItems;
64
71
  /**
@@ -67,7 +74,7 @@ declare class SparkQueryListComponent {
67
74
  * Null for a normal query — an empty array would read as "here are no rows" and suppress the
68
75
  * fetch entirely.
69
76
  */
70
- protected readonly gridData: _angular_core.Signal<PersistentObject[] | null>;
77
+ protected readonly gridData: _angular_core.Signal<QueryResultItem[] | null>;
71
78
  constructor();
72
79
  private onParamsChange;
73
80
  /**
@@ -84,7 +91,7 @@ declare class SparkQueryListComponent {
84
91
  private handleStreamingMessage;
85
92
  private applyFilter;
86
93
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkQueryListComponent, never>;
87
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkQueryListComponent, "spark-query-list", never, { "extraActionsTemplate": { "alias": "extraActionsTemplate"; "required": false; "isSignal": true; }; "showCustomActions": { "alias": "showCustomActions"; "required": false; "isSignal": true; }; }, { "rowClicked": "rowClicked"; "createClicked": "createClicked"; "customActionExecuted": "customActionExecuted"; }, never, never, true, never>;
94
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkQueryListComponent, "spark-query-list", never, { "extraActionsTemplate": { "alias": "extraActionsTemplate"; "required": false; "isSignal": true; }; "showCustomActions": { "alias": "showCustomActions"; "required": false; "isSignal": true; }; "rowRoute": { "alias": "rowRoute"; "required": false; "isSignal": true; }; }, { "rowClicked": "rowClicked"; "createClicked": "createClicked"; "customActionExecuted": "customActionExecuted"; }, never, never, true, never>;
88
95
  }
89
96
 
90
97
  export { SparkQueryListComponent };