@xh/hoist 75.0.0-SNAPSHOT.1754335298677 → 75.0.0-SNAPSHOT.1754604038542

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.
package/data/cube/Cube.ts CHANGED
@@ -9,6 +9,7 @@ import {HoistBase, managed, PlainObject, Some} from '@xh/hoist/core';
9
9
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
10
10
  import {forEachAsync} from '@xh/hoist/utils/async';
11
11
  import {CubeField, CubeFieldSpec} from './CubeField';
12
+ import {ViewRowData} from './ViewRowData';
12
13
  import {Query, QueryConfig} from './Query';
13
14
  import {View} from './View';
14
15
  import {Store, StoreRecordIdSpec, StoreTransaction} from '../Store';
@@ -56,6 +57,35 @@ export interface CubeConfig {
56
57
  omitFn?: OmitFn;
57
58
  }
58
59
 
60
+ /**
61
+ * Function to be called for each node to aggregate to determine if it should be "locked",
62
+ * preventing drilldown into its children. If true returned for a node, no drilldown will be
63
+ * allowed, and the row will be marked with a boolean "locked" property.
64
+ */
65
+ export type LockFn = (row: AggregateRow | BucketRow) => boolean;
66
+
67
+ /**
68
+ * Function to be called for each node during row generation to determine if it should be
69
+ * skipped in tree output. Useful for removing aggregates that are degenerate due to context.
70
+ * Note that skipping in this way has no effect on aggregations -- all children of this node are
71
+ * simply promoted to their parent node.
72
+ */
73
+ export type OmitFn = (row: AggregateRow | BucketRow) => boolean;
74
+
75
+ /**
76
+ * Function to be called for rows making up an aggregated dimension to determine if the children of
77
+ * that dimension should be dynamically bucketed into additional sub-groupings.
78
+ *
79
+ * An example use case would be a grouped collection of portfolio positions, where any closed
80
+ * positions are identified as such by this function and bucketed into a "Closed" sub-grouping,
81
+ * without having to add something like an "openClosed" dimension that would apply to all
82
+ * aggregations and create an unwanted "Open" grouping.
83
+ *
84
+ * @param rows - the rows being checked for bucketing
85
+ * @returns BucketSpec for configuring dynamic sub-aggregations, or null to perform no bucketing.
86
+ */
87
+ export type BucketSpecFn = (rows: BaseRow[]) => BucketSpec;
88
+
59
89
  /**
60
90
  * A data store that supports grouping, aggregating, and filtering data on multiple dimensions.
61
91
  *
@@ -94,7 +124,7 @@ export class Cube extends HoistBase {
94
124
  this.store = new Store({
95
125
  fields: this.parseFields(fields, fieldDefaults),
96
126
  idSpec,
97
- processRawData: processRawData as any,
127
+ processRawData: processRawData,
98
128
  freezeData: false,
99
129
  idEncodesTreePath: true
100
130
  });
@@ -143,9 +173,9 @@ export class Cube extends HoistBase {
143
173
  * @param query - Config for query defining the shape of the view.
144
174
  * @returns data containing the results of the query as a hierarchical set of rows.
145
175
  */
146
- executeQuery(query: QueryConfig): any {
147
- const q = new Query({...query, cube: this});
148
- const view = new View({query: q}),
176
+ executeQuery(query: QueryConfig): ViewRowData[] {
177
+ const q = new Query({...query, cube: this}),
178
+ view = new View({query: q}),
149
179
  rows = view.result.rows;
150
180
 
151
181
  view.destroy();
@@ -153,19 +183,19 @@ export class Cube extends HoistBase {
153
183
  }
154
184
 
155
185
  /**
156
- * Create a View on this data.
186
+ * Create a dynamic {@link View} of the cube data based on a query. Unlike the static snapshot
187
+ * returned by {@link Cube.executeQuery}, a View created with this method can be configured
188
+ * with `connect:true` to automatically update as the underlying data in the Cube changes.
157
189
  *
158
- * Creates a dynamic View of the cube data, based on a query. Useful for binding to grids a
159
- * and efficiently displaying changing results in the Cube.
190
+ * Provide one or more `stores` to automatically populate them with the aggregated data returned
191
+ * by the query, or read the returned {@link View.result} directly.
160
192
  *
161
- * Note: Applications should call {@link View.disconnect} or {@link View.destroy} on the View
162
- * created by this method when appropriate to avoid unnecessary processing.
193
+ * When the returned View is no longer needed, call {@link View.destroy} (or save a reference
194
+ * via an `@managed` model property) to avoid unnecessary processing.
163
195
  *
164
196
  * @param query - query to be used to construct this view.
165
- * @param stores - Stores to be loaded/reloaded with data from this view.
166
- * To receive data only, use the 'results' property of the returned View instead.
167
- * @param connect - true to update View automatically when data in
168
- * the underlying cube is changed. Default false.
197
+ * @param stores - Stores to be automatically loaded/reloaded with View results.
198
+ * @param connect - true to update View automatically when data in the underlying Cube changes.
169
199
  */
170
200
  createView({
171
201
  query,
@@ -183,14 +213,12 @@ export class Cube extends HoistBase {
183
213
  });
184
214
  }
185
215
 
186
- /**
187
- * True if the provided view is connected to this Cube for live updates.
188
- */
216
+ /** True if the provided view is connected to this Cube for live updates. */
189
217
  viewIsConnected(view: View): boolean {
190
218
  return this._connectedViews.has(view);
191
219
  }
192
220
 
193
- /** @param view - view to disconnect from live updates. */
221
+ /** Cease pushing further updates to this Cube's data into a previously connected View. */
194
222
  disconnectView(view: View) {
195
223
  this._connectedViews.delete(view);
196
224
  }
@@ -200,12 +228,10 @@ export class Cube extends HoistBase {
200
228
  //-------------------
201
229
  /**
202
230
  * Populate this cube with a new dataset.
231
+ * This method largely delegates to {@link Store.loadData} - see that method for more info.
203
232
  *
204
- * This method largely delegates to Store.loadData(). See that method for more
205
- * information.
206
- *
207
- * Note that this method will update its views asynchronously, in order to avoid locking
208
- * up the browser when attached to multiple expensive views.
233
+ * Note that this method will update its views asynchronously in order to avoid locking up the
234
+ * browser when attached to multiple expensive views.
209
235
  *
210
236
  * @param rawData - flat array of lowest/leaf level data rows.
211
237
  * @param info - optional metadata to associate with this cube/dataset.
@@ -303,28 +329,3 @@ export class Cube extends HoistBase {
303
329
  super.destroy();
304
330
  }
305
331
  }
306
-
307
- /**
308
- * Function to be called for each node to aggregate to determine if it should be "locked",
309
- * preventing drilldown into its children. If true returned for a node, no drilldown will be
310
- * allowed, and the row will be marked with a boolean "locked" property.
311
- */
312
- export type LockFn = (row: AggregateRow | BucketRow) => boolean;
313
-
314
- /**
315
- * Function to be called for each node during row generation to determine if it should be
316
- * skipped in tree output. Useful for removing aggregates that are degenerate due to context.
317
- * Note that skipping in this way has no effect on aggregations -- all children of this node are
318
- * simply promoted to their parent node.
319
- */
320
- export type OmitFn = (row: AggregateRow | BucketRow) => boolean;
321
-
322
- /**
323
- * Function to be called for each dimension to determine if children of said dimension should be
324
- * bucketed into additional dynamic dimensions.
325
- *
326
- * @param rows - the rows being checked for bucketing
327
- * @returns a BucketSpec for configuring the bucket to place child rows into, or null to perform
328
- * no bucketing
329
- */
330
- export type BucketSpecFn = (rows: BaseRow[]) => BucketSpec;
@@ -5,53 +5,90 @@
5
5
  * Copyright © 2025 Extremely Heavy Industries Inc.
6
6
  */
7
7
 
8
- import {BucketSpecFn, Filter, LockFn, OmitFn, parseFilter, StoreRecord} from '@xh/hoist/data';
9
- import {isEqual, find} from 'lodash';
10
- import {FilterLike, FilterTestFn} from '../filter/Types';
11
- import {CubeField} from './CubeField';
12
- import {Cube} from './Cube';
8
+ import {
9
+ BucketSpecFn,
10
+ Filter,
11
+ FilterLike,
12
+ FilterTestFn,
13
+ LockFn,
14
+ OmitFn,
15
+ parseFilter,
16
+ StoreRecord
17
+ } from '@xh/hoist/data';
13
18
  import {throwIf} from '@xh/hoist/utils/js';
19
+ import {find, isEqual} from 'lodash';
20
+ import {Cube} from './Cube';
21
+ import {CubeField} from './CubeField';
14
22
 
15
23
  /**
16
- * Queries determine what data is extracted from a cube and how it should be grouped + aggregated.
17
- *
18
- * Note that if no dimensions are provided, 'includeRoot' or 'includeLeaves' should be true -
19
- * otherwise no data will be returned!
24
+ * Queries determine what data is extracted, grouped, and aggregated from a {@link Cube}.
20
25
  */
21
26
  export interface QueryConfig {
22
27
  /**
23
- * Associated Cube. Required, but note that `Cube.executeQuery()` will install a reference to
24
- * itself on the query config (automatically)
28
+ * The Cube to query. Required, but note that the preferred {@link Cube.executeQuery} API will
29
+ * install a reference to itself on the query config (automatically).
25
30
  */
26
31
  cube?: Cube;
27
32
 
28
33
  /**
29
- * Fields or field names. If unspecified will include all available fields
30
- * from the source Cube, otherwise supply a subset to optimize aggregation performance.
34
+ * Fields or field names. If unspecified will include all available {@link Cube.fields}.
35
+ * Specify a subset to optimize aggregation performance.
31
36
  */
32
37
  fields?: string[] | CubeField[];
33
38
 
34
39
  /**
35
- * Fields or field names to group on. Any fields provided must also be in fields config, above. If none
36
- * given the resulting data will not be grouped.
40
+ * Fields or field names on which data should be grouped and aggregated. These are the ordered
41
+ * grouping levels in the resulting hierarchy - e.g. ['Country', 'State', 'City'].
42
+ *
43
+ * Any fields provided here must also be included in the `fields` array, if specified.
44
+ *
45
+ * If not provided or empty, the resulting data will not be grouped. Specify 'includeRoot' or
46
+ * 'includeLeaves' in that case, otherwise no data will be returned.
37
47
  */
38
48
  dimensions?: string[] | CubeField[];
39
49
 
40
50
  /**
41
- * One or more filters or configs to create one. If an array, a single 'AND' filter will
42
- * be created.
51
+ * Filters to apply to leaf data, or configs to create. Note that leaf data will be filtered
52
+ * and then aggregated - i.e. the filters provided here will filter in/out the lowest level
53
+ * facts and _won't_ operate directly on any aggregates.
54
+ *
55
+ * Arrays will be combined into a single 'AND' CompoundFilter.
43
56
  */
44
57
  filter?: FilterLike;
45
58
 
46
59
  /**
47
- * IncludeRoot?: True to include a synthetic root node in the return with grand total
48
- * aggregate values.
60
+ * True to include a synthetic root node in the return with grand totals (aggregations across
61
+ * all data returned by the query). Pairs well with {@link StoreConfig.loadRootAsSummary} and
62
+ * {@link GridConfig.showSummary} to display a docked grand total row for grids rendering
63
+ * Cube results.
49
64
  */
50
65
  includeRoot?: boolean;
51
66
 
52
- /** True to include leaf nodes in return.*/
67
+ /**
68
+ * True to include leaf nodes (the "flat" facts originally loaded into the Cube) as the
69
+ * {@link ViewRowData.children} of the lowest level of aggregated `dimensions`.
70
+ *
71
+ * False (the default) to only return aggregate rows based on requested `dimensions`.
72
+ *
73
+ * Useful when you wish to e.g. load Cube results into a tree grid and allow users to expand
74
+ * aggregated groups all the way out to see the source data. See also `provideLeaves`, which
75
+ * will provide access to these nodes without exposing as `children`.
76
+ */
53
77
  includeLeaves?: boolean;
54
78
 
79
+ /**
80
+ * True to provide access to leaf nodes via the {@link ViewRowData.cubeLeaves} getter on the
81
+ * lowest level of aggregated `dimensions`. This will allow programmatic access to the leaves
82
+ * used to produce a given aggregation, without exposing them as `children` in a way that would
83
+ * cause them to be rendered in a tree grid.
84
+ *
85
+ * Useful when e.g. a full leaf-level drill-down is not desired, but the app still needs
86
+ * access to those leaves to display in a separate view or for further processing.
87
+ *
88
+ * See also the more common `includeLeaves`.
89
+ */
90
+ provideLeaves?: boolean;
91
+
55
92
  /**
56
93
  * True (default) to recursively omit single-child parents in the hierarchy.
57
94
  * Apps can implement further omit logic using `omitFn`.
@@ -60,14 +97,21 @@ export interface QueryConfig {
60
97
 
61
98
  /**
62
99
  * Optional function to be called for each aggregate node to determine if it should be "locked",
63
- * preventing drill-down into its children. Defaults to Cube.lockFn.
100
+ * preventing drill-down into its children.
101
+ *
102
+ * Defaults to {@link Cube.lockFn}.
64
103
  */
65
104
  lockFn?: LockFn;
66
105
 
67
106
  /**
68
107
  * Optional function to be called for each dimension during row generation to determine if the
69
108
  * children of that dimension should be bucketed into additional dynamic dimensions.
70
- * Defaults to Cube.bucketSpecFn.
109
+ *
110
+ * This can be used to break selected aggregations into sub-groups dynamically, without having
111
+ * to define another dimension in the Cube and have it apply to all aggregations. See the
112
+ * {@link BucketSpec} interface for additional information.
113
+ *
114
+ * Defaults to {@link Cube.bucketSpecFn}.
71
115
  */
72
116
  bucketSpecFn?: BucketSpecFn;
73
117
 
@@ -85,6 +129,7 @@ export class Query {
85
129
  readonly filter: Filter;
86
130
  readonly includeRoot: boolean;
87
131
  readonly includeLeaves: boolean;
132
+ readonly provideLeaves: boolean;
88
133
  readonly omitRedundantNodes: boolean;
89
134
  readonly cube: Cube;
90
135
  readonly lockFn: LockFn;
@@ -100,6 +145,7 @@ export class Query {
100
145
  filter = null,
101
146
  includeRoot = false,
102
147
  includeLeaves = false,
148
+ provideLeaves = false,
103
149
  omitRedundantNodes = true,
104
150
  lockFn = cube.lockFn,
105
151
  bucketSpecFn = cube.bucketSpecFn,
@@ -110,6 +156,7 @@ export class Query {
110
156
  this.dimensions = this.parseDimensions(dimensions);
111
157
  this.includeRoot = includeRoot;
112
158
  this.includeLeaves = includeLeaves;
159
+ this.provideLeaves = provideLeaves;
113
160
  this.omitRedundantNodes = omitRedundantNodes;
114
161
  this.filter = parseFilter(filter);
115
162
  this.lockFn = lockFn;
@@ -126,6 +173,7 @@ export class Query {
126
173
  filter: this.filter,
127
174
  includeRoot: this.includeRoot,
128
175
  includeLeaves: this.includeLeaves,
176
+ provideLeaves: this.provideLeaves,
129
177
  omitRedundantNodes: this.omitRedundantNodes,
130
178
  lockFn: this.lockFn,
131
179
  bucketSpecFn: this.bucketSpecFn,
@@ -153,8 +201,7 @@ export class Query {
153
201
  }
154
202
 
155
203
  /**
156
- * True if the provided other Query is equivalent to this instance,
157
- * not considering the filter.
204
+ * True if the provided other Query is equivalent to this instance, not considering the filter.
158
205
  */
159
206
  equalsExcludingFilter(other: Query): boolean {
160
207
  return (
@@ -163,6 +210,7 @@ export class Query {
163
210
  this.cube === other.cube &&
164
211
  this.includeRoot === other.includeRoot &&
165
212
  this.includeLeaves === other.includeLeaves &&
213
+ this.provideLeaves === other.provideLeaves &&
166
214
  this.omitRedundantNodes === other.omitRedundantNodes &&
167
215
  this.bucketSpecFn == other.bucketSpecFn &&
168
216
  this.omitFn == other.omitFn &&
package/data/cube/View.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  StoreRecord,
18
18
  StoreRecordId
19
19
  } from '@xh/hoist/data';
20
+ import {ViewRowData} from '@xh/hoist/data/cube/ViewRowData';
20
21
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
21
22
  import {shallowEqualArrays} from '@xh/hoist/utils/impl';
22
23
  import {logWithDebug, throwIf} from '@xh/hoist/utils/js';
@@ -32,19 +33,24 @@ export interface ViewConfig {
32
33
  query: Query;
33
34
 
34
35
  /**
35
- * Store(s) to be automatically (re)loaded with data from this view. Optional - read the View's
36
- * observable `result` property directly to use without a Store.
36
+ * Store(s) to be automatically (re)loaded with data from this view.
37
+ * Optional - read {@link View.result} directly to use without a Store.
37
38
  */
38
39
  stores?: Store[] | Store;
39
40
 
40
41
  /**
41
- * True to reactively update the View's `result` and any connected store(s) when data in the
42
- * underlying Cube is changed. False (default) to have this view run its query once to capture
43
- * a snapshot without further updates based on Cube changes.
42
+ * True to reactively update the View's {@link View.result} and any connected store(s) when data
43
+ * in the underlying Cube changes. False (default) to have this view run its query once to
44
+ * capture a snapshot without further (automatic) updates.
44
45
  */
45
46
  connect?: boolean;
46
47
  }
47
48
 
49
+ export interface ViewResult {
50
+ rows: ViewRowData[];
51
+ leafMap: Map<StoreRecordId, LeafRow>;
52
+ }
53
+
48
54
  export interface DimensionValue {
49
55
  /** Dimension field. */
50
56
  field: CubeField;
@@ -62,34 +68,34 @@ export class View extends HoistBase {
62
68
  return true;
63
69
  }
64
70
 
65
- /** Query defining this View. Update via `updateQuery()`. */
71
+ /** Query defining this View. Update via {@link updateQuery}. */
66
72
  @observable.ref
67
73
  query: Query = null;
68
74
 
69
75
  /**
70
- * Results of this view, an observable object with a `rows` property
71
- * containing an array of hierarchical data objects.
76
+ * Results of this view, an observable object with a `rows` property containing an array of
77
+ * hierarchical {@link ViewRowData} objects.
72
78
  */
73
79
  @observable.ref
74
- result: {rows: PlainObject[]; leafMap: Map<StoreRecordId, LeafRow>} = null;
80
+ result: ViewResult = null;
75
81
 
76
82
  /** Stores to which results of this view should be (re)loaded. */
77
83
  stores: Store[] = null;
78
84
 
79
- /** Cube info associated with this View when last updated. */
85
+ /** The source {@link Cube.info} as of the last time this View was updated. */
80
86
  @observable.ref
81
87
  info: PlainObject = null;
82
88
 
83
- /** timestamp (ms) of the last time this view's data was changed. */
89
+ /** Timestamp (ms) of the last time this view's data was changed. */
84
90
  @observable
85
91
  lastUpdated: number;
86
92
 
87
93
  // Implementation
88
- private _rows: PlainObject[] = null;
89
- private _rowCache: Map<string, BaseRow> = null;
94
+ private _rowDatas: ViewRowData[] = null;
90
95
  private _leafMap: Map<StoreRecordId, LeafRow> = null;
91
96
  private _recordMap: Map<StoreRecordId, StoreRecord> = null;
92
97
  _aggContext: AggregationContext = null;
98
+ _rowCache: Map<string, BaseRow> = null;
93
99
 
94
100
  /** @internal - applications should use {@link Cube.createView} */
95
101
  constructor(config: ViewConfig) {
@@ -144,8 +150,7 @@ export class View extends HoistBase {
144
150
  * Change the query in some way, re-computing the data in this View to reflect the new query.
145
151
  *
146
152
  * @param overrides - changes to be applied to the query. May include any arguments to the query
147
- * constructor except `cube`, which cannot be changed on a view once set
148
- * via the initial query.
153
+ * constructor except `cube`, which cannot be changed once set via the initial query.
149
154
  */
150
155
  @action
151
156
  updateQuery(overrides: Partial<QueryConfig>) {
@@ -164,9 +169,7 @@ export class View extends HoistBase {
164
169
  this.fullUpdate();
165
170
  }
166
171
 
167
- /**
168
- * Gathers all unique values for each dimension field in the query
169
- */
172
+ /** Gather all unique values for each dimension field in the query. */
170
173
  getDimensionValues(): DimensionValue[] {
171
174
  const {_leafMap} = this,
172
175
  fields = this.query.fields.filter(it => it.isDimension);
@@ -229,6 +232,7 @@ export class View extends HoistBase {
229
232
  this.updateResults();
230
233
  }
231
234
 
235
+ @logWithDebug
232
236
  private dataOnlyUpdate(updates: StoreRecord[]) {
233
237
  const {_leafMap, _recordMap, stores} = this,
234
238
  updatedRowDatas = new Set<PlainObject>();
@@ -252,17 +256,17 @@ export class View extends HoistBase {
252
256
  }
253
257
 
254
258
  private loadStores() {
255
- const {_leafMap, _rows} = this;
256
- if (!_leafMap || !_rows) return;
259
+ const {_leafMap, _rowDatas} = this;
260
+ if (!_leafMap || !_rowDatas) return;
257
261
 
258
262
  // Skip degenerate root in stores/grids, but preserve in object api.
259
- const storeRows = _leafMap.size !== 0 ? _rows : [];
263
+ const storeRows = _leafMap.size !== 0 ? _rowDatas : [];
260
264
  this.stores.forEach(s => s.loadData(storeRows));
261
265
  }
262
266
 
263
267
  private updateResults() {
264
- const {_leafMap, _rows} = this;
265
- this.result = {rows: _rows, leafMap: _leafMap};
268
+ const {_leafMap, _rowDatas} = this;
269
+ this.result = {rows: _rowDatas, leafMap: _leafMap};
266
270
  this.info = this.cube.info;
267
271
  this.lastUpdated = Date.now();
268
272
  }
@@ -274,7 +278,7 @@ export class View extends HoistBase {
274
278
  rootId = 'root';
275
279
 
276
280
  const records = this._aggContext.filteredRecords;
277
- const leafMap = new Map();
281
+ const leafMap: Map<StoreRecordId, LeafRow> = new Map();
278
282
  let newRows = this.groupAndInsertRecords(records, dimensions, rootId, {}, leafMap);
279
283
  newRows = this.bucketRows(newRows, rootId, {});
280
284
 
@@ -292,10 +296,10 @@ export class View extends HoistBase {
292
296
 
293
297
  this._leafMap = leafMap;
294
298
 
295
- // This is the magic. We only actually reveal to API the network of *data* nodes.
299
+ // This is the magic. We only actually reveal to API the network of *data* nodes.
296
300
  // This hides all the meta information, as well as unwanted leaves and skipped rows.
297
301
  // Underlying network still there and updates will flow up through it via the leaves.
298
- this._rows = newRows.flatMap(it => it.getVisibleDatas());
302
+ this._rowDatas = newRows.flatMap(it => it.getVisibleDatas());
299
303
  }
300
304
 
301
305
  private groupAndInsertRecords(
@@ -0,0 +1,66 @@
1
+ /*
2
+ * This file belongs to Hoist, an application development toolkit
3
+ * developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
4
+ *
5
+ * Copyright © 2025 Extremely Heavy Industries Inc.
6
+ */
7
+ import {PlainObject, Some} from '@xh/hoist/core';
8
+ import {flatMap} from 'lodash';
9
+
10
+ /**
11
+ * Grouped node data, as returned by {@link Cube.executeQuery} or exposed via {@link View.result}.
12
+ * Designed for direct consumption by hierarchical stores and their associated tree grids.
13
+ */
14
+ export class ViewRowData implements PlainObject {
15
+ constructor(id: string) {
16
+ this.id = id;
17
+ }
18
+
19
+ /** Unique id. */
20
+ id: string;
21
+
22
+ /**
23
+ * Label of the row. The dimension value or, for leaf rows. the underlying cubeId.
24
+ * Suitable for display, although apps will typically wish to customize leaf row rendering.
25
+ */
26
+ cubeLabel: string;
27
+
28
+ /** Dimension on which this row was computed, or null for leaf rows. */
29
+ cubeDimension: string;
30
+
31
+ /**
32
+ * Buckets this row appears in
33
+ */
34
+ cubeBuckets: Record<string, any>;
35
+
36
+ /**
37
+ * Visible children of this row.
38
+ *
39
+ * Note that this may not be the same as the simple aggregation children of this row. In
40
+ * particular, this property is subject to the semantics of row locking, redundant row omission,
41
+ * and bucketing as defined by the Query.
42
+ */
43
+ children: ViewRowData[];
44
+
45
+ /** True for leaf rows loaded into the cube (i.e. not a grouped aggregation). */
46
+ get isCubeLeaf(): boolean {
47
+ return this.cubeDimension == null;
48
+ }
49
+
50
+ /**
51
+ * All visible (i.e. non-locked) cube leaves associated with this row.
52
+ *
53
+ * For this to be populated, either {@link Query.includeLeaves} or {@link Query.provideLeaves}
54
+ * must have been set on the underlying Query.
55
+ */
56
+ get cubeLeaves(): Some<ViewRowData> {
57
+ if (this.isCubeLeaf) return this;
58
+ return this._cubeLeafChildren ?? flatMap(this.children, 'cubeLeaves');
59
+ }
60
+
61
+ //------------------
62
+ // Implementation
63
+ //-----------------
64
+ /** @internal */
65
+ _cubeLeafChildren: ViewRowData[];
66
+ }
@@ -11,14 +11,17 @@ import {View} from '../View';
11
11
  import {BaseRow} from './BaseRow';
12
12
 
13
13
  /**
14
- * Object used by views to gather Aggregate rows.
14
+ * Row within a dataset produced by a Cube / View representing aggregated data on a dimension.
15
+ *
16
+ * This is an internal data structure - {@link ViewRowData} is the public row-level data API.
15
17
  */
16
18
  export class AggregateRow extends BaseRow {
17
19
  override get isAggregate() {
18
20
  return true;
19
21
  }
20
22
 
21
- readonly dim: CubeField = null; // null for summary row
23
+ /** The dimension for which this row is aggregating data. Null for a top-level summary row. */
24
+ readonly dim: CubeField = null;
22
25
  readonly dimName: string = null;
23
26
 
24
27
  constructor(