@objectstack/service-analytics 7.9.0 → 8.0.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.cts CHANGED
@@ -1,6 +1,8 @@
1
- import { IAnalyticsService, Logger, DriverCapabilities, AnalyticsStrategy, AnalyticsQuery, AnalyticsResult, CubeMeta, StrategyContext } from '@objectstack/spec/contracts';
2
- export { AnalyticsStrategy, DriverCapabilities, StrategyContext } from '@objectstack/spec/contracts';
3
- import { Cube } from '@objectstack/spec/data';
1
+ import { IAnalyticsService, Logger, DriverCapabilities, AnalyticsStrategy, AnalyticsQuery, AnalyticsResult, DatasetSelection, CubeMeta, DatasetCompareTo, StrategyContext } from '@objectstack/spec/contracts';
2
+ export { AnalyticsStrategy, DatasetSelection, DriverCapabilities, StrategyContext } from '@objectstack/spec/contracts';
3
+ import { Cube, FilterCondition } from '@objectstack/spec/data';
4
+ import { ExecutionContext } from '@objectstack/spec/kernel';
5
+ import { Dataset } from '@objectstack/spec/ui';
4
6
  import { Plugin, PluginContext } from '@objectstack/core';
5
7
 
6
8
  /**
@@ -52,6 +54,35 @@ declare class CubeRegistry {
52
54
  private fieldTypeToDimensionType;
53
55
  }
54
56
 
57
+ interface DerivedMeasureSpec {
58
+ name: string;
59
+ op: 'ratio' | 'sum' | 'difference' | 'product';
60
+ of: string[];
61
+ }
62
+ interface CompiledDataset {
63
+ /** The Cube the dataset compiles to (consumed by the strategy chain). */
64
+ cube: Cube;
65
+ /**
66
+ * Relationship names declared in `include`. The join allowlist (D-C):
67
+ * the NativeSQLStrategy rejects any join alias not in this set.
68
+ */
69
+ allowedRelationships: Set<string>;
70
+ /** Derived measures, computed post-aggregation by the executor (Q1). */
71
+ derived: DerivedMeasureSpec[];
72
+ /** Definition-level filter (the dataset's intrinsic scope). */
73
+ filter?: FilterCondition;
74
+ /** Per-measure scoped filters, keyed by measure name (applied by executor). */
75
+ measureFilters: Record<string, FilterCondition>;
76
+ }
77
+ /**
78
+ * Resolves a relationship name on a base object to the related object/table
79
+ * name, using the runtime's object graph. Optional: when omitted the compiler
80
+ * trusts the declared `include` names (the NativeSQLStrategy convention assumes
81
+ * the relationship name equals the related table name).
82
+ */
83
+ type RelationshipResolver = (baseObject: string, relationshipName: string) => string | undefined;
84
+ declare function compileDataset(dataset: Dataset, resolver?: RelationshipResolver): CompiledDataset;
85
+
55
86
  /**
56
87
  * Configuration for AnalyticsService.
57
88
  */
@@ -93,6 +124,30 @@ interface AnalyticsServiceConfig {
93
124
  * They are merged with the built-in strategies and sorted by priority.
94
125
  */
95
126
  strategies?: AnalyticsStrategy[];
127
+ /**
128
+ * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). Supplied
129
+ * by the runtime that owns the sharing middleware; receives the current
130
+ * request's ExecutionContext and returns the RLS `FilterCondition` for the
131
+ * object (exactly what `RLSCompiler` emits). The service binds the active
132
+ * context per query and the strategy compiles the filter into alias-qualified
133
+ * SQL injected into every base and joined table.
134
+ */
135
+ getReadScope?: (objectName: string, context?: ExecutionContext) => FilterCondition | null | undefined;
136
+ /**
137
+ * ADR-0021 D-C — join allowlist per cube (the dataset's declared `include`).
138
+ * Joins outside this set are rejected by the strategy. Compiled datasets
139
+ * (via `queryDataset`/`registerDataset`) supply this automatically; this
140
+ * config hook is a fallback for legacy hand-authored cubes.
141
+ */
142
+ getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;
143
+ /**
144
+ * ADR-0021 — optional object-graph resolver used when compiling datasets:
145
+ * `(baseObject, relationshipName) => relatedObjectName | undefined`. When
146
+ * provided, `queryDataset` validates that every declared `include` exists.
147
+ */
148
+ relationshipResolver?: RelationshipResolver;
149
+ /** Pre-defined datasets to compile + register at construction (ADR-0021). */
150
+ datasets?: Dataset[];
96
151
  }
97
152
  /**
98
153
  * AnalyticsService — Multi-driver analytics orchestrator.
@@ -114,14 +169,40 @@ interface AnalyticsServiceConfig {
114
169
  */
115
170
  declare class AnalyticsService implements IAnalyticsService {
116
171
  private readonly strategies;
117
- private readonly strategyCtx;
172
+ /** Context-independent part of the StrategyContext (no per-request scope). */
173
+ private readonly baseCtx;
174
+ /** Context-aware read-scope provider (bound to the request's context per call). */
175
+ private readonly readScopeProvider?;
176
+ /** Compiled datasets by name — feeds the join allowlist (D-C) and queryDataset. */
177
+ private readonly datasetRegistry;
178
+ /** Optional object-graph resolver used when compiling datasets. */
179
+ private readonly relationshipResolver?;
118
180
  readonly cubeRegistry: CubeRegistry;
119
181
  private readonly logger;
120
182
  constructor(config?: AnalyticsServiceConfig);
183
+ /**
184
+ * Build a per-call StrategyContext that binds the read-scope provider to the
185
+ * current request's ExecutionContext (ADR-0021 D-C). The strategy then sees a
186
+ * `getReadScope(objectName)` that already knows the active tenant.
187
+ */
188
+ private callCtx;
121
189
  /**
122
190
  * Execute an analytical query by delegating to the first capable strategy.
123
191
  */
124
- query(query: AnalyticsQuery): Promise<AnalyticsResult>;
192
+ query(query: AnalyticsQuery, context?: ExecutionContext): Promise<AnalyticsResult>;
193
+ /**
194
+ * Compile a `dataset` (ADR-0021) and register its Cube + join allowlist so it
195
+ * can be queried by name. Idempotent (re-registering overwrites). Returns the
196
+ * compiled dataset.
197
+ */
198
+ registerDataset(dataset: Dataset): CompiledDataset;
199
+ /**
200
+ * Execute a semantic-layer dataset (ADR-0021). Compiles the dataset (saved or
201
+ * inline draft — Studio preview), registers its Cube + join allowlist, then
202
+ * runs the selection through the `DatasetExecutor` with the request context so
203
+ * tenant/RLS scoping (D-C) is applied. See {@link IAnalyticsService.queryDataset}.
204
+ */
205
+ queryDataset(dataset: Dataset, selection: DatasetSelection, context?: ExecutionContext): Promise<AnalyticsResult>;
125
206
  /**
126
207
  * Get cube metadata for discovery.
127
208
  */
@@ -129,7 +210,7 @@ declare class AnalyticsService implements IAnalyticsService {
129
210
  /**
130
211
  * Generate SQL for a query without executing it (dry-run).
131
212
  */
132
- generateSql(query: AnalyticsQuery): Promise<{
213
+ generateSql(query: AnalyticsQuery, context?: ExecutionContext): Promise<{
133
214
  sql: string;
134
215
  params: unknown[];
135
216
  }>;
@@ -181,6 +262,20 @@ interface AnalyticsServicePluginOptions {
181
262
  }>;
182
263
  filter?: Record<string, unknown>;
183
264
  }) => Promise<Record<string, unknown>[]>;
265
+ /**
266
+ * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). The
267
+ * runtime supplies this from its sharing middleware so the analytics raw-SQL
268
+ * path cannot bypass tenant isolation. Receives the request's ExecutionContext
269
+ * and returns the RLS `FilterCondition` for the object (what `RLSCompiler`
270
+ * emits). When omitted, the plugin auto-bridges to a registered `'security'`
271
+ * service exposing `getReadFilter(object, context)` if one is present.
272
+ */
273
+ getReadScope?: (objectName: string, context?: ExecutionContext) => FilterCondition | null | undefined;
274
+ /**
275
+ * ADR-0021 D-C — join allowlist per cube (the dataset's declared `include`).
276
+ * Typically wired from the dataset registry's compiled `allowedRelationships`.
277
+ */
278
+ getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;
184
279
  /** Enable debug logging. */
185
280
  debug?: boolean;
186
281
  }
@@ -225,6 +320,59 @@ declare class AnalyticsServicePlugin implements Plugin {
225
320
  destroy(): Promise<void>;
226
321
  }
227
322
 
323
+ /** @deprecated use DatasetCompareTo from @objectstack/spec/contracts */
324
+ type CompareTo = DatasetCompareTo;
325
+ /**
326
+ * Dataset executor (ADR-0021 WS2).
327
+ *
328
+ * Turns a compiled dataset + a presentation's selection (dimensions, measures,
329
+ * runtime filter, compareTo) into one or more `AnalyticsQuery`s against the Cube
330
+ * runtime, then post-processes the results:
331
+ * - resolves the base measures a selection needs (including derived deps),
332
+ * - applies measure-scoped filters via supplementary grouped queries,
333
+ * - evaluates derived measures (ratio/sum/difference/product) row-by-row (Q1),
334
+ * - shifts the query for `compareTo` (previousPeriod / previousYear) and
335
+ * attaches `<measure>__compare` columns.
336
+ *
337
+ * RLS/tenant scoping is NOT handled here — it is enforced inside the strategy
338
+ * via the StrategyContext read-scope hook (D-C). This layer is pure query
339
+ * shaping + arithmetic.
340
+ */
341
+ /** AND two optional FilterConditions into one (MongoDB-style). */
342
+ declare function combineFilters(a?: FilterCondition, b?: FilterCondition): FilterCondition | undefined;
343
+ /**
344
+ * Evaluate derived measures on each aggregated row, mutating a shallow copy.
345
+ * Division by zero (and missing operands) yields `null` rather than Infinity/NaN.
346
+ */
347
+ declare function evaluateDerivedMeasures(rows: Record<string, unknown>[], derived: DerivedMeasureSpec[]): Record<string, unknown>[];
348
+ /** Compute the comparison window for a [start,end] range. */
349
+ declare function shiftRange(range: [string, string], kind: CompareTo['kind']): [string, string];
350
+ declare class DatasetExecutor {
351
+ private readonly service;
352
+ constructor(service: IAnalyticsService);
353
+ /**
354
+ * Execute a dataset selection and return the shaped rows (+ field metadata).
355
+ *
356
+ * @param context - The request's ExecutionContext, threaded into every
357
+ * underlying `IAnalyticsService.query` so the tenant/RLS read scope is
358
+ * applied per request (ADR-0021 D-C).
359
+ */
360
+ execute(compiled: CompiledDataset, selection: DatasetSelection, context?: ExecutionContext): Promise<AnalyticsResult>;
361
+ private buildQuery;
362
+ private runCompare;
363
+ }
364
+ /**
365
+ * Left-merge `extra` rows onto `base` rows by their dimension-key tuple,
366
+ * copying the listed value columns. Rows in `extra` with no base match are
367
+ * appended (outer-ish merge so comparison-only buckets still surface).
368
+ */
369
+ declare function mergeByDimensions(base: Record<string, unknown>[], extra: Record<string, unknown>[], dimensions: string[], valueColumns: string[]): Record<string, unknown>[];
370
+
371
+ declare function compileScopedFilterToSql(filter: FilterCondition, alias: string): {
372
+ sql: string;
373
+ params: unknown[];
374
+ };
375
+
228
376
  /**
229
377
  * NativeSQLStrategy — Priority 1
230
378
  *
@@ -241,6 +389,16 @@ declare class NativeSQLStrategy implements AnalyticsStrategy {
241
389
  sql: string;
242
390
  params: unknown[];
243
391
  }>;
392
+ /**
393
+ * ADR-0021 D-C — inject an object's read scope (tenant + RLS predicate) into
394
+ * the WHERE clause. The scope is a canonical `FilterCondition` (what the
395
+ * RLSCompiler emits); `compileScopedFilterToSql` turns it into alias-qualified,
396
+ * parameterized SQL (fail-closed — it throws rather than drop a predicate).
397
+ * The `?` placeholders are then renumbered into the strategy's `$N` scheme.
398
+ * No-op when the runtime provides no scope hook (the caller is then
399
+ * responsible for isolation — see contract note).
400
+ */
401
+ private applyReadScope;
244
402
  /**
245
403
  * Resolve a dimension/measure/filter SQL expression that may reference a
246
404
  * related table via dot notation (e.g. `account.industry`).
@@ -318,4 +476,4 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
318
476
  private buildFieldMeta;
319
477
  }
320
478
 
321
- export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, CubeRegistry, NativeSQLStrategy, ObjectQLStrategy };
479
+ export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, type CompareTo, type CompiledDataset, CubeRegistry, DatasetExecutor, type DerivedMeasureSpec, NativeSQLStrategy, ObjectQLStrategy, type RelationshipResolver, combineFilters, compileDataset, compileScopedFilterToSql, evaluateDerivedMeasures, mergeByDimensions, shiftRange };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { IAnalyticsService, Logger, DriverCapabilities, AnalyticsStrategy, AnalyticsQuery, AnalyticsResult, CubeMeta, StrategyContext } from '@objectstack/spec/contracts';
2
- export { AnalyticsStrategy, DriverCapabilities, StrategyContext } from '@objectstack/spec/contracts';
3
- import { Cube } from '@objectstack/spec/data';
1
+ import { IAnalyticsService, Logger, DriverCapabilities, AnalyticsStrategy, AnalyticsQuery, AnalyticsResult, DatasetSelection, CubeMeta, DatasetCompareTo, StrategyContext } from '@objectstack/spec/contracts';
2
+ export { AnalyticsStrategy, DatasetSelection, DriverCapabilities, StrategyContext } from '@objectstack/spec/contracts';
3
+ import { Cube, FilterCondition } from '@objectstack/spec/data';
4
+ import { ExecutionContext } from '@objectstack/spec/kernel';
5
+ import { Dataset } from '@objectstack/spec/ui';
4
6
  import { Plugin, PluginContext } from '@objectstack/core';
5
7
 
6
8
  /**
@@ -52,6 +54,35 @@ declare class CubeRegistry {
52
54
  private fieldTypeToDimensionType;
53
55
  }
54
56
 
57
+ interface DerivedMeasureSpec {
58
+ name: string;
59
+ op: 'ratio' | 'sum' | 'difference' | 'product';
60
+ of: string[];
61
+ }
62
+ interface CompiledDataset {
63
+ /** The Cube the dataset compiles to (consumed by the strategy chain). */
64
+ cube: Cube;
65
+ /**
66
+ * Relationship names declared in `include`. The join allowlist (D-C):
67
+ * the NativeSQLStrategy rejects any join alias not in this set.
68
+ */
69
+ allowedRelationships: Set<string>;
70
+ /** Derived measures, computed post-aggregation by the executor (Q1). */
71
+ derived: DerivedMeasureSpec[];
72
+ /** Definition-level filter (the dataset's intrinsic scope). */
73
+ filter?: FilterCondition;
74
+ /** Per-measure scoped filters, keyed by measure name (applied by executor). */
75
+ measureFilters: Record<string, FilterCondition>;
76
+ }
77
+ /**
78
+ * Resolves a relationship name on a base object to the related object/table
79
+ * name, using the runtime's object graph. Optional: when omitted the compiler
80
+ * trusts the declared `include` names (the NativeSQLStrategy convention assumes
81
+ * the relationship name equals the related table name).
82
+ */
83
+ type RelationshipResolver = (baseObject: string, relationshipName: string) => string | undefined;
84
+ declare function compileDataset(dataset: Dataset, resolver?: RelationshipResolver): CompiledDataset;
85
+
55
86
  /**
56
87
  * Configuration for AnalyticsService.
57
88
  */
@@ -93,6 +124,30 @@ interface AnalyticsServiceConfig {
93
124
  * They are merged with the built-in strategies and sorted by priority.
94
125
  */
95
126
  strategies?: AnalyticsStrategy[];
127
+ /**
128
+ * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). Supplied
129
+ * by the runtime that owns the sharing middleware; receives the current
130
+ * request's ExecutionContext and returns the RLS `FilterCondition` for the
131
+ * object (exactly what `RLSCompiler` emits). The service binds the active
132
+ * context per query and the strategy compiles the filter into alias-qualified
133
+ * SQL injected into every base and joined table.
134
+ */
135
+ getReadScope?: (objectName: string, context?: ExecutionContext) => FilterCondition | null | undefined;
136
+ /**
137
+ * ADR-0021 D-C — join allowlist per cube (the dataset's declared `include`).
138
+ * Joins outside this set are rejected by the strategy. Compiled datasets
139
+ * (via `queryDataset`/`registerDataset`) supply this automatically; this
140
+ * config hook is a fallback for legacy hand-authored cubes.
141
+ */
142
+ getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;
143
+ /**
144
+ * ADR-0021 — optional object-graph resolver used when compiling datasets:
145
+ * `(baseObject, relationshipName) => relatedObjectName | undefined`. When
146
+ * provided, `queryDataset` validates that every declared `include` exists.
147
+ */
148
+ relationshipResolver?: RelationshipResolver;
149
+ /** Pre-defined datasets to compile + register at construction (ADR-0021). */
150
+ datasets?: Dataset[];
96
151
  }
97
152
  /**
98
153
  * AnalyticsService — Multi-driver analytics orchestrator.
@@ -114,14 +169,40 @@ interface AnalyticsServiceConfig {
114
169
  */
115
170
  declare class AnalyticsService implements IAnalyticsService {
116
171
  private readonly strategies;
117
- private readonly strategyCtx;
172
+ /** Context-independent part of the StrategyContext (no per-request scope). */
173
+ private readonly baseCtx;
174
+ /** Context-aware read-scope provider (bound to the request's context per call). */
175
+ private readonly readScopeProvider?;
176
+ /** Compiled datasets by name — feeds the join allowlist (D-C) and queryDataset. */
177
+ private readonly datasetRegistry;
178
+ /** Optional object-graph resolver used when compiling datasets. */
179
+ private readonly relationshipResolver?;
118
180
  readonly cubeRegistry: CubeRegistry;
119
181
  private readonly logger;
120
182
  constructor(config?: AnalyticsServiceConfig);
183
+ /**
184
+ * Build a per-call StrategyContext that binds the read-scope provider to the
185
+ * current request's ExecutionContext (ADR-0021 D-C). The strategy then sees a
186
+ * `getReadScope(objectName)` that already knows the active tenant.
187
+ */
188
+ private callCtx;
121
189
  /**
122
190
  * Execute an analytical query by delegating to the first capable strategy.
123
191
  */
124
- query(query: AnalyticsQuery): Promise<AnalyticsResult>;
192
+ query(query: AnalyticsQuery, context?: ExecutionContext): Promise<AnalyticsResult>;
193
+ /**
194
+ * Compile a `dataset` (ADR-0021) and register its Cube + join allowlist so it
195
+ * can be queried by name. Idempotent (re-registering overwrites). Returns the
196
+ * compiled dataset.
197
+ */
198
+ registerDataset(dataset: Dataset): CompiledDataset;
199
+ /**
200
+ * Execute a semantic-layer dataset (ADR-0021). Compiles the dataset (saved or
201
+ * inline draft — Studio preview), registers its Cube + join allowlist, then
202
+ * runs the selection through the `DatasetExecutor` with the request context so
203
+ * tenant/RLS scoping (D-C) is applied. See {@link IAnalyticsService.queryDataset}.
204
+ */
205
+ queryDataset(dataset: Dataset, selection: DatasetSelection, context?: ExecutionContext): Promise<AnalyticsResult>;
125
206
  /**
126
207
  * Get cube metadata for discovery.
127
208
  */
@@ -129,7 +210,7 @@ declare class AnalyticsService implements IAnalyticsService {
129
210
  /**
130
211
  * Generate SQL for a query without executing it (dry-run).
131
212
  */
132
- generateSql(query: AnalyticsQuery): Promise<{
213
+ generateSql(query: AnalyticsQuery, context?: ExecutionContext): Promise<{
133
214
  sql: string;
134
215
  params: unknown[];
135
216
  }>;
@@ -181,6 +262,20 @@ interface AnalyticsServicePluginOptions {
181
262
  }>;
182
263
  filter?: Record<string, unknown>;
183
264
  }) => Promise<Record<string, unknown>[]>;
265
+ /**
266
+ * ADR-0021 D-C — context-aware per-object read scope (tenant + RLS). The
267
+ * runtime supplies this from its sharing middleware so the analytics raw-SQL
268
+ * path cannot bypass tenant isolation. Receives the request's ExecutionContext
269
+ * and returns the RLS `FilterCondition` for the object (what `RLSCompiler`
270
+ * emits). When omitted, the plugin auto-bridges to a registered `'security'`
271
+ * service exposing `getReadFilter(object, context)` if one is present.
272
+ */
273
+ getReadScope?: (objectName: string, context?: ExecutionContext) => FilterCondition | null | undefined;
274
+ /**
275
+ * ADR-0021 D-C — join allowlist per cube (the dataset's declared `include`).
276
+ * Typically wired from the dataset registry's compiled `allowedRelationships`.
277
+ */
278
+ getAllowedRelationships?: (cubeName: string) => Set<string> | undefined;
184
279
  /** Enable debug logging. */
185
280
  debug?: boolean;
186
281
  }
@@ -225,6 +320,59 @@ declare class AnalyticsServicePlugin implements Plugin {
225
320
  destroy(): Promise<void>;
226
321
  }
227
322
 
323
+ /** @deprecated use DatasetCompareTo from @objectstack/spec/contracts */
324
+ type CompareTo = DatasetCompareTo;
325
+ /**
326
+ * Dataset executor (ADR-0021 WS2).
327
+ *
328
+ * Turns a compiled dataset + a presentation's selection (dimensions, measures,
329
+ * runtime filter, compareTo) into one or more `AnalyticsQuery`s against the Cube
330
+ * runtime, then post-processes the results:
331
+ * - resolves the base measures a selection needs (including derived deps),
332
+ * - applies measure-scoped filters via supplementary grouped queries,
333
+ * - evaluates derived measures (ratio/sum/difference/product) row-by-row (Q1),
334
+ * - shifts the query for `compareTo` (previousPeriod / previousYear) and
335
+ * attaches `<measure>__compare` columns.
336
+ *
337
+ * RLS/tenant scoping is NOT handled here — it is enforced inside the strategy
338
+ * via the StrategyContext read-scope hook (D-C). This layer is pure query
339
+ * shaping + arithmetic.
340
+ */
341
+ /** AND two optional FilterConditions into one (MongoDB-style). */
342
+ declare function combineFilters(a?: FilterCondition, b?: FilterCondition): FilterCondition | undefined;
343
+ /**
344
+ * Evaluate derived measures on each aggregated row, mutating a shallow copy.
345
+ * Division by zero (and missing operands) yields `null` rather than Infinity/NaN.
346
+ */
347
+ declare function evaluateDerivedMeasures(rows: Record<string, unknown>[], derived: DerivedMeasureSpec[]): Record<string, unknown>[];
348
+ /** Compute the comparison window for a [start,end] range. */
349
+ declare function shiftRange(range: [string, string], kind: CompareTo['kind']): [string, string];
350
+ declare class DatasetExecutor {
351
+ private readonly service;
352
+ constructor(service: IAnalyticsService);
353
+ /**
354
+ * Execute a dataset selection and return the shaped rows (+ field metadata).
355
+ *
356
+ * @param context - The request's ExecutionContext, threaded into every
357
+ * underlying `IAnalyticsService.query` so the tenant/RLS read scope is
358
+ * applied per request (ADR-0021 D-C).
359
+ */
360
+ execute(compiled: CompiledDataset, selection: DatasetSelection, context?: ExecutionContext): Promise<AnalyticsResult>;
361
+ private buildQuery;
362
+ private runCompare;
363
+ }
364
+ /**
365
+ * Left-merge `extra` rows onto `base` rows by their dimension-key tuple,
366
+ * copying the listed value columns. Rows in `extra` with no base match are
367
+ * appended (outer-ish merge so comparison-only buckets still surface).
368
+ */
369
+ declare function mergeByDimensions(base: Record<string, unknown>[], extra: Record<string, unknown>[], dimensions: string[], valueColumns: string[]): Record<string, unknown>[];
370
+
371
+ declare function compileScopedFilterToSql(filter: FilterCondition, alias: string): {
372
+ sql: string;
373
+ params: unknown[];
374
+ };
375
+
228
376
  /**
229
377
  * NativeSQLStrategy — Priority 1
230
378
  *
@@ -241,6 +389,16 @@ declare class NativeSQLStrategy implements AnalyticsStrategy {
241
389
  sql: string;
242
390
  params: unknown[];
243
391
  }>;
392
+ /**
393
+ * ADR-0021 D-C — inject an object's read scope (tenant + RLS predicate) into
394
+ * the WHERE clause. The scope is a canonical `FilterCondition` (what the
395
+ * RLSCompiler emits); `compileScopedFilterToSql` turns it into alias-qualified,
396
+ * parameterized SQL (fail-closed — it throws rather than drop a predicate).
397
+ * The `?` placeholders are then renumbered into the strategy's `$N` scheme.
398
+ * No-op when the runtime provides no scope hook (the caller is then
399
+ * responsible for isolation — see contract note).
400
+ */
401
+ private applyReadScope;
244
402
  /**
245
403
  * Resolve a dimension/measure/filter SQL expression that may reference a
246
404
  * related table via dot notation (e.g. `account.industry`).
@@ -318,4 +476,4 @@ declare class ObjectQLStrategy implements AnalyticsStrategy {
318
476
  private buildFieldMeta;
319
477
  }
320
478
 
321
- export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, CubeRegistry, NativeSQLStrategy, ObjectQLStrategy };
479
+ export { AnalyticsService, type AnalyticsServiceConfig, AnalyticsServicePlugin, type AnalyticsServicePluginOptions, type CompareTo, type CompiledDataset, CubeRegistry, DatasetExecutor, type DerivedMeasureSpec, NativeSQLStrategy, ObjectQLStrategy, type RelationshipResolver, combineFilters, compileDataset, compileScopedFilterToSql, evaluateDerivedMeasures, mergeByDimensions, shiftRange };