@objectstack/service-analytics 16.0.0 → 17.0.0-rc.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.
package/dist/index.d.ts CHANGED
@@ -119,6 +119,7 @@ declare function compileDataset(dataset: Dataset, resolver?: RelationshipResolve
119
119
  * — reading an object's field map and fetching id→label pairs — are injected via
120
120
  * {@link DimensionLabelDeps} so this module stays free of any engine dependency.
121
121
  */
122
+
122
123
  /** The minimal field shape this resolver needs. */
123
124
  interface FieldMetaLite {
124
125
  type?: string;
@@ -138,9 +139,90 @@ interface DimensionLabelDeps {
138
139
  * Fetch a map of `id → display label` for the given ids of a target object.
139
140
  * The implementation chooses the target's display field. Returning an empty
140
141
  * map (e.g. no display field, no data access) leaves the ids unresolved.
142
+ *
143
+ * `scope` (ADR-0021 D-C, #3602) is the TARGET object's own read scope — the
144
+ * RLS/tenant `FilterCondition` the implementation must AND into the label
145
+ * lookup so this never reveals a related record the target object's RLS would
146
+ * hide. The label lookup is a per-record read (`group by id`) dressed as an
147
+ * aggregate; without the scope it leaks display names whenever the referenced
148
+ * object is more restricted than the base object whose rows carry the id.
149
+ * `undefined` means "no scope for this object" (global table / unrestricted
150
+ * caller) — the same contract as the read-scope provider.
151
+ *
152
+ * `context` is the request's ExecutionContext — the SECOND belt on the same
153
+ * read (#3602). `scope` is the analytics layer's own predicate; forwarding the
154
+ * context lets the ENGINE's middleware chain scope this per-record read
155
+ * itself, so it stays scoped even if a caller ever reaches this hook without
156
+ * a resolved `scope`. Implementations bridging to an ObjectQL engine MUST
157
+ * forward it; a bridge with nowhere to put it may ignore it.
158
+ */
159
+ fetchRecordLabels(targetObject: string, ids: unknown[], scope?: Record<string, unknown>, context?: ExecutionContext): Promise<Map<unknown, string>>;
160
+ }
161
+ /**
162
+ * Resolve the TARGET object's read scope for a label lookup (#3602). Returns the
163
+ * object's RLS/tenant `FilterCondition`, `null`/`undefined` when the object is
164
+ * unscoped, or a rejected promise when the scope cannot be resolved — in which
165
+ * case the resolver fails CLOSED (skips that dimension's labels) rather than
166
+ * fetching unscoped names.
167
+ */
168
+ type LabelScopeResolver = (targetObject: string) => Promise<Record<string, unknown> | null | undefined> | Record<string, unknown> | null | undefined;
169
+ /**
170
+ * Sort-key label resolution for `DatasetSelection.order` (#3680).
171
+ *
172
+ * The executor sorts the assembled grid BEFORE `queryDataset` rewrites stored
173
+ * dimension values into display labels, so an order key naming a `select` or
174
+ * `lookup`/`master_detail` dimension used to sort by the stored value / FK id —
175
+ * an order that presents as arbitrary once the labels render. This hook hands
176
+ * the executor JUST the value→label mapping for such a dimension so it can sort
177
+ * by what the user will actually read, while the rows keep their raw values
178
+ * (drill metadata depends on them) and ordering + windowing stay one adjacent
179
+ * step. The executor stays engine-free: it sees this interface, never the
180
+ * engine behind it.
181
+ */
182
+ interface OrderLabelResolver {
183
+ /**
184
+ * Whether the dimension's stored value differs from the label it renders as
185
+ * (`select` options, `lookup`/`master_detail` FK ids). Synchronous — the
186
+ * executor consults it when deciding whether the window may be pushed into
187
+ * SQL, before any query runs.
188
+ */
189
+ isLabelBearing(dimension: string): boolean;
190
+ /**
191
+ * Map the given raw stored values of one dimension to display labels.
192
+ * Values missing from the map sort by their raw form — the same thing the
193
+ * user will see rendered for them.
141
194
  */
142
- fetchRecordLabels(targetObject: string, ids: unknown[]): Promise<Map<unknown, string>>;
195
+ resolveLabels(dimension: string, values: unknown[]): Promise<Map<unknown, string> | undefined>;
143
196
  }
197
+ /**
198
+ * Build the executor's {@link OrderLabelResolver} from the dataset's dimension
199
+ * list and the injected label capabilities. Mirrors the classification in
200
+ * {@link resolveDimensionLabels}: a dimension is label-bearing when its field
201
+ * carries select `options` or is a lookup/master_detail with a `reference`.
202
+ *
203
+ * - `select` resolves from field metadata — no query at all.
204
+ * - `lookup`/`master_detail` costs ONE batched id→name read over the distinct
205
+ * grouped values, scoped to the REFERENCED object's own RLS (#3602). Fail
206
+ * closed: an unresolvable scope degrades to sorting by the stored id rather
207
+ * than fetching unscoped — consistent with the display pass, which renders
208
+ * the raw id in that case too.
209
+ */
210
+ declare function createOrderLabelResolver(baseObject: string, dims: Array<{
211
+ name: string;
212
+ field: string;
213
+ }>, deps: DimensionLabelDeps, resolveScope?: LabelScopeResolver, context?: ExecutionContext): OrderLabelResolver;
214
+ /**
215
+ * Wrap a {@link DimensionLabelDeps} so repeated `fetchRecordLabels` calls
216
+ * within ONE request fetch each id at most once. A selection that sorts by a
217
+ * lookup dimension resolves labels twice — once PRE-window for the sort keys
218
+ * (#3680, over the full grid's ids), once post-window for display (a subset of
219
+ * the same ids) — so with this cache the display pass costs no extra query.
220
+ *
221
+ * Per-request only: entries are keyed by target object alone, which is safe
222
+ * because an object's read scope is constant within one request. Never share
223
+ * an instance across requests.
224
+ */
225
+ declare function withLabelFetchCache(deps: DimensionLabelDeps): DimensionLabelDeps;
144
226
  /** Date-dimension granularity (mirrors the dataset `dateGranularity` enum). */
145
227
  type DateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year';
146
228
  /**
@@ -151,13 +233,22 @@ type DateGranularity = 'day' | 'week' | 'month' | 'quarter' | 'year';
151
233
  * (row key = `name`)
152
234
  * @param rows - result rows, mutated in place
153
235
  * @param deps - injected runtime capabilities
236
+ * @param resolveScope - (ADR-0021 D-C, #3602) resolves the referenced object's
237
+ * own read scope for a lookup/master_detail dimension's label fetch. When it
238
+ * throws, that dimension's labels are SKIPPED (fail-closed — the raw id renders
239
+ * instead) rather than fetched unscoped. Omit when no read-scope provider is
240
+ * configured (labels then fetch unscoped, as before — no security in play).
241
+ * @param context - the request's ExecutionContext, forwarded to
242
+ * {@link DimensionLabelDeps.fetchRecordLabels} so the engine's own middleware
243
+ * scopes the per-record label read too — the second belt beside `resolveScope`
244
+ * (#3602)
154
245
  */
155
246
  declare function resolveDimensionLabels(baseObject: string, dims: Array<{
156
247
  name: string;
157
248
  field: string;
158
249
  type?: string;
159
250
  dateGranularity?: DateGranularity | string;
160
- }>, rows: Record<string, unknown>[], deps: DimensionLabelDeps): Promise<void>;
251
+ }>, rows: Record<string, unknown>[], deps: DimensionLabelDeps, resolveScope?: LabelScopeResolver, context?: ExecutionContext): Promise<void>;
161
252
  /**
162
253
  * Pick the display field for an object from its field map, by convention:
163
254
  * an explicit `name`/`title`/`label` field, else the first text-like field.
@@ -195,6 +286,15 @@ interface AnalyticsServiceConfig {
195
286
  alias: string;
196
287
  }>;
197
288
  filter?: Record<string, unknown>;
289
+ /** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */
290
+ timezone?: string;
291
+ /**
292
+ * ADR-0021 D-C (#3602) — the request's ExecutionContext. Bridges MUST
293
+ * forward it to `engine.aggregate` so engine-side RLS applies; see
294
+ * `StrategyContext.executeAggregate` for why this is a second belt rather
295
+ * than a replacement for `getReadScope`.
296
+ */
297
+ context?: ExecutionContext;
198
298
  }) => Promise<Record<string, unknown>[]>;
199
299
  /**
200
300
  * Fallback IAnalyticsService (e.g. MemoryAnalyticsService).
@@ -244,6 +344,24 @@ interface AnalyticsServiceConfig {
244
344
  * `StrategyContext.isExternalObject`.
245
345
  */
246
346
  isExternalObject?: (objectName: string) => boolean;
347
+ /**
348
+ * [#3867] Is `name` a registered object in this kernel's schema registry?
349
+ *
350
+ * Consulted by {@link AnalyticsService.ensureCube} on the auto-inference
351
+ * path only. When no Cube is registered under the queried name, the service
352
+ * infers a minimal one whose `sql` IS that name — the intended "metric over
353
+ * an object" path (an `object-metric` KPI widget queries `crm_account`
354
+ * without anyone authoring a Cube). Without this hook that inference accepts
355
+ * ANY string, so an arbitrary physical table name reached the driver: the
356
+ * analytics-side twin of the data-path gap closed in #3770.
357
+ *
358
+ * Optional, and absence means "skip the check" — same tiering as #3770's
359
+ * `assertObjectRegistered`: with no registry to consult the question cannot
360
+ * be answered, and failing closed would break every embedding that runs
361
+ * analytics without a data engine. The production bridge in `plugin.ts`
362
+ * always wires it.
363
+ */
364
+ isRegisteredObject?: (name: string) => boolean;
247
365
  /**
248
366
  * ADR-0021 — optional object-graph resolver used when compiling datasets:
249
367
  * `(baseObject, relationshipName) => relatedObjectName | undefined`. When
@@ -315,6 +433,10 @@ declare class AnalyticsService implements IAnalyticsService {
315
433
  private readonly labelResolver?;
316
434
  /** ADR-0037 P3: pending-seed row resolver for draft data preview. */
317
435
  private readonly draftRowsResolver?;
436
+ /** [#3867] Schema-registry probe gating cube auto-inference. */
437
+ private readonly isRegisteredObject?;
438
+ /** [#3867] One-shot flag for the {@link assertInferableCube} stand-down warning. */
439
+ private warnedNoObjectRegistry;
318
440
  readonly cubeRegistry: CubeRegistry;
319
441
  private readonly logger;
320
442
  constructor(config?: AnalyticsServiceConfig);
@@ -389,6 +511,20 @@ declare class AnalyticsService implements IAnalyticsService {
389
511
  * strategies pick the right aggregation function and field.
390
512
  */
391
513
  private ensureCube;
514
+ /**
515
+ * [#3867] Gate on the cube auto-inference path: a name with no registered
516
+ * Cube may only be inferred into one if it is a registered object.
517
+ *
518
+ * Rejects with `status: 404` / `code: 'CUBE_NOT_FOUND'` so the HTTP boundary
519
+ * answers "no such cube" instead of letting the name reach the driver as a
520
+ * table and surfacing whatever the driver says about it. The message names
521
+ * both ways the request could be made valid, because from here the two are
522
+ * genuinely indistinguishable: register a Cube, or register the object.
523
+ *
524
+ * Skips when `isRegisteredObject` was not supplied — see the config field's
525
+ * doc for why that tier is a deliberate stand-down and not a hole.
526
+ */
527
+ private assertInferableCube;
392
528
  /** Build a minimal Cube from the fields referenced by an AnalyticsQuery. */
393
529
  private inferCubeFromQuery;
394
530
  /**
@@ -427,6 +563,12 @@ interface AnalyticsServicePluginOptions {
427
563
  filter?: Record<string, unknown>;
428
564
  /** Reference timezone (IANA) for date bucketing — ADR-0053 Phase 2. */
429
565
  timezone?: string;
566
+ /**
567
+ * ADR-0021 D-C (#3602) — the request's ExecutionContext. A custom bridge
568
+ * MUST forward it to its engine so engine-side RLS applies; dropping it is
569
+ * what made the built-in bridge fall open in #3597.
570
+ */
571
+ context?: ExecutionContext;
430
572
  }) => Promise<Record<string, unknown>[]>;
431
573
  /**
432
574
  * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). The
@@ -488,25 +630,6 @@ declare class AnalyticsServicePlugin implements Plugin {
488
630
 
489
631
  /** @deprecated use DatasetCompareTo from @objectstack/spec/contracts */
490
632
  type CompareTo = DatasetCompareTo;
491
- /**
492
- * Dataset executor (ADR-0021 WS2).
493
- *
494
- * Turns a compiled dataset + a presentation's selection (dimensions, measures,
495
- * runtime filter, compareTo) into one or more `AnalyticsQuery`s against the Cube
496
- * runtime, then post-processes the results:
497
- * - resolves the base measures a selection needs (including derived deps),
498
- * - applies measure-scoped filters via supplementary grouped queries,
499
- * - evaluates derived measures (ratio/sum/difference/product) row-by-row (Q1),
500
- * - shifts the query for `compareTo` (previousPeriod / previousYear) and
501
- * attaches `<measure>__compare` columns,
502
- * - computes server-side totals (`selection.totals.groupings`, #1753) by
503
- * re-running the selection per dimension subset, so matrix subtotals and
504
- * the grand total use each measure's true aggregate.
505
- *
506
- * RLS/tenant scoping is NOT handled here — it is enforced inside the strategy
507
- * via the StrategyContext read-scope hook (D-C). This layer is pure query
508
- * shaping + arithmetic.
509
- */
510
633
  /** AND two optional FilterConditions into one (MongoDB-style). */
511
634
  declare function combineFilters(a?: FilterCondition, b?: FilterCondition): FilterCondition | undefined;
512
635
  /**
@@ -518,7 +641,16 @@ declare function evaluateDerivedMeasures(rows: Record<string, unknown>[], derive
518
641
  declare function shiftRange(range: [string, string], kind: CompareTo['kind']): [string, string];
519
642
  declare class DatasetExecutor {
520
643
  private readonly service;
521
- constructor(service: IAnalyticsService);
644
+ private readonly orderLabels?;
645
+ /**
646
+ * @param service - The analytics service the executor issues its queries to.
647
+ * @param orderLabels - Optional sort-key label hook (#3680). When provided,
648
+ * an order key naming a label-bearing (`select`/`lookup`) dimension sorts
649
+ * by its display label instead of the stored value. Omit to sort by stored
650
+ * values everywhere (e.g. the draft-preview path, whose seed rows already
651
+ * carry display names).
652
+ */
653
+ constructor(service: IAnalyticsService, orderLabels?: OrderLabelResolver | undefined);
522
654
  /**
523
655
  * Execute a dataset selection and return the shaped rows (+ field metadata).
524
656
  *
@@ -526,7 +658,7 @@ declare class DatasetExecutor {
526
658
  * underlying `IAnalyticsService.query` so the tenant/RLS read scope is
527
659
  * applied per request (ADR-0021 D-C).
528
660
  */
529
- execute(compiled: CompiledDataset, selection: DatasetSelection, context?: ExecutionContext): Promise<AnalyticsResult>;
661
+ execute(compiledInput: CompiledDataset, selectionInput: DatasetSelection, context?: ExecutionContext): Promise<AnalyticsResult>;
530
662
  private executeSelection;
531
663
  private buildQuery;
532
664
  private runCompare;
@@ -652,10 +784,83 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
652
784
  readonly priority = 20;
653
785
  canHandle(query: AnalyticsQuery, ctx: StrategyContext): boolean;
654
786
  execute(query: AnalyticsQuery, ctx: StrategyContext): Promise<AnalyticsResult>;
787
+ /**
788
+ * Render a REPRESENTATIVE SQL string for an ObjectQL aggregate query.
789
+ *
790
+ * This path executes through `engine.aggregate()`, not raw SQL, so the string
791
+ * is documentation rather than the literal statement — but it must be an
792
+ * honest account of what the query does, because dataset responses echo it
793
+ * and authors read it to verify their widget options landed (#3588). It
794
+ * therefore renders date bucketing (`date_trunc`), the WHERE predicate,
795
+ * ordering, and the row window.
796
+ *
797
+ * Filter VALUES are rendered as `$n` placeholders and returned in `params`,
798
+ * never inlined: the echoed statement travels to the browser, and a filter
799
+ * comparand can carry tenant data.
800
+ */
655
801
  generateSql(query: AnalyticsQuery, ctx: StrategyContext): Promise<{
656
802
  sql: string;
657
803
  params: unknown[];
658
804
  }>;
805
+ /**
806
+ * ADR-0021 D-C (#3597) — AND the object's read scope (tenant + RLS) into the
807
+ * filter handed to `engine.aggregate`.
808
+ *
809
+ * This path used to drop the scope entirely, and the engine could not make up
810
+ * for it: the aggregate bridge passes no `ExecutionContext`, so the security
811
+ * middleware's principal-less fall-open skipped its own RLS injection. Both
812
+ * belts were off at once — an authenticated caller received aggregates
813
+ * computed over EVERY tenant's rows.
814
+ *
815
+ * Composed with `$and`, never by key merge: the query's own filter and the
816
+ * scope can name the SAME field (e.g. a dashboard filtering `organization_id`),
817
+ * and a spread would let caller input silently overwrite the security
818
+ * predicate. `$and` makes that structurally impossible.
819
+ */
820
+ private withReadScope;
821
+ /** Is `field` a resolved cross-object (relationship-traversal) reference? */
822
+ private isCrossObjectField;
823
+ /**
824
+ * Plan how to serve cross-object references on this join-less path (#3654).
825
+ *
826
+ * `engine.aggregate()` cannot join. A cross-object DIMENSION within a
827
+ * supported envelope is served by an FK-expand (`executeCrossObject`): group
828
+ * the base aggregate on the lookup FK, resolve the FK to the related attribute
829
+ * with a SCOPED read, re-bucket in memory. Returns `null` for a base-only
830
+ * query (direct path), a plan for an in-envelope cross-object query.
831
+ *
832
+ * THROWS for anything outside the envelope — a cross-object MEASURE or FILTER
833
+ * (needs a real join to evaluate), a MULTI-HOP dimension (`a.b.c`), or a
834
+ * non-recombinable measure (`avg`/`count_distinct`, whose sub-bucket values
835
+ * cannot be merged). A loud error beats the silent mis-bucket #3654 kills.
836
+ * `generateSql()` calls this too, so the preview accepts/rejects the same set.
837
+ *
838
+ * Detection is on RESOLVED field names, so a dotted dimension the cube
839
+ * flattens to a real column is treated as base, not cross-object.
840
+ */
841
+ private planCrossObject;
842
+ /**
843
+ * Serve a cross-object-dimension query by FK-expand (#3654). The pure
844
+ * re-bucketing step lives in `cross-object-rebucket.ts`.
845
+ */
846
+ private executeCrossObject;
847
+ /**
848
+ * Resolve `fkValues` (ids of `refObject`) to their `attr` values, applying the
849
+ * referenced object's OWN read scope (#3654 / #3602). Reuses the aggregate
850
+ * bridge — `group by (id, attr)` is one row per record. Ids the scope hides
851
+ * are simply absent from the map (⇒ RESTRICTED bucket downstream).
852
+ */
853
+ private resolveFkAttr;
854
+ /**
855
+ * Render one normalized filter as a display SQL predicate for `generateSql`.
856
+ *
857
+ * Mirrors `NativeSQLStrategy.buildFilterClause`'s operator vocabulary so the
858
+ * two previews read alike, but binds through `coerceFilterValueForObjectQL`:
859
+ * the comparand shown is the one THIS path actually hands the engine (a real
860
+ * boolean, not SQL's 1/0). Returns null for an operator/value combination
861
+ * that carries no predicate, matching `execute()`, which drops it too.
862
+ */
863
+ private buildFilterClauseSql;
659
864
  /**
660
865
  * Resolve a member ref to a `{ sql, type? }` definition.
661
866
  *
@@ -671,9 +876,65 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
671
876
  private lookupMember;
672
877
  private resolveFieldName;
673
878
  private resolveMeasureAggregation;
879
+ /**
880
+ * AND one more operand onto `filter[field]`, merging operator objects rather
881
+ * than overwriting them. Returns a standalone conjunct when the two cannot
882
+ * share one entry, or `null` when the merge absorbed the operand.
883
+ *
884
+ * Every predicate this strategy contributes goes through here — the caller's
885
+ * `where` and the time-dimension `dateRange` alike. Two operands on one field
886
+ * are the normal case (`{$gte}` from a `where` plus `{$gte,$lte}` from a
887
+ * window on `close_date`), and a plain assignment would keep only the last:
888
+ * that is how a range used to lose a bound.
889
+ *
890
+ * Spreading is sound only while the operands name DIFFERENT operators. Where
891
+ * they collide — two `$gte` bounds on one field, which a window makes routine
892
+ * and which a `where` can already produce on its own through `$and` — the
893
+ * spread keeps whichever came last and WIDENS the query. Same for a bare
894
+ * equality meeting an operator object: neither can absorb the other. Those
895
+ * are handed back for the caller to AND in separately, so the engine
896
+ * intersects them instead of the strategy picking a winner.
897
+ */
898
+ private mergeFilterOperand;
899
+ /**
900
+ * Lower `timeDimensions[].dateRange` into resolved-field bounds (#3650).
901
+ *
902
+ * `dateRange` states a WINDOW on a time dimension; it is a SIBLING of `where`,
903
+ * never folded into it. `normalizeAnalyticsFilters` reads only `where`, so
904
+ * this path used to drop the window on the floor — no error, just every row
905
+ * ever recorded. Nor is that a corner case: `NativeSQLStrategy.canHandle`
906
+ * declines any query carrying a `granularity`, so a date-bucketed trend lands
907
+ * HERE on every driver — and "bucketed trend" is precisely the shape that also
908
+ * carries a range ("last 12 months", "this quarter").
909
+ *
910
+ * Bounds are inclusive on both ends — the same `$gte`/`$lte` pair
911
+ * `NativeSQLStrategy` binds as `BETWEEN` and the memory driver builds as a
912
+ * `$match`, so one dashboard reads the same on every driver.
913
+ *
914
+ * Comparands are coerced by the SAME helper the `where` path uses, so an
915
+ * epoch-ms bound recovers as a number and an ISO string stays a string. No
916
+ * STORAGE coercion happens here, deliberately: `NativeSQLStrategy` needs
917
+ * `coerceTemporal` because it binds into raw SQL and had to learn that a
918
+ * SQLite `Field.datetime` is an INTEGER epoch (#2034); this path goes through
919
+ * `engine.aggregate()`, where the driver's own CRUD filter coercion applies —
920
+ * the very coercion that already makes a `where` bound on that same column
921
+ * work today.
922
+ *
923
+ * A bare-string `dateRange` degenerates to the single point `[s, s]`, matching
924
+ * `NativeSQLStrategy`. Relative phrases ("Last 7 days") are NOT resolved here;
925
+ * neither SQL path resolves them, and inventing a second interpretation on the
926
+ * driver-independent path is how the two would drift apart again.
927
+ *
928
+ * An oddly-sized array (the schema types `dateRange` as a plain `string[]`)
929
+ * takes its first two entries, a one-entry array degenerating to a point.
930
+ * `NativeSQLStrategy` drops such a window entirely — but "drop the window"
931
+ * means "plot all of history", which is the very failure this fixes, so the
932
+ * fallback here errs toward the narrower query instead.
933
+ */
934
+ private dateRangeBounds;
674
935
  private convertFilter;
675
936
  private extractObjectName;
676
937
  private buildFieldMeta;
677
938
  }
678
939
 
679
- export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, type CompareTo, type CompiledDataset, CubeRegistry, DatasetExecutor, type DerivedMeasureSpec, type DimensionLabelDeps, type FieldMetaLite, NativeSQLStrategy, ObjectQLStrategy, type RelationshipResolver, type RelationshipTarget, combineFilters, compileDataset, compileScopedFilterToSql, evaluateDerivedMeasures, mergeByDimensions, pickDisplayField, resolveDimensionLabels, shiftRange };
940
+ export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, type CompareTo, type CompiledDataset, CubeRegistry, DatasetExecutor, type DerivedMeasureSpec, type DimensionLabelDeps, type FieldMetaLite, NativeSQLStrategy, ObjectQLStrategy, type OrderLabelResolver, type RelationshipResolver, type RelationshipTarget, combineFilters, compileDataset, compileScopedFilterToSql, createOrderLabelResolver, evaluateDerivedMeasures, mergeByDimensions, pickDisplayField, resolveDimensionLabels, shiftRange, withLabelFetchCache };